当前位置:服务支持 >  软件文章 >  ESP32CAM结合OPENCV实现创新手势操控鼠标方案

ESP32CAM结合OPENCV实现创新手势操控鼠标方案

阅读数 31
点赞 0
article_banner

我们将使用ESP32-CAM和OpenCV开发手势控制的虚拟鼠标。ESP32相机模块结合Python程序可以无线控制鼠标的跟踪和点击操作。

注意事项

  1. Python版本:3.8.10

  2. Python库安装:

pip install numpy
pip install opencv-python
pip install autopy
pip install mediapipe

3. Python文件名需命名正确

《代码1: track_hand.py 》

手部检测器的实现,使用了OpenCV和MediaPipe库来检测摄像头图像中的手部,并提取手部关键点的位置信息和手势状态。

具体来说,这段代码做了以下几件事情:

  1. 导入了cv2mediapipetimemathnumpy库。

  2. 定义了一个handDetector类,用于检测手部并提取关键信息。

  3. handDetector类的初始化方法设置了一些检测参数,并创建了手部检测器和绘制工具对象。

  4. handDetector类的方法包括:

    • findHands:在输入图像中检测手部,并可选择是否绘制手部关键点和连接线。

    • findPosition:提取手部关键点的位置信息,并返回关键点列表和边界框。

    • fingersUp:判断手指状态,返回一个代表每个手指状态的列表。

    • findDistance:计算两个手指之间的距离,并可选择是否绘制相关信息。

  1. main函数用于运行实时手部检测和手势识别:

    • 创建一个摄像头对象。

    • 创建一个handDetector对象。

    • 在循环中,读取摄像头图像,调用handDetector的方法进行手部检测和手势识别。

    • 输出帧率信息并显示处理后的图像。

import cv2                           # 导入OpenCV库,用于图像处理和计算机视觉任务
import mediapipe as mp              # 导入Mediapipe库,用于手部检测和关键点识别
import time                         # 导入time库,用于计时和延时操作
import math                         # 导入math库,用于数学计算
import numpy as np                  # 导入numpy库,用于数组和矩阵运算

class handDetector():               # 定义handDetector类,用于手部检测和关键点识别
    def __init__(self, mode=False, maxHands=1, modelComplexity=1, detectionCon=0.5, trackCon=0.5):
        # 初始化函数,设置类的属性和参数
        self.mode = mode                                          # 是否启用检测模式
        self.maxHands = maxHands                                  # 最大检测手部数量
        self.modelComplex = modelComplexity                       # 模型复杂度
        self.detectionCon = detectionCon                          # 检测阈值
        self.trackCon = trackCon                                  # 跟踪阈值
        self.mpHands = mp.solutions.hands                          # 创建Hand对象
        self.hands = self.mpHands.Hands(self.mode, self.maxHands, self.modelComplex,
                                        self.detectionCon, self.trackCon)  # 初始化Hand对象
        self.mpDraw = mp.solutions.drawing_utils                  # 创建drawing_utils对象,用于绘制关键点和连接线
        self.tipIds = [4, 8, 12, 16, 20]                           # 指尖关键点的ID列表

    def findHands(self, img, draw=True):
        # 在图像中检测手部并绘制关键点和连接线
        imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)              # 将图像从BGR格式转换为RGB格式
        self.results = self.hands.process(imgRGB)                  # 处理RGB图像,获取手部检测结果

        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 = []                                            # 存储关键点的ID、x坐标和y坐标的列表
        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)                                     # 将x坐标添加到列表中
                yList.append(cy)                                     # 将y坐标添加到列表中
                self.lmList.append([id, cx, cy])                      # 将关键点的ID、x坐标和y坐标添加到列表中
                if draw:                                             # 如果要绘制关键点
                    cv2.circle(img, (cx, cy), 5, (255, 0, 255), cv2.FILLED)  # 在图像上绘制关键点

            xmin, xmax = min(xList), max(xList)                      # 计算关键点的x坐标范围
            ymin, ymax = min(yList), max(yList)                      # 计算关键点的y坐标范围
            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 = []                                                # 存储手指状态的列表
        # 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)                                   # 手指放下

        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 = detector.findHands(img)                               # 在图像中检测手部并绘制关键点和连接线
        lmList, bbox = detector.findPosition(img)                   # 查找关键点的位置并绘制关键点
        if len(lmList) != 0:                                        # 如果检测到关键点
            print(lmList[4])                                        # 打印第五个关键点的坐标

        cTime = time.time()                                         # 获取当前时间
        fps = 1 / (cTime - pTime)                                   # 计算帧率
        pTime = cTime                                               # 更新上一帧的时间
        fingers = detector.fingersUp()                              # 获取手指状态列表

        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()                                                         # 执行主函数

《代码2: final.py》此代码为电脑摄像头使用

使用手势识别控制鼠标移动和点击的应用程序。它依赖于一个名为的模块(或脚本),该模块可能包含有关手部检测和手势识别的功能。

具体来说,代码2做了以下几件事情:

  1. 导入了numpytrack_handtimeautopycv2库。

  2. 设置了摄像头和屏幕的宽度和高度参数,以及一些其他参数。

  3. 创建了一个摄像头对象,并设置摄像头的宽度和高度。

  4. 创建了一个handDetector对象(假设在track_hand模块中定义),用于手部检测和手势识别。

  5. 获取屏幕的宽度和高度。

  6. 在一个循环中,读取摄像头图像,调用handDetector对象的方法进行手部检测和手势识别。

  7. 获取手部关键点的位置信息和手指状态。

  8. 如果手指状态满足某些条件,根据手指位置控制鼠标移动或点击。

  9. 在图像上绘制相关信息,如手部关键点、矩形框和帧率。

  10. 显示处理后的图像。

  11. 循环会一直运行,直到用户关闭窗口。

