大家好,今天和各位分享一下如何使用 mediapipe+opencv 制作桌上冰球的交互式小游戏。先放张图看效果。

规则如下:左手控制白色球拍右手控制紫色球拍球拍只能上下移动;红色圆形就是冰球;球碰撞到上下两侧的蓝色边框,和两侧的球拍就会反弹;如果球进入了黄色区域,游戏结束;下面的粉色计数板,记录左右两侧各击球多少次。


1. 文件配置

1.1 导入工具包

pip install opencv_python==4.2.0.34  # 安装opencv
pip install mediapipe  # 安装mediapipe
# pip install mediapipe --user  #有user报错的话试试这个
pip install cvzone  # 安装cvzone# 导入工具包
import cv2
import cvzone
from cvzone.HandTrackingModule import HandDetector  # 导入手部检测模块

21个手部关键点坐标如下:


1.2 素材图片准备

开始之前,先准备球桌的图片,球的图片,球拍的图片。我是用PPT画的图,球和球拍的图片一定要保存成 .png 格式的。放在同一个文件夹中以备读取。


1. 手部关键点检测、素材导入

1.1 方法介绍

(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()

MULTI_HAND_LANDMARKS: 被检测/跟踪的手的集合,其中每只手被表示为21个手部地标的列表每个地标由x, y, z组成

MULTI_HANDEDNESS: 被检测/追踪的手是左手还是右手的集合。每只手由label(标签)和score(分数)组成。 label 是 'Left' 或 'Right' 值的字符串。 score 是预测左右手的估计概率。


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

参数:

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

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

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

返回值:

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

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


(3)cv2.addWeighted()   图像融合

将两张图像按一定比例融合在一起,需要两张图像的size和通道数相同

两张图像按一定比例融合: cv2.addWeighted(图像1, 权重1, 图像2, 权重2, 亮度偏置)

相当于 y = a x1 + b x2 + c,其中 a、b 代表权重,c 代表亮度上提亮多少


1.2 代码展示

首先 cv2.imread() 中的参数 cv2.IMREAD_UNCHANGED 是指用图片的原来格式打开包含Alpha通道。即以不改变图片的方式打开,图片是彩色那么读进来就是彩色,图片是灰度图那么读进来就是灰度图,读进来的图片的shape如下

该部分代码主要负责手部关键点检测融合背景图像和视频帧图像

import cv2
import cvzone
from cvzone.HandTrackingModule import HandDetector  # 导入手部检测模块#(1)捕获摄像头
cap = cv2.VideoCapture(0)  # 0代表电脑自带的摄像头
cap.set(3, 1280)  # 读入的图像的宽
cap.set(4, 720)   # 读入的图像的高#(2)文件配置
# 导入所有需要对图片文件
imgDesk = cv2.imread('games/desk.jpg')  # 球桌的图片
imgBall = cv2.imread('games/ball.png', cv2.IMREAD_UNCHANGED)  # 球的图片
imgBlock1 = cv2.imread('games/block1', cv2.IMREAD_UNCHANGED)  # 球拍的图片
imgBlock2 = cv2.imread('games/block2', cv2.IMREAD_UNCHANGED)  # 球拍的图片
# 调整球桌图片的size
imgDesk = cv2.resize(imgDesk, dsize=(1280,720))#(3)参数设置
# 接收手部关键点识别的方法,最小手部检测模块置信度0.8,最多检测2只手
detector = HandDetector(detectionCon=0.8, maxHands=2)#(4)处理帧图像
while True:# 返回是否读取成功,以及读取后的帧图像success, img = cap.read()  # 每次执行读取一帧# 图片翻转呈镜像关系,1代表左右翻转,0代表上下翻转img = cv2.flip(img, flipCode=1)# 手部关键点检测,返回每个只手的信息和绘制后的图像hands, img = detector.findHands(img, flipType=False)  # 上面翻转过了这里就不用翻转了# 将球桌图片和视频帧图像融合在一起, 两张图的shape要相同# 给出每张图片的融合权重, 亮度偏置为0,这样就变成了半透明的显示形式img = cv2.addWeighted(img, 0.3, imgDesk, 0.7, 0)#(5)添加桌球的图片,将imgBall放在球桌img的指定坐标位置img = cvzone.overlayPNG(img, imgBall, (100,100))# 图像展示cv2.imshow('img', img)# 每帧滞留1ms后消失k = cv2.waitKey(1)# ESC键退出程序if k & 0XFF==27:break# 释放视频资源
cap.release()
cv2.destroyAllWindows()

效果图如下:


2. 关键点处理、球拍移动

2.1 方法介绍

这部分主要完成两项工作,第一是左右手分别控制左侧和右侧的球拍,第二个是球以一定的速度移动。

(1)控制球拍。

hand['bbox'] 中包含了手部检测框的左上角坐标和检测框的宽高使用手掌中心点的 y 坐标来控制球拍的上下移动。由于两个球拍的shape是相同的,因此只要获取一个球拍的高度 h1 即可。使用掌心中点 y 坐标控制球拍中点的 y1 坐标,公式为:y1 = (y + h) // 2 - h1 // 2

接着使用 cvzone.overlayPNG() 就可以将球拍图片覆盖在原图片的指定区域,其中坐标参数是指覆盖区域的左上角坐标。固定横坐标,只上下移动。

(2)球移动

首先要规定球的移动速度 speedx, speedy = 10, 10 代表球每一帧沿x轴正方向移动10个像素,沿y轴正方向移动10个像素,那么球的初始合速度方向是沿图片的正右下角移动

如果球碰撞到了球桌的上下边框,就反弹speedy = -speedy。代表x方向每帧移动的步长不变,y方向每帧移动的方向反转,即入射角等于出射角。


2.2 代码展示

在上述代码中补充

import cv2
import cvzone
import numpy as np
from cvzone.HandTrackingModule import HandDetector  # 导入手部检测模块#(1)捕获摄像头
cap = cv2.VideoCapture(0)  # 0代表电脑自带的摄像头
cap.set(3, 1280)  # 读入的图像的宽
cap.set(4, 720)   # 读入的图像的高#(2)文件配置
# 导入所有需要对图片文件
imgDesk = cv2.imread('games/desk.jpg')  # 球桌的图片
imgBall = cv2.imread('games/ball.png', cv2.IMREAD_UNCHANGED)  # 球的图片
imgBlock1 = cv2.imread('games/block1.png', cv2.IMREAD_UNCHANGED)  # 球拍的图片
imgBlock2 = cv2.imread('games/block2.png', cv2.IMREAD_UNCHANGED)  # 球拍的图片
# 调整球桌图片的size
imgDesk = cv2.resize(imgDesk, dsize=(1280,720))
# 调整球拍的size
imgBlock1 = cv2.resize(imgBlock1, dsize=(50,200))
imgBlock2 = cv2.resize(imgBlock2, dsize=(50,200))#(3)参数设置
# 接收手部关键点识别的方法,最小手部检测模块置信度0.8,最多检测2只手
detector = HandDetector(detectionCon=0.8, maxHands=2)# 球的默认位置
ballpos = [100, 100]# 球的移动速度,每帧15个像素
speedx, speedy = 10, 10#(4)处理帧图像
while True:# 返回是否读取成功,以及读取后的帧图像success, img = cap.read()  # 每次执行读取一帧# 图片翻转呈镜像关系,1代表左右翻转,0代表上下翻转img = cv2.flip(img, flipCode=1)# 手部关键点检测,返回每个只手的信息和绘制后的图像hands, img = detector.findHands(img, flipType=False)  # 上面翻转过了这里就不用翻转了# 将球桌图片和视频帧图像融合在一起, 两张图的shape要相同# 给出每张图片的融合权重, 亮度偏置为0,这样就变成了半透明的显示形式img = cv2.addWeighted(img, 0.4, imgDesk, 0.6, 0)#(5)处理手部关键点,如果检测到手了就进行下一步if hands:# 遍历每检测的2只手,获取每一只手的坐标for hand in hands:# 获取手部检测框的左上坐标xy,宽高whx, y, w, h = hand['bbox']# 获取球拍的宽高h1, w1 = imgBlock1.shape[0:2]# 球拍的中心y坐标,随着掌心移动y1 = (y + h) // 2 - h1 // 2# 如果检测到了左手if hand['type'] == 'Left':# 左侧的球拍x轴固定,y坐标随左手掌间中点移动img = cvzone.overlayPNG(img, imgBlock1, (55,y1))# 如果检测到了右手if hand['type'] == 'Right':# 右侧的球拍x轴固定,y坐标随右手掌间中点移动img = cvzone.overlayPNG(img, imgBlock2, (1280-55,y1))#(6)改变球的位置# 如果球的y坐标在超出了桌面的上或下边框范围,调整移动方向if ballpos[1] >= 600 or ballpos[1] <= 50:# y方向的速度调整为反方向,那么x方向和y方向的合速度方向调整了speedy = -speedyballpos[0] = ballpos[0] + speedx  # 调整球的x坐标ballpos[1] = ballpos[1] + speedy  # 调整球的y坐标#(5)添加桌球的图片,将imgBall放在球桌img的指定坐标位置img = cvzone.overlayPNG(img, imgBall, ballpos)# 图像展示cv2.imshow('img', img)# 每帧滞留1ms后消失k = cv2.waitKey(1)# ESC键退出程序if k & 0XFF==27:break# 释放视频资源
cap.release()
cv2.destroyAllWindows()

效果图如下:


3. 球拍击球、游戏完善

3.1 方法介绍

这一部分主要完成三项工作,第一是球拍击打到球,球需要反弹;第二是如果球进入黄色区域,游戏结束;第三是左右侧击球得分计数器。

(1)球拍击球

看到代码中的第(5)步,ballpos 代表球的左上角坐标(x,y),100 < ballpos[0] < 100+w1 代表球到了球拍横坐标区域范围内部了,y1 < ballpos[1] < y1+h1 代表球的y坐标在球拍y坐标内部,这时表明击球成功,speedx = -speedx 只改变沿x轴的速度方向,不改变沿y轴的速度方向

(2)球进黄区,游戏结束

if ballpos[0] < 50 or ballpos[0] > 1150,如果球图片的左上坐标的 x 坐标,在黄区边缘,整个程序退出。当然也可以做一个游戏结束界面,我之前的博文里也有介绍,我偷个懒不写了。

(3)计数器

首先定义个变量初始化记录左右侧的击球次数 score = [0, 0],如果有一侧的球拍击中球,那么对应该侧计数加一。


3.2 代码展示

上面代码是掌心控制球拍,这里改成食指指尖控制球拍中点移动。

import cv2
import cvzone
from cvzone.HandTrackingModule import HandDetector  # 导入手部检测模块#(1)捕获摄像头
cap = cv2.VideoCapture(0)  # 0代表电脑自带的摄像头
cap.set(3, 1280)  # 读入的图像的宽
cap.set(4, 720)   # 读入的图像的高#(2)文件配置
# 导入所有需要对图片文件
imgDesk = cv2.imread('games/desk.jpg')  # 球桌的图片
imgBall = cv2.imread('games/ball.png', cv2.IMREAD_UNCHANGED)  # 球的图片
imgBlock1 = cv2.imread('games/block1.png', cv2.IMREAD_UNCHANGED)  # 球拍的图片
imgBlock2 = cv2.imread('games/block2.png', cv2.IMREAD_UNCHANGED)  # 球拍的图片
# 调整球桌图片的size
imgDesk = cv2.resize(imgDesk, dsize=(1280,720))
# 调整球拍的size
imgBlock1 = cv2.resize(imgBlock1, dsize=(50,200))
imgBlock2 = cv2.resize(imgBlock2, dsize=(50,200))#(3)参数设置
# 接收手部关键点识别的方法,最小手部检测模块置信度0.8,最多检测2只手
detector = HandDetector(detectionCon=0.8, maxHands=2)# 球的默认位置
ballpos = [100, 100]# 球的移动速度,每帧15个像素
speedx, speedy = 10, 10# 记录是否游戏结束
gameover = False# 记录左右的击球数
score = [0, 0]#(4)处理帧图像
while True:# 返回是否读取成功,以及读取后的帧图像success, img = cap.read()  # 每次执行读取一帧# 图片翻转呈镜像关系,1代表左右翻转,0代表上下翻转img = cv2.flip(img, flipCode=1)# 手部关键点检测,返回每个只手的信息和绘制后的图像hands, img = detector.findHands(img, flipType=False)  # 上面翻转过了这里就不用翻转了# 将球桌图片和视频帧图像融合在一起, 两张图的shape要相同# 给出每张图片的融合权重, 亮度偏置为0,这样就变成了半透明的显示形式img = cv2.addWeighted(img, 0.4, imgDesk, 0.6, 0)#(5)处理手部关键点,如果检测到手了就进行下一步if hands:# 遍历每检测的2只手,获取每一只手的坐标for hand in hands:# 获取食指坐标(x,y,z)x, y, z = hand['lmList'][8]# 获取球拍的宽高h1, w1 = imgBlock1.shape[0:2]# 球拍的中心y坐标,随着掌心移动y1 = y - h1 // 2# 如果检测到了左手if hand['type'] == 'Left':# 左侧的球拍x轴固定,y坐标随左手掌间中点移动img = cvzone.overlayPNG(img, imgBlock1, (100,y1))# 检查球是否被左球拍击中, 球的xy坐标是否在球拍xy坐标附近if 100 < ballpos[0] < 100+w1 and y1 < ballpos[1] < y1+h1:# 满足条件代表球拍击中了,改变球的移动方向speedx = -speedx  # x方向设为反方向# 得分加一score[0] += 1# 如果检测到了右手if hand['type'] == 'Right':# 右侧的球拍x轴固定,y坐标随右手掌间中点移动img = cvzone.overlayPNG(img, imgBlock2, (1150,y1))# 检查球是否被右球拍击中if 1050 < ballpos[0] < 1050+w1 and y1 < ballpos[1] < y1+h1:# 满足条件代表球拍击中了,改变球的移动方向speedx = -speedx  # x方向设为反方向# 得分加一score[1] += 1#(6)检查球是否没接到,那么游戏结束if ballpos[0] < 50 or ballpos[0] > 1150:gameover = True# 游戏结束,画面就不动了if gameover is True:break# 游戏没结束就接下去执行else:#(7)调整球的坐标# 如果球的y坐标在超出了桌面的上或下边框范围,调整移动方向if ballpos[1] >= 600 or ballpos[1] <= 50:# y方向的速度调整为反方向,那么x方向和y方向的合速度方向调整了speedy = -speedy# 每一整都调整xy坐标ballpos[0] = ballpos[0] + speedx  # 调整球的x坐标ballpos[1] = ballpos[1] + speedy  # 调整球的y坐标#(8)添加桌球的图片,将imgBall放在球桌img的指定坐标位置img = cvzone.overlayPNG(img, imgBall, ballpos)#(9)显示记分板cvzone.putTextRect(img, f'Left:{score[0]} and Right:{score[1]}', (400,710))#(10)图像展示cv2.imshow('img', img)# 每帧滞留1ms后消失k = cv2.waitKey(1)# ESC键退出程序if k & 0XFF==27:break# 释放视频资源
cap.release()
cv2.destroyAllWindows()

效果图如下:

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

  1. 【MediaPipe】(3) AI视觉,人脸识别,附python完整代码

    各位同学好,今天和大家分享一下如何使用MediaPipe完成人脸实时跟踪检测,先放张图看效果,FPS值为14,右侧的输出为:每帧图像是人脸的概率,检测框的左上角坐标及框的宽高. 有需要的可以使用 cv ...

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

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

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

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

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

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

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

    各位同学好,今天和大家分享一下如何使用 opencv + mediapipe 创建一个AI视觉小游戏,先放图看效果. 游戏规则,用手按下屏幕上的圆形按钮,每按一次后松开,按钮就随机出现在屏幕上的一个位 ...

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

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

  7. 【Java】推箱子小游戏(带背景音乐)完整代码

    Java实现推箱子小游戏 一.整体框架 二.游戏效果图 三.推箱子四大类 1. GameFrame类 2. Map类 3. MapFactory类 4. Sound类 四.游戏分析 1.游戏操作 2. ...

  8. 请用python编写一个斗地主小游戏,并给出完整代码

    很高兴为您服务!这是一个使用Python编写的斗地主小游戏的完整代码:# 导入必备模块 import random# 定义一副牌 poker = ['A', 2, 3, 4, 5, 6, 7, 8, ...

  9. 【深度学习】(5) 简单网络,案例:服装图片分类,附python完整代码

    1. 数据获取 使用系统内部的服装数据集构建神经网络.首先导入需要的库文件,x和y中保存训练集的图像和目标.x_test和y_test中保存测试集需要的图像和目标.(x, y)及(x_test, y_ ...

最新文章

  1. 谷歌Waymo自建车厂,L4级无人车量产指日可待
  2. 搭建网站服务器是选择高配置还是选择性能稳定?
  3. DTP模型之二:(XA协议之二)jotm分布式事务实现
  4. html列表的列选择事件,html5 datalist 选中option选项后的触发事件
  5. 计算机图形学多边形填充代码_零基础学计算机图形学太难?或许你缺的只是一本好书...
  6. 160809308周子济第7次作业
  7. .net学习笔记之协变和抗变(原创)
  8. 网易:Flink + Iceberg 数据湖探索与实践
  9. Direct3D学习笔记
  10. linux 下的下载管理工具
  11. 红米Note8手机图纸-电路原理图+主板元件位号图
  12. 10种微信公众号的推广吸粉方法
  13. 面试常见SQL练习题
  14. 我的世界java版1.7.10咋刷物品,1.7指令方块刷自定义药水教程
  15. 日常刷题_cf_6.26
  16. 毕业论文答辩PPT攻略
  17. TSNE高维数据降维可视化工具 + python实现
  18. java 转换word doc docx 等office文档 为pdf,无需破解 aspose ,无水印
  19. 使用laravel数据库查询结果自动转数组
  20. layui表格数据的批量操作

热门文章

  1. 华为watchfit能升级鸿蒙吗,华为Watch3有望五月登场,升级支持eSIM功能
  2. 暑期实践项目学习报告
  3. 使Arduino支持ATMEGA 168P(烧录BOOTLOADER)
  4. Vue3.x 父组件Setup、Ref操纵子组件中的元素方法
  5. RT-Thread--外部 flash 挂载 fatfs 文件系统
  6. 2019-走向视频中的主观暴力检测TOWARD SUBJECTIVE VIOLENCE DETECTION IN VIDEOS
  7. 串口转波形软件SerialPlot的使用
  8. 高血压人群,每天应该怎么吃
  9. HY-Stibium加入HY-Studio公告
  10. Linux下配置IPV6,C程序适配IPV6