计算机视觉-新手项目实战(opencv-手势识别-鼠标控制)

这篇文章是自己跟着油管大神做的,(有条件的建议自己搭梯子跟着去做一遍Hand Tracking 30 FPS using CPU | OpenCV Python (2021) | Computer Vision - YouTube)由于基础不太好,我就自己做了一遍,里面做了大量注释,有opencv的,也有python基础语法的,希望对做计算机视觉的新手有所帮助,大佬就可以略过啦。有什么问题欢迎指出。

首先做一个小model,用于满足手部追踪的最低要求handTrackingMin.py,后面的模块在此基础上增加功能

import cv2
import mediapipe as mp
import time

cap = cv2.VideoCapture(1)
mpHands = mp.solutions.hands
hands = mpHands.Hands()
mpDraw = mp.solutions.drawing_utils  # 直接调用mediapipe的坐标绘制方法

preTime = 0
curTime = 0  # 先定义好前一帧的时间和当前时间,用以描述帧率

while True:
    success, img = cap.read()
    imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 图像色彩转换,因为hands = mpHands.Hands()这个对象只能读入RGB图像信息
    results = hands.process(imgRGB)
    # print(reults.multi_hand_landmarks)  # 打印输出landmark,若未检测到,就输出None,检测到了就输出坐标值
    if results.mult_hand_landmarks:
        for handLms in results.mult_hand_landmarks:  # 遍历每只手的坐标 共21个坐标
            for id, lm in enumerate(handLms.landmarks):  # 将其中一只手的每个关键点的id(序号)和坐标进行列举出来
                # enumerate是python内置函数,用于枚举、列举,同时对列出的内容进行编号,因此可以同时获得索引和值,索引从0开始,
                # 其有两个参数,第一个参数为要枚举的序列,第二个参数为起始序号,默认为0开始

                # print(id, lm)

                h, w, c = img.shape  # 获取图像尺寸
                cx, cy = int(lm.x * w), int(lm.y * h)  # 坐标乘以相应尺寸,并转换成整数
                print(id, cx, cy)
                # """但现在的问题是,我们有了坐标值,但这些坐标值是小数,对于图片像素点来说是不好映射的,它实际上是相对于图像的比例值,因此为了能映射到
                #     实际图像上,需要乘以图像的尺寸"""

                cv2.circle(img, (cx, cy), 25, (255, 255, 0))  # 为了能明显区分每一个landmark,对其进行标注

            mpDraw.draw_landmarks(img, handLms,
                                  mpHands.HAND_CONNECTIONS)  # 第一个参数是传入的图片,第二个参数是传入的坐标,这个方法可以将各个landmark绘制在画面中,
            # 第三个参数将各个点连在一起
            """现在的问题在于,但我们不知道如果运用这些值。管他有没有用, 先将序号和坐标存入一个列表中,需要时就调用"""

    curTime = time.time()  # 获取当前帧的时间
    fps = 1 / (curTime - preTime)  # 帧率
    preTime = curTime  # 时间更新
    cv2.putText(img, str(int(fps)), (255, 10), cv2.FONT_HERSHEY_SIMPLEX,
                3, (255, 0, 255), None)  # 在画面上显示帧率,并四舍五入为整数

    cv2.imshow(img)
    cv2.waitKey(1)

正式创建了一个手部跟踪模块handTrackingModule.py

import cv2
import mediapipe as mp
import time

