边学边玩AI视觉,距离测量,自制AI小游戏,附python完整代码
各位小伙伴好,今天和大家分享一下如何使用 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) # 显示窗口高度720 pTime = 0 # 处理第一帧图像的起始时间 #(2)接收手部检测方法 detector = HandDetector(mode=False, # 静态图模式,若为True,每一帧都会调用检测方法,导致检测很慢 maxHands=1, # 最多检测几只手 detectionCon=0.8, # 最小检测置信度 minTrackCon=0.5) # 最小跟踪置信度 #(3)处理每一帧图像 while True: # 返回图像是否读取成功,以及读取的帧图像img success, img = cap.read() #(4)获取手部关键点信息 # 检测手部信息,返回手部关键点信息hands字典,绘制关键点和连线后的图像img hands, 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) # 显示窗口高度720 pTime = 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: # 返回图像是否读取成功,以及读取的帧图像img success, 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为lmList lmList = 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 + C distanceCM = A*distance**2 + B*distance + C print("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) # 显示窗口高度720 pTime = 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: # 返回图像是否读取成功,以及读取的帧图像img success, 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为lmList lmList = 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 + C distanceCM = A*distance**2 + B*distance + C print("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 游戏界面 # 创建按钮,触碰到了就变颜色 # 按钮出现在屏幕的随机位置,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()
当手掌按下按钮,按钮颜色从青色变成红色,手不松开按钮的话按钮的颜色保持是不变,位置也不变,并且得分板也不增加。只有松开后才会重置位置,计数加一。练习拍击按钮的快准狠。
Win764位系统电脑双显卡如何切换的方法有不少深度技术的小伙伴都应该发现了,现在有许多笔记本都是支持双显卡的了。但是有深度win7系统就会问了,双显卡到底要如何切换呢。今天就由深度小编来告诉你们在笔记本中怎么切换双显卡的
Win764位纯净版打开IE无法显示此页问题有不少win7纯净版的用户都应该遇到过打开IE浏览器就出现无法显示此页的提示问题吧。说真的,遇到这样的问题真的很糟心啊,但是尽管如此我们还是要微笑着解决问题。所以接下来深度技术系统
win7系统中如何设置显示器为144hz的图文教程有很多热爱游戏的玩家为了让游戏画面显示更加流畅,就使用了144Hz显示器,可是小伙伴却不知道在win7系统中如何设置它的刷新率,那么win7系统中如何把显示器设置144hz呢?其实
win1021h1正式版如何设置备份系统的操作方法有不少深度技术的朋友都升级win1021h1版本的系统了,但是安装好软件工具以后,问小编如何备份系统。其实,我们一般可以通过ghost来备份系统,如果嫌麻烦,我们可以使用win10
相同距离下,高速和国道哪个更省钱?大多数车主没算过这笔账时间的发展给生活带来了极大的改变,除了身边无处不在的数码产品,基础设施建设也走向了新高度,我们身边曾经的荒地盖上了高楼大厦,曾经的乡间小路也变成了高速公路,基础设施的完善让城市之间
华为花式曝光高通骁龙898,价格或涨1000元数码博主菊厂影业Fans发现,在华为最新一期的调查问卷中竟然出现了高通下一代芯片骁龙898的名称。虽然无法表明下一代骁龙8系芯片将定名898但至少可认为华为有意推出更多搭载高通旗舰
11月2日军情!3条最新消息,蔡英文害怕的事情越来越多了随着气温骤降,全国不少城市进入供暖季,冬天的脚步越来越近了。此前,蔡英文大肆炒作岛内问题声称互不隶属,一直活跃于国际舆论并在涉台问题上屡屡大放蹶词的她,这两天突然消停了。3条最新消
买电车还是油车比较纠结买电车还是油车最近计划买车,原本一直看的是油车,最斤同事给我安利了电车,觉得也是不错。现在非常纠结,大家提点建议目前的用车需求(1)家庭首辆车,出行都靠他,目前没有小孩(1)日常通
win10镜像安装后没有网上邻居的解决方法有一位刚刚升级win10镜像系统的深度技术用户,电脑系统安装好之后发现桌面上没有网上邻居的图标,之前他使用的win7旗舰版系统明明都存在的,那么win10系统有网上邻居吗?下面,深
出差好物共享地平线8号行李箱新玩法前沿不管是出差还是出门旅行,带着行李箱出门是再合适不过的了。对于短时间的出行,叶涵喜欢带20寸的行李箱,一来是方便机场过安检,二来是20寸的行李箱容量足够又不会太沉重,即拖即走。今
推荐一款神级动态壁纸电脑软件我们经常在网上看到别人的动态桌面,心动不已。今天推荐这款WallpaperEngine桌面软件,是发布于Steam平台的一款动态壁纸软件,区别于其他形式的壁纸软件,Wallpape