各位同学好,今天和大家分享一下如何使用 opencv + mediapipe 创建一个AI视觉小游戏,先放图看效果。

游戏规则,用手按下屏幕上的圆形按钮,每按一次后松开,按钮就随机出现在屏幕上的一个位置看规定时间内能准确按下多少次按钮。根据手和摄像头之间的距离,当距离小于30cm,并且按钮在绿框内部,则认为是按下按钮,按钮变颜色,松开后,得分加一,并且按钮随机出现在另外一个位置。

游戏界面,左上角31代表FPS值,中间Score代表得分,Time代表游戏时间剩余几秒,31cm代表手和摄像机的之间距离。

结算界面,显示最终得分score,按下键盘上的 r 键重新开始游戏


1. 导入工具包

# 安装工具包
pip install opencv-contrib-python  # 安装opencv
pip install mediapipe  # 安装mediapipe
# pip install mediapipe --user  #有user报错的话试试这个
pip install cvzone  # 安装cvzone# 导入工具包
import cv2
from cvzone.HandTrackingModule import HandDetector  # 手部追踪方法
import time
import math
import random

21个手部关键点信息如下,本节我们主要研究食指根部"5"小指根部'17'的坐标信息。

本节所用的手部关键点检测MediaPipe基本方法我参考我之前的文章:https://blog.csdn.net/dgvv4/article/details/122023047?spm=1001.2014.3001.5501,这里直接使用已经定义好的手部关键点检测方法。


2. 检测手部关键点

(1) cvzone.HandTrackingModule.HandDetector()  是手部关键点检测方法

参数:

mode: 默认为 False,将输入图像视为视频流。它将尝试在第一个输入图像中检测手,并在成功检测后进一步定位手的坐标。在随后的图像中,一旦检测到所有 maxHands 手并定位了相应的手的坐标,它就会跟踪这些坐标,而不会调用另一个检测,直到它失去对任何一只手的跟踪。这减少了延迟,非常适合处理视频帧。如果设置为 True,则在每个输入图像上运行手部检测,用于处理一批静态的、可能不相关的图像。

maxHands: 最多检测几只手,默认为 2

detectionCon: 手部检测模型的最小置信值(0-1之间),超过阈值则检测成功。默认为 0.5

minTrackingCon: 坐标跟踪模型的最小置信值 (0-1之间),用于将手部坐标视为成功跟踪,不成功则在下一个输入图像上自动调用手部检测。将其设置为更高的值可以提高解决方案的稳健性,但代价是更高的延迟。如果 mode 为 True,则忽略这个参数,手部检测将在每个图像上运行。默认为 0.5

它的参数和返回值类似于官方函数 mediapipe.solutions.hands.Hands()

(2)cvzone.HandTrackingModule.HandDetector.findHands()    找到手部关键点并绘图

参数:

img: 需要检测关键点的帧图像,格式为BGR

draw: 是否需要在原图像上绘制关键点及识别框

flipType: 图像是否需要翻转,当视频图像和我们自己不是镜像关系时,设为True就可以了

返回值:

hands: 检测到的手部信息,由0或1或2个字典组成的列表。如果检测到两只手就是由两个字典组成的列表。字典中包含:21个关键点坐标,检测框坐标及宽高,检测框中心坐标,检测出是哪一只手。

img: 返回绘制了关键点及连线后的图像

代码如下