class handDetector:  # 创建一个手部检测器类,他是根据mediapipe中的hands模块来创建的


    def __init__(self, static_image_mode = False,  # 是否将输入图像看作静态图像,False代表当作视频
        max_num_hands = 2,  # 最大检测数量
        model_complexity = 1,   #模型复杂度(0或1),landmark准确率和推理延迟通常会随着复杂度升高而升高
        min_detection_confidence = 0.5,  # 最小检测置信度
        min_tracking_confidence = 0.5 ):  # 最小追踪置信度

        self.mode = static_image_mode
        self.maxHands = max_num_hands
        self.detectionCon = min_detection_confidence
        self.trackCon = min_tracking_confidence

        self.mpHands = mp.solutions.hands
        self.hands = self.mpHands.Hands(self.mode, self.maxHands, self.detectionCon, self.trackCon)
        self.mpDraw = mp.solutions.drawing_utils  # 直接调用mediapipe的坐标绘制方法


    def findHands(self, img, draw=True):  # 其中draw=True可以决定是否想要画出landmark及其连接线
        imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 图像色彩转换,因为hands = mpHands.Hands()这个对象只能读入RGB图像信息
        self.results = self.hands.process(imgRGB)  # 这里定义了一个实例变量
        # print(reults.multi_hand_landmarks)  # 打印输出landmark,若未检测到,就输出None,检测到了就输出坐标值
        if self.results.mult_hand_landmarks:
            for handLms in self.results.mult_hand_landmarks:  # 遍历每只手的坐标 共21个坐标
                if draw:
                    self.mpDraw.draw_landmarks(img, handLms,
                                      self.mpHands.HAND_CONNECTIONS)  # 第一个参数是传入的图片,第二个参数是传入的坐标,这个方法可以将各个landmark绘制在画面中,
                # 第三个参数将各个点连在一起
        return img  # 返回已经绘制好连接点的图像
        """现在的问题在于,但我们不知道如果运用这些值。管他有没有用, 先将序号和坐标存入一个列表中,需要时就调用.因此,我们需要创建一个findPosition函数来将"""


    def findPosition(self, img, handNumber, draw=True):  # 在这里,我们并不需要图像的各项参数,只要它的尺寸,用来确定位置信息,其中handNumber表示手的序号
        lmList = []  # 定义一个空列表,用于接收后面产生的landmark序号及坐标
        # 判断是否检测到有手部信息,如果有,则输出序号及位置
        if self.results.mult_hand_landmarks:
            myHand = self.results.mult_hand_landmarks[handNumber]  # 将手的序号传给myHand
            for id, lm in enumerate(myHand.landmarks):  # 将其中一只手的每个关键点的id(序号)和坐标进行列举出来
                # enumerate是python内置函数,用于枚举、列举,同时对列出的内容进行编号,因此可以同时获得索引和值,索引从0开始,
                # 其有两个参数,第一个参数为要枚举的序列,第二个参数为起始序号,默认为0开始

                # print(id, lm)

                h, w, c = img.shape  # 获取图像尺寸
                cx, cy = int(lm.x * w), int(lm.y * h)  # 坐标乘以相应尺寸,并转换成整数
                # print(id, cx, cy)
                lmList.append([id, cx, cy ])  # 注意:这里lmList追加的是一个列表,追加之后就成了两级列表,如:[[id1, cx1, cy1], [id2, cx2, cy2], [id3, cx3, cy3]......]
                # """但现在的问题是,我们有了坐标值,但这些坐标值是小数,对于图片像素点来说是不好映射的,它实际上是相对于图像的比例值,因此为了能映射到
                #     实际图像上,需要乘以图像的尺寸"""
                if draw:
                    cv2.circle(img, (cx, cy), 25, (255, 255, 0), cv2.FILLED)  # 为了能明显区分每一个landmark,对其进行标注

        return lmList




def main():  # 以下代码可以用在不同项目中
    preTime = 0
    curTime = 0  # 先定义好前一帧的时间和当前时间,用以描述帧率
    cap = cv2.VideoCapture(1)
    detector = handDetector()  # 创建一个类外实例,这里无需参数,因为已经默认设置好了,注意:创建类外实例时无需self

    while True:
        success, img = cap.read()
        img = detector.findHands(img)
        lmList =detector.findPosition(img)  # 接收序号及位置信息
        if len(lmList) != 0: # 判断列表是否为空,若不为空则打印,如果没有此判断句,当程序运行时没有检测到手,lmList就没有值,就会报错(Index out of range),因此一定要加
            print(lmList[4])  # 这里我们尝试打印4号位置(大拇指尖)的序号及坐标信息

        curTime = time.time()  # 获取当前帧的时间
        fps = 1 / (curTime - preTime)  # 帧率
        preTime = curTime  # 时间更新
        cv2.putText(img, str(int(fps)), (255, 10), cv2.FONT_HERSHEY_SIMPLEX,
                    3, (255, 0, 255), None)  # 在画面上显示帧率,并四舍五入为整数

        cv2.imshow(img)
        cv2.waitKey(1)


if __name__ == "__main__":  # 在本py文件中,这段话表明程序的入口,当单独执行本py文件的时候,if __name__ == "__main__":下面的
    # 语句会自动执行,但本py文件被当作模块导入时,if __name__ == "__main__":下面的语句不会被执行
    main()

最后实现鼠标追踪AiVirtualMouse.py,其中调用了上面的手部追踪模块

import numpy as np

import handTrackModule as htm
import cv2
import numpy
import time
import autopy  # 它包括用于控制键盘和鼠标,在屏幕上查找颜色和位图以及显示警报的功能

