计算机视觉入门(实战篇:手势控制虚拟鼠标)

手势控制虚拟鼠标
本代码基于 Advance Computer Vision with Python 进行修改,更加适合中国宝宝体质

我的相关代码及数据集已经上传GitHub仓库,欢迎使用 Advance-Computer-Vision-with-Python

AIVirtualMouseProject.py

import cv2
import numpy as np
import HandTrackingModule as htm
import time
import autopy

##########################
wCam, hCam = 640, 480
frameR = 100  # 边框缩减
smoothening = 7  # 平滑系数
#########################
# 以上是用来在代码中创建视觉分隔线的注释。它们可以帮助开发者更清晰地分隔代码的不同部分,使代码更易读。这些行本身并没有功能,只是为了提高代码的可读性

pTime = 0
plocX, plocY = 0, 0
clocX, clocY = 0, 0

cap = cv2.VideoCapture(0)
cap.set(3, wCam)
cap.set(4, hCam)
detector = htm.handDetector(maxHands=1)
wScr, hScr = autopy.screen.size()  # 获取屏幕的宽度和高度
# autopy.screen.size() 返回屏幕的尺寸,以像素为单位
# wScr 是屏幕的宽度,hScr 是屏幕的高度
# print(wScr, hScr)

while True:
    # 1. 检测手部标志点
    success, img = cap.read()
    img = cv2.flip(img, 1)  # 水平翻转图像
    img = detector.findHands(img)
    lmList, bbox = detector.findPosition(img)

    # 2. 获取食指和中指指尖的位置
    if len(lmList) != 0:
        x1, y1 = lmList[8][1:]
        x2, y2 = lmList[12][1:]
        # print(x1, y1, x2, y2)

        # 3. 检查哪些手指是抬起的
        fingers = detector.fingersUp()
        # print(fingers)
        cv2.rectangle(
            img, (frameR, frameR), (wCam - frameR, hCam - frameR), (255, 0, 255), 2
        )

        # 4. 只有食指抬起:移动模式
        if fingers[1] == 1 and fingers[2] == 0:
            # 5. 转换坐标
            x3 = np.interp(x1, (frameR, wCam - frameR), (0, wScr))
            y3 = np.interp(y1, (frameR, hCam - frameR), (0, hScr))
            # np.interp() 是一个用于线性插值的函数。它的基本用法: np.interp(x, xp, fp)
            # 其中,x 为需要插值的点,xp 为输入数据点的范围(已知的 x 值),fp 为输出数据点的范围(已知的 y 值)
            # 在这里,x1 和 y1 是手指在摄像头图像中的位置,(frameR, wCam - frameR) 是摄像头图像中手指活动的范围,(0, wScr) 和 (0, hScr) 是屏幕的坐标范围
            # 通过 np.interp(),程序将手指在摄像头图像中的位置映射到屏幕的坐标上,从而实现手指移动与鼠标光标移动的对应关系。这使得手指在摄像头中的移动能够控制屏幕上的鼠标光标

            # 6. 平滑处理数值
            clocX = plocX + (x3 - plocX) / smoothening
            clocY = plocY + (y3 - plocY) / smoothening
            # clocX 和 clocY 是当前平滑处理后的坐标,plocX 和 plocY 是前一帧的坐标
            # x3 和 y3 是当前帧经过插值后的目标坐标,smoothening 是一个平滑系数,用于控制平滑的程度
            # 每次更新时,当前位置(clocX, clocY)向目标位置(x3, y3)移动一小部分,smoothening 控制移动的步长,值越大,移动越慢,越平滑
            # 平滑处理会让鼠标指针移动稍微慢一些,但这样做的目的是为了减少抖动,使移动更加平稳
            # 如果你觉得移动速度太慢,可以尝试减小 smoothening 的值,这样指针会更快地跟随手指移动

            # 7. 移动鼠标
            # autopy.mouse.move(wScr - clocX, clocY)
            # 如果没有翻转,则使用上面这条语句
            autopy.mouse.move(clocX, clocY)
            cv2.circle(img, (x1, y1), 15, (255, 0, 255), cv2.FILLED)
            plocX, plocY = clocX, clocY

        # 8. 食指和中指都抬起:点击模式
        if fingers[1] == 1 and fingers[2] == 1:
            # 9. 计算两指间的距离
            length, img, lineInfo = detector.findDistance(8, 12, img)
            print(length)

            # 10. 如果距离很短,则点击鼠标
            if length < 40:
                cv2.circle(img, (lineInfo[4], lineInfo[5]), 15, (0, 255, 0), cv2.FILLED)
                autopy.mouse.click()

    # 11. 帧率
    cTime = time.time()
    fps = 1 / (cTime - pTime)
    pTime = cTime
    cv2.putText(img, str(int(fps)), (20, 50), cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)

    # 12. 显示
    cv2.imshow("Image", img)
    cv2.waitKey(1)