import cv2
from cvzone.HandTrackingModule import HandDetector
import time
import math#(1)捕获摄像头
cap = cv2.VideoCapture(0) # 捕获电脑摄像头
cap.set(3, 1280)  # 设置显示窗口宽度1280
cap.set(4, 720)   # 显示窗口高度720pTime = 0  # 处理第一帧图像的起始时间#(2)接收手部检测方法
detector = HandDetector(mode=False, # 静态图模式,若为True,每一帧都会调用检测方法,导致检测很慢maxHands=1, # 最多检测几只手detectionCon=0.8, # 最小检测置信度minTrackCon=0.5)  # 最小跟踪置信度#(3)处理每一帧图像
while True:# 返回图像是否读取成功,以及读取的帧图像imgsuccess, img = cap.read()#(4)获取手部关键点信息# 检测手部信息,返回手部关键点信息hands字典,绘制关键点和连线后的图像imghands, img = detector.findHands(img)print(hands)#(5)图像显示# 计算FPS值cTime = time.time()  # 处理一帧图像所需的时间fps = 1/(cTime-pTime) pTime = cTime  # 更新处理下一帧的起始时间# 把fps值显示在图像上,img画板,显示字符串,显示的坐标位置,字体,字体大小,颜色,线条粗细cv2.putText(img, str(int(fps)), (50,70), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3)# 显示图像,输入窗口名及图像数据# cv2.namedWindow("img", 0)  # 窗口大小可手动调整cv2.imshow('img', img)    if cv2.waitKey(20) & 0xFF==27:  #每帧滞留20毫秒后消失,ESC键退出break# 释放视频资源
cap.release()
cv2.destroyAllWindows()

打印检测到的手部关键点信息hands列表lmList中存放21个手部关键点的像素坐标,bbox中存放检测框的左上角坐标和框的宽高center存放检测框的中心坐标type检测的是左手还是右手

-----------------------------------------------------------------
[{'lmList': [[227, 607], [335, 585], [439, 515], [508, 440], [563, 384], [434, 384], [491, 292], [520, 231], [543, 176], [380, 349], [423, 241], [445, 169], [459, 106], [320, 336], [347, 228], [368, 156], [387, 94], [250, 339], [255, 245], [264, 183], [279, 126]],'bbox': (227, 94, 336, 513),
'center': (395, 350),
'type': 'Left'}]
[{'lmList': [[219, 628], [324, 605], [427, 532], [489, 451], [540, 390], [424, 401], [483, 310], [511, 250], [532, 195], [369, 366], [415, 263], [436, 192], [449, 129], [308, 353], [340, 250], [362, 181], [382, 120], [238, 358], [248, 268], [261, 209], [278, 154]],
'bbox': (219, 120, 321, 508),
'center': (379, 374),
'type': 'Left'}]
-----------------------------------------------------------------

图像显示结果如下:


3. 距离检测,确定像素距离和实际厘米距离之间的映射关系

距离检测的思路是,获取手掌关键点信息中的食指根部"5"坐标lmList[5] 小指根部'17'坐标lmList[17],计算这两个关键点之间的像素距离distance将像素距离映射到手掌距离屏幕的实际距离

在确定映射公式之前我们得先看一下掌间距离和相机与手之间的距离的对应关系,如下面代码中的第(3)步x代表掌间距离,y代表相机和手之间的距离,举个例子,手掌间的像素距离为300时,对应的相机和手之间的距离是20cm。绘图查看对应关系。

这里就简单的使用一个二次多项式去拟合这条曲线得到手掌和摄像机之间的大致的距离。感兴趣的可以用指数拟合,更准确一些。使用 np.polyfit(x, y, 2) 函数,指定 x 和 y 之间是 2 次多项式关系,即 。返回值是一个数组coff,存放多项式的系数A、B、C。

因此,在计算实际距离distanceCM时,就可以根据二次多项式公式计算每一帧图像的手掌和摄像机之间的距离,distanceCM = A*distance**2 + B*distance + C

我们在上述代码中补充。

import cv2
import cvzone
from cvzone.HandTrackingModule import HandDetector
import time
import math#(1)捕获摄像头
cap = cv2.VideoCapture(0) # 捕获电脑摄像头
cap.set(3, 1280)  # 设置显示窗口宽度1280
cap.set(4, 720)   # 显示窗口高度720pTime = 0  # 处理第一帧图像的起始时间#(2)接收手部检测方法
detector = HandDetector(mode=False, # 静态图模式,若为True,每一帧都会调用检测方法,导致检测很慢maxHands=1, # 最多检测几只手detectionCon=0.8, # 最小检测置信度minTrackCon=0.5)  # 最小跟踪置信度#(3)找到手掌间的距离和实际的手与摄像机之间的距离的映射关系
# x 代表手掌间的距离(像素距离),y 代表手和摄像机之间的距离(cm)
x = [300, 245, 200, 170, 145, 130, 112, 103, 93, 87, 80, 75, 70, 67, 62, 59, 57]
y = [20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]# 绘图查看xy的对应关系
import matplotlib.pyplot as plt
plt.plot(x,y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('reflection')# 因此我们需要一个类似 y = AX^2 + BX + C 的方程来拟合
import numpy as np
coff = np.polyfit(x, y, 2)  #构造二阶多项式方程
# coff中存放的是二阶多项式的系数 A,B,C#(4)处理每一帧图像
while True:# 返回图像是否读取成功,以及读取的帧图像imgsuccess, img = cap.read()#(5)获取手部关键点信息# 检测手部信息,返回手部关键点信息hands字典,不绘制图像hands = detector.findHands(img, draw=False)# 如果检测到手的话hands字典就不为空if hands:# 获取检测框的信息(x,y,w,h)x, y, w, h = hands[0]['bbox']        # 获取字典中的关键点信息,key为lmListlmList = hands[0]['lmList']  # hands[0]代表检测到的这只手的字典信息,hands是一个列表print('hands_landmarks:', lmList)# 获取食指根部'5'和小指根部'17'的坐标点x1, y1 = lmList[5] x2, y2 = lmList[17]# 勾股定理计算关键点'5'和'17'之间的距离,并变成整型distance = int(math.sqrt((x2-x1)**2 + (y2-y1)**2))print('distance between 5 and 17:', distance)# 拟合的二次多项式的系数保存在coff数组中,即掌间距离和手与相机间的距离的对应关系的系数A, B, C = coff# 得到像素距离转为实际cm距离的公式 y = Ax^2 + Bx + CdistanceCM = A*distance**2 + B*distance + Cprint('distance CM:', distanceCM)# 把距离绘制在图像上,简化了cv2.putText(),cvzone.putTextRect(img, f'{(int(distanceCM))} cm', (x+10,y-10))# 绘制手部检测框cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)#(6)图像显示# 计算FPS值cTime = time.time()  # 处理一帧图像所需的时间fps = 1/(cTime-pTime) pTime = cTime  # 更新处理下一帧的起始时间# 把fps值显示在图像上,img画板,显示字符串,显示的坐标位置,字体,字体大小,颜色,线条粗细cv2.putText(img, str(int(fps)), (50,70), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3)# 显示图像,输入窗口名及图像数据# cv2.namedWindow("img", 0)  # 窗口大小可手动调整cv2.imshow('img', img)    if cv2.waitKey(20) & 0xFF==27:  #每帧滞留20毫秒后消失,ESC键退出break# 释放视频资源
cap.release()
cv2.destroyAllWindows()

打印每帧的21个关键点信息 hands_landmarks掌间像素距离 distance between 5 and 17手掌和相机间的厘米距离 distance CM

-----------------------------------------------------------------------
hands_landmarks: [[211, 581], [276, 570], [340, 530], [373, 468], [371, 413], [360, 465], [382, 403], [358, 423], [340, 458], [327, 443], [345, 384], [311, 424], [292, 466], [294, 428], [306, 374], [281, 414], [266, 457], [261, 419], [271, 378], [256, 407], [246, 443]]
distance between 5 and 17: 109
distance CM: 56.75208816895032
hands_landmarks: [[151, 608], [212, 607], [286, 557], [306, 486], [280, 436], [301, 483], [322, 418], [295, 473], [287, 505], [262, 466], [273, 409], [248, 478], [246, 502], [222, 457], [229, 409], [210, 478], [210, 503], [180, 451], [185, 417], [177, 467], [177, 491]]
distance between 5 and 17: 125
distance CM: 48.49262820874043
-----------------------------------------------------------------------

显示结果如图,23cm代表手掌距离摄像机有多远。


4. 创建虚拟按键,建立游戏规则

从第(8)步开始,如果手掌距离摄像机小于30cm,并且按钮的中心点坐标(cx, cy)在检测框内部,那么就认为此时手掌已经按下按钮counter变成1,按钮变成红色,counter变成2。如果手掌一直按着按钮,那么counter一直保持着 counter=2。如果松开那么此时的counter自动从2加1,变成counter=3颜色置为初始值,得分加一,按钮随机出现在屏幕中的任意位置random.randint(),重置按钮确认器counter=0。

key == ord('r') 表示当点击键盘上的R键时,可以重新开始游戏。

import cv2
import cvzone
from cvzone.HandTrackingModule import HandDetector
import time
import math
import random#(1)捕获摄像头
cap = cv2.VideoCapture(0) # 捕获电脑摄像头
cap.set(3, 1280)  # 设置显示窗口宽度1280
cap.set(4, 720)   # 显示窗口高度720pTime = 0  # 处理第一帧图像的起始时间#(2)接收手部检测方法
detector = HandDetector(mode=False, # 静态图模式,若为True,每一帧都会调用检测方法,导致检测很慢maxHands=1, # 最多检测几只手detectionCon=0.8, # 最小检测置信度minTrackCon=0.5)  # 最小跟踪置信度#(3)找到手掌间的距离和实际的手与摄像机之间的距离的映射关系
# x 代表手掌间的距离(像素距离),y 代表手和摄像机之间的距离(cm)
x = [300, 245, 200, 170, 145, 130, 112, 103, 93, 87, 80, 75, 70, 67, 62, 59, 57]
y = [20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]# 绘图查看xy的对应关系
import matplotlib.pyplot as plt
plt.plot(x,y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('reflection')# 因此我们需要一个类似 y = AX^2 + BX + C 的方程来拟合
import numpy as np
coff = np.polyfit(x, y, 2)  #构造二阶多项式方程
# coff中存放的是二阶多项式的系数 A,B,C# 创建初始的按钮的位置
cx, cy = 255, 255
# 初始的按钮颜色红色,如果接下来手碰到了它就变颜色
color = (255,255,0)
# 设置计数器,有没有碰到按钮
counter = 0
# 设置初始得分
score = 0
# 设置游戏开始的起始时间
startTime = time.time()
# 设置游戏的总时间10s
totalTime = 20#(4)处理每一帧图像
while True:# 返回图像是否读取成功,以及读取的帧图像imgsuccess, img = cap.read()# 水平翻转图像,呈镜像关系img = cv2.flip(img, 1)  # 0代表垂直方向翻转,1代表水平方向# 如果当前帧时间减去起始时间小于预设的时间,那么游戏继续进行if time.time() - startTime <= totalTime:#(5)获取手部关键点信息# 检测手部信息,返回手部关键点信息hands字典,不绘制图像hands = detector.findHands(img, draw=False)# 如果检测到手的话hands字典就不为空if hands:# 获取检测框的信息(x,y,w,h)x, y, w, h = hands[0]['bbox']        # 获取字典中的关键点信息,key为lmListlmList = hands[0]['lmList']  # hands[0]代表检测到的这只手的字典信息,hands是一个列表print('hands_landmarks:', lmList)# 获取食指根部'5'和小指根部'17'的坐标点x1, y1 = lmList[5] x2, y2 = lmList[17]# 勾股定理计算关键点'5'和'17'之间的距离,并变成整型distance = int(math.sqrt((x2-x1)**2 + (y2-y1)**2))print('distance between 5 and 17:', distance)# 拟合的二次多项式的系数保存在coff数组中,即掌间距离和手与相机间的距离的对应关系的系数A, B, C = coff# 得到像素距离转为实际cm距离的公式 y = Ax^2 + Bx + CdistanceCM = A*distance**2 + B*distance + Cprint('distance CM:', distanceCM)# 把距离绘制在图像上,简化了cv2.putText(),cvzone.putTextRect(img, f'{(int(distanceCM))} cm', (x+10,y-10))# 绘制手部检测框cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 3)#(8)设置游戏规则if distanceCM <= 30:  # 如果手距相机的距离小于40cm,并且按钮在检测框内部,就认为碰到了  if x <cx< x+w and y <cy< y+h:  # 按钮在检测框内部counter = 1  # 计数器变成1证明碰到了# 如果手碰到了按钮if counter:counter += 1  # 如果碰到counter就一直是2,接下去画图color = (0,0,255)  # 按钮变成红色# 如果没有碰到,那么程序执行到这里时counter等于3,将按钮颜色重置if counter == 3:  # 手一旦没有碰到按钮,按钮就随机换位置cx = random.randint(100,1100)cy = random.randint(100,620)# 得分加1分,因为是按下按钮后松开才能得分score += 1# 重置按钮颜色color = (255,255,0)counter = 0#(9)创建游戏界面# 创建按钮,触碰到了就变颜色# 按钮出现在屏幕的随机位置,img画板,圆心位置,半径,颜色color,填充cv2.circle(img, (cx,cy), 30, color, cv2.FILLED)cv2.circle(img, (cx,cy), 20, (0,255,255), 4)  # 把按钮做得好看一些  cv2.circle(img, (cx,cy), 10, (100,100,255), 4)# 创建计时器,img画板,显示文本,位置,大小,背景颜色,offset上下左右填充nowTime = totalTime - int(time.time()-startTime)  # 显示剩余时间cvzone.putTextRect(img, f'Time:{nowTime}', (900,80), scale=4, colorR=(255,0,0), offset=20)# 创建得分计数板,在规定时间内碰到了几次按钮score_get = str(score).zfill(2)  # 字符串, 两位数'01','02'cvzone.putTextRect(img, 'score:' + score_get, (400,80), scale=4, colorT=(0,0,255), colorR=(0,255,255), offset=20)# 如果时间到了,显示总得分else:cvzone.putTextRect(img, 'Game Over', (400,250), scale=5, colorT=(0,0,255), colorR=(255,255,0), offset=20, thickness=8)cvzone.putTextRect(img, 'score:' + score_get, (490,350), scale=4, colorT=(0,0,255), colorR=(0,255,0), offset=20)cvzone.putTextRect(img, 'press r to restart', (350,450), scale=4, colorT=(255,255,255), colorR=(255,0,255), offset=20)#(10)图像显示# 计算FPS值cTime = time.time()  # 处理一帧图像所需的时间fps = 1/(cTime-pTime) pTime = cTime  # 更新处理下一帧的起始时间# 把fps值显示在图像上,img画板,显示字符串,显示的坐标位置,字体,字体大小,颜色,线条粗细cv2.putText(img, str(int(fps)), (50,70), cv2.FONT_HERSHEY_PLAIN, 4, (255,0,0), 3)# 显示图像,输入窗口名及图像数据cv2.imshow('img', img)key = cv2.waitKey(1)# 重置游戏if key == ord('r'):startTime = time.time()  # 重置开始时间score = 0 # 重置得分# 退出游戏if key==27:  # ESC键退出显示break# 释放视频资源
cap.release()
cv2.destroyAllWindows()

当手掌按下按钮,按钮颜色从青色变成红色,手不松开按钮的话按钮的颜色保持是不变,位置也不变,并且得分板也不增加。只有松开后才会重置位置,计数加一。练习拍击按钮的快准狠。

【机器视觉案例】(6) AI视觉,距离测量,自制AI小游戏,附python完整代码相关推荐

  1. 【机器视觉案例】(9) AI视觉,手势控制电脑键盘,附python完整代码

    各位同学好,今天和大家分享一下如何使用 opencv+mediapipe 完成远程手势控制电脑键盘.感兴趣的可以看一下我前面一篇手势控制电脑鼠标:https://blog.csdn.net/dgvv4 ...

  2. 【机器视觉案例】(5) AI视觉,手势调节物体尺寸,附python完整代码

    各位同学好,今天和大家分享一下如何使用opencv+mediapipe完成远程手势调节图片尺寸的案例.先放张图看效果.当拇指和食指竖起时,根据食指间的连线的长度自由缩放图片尺寸.图片的中点始终位于指尖 ...

  3. 【机器视觉案例】(5) AI视觉,远程手势控制虚拟计算器,附python完整代码

    各位同学好,今天和大家分享一下如何使用MediaPipe+Opencv完成虚拟计算器,先放张图看效果.FPS值为29,食指和中指距离小于规定阈值则认为点击按键,为避免重复数字出现,规定每20帧可点击一 ...

  4. 【MediaPipe】(4) AI视觉,远程手势调节电脑音量,附python完整代码

    各位同学好,今天和大家分享一下如何使用MediaPipe完成手势调节电脑音量,先放张图看效果. 注意!! 本节需要用到手部关键点的实时跟踪,我已经在之前的文章中详细写过了,本节会直接使用,有疑问的同学 ...

  5. 【机器视觉案例】(12) 自制AI视觉小游戏--贪吃蛇,附python完整代码

    各位同学好,今天和大家分享一下如何使用 mediapipe+opencv 自制贪吃蛇小游戏.先放张图看效果. 规则:食指指尖控制蛇头,指尖每接触到黄色方块,计数加一,蛇身变长,方块随机切换位置.如果指 ...

  6. 【图像分类案例】(1) ResNeXt 交通标志四分类,附Tensorflow完整代码

    各位同学好,今天和大家分享一下如何使用 Tensorflow 构建 ResNeXt 神经网络模型,通过案例实战 ResNeXt 的训练以及预测过程.每个小节的末尾有网络.训练.预测的完整代码.想要数据 ...

  7. 【机器视觉案例】(10) AI视觉搭积木,手势移动虚拟物体,附python完整代码

    各位同学好,今天和大家分享一下如何使用 opencv+mediapipe 完成手势移动虚拟物体,可自定义各种形状的物体,通过手势搭积木.先放张图看效果. 规则:当食指在某个物体内部,并且中指指尖和食指 ...

  8. 【机器视觉案例】(8) AI视觉,手势控制电脑鼠标,附python完整代码

    各位同学好,今天和大家分享一下如何使用 MediaPipe+Opencv 通过手势识别来控制电脑鼠标的移动和点击,如果有兴趣的话,可以代替鼠标去打游戏.先放图看效果.用画图板来测试 黄框代表电脑屏幕的 ...

  9. 【机器视觉案例】(16) 自制视觉小游戏--桌上冰球,附python完整代码

    大家好,今天和各位分享一下如何使用 mediapipe+opencv 制作桌上冰球的交互式小游戏.先放张图看效果. 规则如下:左手控制白色球拍:右手控制紫色球拍:球拍只能上下移动:红色圆形就是冰球:球 ...

最新文章

  1. PortICASetDefaults.exe /o命令返回为空
  2. dropout的正则化理解
  3. 订单操作-分页查询所有订单
  4. 用webstorm自动编译less产出css和sourcemap
  5. 90-40-010-源码-CUBE-引擎为MR写入Druid的构建
  6. python中文字体下载_python中matlabplot和seaborn中文字体显示的一种解决方案
  7. Linux学习第一篇之Linux系统安装——系统分区
  8. 最详尽的 JS 原型与原型链终极详解(1)(2)(3)===转载
  9. sudo: unable to resolve host xxx解决办法
  10. 史上最全的人工智能知识图谱
  11. 用户常见的问题以及特殊技术问题
  12. ROS install
  13. linux安装谷歌浏览器(Chrome)
  14. Cheat Engine逆向修改植物大战僵尸(外挂)
  15. jieba结巴分词加入自定义词典
  16. 深入浅出系列1:词向量
  17. Manner-Kendall(M-K)---突变检验
  18. 【英语做题】英语“八股文“学习
  19. 12个基本的在线设计竞赛目录
  20. 秋招寒冬不如换个思维——试试中小厂

热门文章

  1. 51单片机 小车 L298N pwm调速 串口控制 按键控制
  2. php作为文本进行处理,PHP处理文本和爬虫技巧
  3. C++ 笔记(01)— 环境设置(安装g++、g++ 编译 C++、 生成可执行文件流程、解释器与编译器区别)
  4. Unity3D中的函数方法及解释
  5. 从零开始编写自己的C#框架(16)——Web层后端父类
  6. Taglib原理和实现:再论El和JST
  7. php pthread安装编译,php 多线程扩展 pthreads 安装 及 使用
  8. linux wifi-tools,Linux下WiFi工具wireless_tools交叉编译,及其支持生成iwconfig使用的内核配置...
  9. linux ls没有反应_Linux入门②“命令格式”
  10. 计划任务执行php文件,linux系统下添加计划任务执行php文件方法