import numpy as np  # 导入numpy库,用于数值计算
import track_hand as htm  # 导入手部追踪模块
import time  # 导入time模块,用于时间相关操作
import autopy  # 导入autopy库,用于模拟鼠标和键盘操作
import cv2  # 导入OpenCV库,用于图像处理

wCam, hCam = 1280, 720  # 摄像头捕获画面的宽度和高度
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()  # 获取屏幕的宽度和高度

while True:
    fingers = [0, 0, 0, 0, 0]  # 初始化手指状态,默认都是放下的状态
    success, img = cap.read()  # 读取摄像头捕获的图像
    img = detector.findHands(img)  # 在图像中检测手部
    lmList, bbox = detector.findPosition(img)  # 获取手部关键点的位置信息

    if len(lmList) != 0:
        x1, y1 = lmList[8][1:]  # 获取食指指尖的坐标
        x2, y2 = lmList[12][1:]  # 获取中指指尖的坐标

    	fingers = detector.fingersUp()  # 判断手指的状态

    cv2.rectangle(img, (frameR, frameR), (wCam - frameR, hCam - frameR),
                  (255, 0, 255), 2)  # 在图像上绘制裁剪区域的矩形框

    if fingers[1] == 1 and fingers[2] == 0:  # 只有食指竖起,手势为移动模式
        x3 = np.interp(x1, (frameR, wCam - frameR), (0, wScr))  # 将食指指尖的x坐标映射到屏幕坐标系中
        y3 = np.interp(y1, (frameR, hCam - frameR), (0, hScr))  # 将食指指尖的y坐标映射到屏幕坐标系中

        clocX = plocX + (x3 - plocX) / smoothening  # 平滑鼠标的x坐标
        clocY = plocY + (y3 - plocY) / smoothening  # 平滑鼠标的y坐标

        autopy.mouse.move(wScr - clocX, clocY)  # 移动鼠标到计算得到的坐标位置
        cv2.circle(img, (x1, y1), 15, (255, 0, 255), cv2.FILLED)  # 在图像上绘制食指指尖的圆点
        plocX, plocY = clocX, clocY  # 更新上一帧鼠标位置

    if fingers[1] == 1 and fingers[2] == 1:  # 食指和中指都竖起,手势为点击模式
        length, img, lineInfo = detector.findDistance(8, 12, img)  # 计算食指和中指之间的距离
        print(length)  # 输出距离值

        if length < 40:  # 如果距离小于40,执行点击操作
            cv2.circle(img, (lineInfo[4], lineInfo[5]), 15, (0, 255, 0), cv2.FILLED)  # 在图像上绘制点击位置的圆点
            autopy.mouse.click()  # 执行鼠标点击操作

    cTime = time.time()  # 当前帧的时间戳
    fps = 1 / (cTime - pTime)  # 计算帧率
    pTime = cTime  # 更新上一帧的时间戳

    img = cv2.flip(img, 1)  # 对图像进行水平翻转

    cv2.putText(img, str(int(fps)), (20, 50), cv2.FONT_HERSHEY_PLAIN, 3,
                (255, 0, 0), 3)  # 在图像上绘制帧率信息

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

《代码3:final2.py》esp32cam搭配使用

  1. 添加了对手指状态的检测和处理逻辑,用于确定鼠标移动模式或点击模式。

  2. 添加了鼠标移动和点击的功能,使用autopy库来控制鼠标操作。

  3. 添加了显示帧率和鼠标状态的文本信息。

此代码片段实现了通过手势控制鼠标移动和点击的功能,使用了外部图像源而不是摄像头来获取图像数据。它使用自定义的手势检测模块来检测手部关键点和手势状态,并根据手势状态来移动鼠标或进行鼠标点击操作。代码还包含了帧率计算和显示的功能。

import numpy as np  # 导入numpy库,用于数组操作
import track_hand as htm  # 导入自定义的手势追踪模块
import time  # 导入time模块,用于时间相关操作
import autopy  # 导入autopy库,用于控制鼠标
import cv2  # 导入OpenCV库,用于图像处理和显示
import urllib.request  # 导入urllib库,用于从URL获取图像数据

url = "http://192.168.1.61/cam-hi.jpg"  # 这里要更改Arduino串口显示的IP,替换:192.168.1.61

wCam, hCam = 800, 600  # 摄像头的宽度和高度
frameR = 100  # 边框大小
smoothening = 7  # 平滑系数

pTime = 0  # 上一帧的时间
plocX, plocY = 0, 0  # 上一帧食指指尖的位置
clocX, clocY = 0, 0  # 当前帧食指指尖的位置

detector = htm.handDetector(maxHands=1)  # 创建手势检测器实例
wScr, hScr = autopy.screen.size()  # 获取屏幕的大小

while True:
    # 1. 寻找手部关键点
    fingers = [0, 0, 0, 0, 0]  # 用于存储手指状态的列表

    img_resp = urllib.request.urlopen(url)  # 获取图像数据
    imgnp = np.array(bytearray(img_resp.read()), dtype=np.uint8)
    img = cv2.imdecode(imgnp, -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:]  # 中指指尖的坐标

    # 3. 检测手指的状态
        fingers = detector.fingersUp()  # 判断每个手指的状态

    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))
        # 6. 平滑处理
        clocX = plocX + (x3 - plocX) / smoothening
        clocY = plocY + (y3 - plocY) / smoothening

        # 7. 移动鼠标
        autopy.mouse.move(wScr - 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)


免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删
相关文章
QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空