基于計(jì)算機(jī)視覺(jué)(opencv)的運(yùn)動(dòng)計(jì)數(shù)系統(tǒng)
Notice:本文的源碼均為開(kāi)源代碼,(由于文章篇幅有限,文章中的源碼均是偽代碼),需要源碼的小伙伴可以先關(guān)注我,私信或評(píng)論與我聯(lián)系,大家一起學(xué)習(xí)
1、項(xiàng)目簡(jiǎn)介
1.1研究背景及意義
隨著全民健身熱潮的興起,越來(lái)越多的人積極參加健身鍛煉,但由于缺乏科學(xué)的運(yùn)動(dòng)指導(dǎo),使健身難以取得相應(yīng)的效果。據(jù)市場(chǎng)調(diào)查顯示,沒(méi)有產(chǎn)品可以自動(dòng)分析健身運(yùn)動(dòng)并提供指導(dǎo)。而近年深度神經(jīng)網(wǎng)絡(luò)在人體姿態(tài)識(shí)別上已經(jīng)取得了巨大的成功,針對(duì)這個(gè)現(xiàn)象,本課程設(shè)計(jì)制作了一個(gè)基于MediaPipe的運(yùn)動(dòng)計(jì)數(shù)系統(tǒng)。該系統(tǒng)主要內(nèi)容包括對(duì)于單人人體關(guān)鍵點(diǎn)的檢測(cè),關(guān)鍵點(diǎn)的連接,以及運(yùn)動(dòng)健身關(guān)鍵點(diǎn)的角度變化來(lái)實(shí)現(xiàn)對(duì)俯臥撐和深蹲的計(jì)數(shù)。
1.2項(xiàng)目設(shè)計(jì)方案
系統(tǒng)框架如下圖所示,本系統(tǒng)將視頻或者攝像頭實(shí)時(shí)捕捉的俯臥撐或者深蹲的視頻一步步進(jìn)行處理,首先我們獲得俯臥撐和深蹲的訓(xùn)練樣本,之后將訓(xùn)練樣本轉(zhuǎn)換為k-NN分類訓(xùn)練集,將預(yù)測(cè)的landmarks轉(zhuǎn)移儲(chǔ)存至CSV文件中,之后進(jìn)行KNN算法分類。最后實(shí)現(xiàn)重復(fù)計(jì)數(shù)將結(jié)果顯示在面板中。
具體方案如下:
(1)收集目標(biāo)練習(xí)的圖像樣本并對(duì)其進(jìn)行姿勢(shì)預(yù)測(cè)
(2)將獲得的姿態(tài)標(biāo)志轉(zhuǎn)換為適合 k-NN 分類器的數(shù)據(jù),并使用這些數(shù)據(jù)形成訓(xùn)練集
(3)執(zhí)行分類本身,然后進(jìn)行重復(fù)計(jì)數(shù)
(4)面板顯示結(jié)果
2、程序設(shè)計(jì)
2.1 程序設(shè)計(jì)方案
2.1.1 建立訓(xùn)練樣本
為了建立一個(gè)好的分類器,應(yīng)該為訓(xùn)練集收集適當(dāng)?shù)臉颖荆豪碚撋厦總€(gè)練習(xí)的每個(gè)最終狀態(tài)大約有幾百個(gè)樣本(例如,俯臥撐和深蹲的“向上”和“向下”位置),收集的樣本涵蓋不同的攝像機(jī)角度、環(huán)境條件、身體形狀和運(yùn)動(dòng)變化,這一點(diǎn)很重要,能做到這樣最好。但實(shí)際中如果嫌麻煩的話每種狀態(tài)15-25張左右都可以,然后拍攝角度要注意多樣化,最好每隔15度拍攝一張。
具體代碼如下:
訓(xùn)練樣本圖片格式
Required structure of the images_in_folder:
fitness_poses_images_in/
squat_up/
image_001.jpg
image_002.jpg
...
squat_down/
image_001.jpg
image_002.jpg
2.1.2 獲取歸一化landmarks
要將樣本轉(zhuǎn)換為 k-NN 分類器訓(xùn)練集,在給定圖像上運(yùn)行 BlazePose 模型,并將預(yù)測(cè)的landmarks轉(zhuǎn)儲(chǔ)到 CSV 文件中。此外,Pose Classification Colab (Extended)通過(guò)針對(duì)整個(gè)訓(xùn)練集對(duì)每個(gè)樣本進(jìn)行分類,提供了有用的工具來(lái)查找異常值(例如,錯(cuò)誤預(yù)測(cè)的姿勢(shì))和代表性不足的類別(例如,不覆蓋所有攝像機(jī)角度)。之后,能夠在任意視頻上測(cè)試該分類器。fitness_poses_csvs_out文件夾里面的csv文件就是使用拍攝的訓(xùn)練樣本提取出來(lái)的深蹲和俯臥撐的訓(xùn)練集文件,有了該文件后就可以直接運(yùn)行本項(xiàng)目。生成的CSV文件如圖:
具體代碼如下:
class FullBodyPoseEmbedder(object):
Converts 3D pose landmarks into 3D embedding."""
def __init__(self, torso_size_multiplier):
乘數(shù)應(yīng)用于軀干以獲得最小的身體尺寸
def __call__(self, landmarks):
Normalizes pose landmarks and converts to embedding
獲取 landmarks.歸一化姿勢(shì)landmarks并轉(zhuǎn)換為embedding
具有形狀 (M, 3) 的姿勢(shì)embedding的 Numpy 數(shù)組,其中“M”是“_get_pose_distance_embedding”中定義的成對(duì)距離的數(shù)量。
return embedding
def _normalize_pose_landmarks(self, landmarks):
Normalizes landmarks translation and scale.歸一化landmarks的平移和縮放
return landmarks
def _get_pose_center(self, landmarks):
Calculates pose center as point between hips.將姿勢(shì)中心計(jì)算為臀部之間的點(diǎn)。
return center
def _get_pose_size(self, landmarks, torso_size_multiplier):
Calculates pose size.計(jì)算姿勢(shì)大小。
它是下面兩個(gè)值的最大值:
* 軀干大小乘以`torso_size_multiplier`
* 從姿勢(shì)中心到任何姿勢(shì)地標(biāo)的最大距離
這種方法僅使用 2D landmarks來(lái)計(jì)算姿勢(shì)大小分別計(jì)算
臀部中心、兩肩中心、軀干尺寸作為最小的身體尺寸到姿勢(shì)中心的最大距離.
return max(torso_size * torso_size_multiplier, max_dist)
def _get_pose_distance_embedding(self, landmarks):
Converts pose landmarks into 3D embedding.
將姿勢(shì)landmarks轉(zhuǎn)換為 3D embedding.
我們使用幾個(gè)成對(duì)的 3D 距離來(lái)形成姿勢(shì)embedding。 所有距離都包括帶符號(hào)的 X 和 Y 分量。
我們使用不同類型的對(duì)來(lái)覆蓋不同的姿勢(shì)類別。 Feel free to remove some or add new.
2.1.3 使用KNN算法分類
用于姿勢(shì)分類的 k-NN 算法需要每個(gè)樣本的特征向量表示和一個(gè)度量來(lái)計(jì)算兩個(gè)這樣的向量之間的距離,以找到最接近目標(biāo)的姿勢(shì)樣本。
為了將姿勢(shì)標(biāo)志轉(zhuǎn)換為特征向量,使用預(yù)定義的姿勢(shì)關(guān)節(jié)列表之間的成對(duì)距離,例如手腕和肩膀、腳踝和臀部以及兩個(gè)手腕之間的距離。由于該算法依賴于距離,因此在轉(zhuǎn)換之前所有姿勢(shì)都被歸一化以具有相同的軀干尺寸和垂直軀干方向。
可以根據(jù)運(yùn)動(dòng)的特點(diǎn)選擇所要計(jì)算的距離對(duì)(例如,引體向上可能更加關(guān)注上半身的距離對(duì))。
為了獲得更好的分類結(jié)果,使用不同的距離度量調(diào)用了兩次 KNN 搜索:
首先,為了過(guò)濾掉與目標(biāo)樣本幾乎相同但在特征向量中只有幾個(gè)不同值的樣本(這意味著不同的彎曲關(guān)節(jié)和其他姿勢(shì)類),使用最小坐標(biāo)距離作為距離度量,然后使用平均坐標(biāo)距離在第一次搜索中找到最近的姿勢(shì)簇。
最后,應(yīng)用指數(shù)移動(dòng)平均(EMA) 平滑來(lái)平衡來(lái)自姿勢(shì)預(yù)測(cè)或分類的任何噪聲。不僅搜索最近的姿勢(shì)簇,而且計(jì)算每個(gè)姿勢(shì)簇的概率,并將其用于隨著時(shí)間的推移進(jìn)行平滑處理。
class PoseSample(object):
獲取捕捉到的圖像landmarks
class PoseClassifier(object):
根據(jù)捕捉到圖像的landmarks,對(duì)圖像進(jìn)行分類
*根據(jù)提供的訓(xùn)練圖片,將訓(xùn)練的圖片分類,例如蹲起或者俯臥撐,分為兩類,放入不同文件夾
*根據(jù)不同類別的圖片分別生成.csv的文件
訓(xùn)練圖片的文件結(jié)構(gòu):
neutral_standing.csv
pushups_down.csv
pushups_up.csv
squats_down.csv
...
csv文件結(jié)構(gòu):
sample_00001,x1,y1,z1,x2,y2,z2,....
sample_00002,x1,y1,z1,x2,y2,z2,....
return pose_samples
def find_pose_sample_outliers(self):
針對(duì)整個(gè)數(shù)據(jù)庫(kù)對(duì)每個(gè)樣本進(jìn)行分類
*找出目標(biāo)姿勢(shì)中的異常值
*為目標(biāo)找到最近的姿勢(shì)
*如果最近的姿勢(shì)具有不同的類別或多個(gè)姿勢(shì)類別被檢測(cè)為最近,則樣本是異常值
return outliers
def __call__(self, pose_landmarks):
對(duì)給定的姿勢(shì)進(jìn)行分類。
分類分兩個(gè)階段完成:
*按 MAX 距離選取前 N 個(gè)樣本。 它允許刪除與給定姿勢(shì)幾乎相同但有一些關(guān)節(jié)在向一個(gè)方向彎曲的樣本。
*按平均距離選擇前 N 個(gè)樣本。 在上一步移除異常值后, 我們可以選擇在平均值上接近的樣本。
*按平均距離過(guò)濾,去除異常值后,我們可以通過(guò)平均距離找到最近的姿勢(shì)
return result
2.1.4 計(jì)數(shù)實(shí)現(xiàn)
為了計(jì)算重復(fù)次數(shù),該算法監(jiān)控目標(biāo)姿勢(shì)類別的概率。深蹲的“向上”和“向下”終端狀態(tài):
當(dāng)“下”位姿類的概率第一次通過(guò)某個(gè)閾值時(shí),算法標(biāo)記進(jìn)入“下”位姿類。
一旦概率下降到閾值以下(即起身超過(guò)一定高度),算法就會(huì)標(biāo)記“向下”姿勢(shì)類別,退出并增加計(jì)數(shù)器。
為了避免概率在閾值附近波動(dòng)(例如,當(dāng)用戶在“向上”和“向下”狀態(tài)之間暫停時(shí))導(dǎo)致幻像計(jì)數(shù)的情況,用于檢測(cè)何時(shí)退出狀態(tài)的閾值實(shí)際上略低于用于檢測(cè)狀態(tài)退出的閾值。
動(dòng)作計(jì)數(shù)器
class RepetitionCounter(object):
# 計(jì)算給定目標(biāo)姿勢(shì)類的重復(fù)次數(shù)
def __init__(self, class_name, enter_threshold=6, exit_threshold=4):
self._class_name = class_name
# 如果姿勢(shì)通過(guò)了給定的閾值,那么我們就進(jìn)入該動(dòng)作的計(jì)數(shù)
self._enter_threshold = enter_threshold
self._exit_threshold = exit_threshold
# 是否處于給定的姿勢(shì)
self._pose_entered = False
# 退出姿勢(shì)的次數(shù)
self._n_repeats = 0
@property
def n_repeats(self):
return self._n_repeats
def __call__(self, pose_classification):
# 計(jì)算給定幀之前發(fā)生的重復(fù)次數(shù)
# 我們使用兩個(gè)閾值。首先,您需要從較高的位置上方進(jìn)入姿勢(shì),然后您需要從較低的位置下方退出。
# 閾值之間的差異使其對(duì)預(yù)測(cè)抖動(dòng)穩(wěn)定(如果只有一個(gè)閾值,則會(huì)導(dǎo)致錯(cuò)誤計(jì)數(shù))。
# 參數(shù):
# pose_classification:當(dāng)前幀上的姿勢(shì)分類字典
# Sample:
# {
# 'squat_down': 8.3,
# 'squat_up': 1.7,
# }
# 獲取姿勢(shì)的置信度.
pose_confidence = 0.0
if self._class_name in pose_classification:
pose_confidence = pose_classification[self._class_name]
# On the very first frame or if we were out of the pose, just check if we
# entered it on this frame and update the state.
# 在第一幀或者如果我們不處于姿勢(shì)中,只需檢查我們是否在這一幀上進(jìn)入該姿勢(shì)并更新?tīng)顟B(tài)
if not self._pose_entered:
self._pose_entered = pose_confidence > self._enter_threshold
return self._n_repeats
# 如果我們處于姿勢(shì)并且正在退出它,則增加計(jì)數(shù)器并更新?tīng)顟B(tài)
if pose_confidence < self._exit_threshold:
self._n_repeats += 1
self._pose_entered = False
return self._n_repeats
2.1.5 界面實(shí)現(xiàn)
利用Python的QtDesinger來(lái)進(jìn)行設(shè)計(jì)界面,添加攝像頭捕捉區(qū)域,以及捕捉到的圖像的最高點(diǎn)和最低點(diǎn)的變化曲線區(qū)域,和計(jì)算運(yùn)動(dòng)次數(shù)并顯示的區(qū)域。整體采用了水平的布局,添加按鈕以及界面關(guān)閉提示。攝像頭則采用英文狀態(tài)下,輸入“q”來(lái)進(jìn)行退出,方法很簡(jiǎn)單。具體運(yùn)行結(jié)果如下圖:
class Ui_MainWindow(object):
def __init__(self):
self.RowLength = 0
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1213, 680)
設(shè)置窗體固定大小
MainWindow.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
圖片區(qū)域
self.centralwidget.setObjectName("centralwidget")
設(shè)置窗口大小
self.tableWidget = QtWidgets.QTableWidget(self.scrollAreaWidgetContents_1)
設(shè)置布局
設(shè)置窗口大小及顏色
self.tableWidget.setObjectName("tableWidget")
self.tableWidget.setColumnCount(6)
設(shè)置1列的寬度
設(shè)置2列的寬度
設(shè)置3列的寬度
設(shè)置4列的寬度
設(shè)置5列的寬度
設(shè)置6列的寬度
self.tableWidget.setRowCount(self.RowLength)
隱藏垂直表頭
self.tableWidget.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.tableWidget.raise_()
self.scrollArea_4.setWidget(self.scrollAreaWidgetContents_4)
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
獲取當(dāng)前工程文件位置
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
設(shè)置UI界面標(biāo)題為:基于姿態(tài)估計(jì)的運(yùn)動(dòng)計(jì)數(shù)系統(tǒng)
設(shè)置左側(cè)UI界面橫坐標(biāo)為:最高點(diǎn)與最低點(diǎn)高度變化曲線
設(shè)置左側(cè)UI界面標(biāo)題為:攝像頭捕捉
設(shè)置右側(cè)UI界面標(biāo)題為:計(jì)數(shù)區(qū)域
self.scrollAreaWidgetContents_1.show()
2.1.6 入口函數(shù)main
有兩種選擇模式,可以從本地上傳視頻進(jìn)行檢測(cè),也可以調(diào)用攝像頭進(jìn)行實(shí)時(shí)檢測(cè)。具體的模式選擇可以通過(guò)輸入選擇的數(shù)字進(jìn)行選擇:
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
調(diào)用主窗口函數(shù)
調(diào)用UI界面
while True:
menu = int(input("請(qǐng)輸入檢測(cè)模式(數(shù)字):1. 從本地導(dǎo)入視頻檢測(cè)t2. 調(diào)用攝像頭檢測(cè)n"))
if 輸入數(shù)字為1:
flag = int(input("請(qǐng)輸入檢測(cè)的運(yùn)動(dòng)類型(數(shù)字):1. 俯臥撐t2. 深蹲n"))
video_path = input("請(qǐng)輸入視頻路徑:")
tp.trainset_process(flag)
vp.video_process(video_path, flag)
continue
elif 輸入數(shù)字為2:
flag = int(input("請(qǐng)輸入檢測(cè)的運(yùn)動(dòng)類型(數(shù)字):1. 俯臥撐t2. 深蹲n"))
print("n按英文狀態(tài)下的q或esc退出攝像頭采集")
tp.trainset_process(flag)
vc.process(flag)
continue
elif 輸入數(shù)字為3:
break
else:
print("輸入錯(cuò)誤,請(qǐng)重新輸入!")
continue
sys.exit(app.exec_())
3、成果展示
由我們組成員分別對(duì)系統(tǒng)的鏡頭采集功能和視頻采集功能進(jìn)行了實(shí)驗(yàn),當(dāng)選擇鏡頭采集功能時(shí),利用opencv調(diào)用電腦攝像頭進(jìn)行采集畫(huà)面。
如圖所示為打開(kāi)攝像頭界面,左邊是攝像頭的捕捉區(qū)域,并附帶檢測(cè)屏幕中人體最高點(diǎn)和最低點(diǎn)的曲線變化圖;當(dāng)曲線發(fā)生階躍性突變,且達(dá)到一定的峰值,攝像頭中的右上方的數(shù)字就會(huì)進(jìn)行計(jì)數(shù)。上圖選擇的模式為蹲起計(jì)數(shù),同時(shí)我們也添加了俯臥撐計(jì)數(shù)的模式,只需在模式選擇下進(jìn)行調(diào)整即可。模型會(huì)根據(jù)選擇的模式,自動(dòng)進(jìn)行分類。
上圖可以清晰的看出測(cè)試者在經(jīng)過(guò)蹲起測(cè)試之后截下的圖片,一共測(cè)試了16次蹲起,曲線形象的記錄下了每一次的過(guò)程,藍(lán)色表示最高點(diǎn)的變化,黃色表示最低點(diǎn)的變化。
上圖采用的是對(duì)視頻中的運(yùn)動(dòng)進(jìn)行計(jì)數(shù),與攝像頭調(diào)用是類似的方法,此方法可以直接生成結(jié)果視頻,可視化的效果對(duì)計(jì)數(shù)結(jié)果進(jìn)行驗(yàn)證。
4、結(jié)論及展望
本設(shè)計(jì)實(shí)現(xiàn)的人體姿態(tài)識(shí)別網(wǎng)絡(luò)模型雖然可以在一般設(shè)備上運(yùn)行,并且基本達(dá)到了實(shí)時(shí)檢測(cè)的效果,但總體來(lái)說(shuō)檢測(cè)速度還是偏慢。目前所達(dá)到的水平還有待提升,需要在保證精度的情況下,進(jìn)一步進(jìn)行輕量化。
本文中的人體姿態(tài)識(shí)別的過(guò)程中攝像頭不移動(dòng)并且固定在一個(gè)位置,因此只能檢測(cè)到特定區(qū)域的人體動(dòng)作姿態(tài),如果人體所在位置超出該攝像頭的覆蓋范圍,那么系統(tǒng)就無(wú)法識(shí)別人體的動(dòng)作姿態(tài)。所以下一步可以考慮將攝像機(jī)安裝在機(jī)器人上,通過(guò)激光雷達(dá)和目標(biāo)跟蹤等技術(shù)控制機(jī)器人對(duì)人體進(jìn)行實(shí)時(shí)跟蹤,通過(guò)移動(dòng)的攝像頭實(shí)時(shí)捕捉人體的動(dòng)作姿態(tài),可以在更大范圍內(nèi)更加有效的識(shí)別人體動(dòng)作姿態(tài)。
至此,文章主要內(nèi)容介紹完畢,項(xiàng)目除上述主要模塊以外,還包含訓(xùn)練數(shù)據(jù)集等文件,需要源碼的朋友關(guān)注再私信與我聯(lián)系,本人看到一定回復(fù)?。。。?/strong>
博客主頁(yè):https://blog.csdn.net/weixin_51141489,需要源碼或相關(guān)資料實(shí)物的友友請(qǐng)關(guān)注、點(diǎn)贊,私信吧!