我们将使用ESP32-CAM和OpenCV开发手势控制的虚拟鼠标。ESP32相机模块结合Python程序可以无线控制鼠标的跟踪和点击操作。
注意事项
Python版本:3.8.10
Python库安装:
pip install numpy
pip install opencv-python
pip install autopy
pip install mediapipe
3. Python文件名需命名正确
《代码1: track_hand.py 》
手部检测器的实现,使用了OpenCV和MediaPipe库来检测摄像头图像中的手部,并提取手部关键点的位置信息和手势状态。
具体来说,这段代码做了以下几件事情:
导入了cv2、mediapipe、time、math和numpy库。
定义了一个handDetector类,用于检测手部并提取关键信息。
handDetector类的初始化方法设置了一些检测参数,并创建了手部检测器和绘制工具对象。
handDetector类的方法包括:
findHands:在输入图像中检测手部,并可选择是否绘制手部关键点和连接线。
findPosition:提取手部关键点的位置信息,并返回关键点列表和边界框。
fingersUp:判断手指状态,返回一个代表每个手指状态的列表。
findDistance:计算两个手指之间的距离,并可选择是否绘制相关信息。
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做了以下几件事情:
导入了numpy、track_hand、time、autopy和cv2库。
设置了摄像头和屏幕的宽度和高度参数,以及一些其他参数。
创建了一个摄像头对象,并设置摄像头的宽度和高度。
创建了一个handDetector对象(假设在track_hand模块中定义),用于手部检测和手势识别。
获取屏幕的宽度和高度。
在一个循环中,读取摄像头图像,调用handDetector对象的方法进行手部检测和手势识别。
获取手部关键点的位置信息和手指状态。
如果手指状态满足某些条件,根据手指位置控制鼠标移动或点击。
在图像上绘制相关信息,如手部关键点、矩形框和帧率。
显示处理后的图像。
循环会一直运行,直到用户关闭窗口。
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搭配使用
添加了对手指状态的检测和处理逻辑,用于确定鼠标移动模式或点击模式。
添加了鼠标移动和点击的功能,使用autopy库来控制鼠标操作。
添加了显示帧率和鼠标状态的文本信息。
此代码片段实现了通过手势控制鼠标移动和点击的功能,使用了外部图像源而不是摄像头来获取图像数据。它使用自定义的手势检测模块来检测手部关键点和手势状态,并根据手势状态来移动鼠标或进行鼠标点击操作。代码还包含了帧率计算和显示的功能。
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)