HandTrackingModule.py

"""
Hand Tracking Module
By: Murtaza Hassan
Youtube: http://www.youtube.com/c/MurtazasWorkshopRoboticsandAI
Website: https://www.computervision.zone

Modified by: Diraw
Date: 20240812
Description:
1. Modified the initialization of the `Hands` object to use named parameters for better clarity and compatibility with the latest version of the mediapipe library. This change ensures that the parameters are correctly mapped to the expected arguments in the `Hands` class.
2. Added a line to flip the image horizontally using `cv2.flip(img, 1)` to ensure the hand movements appear mirrored, which is more intuitive for user interaction
3. Updated the findPosition method to check if xList and yList are not empty before calculating the minimum and maximum values. This prevents errors when no hand landmarks are detected.
"""

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.5, trackCon=0.5):
        # 初始化手部检测器
        self.mode = mode  # 模式:静态或动态
        self.maxHands = maxHands  # 最大检测手数
        self.detectionCon = detectionCon  # 检测置信度
        self.trackCon = trackCon  # 跟踪置信度

        self.mpHands = mp.solutions.hands  # Mediapipe手部解决方案
        # self.hands = self.mpHands.Hands(
        #     self.mode, self.maxHands, self.detectionCon, self.trackCon
        # )
        self.hands = self.mpHands.Hands(
            static_image_mode=self.mode,
            max_num_hands=self.maxHands,
            min_detection_confidence=self.detectionCon,
            min_tracking_confidence=self.trackCon,
        )
        self.mpDraw = mp.solutions.drawing_utils  # 绘图工具
        self.tipIds = [4, 8, 12, 16, 20]  # 指尖的ID

    def findHands(self, img, draw=True):
        # 查找手部并绘制
        imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 转换为RGB
        self.results = self.hands.process(imgRGB)  # 处理图像

        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 = []  # X坐标列表
        yList = []  # Y坐标列表
        bbox = []  # 边界框
        self.lmList = []  # 存储手部关键点

        if self.results.multi_hand_landmarks:
            myHand = self.results.multi_hand_landmarks[handNo]
            for id, lm in enumerate(myHand.landmark):
                h, w, c = img.shape  # 获取图像尺寸
                cx, cy = int(lm.x * w), int(lm.y * h)  # 转换为像素坐标
                xList.append(cx)
                yList.append(cy)
                self.lmList.append([id, cx, cy])
                if draw:
                    cv2.circle(img, (cx, cy), 5, (255, 0, 255), cv2.FILLED)  # 绘制关键点

        if xList and yList:  # 检查列表是否不为空
            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, bbox

    def fingersUp(self):
        # 判断手指是否竖起
        fingers = []
        # 拇指
        if self.lmList[self.tipIds[0]][1] < self.lmList[self.tipIds[0] - 1][1]:
            # 同前几个Project,翻转前判断右手是if self.lmList[self.tipIds[0]][1] > self.lmList[self.tipIds[0] - 1][1]:
            # 翻转后,变大于为小于号
            fingers.append(1)
        else:
            fingers.append(0)

        # 其他手指
        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)

        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]


def main():
    # 主函数
    pTime = 0  # 上一帧时间
    cTime = 0  # 当前时间
    cap = cv2.VideoCapture(0)  # 打开摄像头
    detector = handDetector()  # 创建手部检测器

    while True:
        success, img = cap.read()  # 读取摄像头图像
        img = cv2.flip(img, 1)  # 水平翻转图像
        img = detector.findHands(img)  # 查找手部
        lmList, bbox = detector.findPosition(img)  # 获取手部位置
        if len(lmList) != 0:
            print(lmList[4])  # 打印大拇指位置

        cTime = time.time()
        fps = 1 / (cTime - pTime)  # 计算帧率
        pTime = cTime

        cv2.putText(
            img, str(int(fps)), (10, 70), cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 255), 3
        )  # 显示帧率

        cv2.imshow("Image", img)  # 显示图像
        cv2.waitKey(1)  # 等待按键


if __name__ == "__main__":
    main()  # 运行主函数

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值