規則如下:左手控制白色球拍;右手控制紫色球拍;球拍只能上下移動;紅色圓形就是冰球;球碰撞到上下兩側的藍色邊框,和兩側的球拍就會反彈;如果球進入了黃色區域,遊戲結束;下面的粉紅色計數板,記錄左右兩側各擊球多少次。
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個手部關鍵點座標如下:
開始前,先準備球桌的圖片,球的圖片,球拍的圖片。我是用PPT畫的圖,球和球拍的圖片一定要存成 .png 格式的。放在同一個資料夾中以備讀取。
(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 代表亮度上提亮多少
首先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()
效果圖如下:
這部分主要完成兩項工作,第一是左右手分別控制左側和右側的球拍,第二個是球以一定的速度移動。
(1)控制球拍
hand['bbox'] 中包含了手部检测框的左上角坐标和检测框的宽高,使用手掌中心点的 y 坐标来控制球拍的上下移动。由于两个球拍的shape是相同的,因此只要获取一个球拍的高度 h2 即可。使用掌心中点 y 坐标控制球拍中点的 y1 坐标,公式为:y1 = (y + h) // 2 - h2 // 2
接着使用 cvzone.overlayPNG() 就可以将球拍图片覆盖在原图片的指定区域,其中坐标参数是指覆盖区域的左上角坐标。固定横坐标,只上下移动。
(2)球移动
首先要规定球的移动速度 speedx, speedy = 10, 10 代表球每一帧沿x轴正方向移动10个像素,沿y轴正方向移动10个像素,那么球的初始合速度方向是沿图片的正右下角移动
如果球碰撞到了球桌的上下边框,就反弹。speedy = -speedy。代表x方向每帧移动的步长不变,y方向每帧移动的方向反转,即入射角等于出射角。
在上述代码中补充
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,宽高wh x, y, w, h = hand['bbox'] # 获取球拍的宽高 h2, w1 = imgBlock1.shape[0:2] # 球拍的中心y坐标,随着掌心移动 y1 = (y + h) // 2 - h2 // 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 = -speedy ballpos[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()
效果图如下:
这一部分主要完成三项工作,第一是球拍击打到球,球需要反弹;第二是如果球进入黄色区域,游戏结束;第三是左右侧击球得分计数器。
(1)球拍击球
看到代码中的第(5)步,ballpos 代表球的左上角坐标(x,y),100
(2)球进黄区,游戏结束
if ballpos[0] 1150,如果球图片的左上坐标的 x 坐标,在黄区边缘,整个程序退出。当然也可以做一个游戏结束界面,我之前的博文里也有介绍,我偷个懒不写了。
(3)计数器
首先定义个变量初始化记录左右侧的击球次数 score = [0, 0],如果有一侧的球拍击中球,那么对应该侧计数加一。
上面代码是掌心控制球拍,这里改成食指指尖控制球拍中点移动。
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] # 获取球拍的宽高 h2, w1 = imgBlock1.shape[0:2] # 球拍的中心y坐标,随着掌心移动 y1 = y - h2 // 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+h2: # 满足条件代表球拍击中了,改变球的移动方向 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+h2: # 满足条件代表球拍击中了,改变球的移动方向 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()
效果图如下:
以上是如何用Python製作一個桌上冰球視覺小遊戲的詳細內容。更多資訊請關注PHP中文網其他相關文章!