【项目背景】 跳绳是一个很好的健身项目,为了获知所跳个数,有的跳绳上会有计数器。但这也只能跳完这后看到,能不能在跳的过程中就能看到,这样能让我们坚持跳的更多,更有趣味性。 【项目设计】   通过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灯随跳绳次数增加亮灯数。
 
     |