手势识别是人机交互中的常用功能。本文演示如何用 Python + MediaPipe + OpenCV 实现实时手指计数,并在摄像头画面上显示左右手手指数量。用这个模块来判断运动员的号码


功能亮点

  1. 实时检测左右手。

  2. 自动识别手指伸直情况。

  3. 可显示左右手手指数量。

  4. 可扩展为手势控制、游戏交互等场景。


环境准备


pip install mediapipe opencv-python

Python 3.8+ 推荐。


核心思路

  1. MediaPipe Hands 用于识别手部关键点。

  2. 拇指根据左右手不同方向判断是否伸直。

  3. 其他四指根据指尖与指根(PIP 关节)y 坐标判断伸直状态。

  4. 在画面上显示左右手的手指数量。


代码实现


import mediapipe as mp
import cv2
class FingerCounter:
    def __init__(self):
        mp_hands = mp.solutions.hands
        self.hands = mp_hands.Hands(
            static_image_mode=False, max_num_hands=2, min_detection_confidence=0.5
        )
        self.drawing = mp.solutions.drawing_utils

    @staticmethod
    def count_fingers(lms, hand_label):  # 新增 hand_label 参数
        tips = [4, 8, 12, 16, 20]
        fingers = 0

        # 拇指:根据左右手调整判断方向
        if hand_label == "Right":
            # 右手:拇指尖 x < 关节 x 表示张开
            if lms.landmark[tips[0]].x < lms.landmark[tips[0] - 1].x:
                fingers += 1
        else:  # Left hand
            # 左手:拇指尖 x > 关节 x 表示张开(镜像)
            if lms.landmark[tips[0]].x > lms.landmark[tips[0] - 1].x:
                fingers += 1

        # 其他四指:指尖 y < 指根(PIP关节,即 index-2)表示伸直(通用)
        for i in range(1, 5):
            if lms.landmark[tips[i]].y < lms.landmark[tips[i] - 2].y:
                fingers += 1

        return fingers

    def detect(self, frame, left_kp, right_kp, fps, draw=False,save_frame=False, save_path="frame.jpg"):
        """
        返回:数字总和
        触发条件:至少一只手腕高于肩,并保持 0.5 秒
        """

        if not hasattr(self, "raise_frames"):
            self.raise_frames = 0
        if save_frame:
            cv2.imwrite(save_path, frame)
        left_sh, left_el, left_wr = left_kp
        right_sh, right_el, right_wr = right_kp

        # 判断哪只手举起
        right_up = left_wr[1] < left_sh[1]
        left_up = right_wr[1] < right_sh[1]

        any_up = left_up or right_up
        half_sec_frames = int(1 * fps)

        if any_up:
            self.raise_frames += 1
        else:
            # 一旦手放下立即重置,避免残留
            self.raise_frames = 0

        # print("raise_frames:", self.raise_frames)

        # 未达到持续时间,直接返回 None
        if self.raise_frames <= half_sec_frames:
            return None

        # -----------------------
        # 到达触发条件 → 开始识别手指
        # -----------------------
        results = self.hands.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))

        left_num = right_num = 0

        if results.multi_hand_landmarks:
            for lm, handed in zip(results.multi_hand_landmarks, results.multi_handedness):
                if draw:
                    self.drawing.draw_landmarks(frame, lm, mp.solutions.hands.HAND_CONNECTIONS)

                num = self.count_fingers(lm, handed.classification[0].label)

                if handed.classification[0].label == "Left":
                    left_num = num
                else:
                    right_num = num
                # print("left_num,right_num :",left_num,right_num)

        # -----------------------
        # 关键修正:根据举手模式返回数字
        # -----------------------
        if left_up and not right_up:
            # print("left_up")
            return left_num

        if right_up and not left_up:
            # print("right_up")
            return right_num

        if left_up and right_up:
            return left_num + right_num

        return None

def main():
    fc = FingerCounter()
    cap = cv2.VideoCapture(0)   # 打开默认摄像头

    fps = cap.get(cv2.CAP_PROP_FPS)
    if fps == 0:
        fps = 30   # 某些摄像头不返回fps,默认30

    print("摄像头已启动,按 q 退出")

    while True:
        ret, frame = cap.read()
        frame = cv2.flip(frame, 1)
        if not ret:
            break

        # 你目前 detect() 的参数里必须传肩、肘、腕坐标,但摄像头里没有这些数据
        # 所以这里先传假值(手腕永远不高于肩部 → 不会触发计数)
        # 你只是测试 count_fingers() 准不准,所以直接绕过触发条件

        # ---------------------------------------------------
        # 绕过触发条件:直接手动调用 hands 识别然后 count_fingers
        # ---------------------------------------------------
        results = fc.hands.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))

        if results.multi_hand_landmarks:
            for lm, handed in zip(results.multi_hand_landmarks, results.multi_handedness):
                # 绘制
                fc.drawing.draw_landmarks(frame, lm, mp.solutions.hands.HAND_CONNECTIONS)

                # 数手指
                num = fc.count_fingers(lm, handed.classification[0].label)

                # 在画面上显示
                label = handed.classification[0].label
                cv2.putText(frame, f"{label}: {num}", (10, 40 if label=='Left' else 80),
                            cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0,255,0), 3)

        cv2.imshow("Finger Counter Test", frame)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()


if __name__ == "__main__":
    main()

使用说明

  1. 运行脚本,会打开摄像头窗口。

  2. 将手放在摄像头前,可以看到左右手手指数量实时显示。

  3. q 关闭窗口。

源码自取:通过网盘分享的文件:basketball_foul_detect.zip
链接: https://pan.baidu.com/s/1kwi9Mn_v97PBRb7kn5sFRw?pwd=emd3 提取码: emd3 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