【项目背景】 跳绳是一个很好的健身项目,为了获知所跳个数,有的跳绳上会有计数器。但这也只能跳完这后看到,能不能在跳的过程中就能看到,这样能让我们坚持跳的更多,更有趣味性。 【项目设计】
通过Mind+Python模式下加载Google的开源Mediapipe人工智能算法库,识别人体姿态,来判断跳绳次数,并通过Pinpong库控制LED灯实时显示次数。 【测试程序】 测试程序中,使用人体姿态23,24两坐标点中点与标准点的比较来确认跳绳完成程度。
import numpy as np import time import cv2 import PoseModule as pm cap = cv2.VideoCapture("tiaosheng.mp4") detector = pm.poseDetector() count = 0 dir = 0 pTime = 0 success=True point_sd=0 while success: success, img = cap.read() if success: img = cv2.resize(img, (640, 480)) img = detector.findPose(img, False) lmList = detector.findPosition(img, False) if len(lmList) != 0: point = detector.midpoint(img, 24, 23) if point_sd==0: point_sd=point print(point_sd["y"]) # 计算个数 print(point["y"]) if point["y"]> point_sd["y"]+15: if dir == 0: count += 0.5 dir = 1 if point["y"]<point_sd["y"]+5: if dir == 1: count += 0.5 dir = 0 #print(count) cv2.putText(img, str(int(count)), (45, 460), cv2.FONT_HERSHEY_PLAIN, 7,(255, 0, 0), 8) cTime = time.time() fps = 1 / (cTime - pTime) pTime = cTime cv2.putText(img, str(int(fps)), (50, 100), cv2.FONT_HERSHEY_PLAIN, 5,(255, 0, 0), 5) cv2.imshow("Image", img) cv2.waitKey(1)
复制代码 【PoseModule.py】
上面程序用到的“PoseModule.py”文件中,在”poseDetector“类中增加了“midpoint”函数,用于求两点的中点坐标。
import math import mediapipe as mp import cv2 class poseDetector(): def __init__(self, mode=False, upBody=False, smooth=True, detectionCon=0.5, trackCon=0.5): self.mode = mode self.upBody = upBody self.smooth = smooth self.detectionCon = detectionCon self.trackCon = trackCon self.mpDraw = mp.solutions.drawing_utils self.mpPose = mp.solutions.pose self.pose = self.mpPose.Pose(self.mode, self.upBody, self.smooth, self.detectionCon, self.trackCon) def findPose(self, img, draw=True): imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) self.results = self.pose.process(imgRGB) if self.results.pose_landmarks: if draw: self.mpDraw.draw_landmarks(img, self.results.pose_landmarks, self.mpPose.POSE_CONNECTIONS) return img def findPosition(self, img, draw=True): self.lmList = [] if self.results.pose_landmarks: for id, lm in enumerate(self.results.pose_landmarks.landmark): h, w, c = img.shape # print(id, lm) cx, cy = int(lm.x * w), int(lm.y * h) self.lmList.append([id, cx, cy]) if draw: cv2.circle(img, (cx, cy), 5, (255, 0, 0), cv2.FILLED) return self.lmList def midpoint(self,img,p1,p2,draw=True): x1, y1 = self.lmList[p1][1:] x2, y2 = self.lmList[p2][1:] x3=int((x1+x2)/2) y3=int((y1+y2)/2) if draw: cv2.circle(img, (x3, y3), 10, (0, 0, 255), cv2.FILLED) cv2.circle(img, (x3, y3), 15, (0, 0, 255), 2) point={"x":x3,"y":y3} return point def findAngle(self, img, p1, p2, p3, draw=True): # Get the landmarks x1, y1 = self.lmList[p1][1:] x2, y2 = self.lmList[p2][1:] x3, y3 = self.lmList[p3][1:] # Calculate the Angle angle = math.degrees(math.atan2(y3 - y2, x3 - x2) - math.atan2(y1 - y2, x1 - x2)) if angle < 0: angle += 360 # print(angle) # Draw if draw: cv2.line(img, (x1, y1), (x2, y2), (255, 255, 255), 3) cv2.line(img, (x3, y3), (x2, y2), (255, 255, 255), 3) cv2.circle(img, (x1, y1), 10, (0, 0, 255), cv2.FILLED) cv2.circle(img, (x1, y1), 15, (0, 0, 255), 2) cv2.circle(img, (x2, y2), 10, (0, 0, 255), cv2.FILLED) cv2.circle(img, (x2, y2), 15, (0, 0, 255), 2) cv2.circle(img, (x3, y3), 10, (0, 0, 255), cv2.FILLED) cv2.circle(img, (x3, y3), 15, (0, 0, 255), 2) cv2.putText(img, str(int(angle)), (x2 - 50, y2 + 50), cv2.FONT_HERSHEY_PLAIN, 2, (0, 0, 255), 2) return angle
复制代码 【测试网络视频】
【存在的问题】 测试结果令人比较满意,但这里存在这样两个问题:1、标准点point_sd这个坐标是以视频开始第一帧画面是站在原地未起跳为前提。 2、标准点纵坐标的判定区间(point_sd["y"]+5与 point_sd["y"]+15)是根据运行后的数据人为分析出来的,只对这一段视频有效,不具有通用性。 【解决问题思路】 1、在正式跳绳计数前,先试跳,通过数据分析出标准点、判定区间(防止数据在判定点抖动,出现错误计数)。在上个程序中判定点为:point_sd["y"]+10。 2、以手势控制屏幕上的虚拟按钮来分析初始化数据,并启动跳绳计数及终止计数。 【解决问题步骤】
第一步:实现手势控制屏幕按钮。 程序中使用了计时器,以防止连续触发问题。
import cv2 import numpy as np import time import os import HandTrackingModule as htm ####################### brushThickness = 25 eraserThickness = 100 ######################## drawColor = (255, 0, 255) cap = cv2.VideoCapture(0) cap.set(3, 640) cap.set(4, 480) detector = htm.handDetector(detectionCon=0.65,maxHands=1) imgCanvas = np.zeros((480, 640, 3), np.uint8) rect=[(20, 20), (120, 120)] font = cv2.FONT_HERSHEY_SIMPLEX cv2.rectangle(imgCanvas, rect[0], rect[1],(0, 255, 0), 2) cv2.putText(imgCanvas, "SET", (45,85), font, 1, drawColor, 2) bs=0 bs2=0 while True: # 1. Import image success, img = cap.read() if success: img = cv2.flip(img, 1) # 2. Find Hand Landmarks img = detector.findHands(img) lmList = detector.findPosition(img, draw=False) if len(lmList) !=0: # tip of index and middle fingers x1, y1 = lmList[8][1:] x2, y2 = lmList[12][1:] # 3. Check which fingers are up fingers = detector.fingersUp() # print(fingers) # 5. Index finger is up if fingers[1] and fingers[2] == False: cv2.circle(img, (x1, y1), 15, drawColor, cv2.FILLED) if bs2==1: if time.time()-time_start>3: bs2=0 else: if x1>rect[0][0] and x1<rect[1][0] and y1>rect[0][1] and y1<rect[1][1]: if bs==0: print("OK") imgCanvas = np.zeros((480, 640, 3), np.uint8) cv2.rectangle(imgCanvas, rect[0], rect[1],(0, 255, 0), 2) cv2.putText(imgCanvas, "STOP", (30,85), font, 1, drawColor, 2) bs=1 bs2=1 time_start=time.time() else: imgCanvas = np.zeros((480, 640, 3), np.uint8) imgGray = cv2.cvtColor(imgCanvas, cv2.COLOR_BGR2GRAY) img = cv2.bitwise_or(img,imgCanvas) # img = cv2.addWeighted(img,0.5,imgCanvas,0.5,0) cv2.imshow("Image", img) cv2.waitKey(1)
复制代码
上面程序引用的“HandTrackingModule.py”文件。
import cv2 import mediapipe as mp import time import math import numpy as np class handDetector(): def __init__(self, mode=False, maxHands=2, detectionCon=0.8, trackCon=0.5): self.mode = mode self.maxHands = maxHands self.detectionCon = detectionCon self.trackCon = trackCon self.mpHands = mp.solutions.hands self.hands = self.mpHands.Hands(self.mode, self.maxHands, self.detectionCon, self.trackCon) self.mpDraw = mp.solutions.drawing_utils self.tipIds = [4, 8, 12, 16, 20] def findHands(self, img, draw=True): imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) self.results = self.hands.process(imgRGB) # print(results.multi_hand_landmarks) if self.results.multi_hand_landmarks: for handLms in self.results.multi_hand_landmarks: if draw: self.mpDraw.draw_landmarks(img, handLms, self.mpHands.HAND_CONNECTIONS) return img def findPosition(self, img, handNo=0, draw=True): xList = [] yList = [] bbox = [] self.lmList = [] if self.results.multi_hand_landmarks: myHand = self.results.multi_hand_landmarks[handNo] for id, lm in enumerate(myHand.landmark): # print(id, lm) h, w, c = img.shape cx, cy = int(lm.x * w), int(lm.y * h) xList.append(cx) yList.append(cy) # print(id, cx, cy) self.lmList.append([id, cx, cy]) if draw: cv2.circle(img, (cx, cy), 5, (255, 0, 255), cv2.FILLED) xmin, xmax = min(xList), max(xList) ymin, ymax = min(yList), max(yList) bbox = xmin, ymin, xmax, ymax if draw: cv2.rectangle(img, (xmin - 20, ymin - 20), (xmax + 20, ymax + 20), (0, 255, 0), 2) return self.lmList def fingersUp(self): fingers = [] # Thumb if self.lmList[self.tipIds[0]][1] > self.lmList[self.tipIds[0] - 1][1]: fingers.append(1) else: fingers.append(0) # Fingers for id in range(1, 5): if self.lmList[self.tipIds[id]][2] < self.lmList[self.tipIds[id] - 2][2]: fingers.append(1) else: fingers.append(0) # totalFingers = fingers.count(1) return fingers def findDistance(self, p1, p2, img, draw=True,r=15, t=3): x1, y1 = self.lmList[p1][1:] x2, y2 = self.lmList[p2][1:] cx, cy = (x1 + x2) // 2, (y1 + y2) // 2 if draw: cv2.line(img, (x1, y1), (x2, y2), (255, 0, 255), t) cv2.circle(img, (x1, y1), r, (255, 0, 255), cv2.FILLED) cv2.circle(img, (x2, y2), r, (255, 0, 255), cv2.FILLED) cv2.circle(img, (cx, cy), r, (0, 0, 255), cv2.FILLED) length = math.hypot(x2 - x1, y2 - y1) return length, img, [x1, y1, x2, y2, cx, cy]
复制代码 第二步,分析数据,得到判定点纵坐标。思路是,坐标数据是上下波动,将数据中的波峰和波谷分别提取出来计算均值,然后取中值,和差值。中值为判定点,差值用来确定判定区域。波峰和波谷的判定采用的是两边数据与当前数据做差值看差值方向,如果方向相反,即为峰值。但这里就存在,Mediapipe识别准确度的问题,可能在上升或下降的过程中数据不平滑,出现数据波动。可能在分析时,出现误判,采集到错误的峰值。后期可采用滤波算法处理此问题。现在看效果,还不错。
import numpy as np import time import cv2 import PoseModule as pm import math def max_min(a): h = [] l = [] for i in range(1, len(a)-1): if(a[i-1] < a[i] and a[i+1] < a[i]): h.append(a[i]) elif(a[i-1] > a[i] and a[i+1] > a[i]): l.append(a[i]) if(len(h) == 0): h.append(max(a)) if(len(l) == 0): l.append(min(a[a.index(max(a)):])) mid=(np.mean(h)+np.mean(l))/2 print(int(mid),int(np.mean(h)-np.mean(l))) return(int(mid),int(np.mean(h)-np.mean(l))) cap = cv2.VideoCapture("tiaosheng.mp4") detector = pm.poseDetector() count = 0 dir = 0 pTime = 0 success=True point=[] while success: success, img = cap.read() if success: img = cv2.resize(img, (640, 480)) img = detector.findPose(img, False) lmList = detector.findPosition(img, False) if len(lmList) != 0: point_tem=detector.midpoint(img, 24, 23) point.append(point_tem['y']) cv2.putText(img, str(point_tem['y']), (45, 460), cv2.FONT_HERSHEY_PLAIN, 7,(255, 0, 0), 8) cTime = time.time() fps = 1 / (cTime - pTime) pTime = cTime cv2.putText(img, str(int(fps)), (50, 100), cv2.FONT_HERSHEY_PLAIN, 5,(255, 0, 0), 5) cv2.imshow("Image", img) cv2.waitKey(1) max_min(point) cap.release() cv2.destroyAllWindows()
复制代码
最终得到“304 26”为“中值 差值”
【完整程序】
将以上分段程序进行整合,得到完整程序,并进行实地测试。(纯手工敲码)
import cv2 import numpy as np import time import os import HandTrackingModule as htm import PoseModule as pm #计算判定点 def max_min(a): h = [] l = [] for i in range(1, len(a)-1): if(a[i-1] < a[i] and a[i+1] < a[i]): h.append(a[i]) elif(a[i-1] > a[i] and a[i+1] > a[i]): l.append(a[i]) if(len(h) == 0): h.append(max(a)) if(len(l) == 0): l.append(min(a[a.index(max(a)):])) mid=(np.mean(h)+np.mean(l))/2 print(int(mid),int(np.mean(h)-np.mean(l))) return(int(mid),int(np.mean(h)-np.mean(l))) ####################### brushThickness = 25 eraserThickness = 100 ######################## drawColor = (255, 0, 255) cap = cv2.VideoCapture(0) cap.set(3, 640) cap.set(4, 480) detector_hand = htm.handDetector(detectionCon=0.65,maxHands=1) detector_pose = pm.poseDetector() imgCanvas = np.zeros((480, 640, 3), np.uint8) rect=[(20, 20), (120, 120)] font = cv2.FONT_HERSHEY_SIMPLEX cv2.rectangle(imgCanvas, rect[0], rect[1],(0, 255, 0), 2) cv2.putText(imgCanvas, "SET", (45,85), font, 1, drawColor, 2) bs=0 bs2=0 bs3=0 point=[] count=0 pTime = 0 dire=0 while True: success, img = cap.read() if success: img = cv2.flip(img, 1) if bs==1 and bs2==0: if bs3==1: if time.time()-time_start<4: cv2.putText(img, str(3-int(time.time()-time_start)), (300, 240), cv2.FONT_HERSHEY_PLAIN, 10,(255, 255, 0), 5) else: bs3=0 time_start=time.time() else: if time.time()-time_start<11: img = detector_pose.findPose(img, False) lmList = detector_pose.findPosition(img, False) if len(lmList) != 0: point_tem=detector_pose.midpoint(img, 24, 23) point.append(point_tem['y']) cv2.putText(img, str(point_tem['y']), (45, 460), cv2.FONT_HERSHEY_PLAIN, 7,(255, 0, 0), 8) cv2.putText(img, str(10-int(time.time()-time_start)), (500, 460), cv2.FONT_HERSHEY_PLAIN, 10,(255, 255, 0), 5) else: point_sd,l=max_min(point) bs=2 cv2.rectangle(imgCanvas, rect[0], rect[1],(0, 255, 0), 2) cv2.putText(imgCanvas, "START", (30,85), font, 1, drawColor, 2) if bs==3 and bs2==0: if bs3==1: if time.time()-time_start<4: cv2.putText(img, str(3-int(time.time()-time_start)), (300, 240), cv2.FONT_HERSHEY_PLAIN, 10,(255, 255, 0), 5) else: bs3=0 time_start=time.time() else: img = detector_pose.findPose(img, False) lmList = detector_pose.findPosition(img, False) if len(lmList) != 0: point = detector_pose.midpoint(img, 24, 23) if point["y"]> point_sd+l/4: if dire == 0: count += 0.5 dire = 1 if point["y"]<point_sd-l/4: if dire == 1: count += 0.5 dire = 0 cv2.putText(img, str(int(count)), (45, 460), cv2.FONT_HERSHEY_PLAIN, 7,(255, 0, 0), 8) if bs2==1:#等待三秒 if time.time()-time_start>4: bs2=0 time_start=time.time() else: #手势操作 img = detector_hand.findHands(img) lmList = detector_hand.findPosition(img, draw=False) if len(lmList) !=0: x1, y1 = lmList[8][1:] x2, y2 = lmList[12][1:] fingers = detector_hand.fingersUp() #出示食指 if fingers[1] and fingers[2] == False: cv2.circle(img, (x1, y1), 15, drawColor, cv2.FILLED) if x1>rect[0][0] and x1<rect[1][0] and y1>rect[0][1] and y1<rect[1][1]:#食指进入按钮区域 if bs==0: print("OK") imgCanvas = np.zeros((480, 640, 3), np.uint8) bs=1 bs2=1 bs3=1 time_start=time.time() elif bs==1: imgCanvas = np.zeros((480, 640, 3), np.uint8) bs2=1 bs3=1 time_start=time.time() elif bs==2: imgCanvas = np.zeros((480, 640, 3), np.uint8) cv2.rectangle(imgCanvas, rect[0], rect[1],(0, 255, 0), 2) cv2.putText(imgCanvas, "STOP", (30,85), font, 1, drawColor, 2) bs=3 bs2=1 bs3=1 time_start=time.time() elif bs==3: imgCanvas = np.zeros((480, 640, 3), np.uint8) cv2.rectangle(imgCanvas, rect[0], rect[1],(0, 255, 0), 2) cv2.putText(imgCanvas, "START", (30,85), font, 1, drawColor, 2) bs=2 bs2=1 bs3=1 time_start=time.time() cTime = time.time() fps = 1 / (cTime - pTime) pTime = cTime cv2.putText(img, str(int(fps)), (500, 100), cv2.FONT_HERSHEY_PLAIN, 5,(255, 0, 0), 5) imgGray = cv2.cvtColor(imgCanvas, cv2.COLOR_BGR2GRAY) img = cv2.bitwise_or(img,imgCanvas) cv2.imshow("Image", img) cv2.waitKey(1)
复制代码
【计数炫灯】 使用Pinpong库,连接Micro:bit,控制LED灯随跳绳次数增加亮灯数。
|