###################################
wCam, hCam = 640, 480  # 定义画面尺寸
framR = 100  # 限定的尺寸
smoothening = 7  # 平滑度
###################################
cap = cv2.VideoCapture(1)

cap.set(3, wCam)
cap.set(4, hCam)  # 定义画面尺寸
pTime = 0

preLocaX, preLocaY = 0, 0  # 先前的坐标值
curLocaX, curLocaY = 0, 0  # 当前的坐标值

detector = htm.handDetector(maxHands=1)  # 检测器,检测最大数量
wScr, hScr = autopy.screen.size()  # 自动获取屏幕尺寸

while True:
    # 1. find hand landmarks
    success, img = cap.read()  # 读取视频内容
    img = detector.findHands(img)  # 如果想要画出landmark及其连线,加入draw=True
    lmlist, bbox = detector.findPosition(img)  # 获取整只手的坐标,存入lmlist,bbox为边界框数组;findPosition(img)中有两个元组,一个代表lmlist,一个代表bbox
    # 如果想要画出●圆圈作为标记点,在后面加draw=True

    # 2. 获得食指和中指的指尖
    if len(lmlist) != 0:
        x1, y1 = lmlist[8][1:]  # 食指指尖坐标
        x2, y2 = lmlist[12][1:]  # 中指指尖坐标

        print(x1, y1, x2, y2)

    # 3. 检查哪个指尖向上

    fingers = detector.fingersUp()  # 将5个值作为数组传给fingers[1,1,1,1,1] 其中1代表指尖向上,0表示无指尖,一次代表大拇指到小拇指
    cv2.rectangle(img, (framR, framR), (wCam - framR, hCam - framR), (255, 255, 0), 2)  # 画框,设置框尺寸 rectangle参数:图像源,左上顶点坐标,右下对角线顶点坐标,颜色,粗细,线条类型


    # 4. 仅检测到食指:移动模式

    if fingers[1] == 1 and fingers[2] == 0:  # 食指为1,中指为0

        # 5. 转换坐标,用来检测食指移动到哪里,然后将坐标发送给鼠标

        x3 = np.interp(x1, (framR, wCam - framR), (0, wScr))  # 线性插值,将手指相对于摄像头画面的位置转换为鼠标相对于屏幕的位置
        y3 = np.interp(y1, (framR, hCam - framR), (0, hScr))

    # 6. 设置平滑值

    curLocaX = preLocaX + (x3 - preLocaX) / smoothening  # 平滑处理,如果smoothening越大,动作越平滑,但数值大会产生运动过慢和运动滞后的问题,
    curLocaY = preLocaY + (y3 - preLocaY) / smoothening
    # 7. 移动鼠标

    autopy.mouse.move(wScr - curLocaX, curLocaY)  # 将转换过的坐标发送给鼠标
    cv2.circle(img, (x1, y1), 15, (255, 0, 255), cv2.FILLED)  # 以当前坐标为圆心画圆圈,用以表示手指位置
    preLocaX, preLocaY = curLocaX, curLocaY  # 对位置坐标进行迭代
    """但出现了一个问题,当手往下移动的时候,由于检测不到手的全貌,导致鼠标无法停留在屏幕最下方,因此需要划定一个操作区域,于是设置 framR=100 """

    # 8. 食指和中指都向上,点击模式
    if fingers[1] == 1 and fingers[2] == 1:  # 食指为1,中指也为1
        # 9. 检测指间距离
        length, img, infoLine, = detector.findDistance(8, 12, img)  # infoLine表示信息线
        # 10. 如果距离短,点击鼠标
        if length < 40:  # 设定一个阈值,当两指指尖小于这个阈值,才被视为点击模式
            cv2.circle(img, (infoLine[4], infoLine[5]), 15, (0, 255, 255), cv2.FILLED)   # 在两指之间画圈,其中(infoLine[4], infoLine[5])表示两指之间圆心坐标
            autopy.mouse.click()  # 点击

    """但问题在于,由于是一帧一帧进行检测,坐标会发生抖动,最终会使点击不准确,于是设置平滑度,第6步"""




    # 11. 帧率

    cTime = time.time()  # 获取当前时间
    fps = 1 / (cTime - pTime)  # 获取帧率
    pTime = cTime
    cv2.putText(img, str(int(fps)), (40, 70), cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 255, 0), 3)  # 设置文字及其格式
    # 12. 显示
    cv2.namedWindow()
    cv2.imshow("Image", img)
    cv2.waitKey(1)

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值