【开源】CV打地鼠(使用opencv做的一个小游戏)
最近期末,因为选修了一门python实验课,正好最近在学opencv,想着能不能用opencv结合mediapipe做一点有意思的小东西。于是,这个CV打地鼠应运而生。
·
最近期末,因为选修了一门python实验课,正好最近在学opencv,想着能不能用opencv结合
mediapipe做一点有意思的小东西。于是,这个CV打地鼠应运而生。我们来看图:
演示图片:

代码模块:

代码:
import os
os.environ['GLOG_minloglevel'] = '3'
import cv2 as cv
import numpy as np
import mediapipe as mp
import random
import time
class Game:
def __init__(self):
self.cap=cv.VideoCapture(0)
self.mp_hands = mp.solutions.hands
self.mp_draw = mp.solutions.drawing_utils
self.score=0
self.mouse_region=1
self.start_time=time.time()
self.current_time=self.start_time
self.duration_time=30
self.mouse_time=self.current_time
self.has_mouse=False
self.mouse_duration=4
self.feedback_time=self.mouse_time
self.score_active=True
self.fireworks_active=False
self.firework_time=self.current_time
self.firework_region=self.mouse_region
self.gou_active=False
self.cha_active=False
self.gou_region=self.mouse_region
self.cha_time=self.mouse_time
def open_video_check(self):
# 检测摄像头是否打开
if not self.cap.isOpened():
print("摄像头打开失败")
return False
else:
return True
def get_h_w(self,photo):
"""
# 获取高度和宽度
:param photo:
:return: w 宽度,h 高度
"""
return photo.shape[:2]
def create_mouse(self,w,h,photo):
'''
# 随机生成老鼠
:param w:
:param h:
:return:
'''
w=w//2
h=h//2
region=[
(0,0),
(w,0),
(0,h),
(w,h)
]
if not self.has_mouse:
if time.time() - self.mouse_time<0.4:
pass
choose=random.randint(1,4)
while choose==self.mouse_region:
choose=random.randint(1,4)
else:
self.mouse_region=choose
self.has_mouse = True
self.mouse_time = self.current_time
self.mouse_duration=random.randint(3,5)
x,y=region[self.mouse_region-1]
self.draw_mouse(photo,x,y,w,h)
if time.time() - self.mouse_time > self.mouse_duration:
self.has_mouse = False
def draw_score_time(self,photo,w,h):
'''
# 显示剩余时间
:param photo:
:param w:
:param h:
:return:
'''
cv.putText(photo, f"Remaining {int(self.duration_time-self.current_time+self.start_time)}", (10, h-30), cv.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
cv.putText(photo, f"score {self.score}", (10+w//2, h-30), cv.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
def draw_X(self,photo,w,h):
"""
# 绘制大叉叉
:param photo:
:param x:
:param y:
:param w:
:param h:
:return:
"""
w = w // 2
h = h // 2
region = [
(0, 0),
(w, 0),
(0, h),
(w, h)
]
x, y = region[self.gou_region - 1]
# 计算对角线的端点坐标
pt1 = (x + 20, y + 20)
pt2 = (x + w - 20, y + h - 20)
pt3 = (x + w - 20, y + 20)
pt4 = (x + 20, y + h - 20)
# 绘制两条对角线
cv.line(photo, pt1, pt2, (0, 0, 255), 3) # 红色线条
cv.line(photo, pt3, pt4, (0, 0, 255), 3) # 红色线条
def draw_gou(self,photo,w,h):
'''
# 绘制大勾勾
:param photo:
:param x:
:param y:
:param w:
:param h:
:return:
'''
w=w//2
h=h//2
region = [
(0, 0),
(w, 0),
(0, h),
(w, h)
]
x,y=region[self.firework_region-1]
w1=w
h1=h
pt1=(x+30,y+h1-40)
pt2=(x+w1//2,y+h1-10)
pt3=(x+w1-30,y+30)
cv.line(photo, pt1, pt2, (0, 255, 0), 3)
cv.line(photo, pt2, pt3, (0, 255, 0), 3)
def draw_mouse(self, photo, x, y, w, h):
"""
# 绘制米老鼠风格的简笔画老鼠
:param photo: 图像帧
:param x: 区域左上角x坐标
:param y: 区域左上角y坐标
:param w: 区域宽度
:param h: 区域高度
"""
x = x + w // 2
y = y + h // 2
r = min(w, h) // 6
# 身体轮廓
cv.circle(photo, (x, y), r * 2, (255, 0, 0), 1)
# 头部轮廓
cv.circle(photo, (x, y - r), r, (255, 0, 0), 1)
# 耳朵轮廓
cv.circle(photo, (x - r, y - r * 2), r // 2, (255, 0, 0), 1)
cv.circle(photo, (x + r, y - r * 2), r // 2, (255, 0, 0), 1)
# 眼睛
cv.circle(photo, (x - r // 2, y - r + r // 3), r // 5, (255, 0, 0), 1)
cv.circle(photo, (x + r // 2, y - r + r // 3), r // 5, (255, 0, 0), 1)
# 尾巴
cv.line(photo, (x - r * 2, y), (x - r * 4, y + r), (255, 0, 0), 1)
def check_fists(self, photo, region, hands):
"""
# 检测拳头在那个区域
:param photo:
:param region:
:param hands:
:return:
"""
x, y, w, h = region
position = photo[y:y + h, x:x + w]
photo_rgb = cv.cvtColor(position, cv.COLOR_BGR2RGB)
res = hands.process(photo_rgb)
if res.multi_hand_landmarks:
for hand_landmarks in res.multi_hand_landmarks:
# 将局部坐标转换为全局坐标
landmarks = np.array([[lm.x * w + x, lm.y * h + y] for lm in hand_landmarks.landmark])
tip_ids = [8, 12, 16, 20] # 手指指尖索引
wrist = landmarks[0]
sum_dist = 0
for tip_id in tip_ids:
dist = np.linalg.norm(landmarks[tip_id] - wrist)
sum_dist += dist
avg_dist = sum_dist / len(tip_ids)
# 握拳判断
is_fist = avg_dist < 80
# 绘制手势关键点
self.mp_draw.draw_landmarks(photo, hand_landmarks, self.mp_hands.HAND_CONNECTIONS)
# 绘制包围手势的矩形框
x_min, y_min = np.min(landmarks, axis=0).astype(int)
x_max, y_max = np.max(landmarks, axis=0).astype(int)
cv.rectangle(photo, (x_min - 10, y_min - 10), (x_max + 10, y_max + 10), (255, 0, 0), 2)
# 显示是否握拳
label = "Fist" if is_fist else "Open"
cv.putText(photo, label, (x_min, y_min - 20), cv.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
# 判断属于哪个大区域
center_x = (x_min + x_max) // 2
center_y = (y_min + y_max) // 2
height, width = photo.shape[:2]
half_w, half_h = width // 2, height // 2
# 判断是否在某个完整区域内
if is_fist:
if center_x < half_w-40 and center_y < half_h-40:
return 1
elif center_x >= half_w-40 and center_y < half_h-40:
return 2
elif center_x < half_w-40 and center_y >= half_h-40:
return 3
elif center_x >= half_w-40 and center_y >= half_h-40:
return 4
else:
return 0 # 在边界或无法确定
return 0 # 未检测到手势
def draw_fireworks(self, photo, w, h):
'''
# 绘画粒子效果的烟花
:param photo:
:param x: 区域左上角x坐标
:param y: 区域左上角y坐标
:param w: 区域宽度
:param h: 区域高度
:return:
'''
w = w // 2
h = h // 2
region = [
(0, 0),
(w, 0),
(0, h),
(w, h)
]
x1,y1=region[self.firework_region-1]
for _ in range(50): # 粒子数量
x = np.random.randint(x1, x1 + w)
y = np.random.randint(y1, y1 + h)
color = tuple(map(int, np.random.randint(0, 255, 3)))
cv.circle(photo, (x, y), 5, color, -1)
def draw_divide_region(self,photo,w,h):
"""
# 绘画四个分区
:return:无
"""
cv.putText(photo,f"Region 1",(10,30),cv.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2)
cv.putText(photo,f"Region 2",(10+w//2,30),cv.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2)
cv.putText(photo,f"Region 3",(10,30+h//2),cv.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2)
cv.putText(photo,f"Region 4",(10+w//2,30+h//2),cv.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2)
cv.line(photo,(0,h//2),(w,h//2),(0,255,0),2)
cv.line(photo,(w//2,0),(w//2,h),(0,255,0),2)
cv.rectangle(photo,(0,0),(w-1,h-1),(0,255,0),3)
def run(self):
if not self.open_video_check():
return
"""
# 运行模块
:ret 表示cap对象的第一个返回值,True表示摄像头读取成功,False失败
:photo 表示numpy数组的图像帧
:cv.flip 表示反转图像
"""
with self.mp_hands.Hands(static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.6) as hands:
while True:
self.current_time = time.time()
ret,photo=self.cap.read()
photo=cv.flip(photo,1)
h,w=self.get_h_w(photo)
self.draw_divide_region(photo,w,h)
self.draw_score_time(photo,w,h)
# self.draw_mouse(photo,0,0,w//2,h//2)
self.create_mouse(w,h,photo)
region = (0, 0, w, h)
region_id=self.check_fists(photo, region,hands)
# self.draw_gou(photo,0,0,w,h)
# self.draw_X(photo,0,0,w,h)
if self.cha_active and time.time()-self.cha_time<1:
self.draw_X(photo,w, h)
else:
self.cha_active=False
if self.gou_active and self.gou_region!=self.mouse_region and time.time()-self.firework_time<1:
self.draw_gou(photo, w, h)
else:
self.gou_active=False
if self.fireworks_active and time.time()-self.feedback_time<3:
self.draw_fireworks(photo, w, h)
else:
self.fireworks_active=False
# self.draw_firewprks(photo,0,0,w//2,h//2)
if int(time.time() - self.start_time) > self.duration_time:
cv.putText(photo, f"The end!", (w//2-240, h//2), cv.FONT_HERSHEY_SIMPLEX, 3, (255, 0, 0), 3)
cv.imshow("photo", photo)
if cv.waitKey(0) == ord('q'):
break
cv.imshow("photo",photo)
if region_id != 0 :
print(f"在区域 {region_id} 检测到拳头")
if region_id == self.mouse_region:
if time.time() - self.feedback_time > 0.6:
self.firework_region = region_id
self.feedback_time = time.time()
self.has_mouse = False
self.score += 1 # 命中加分
self.score_active=False
self.fireworks_active=True
self.firework_time=time.time()
self.gou_active=True
# self.cha_active=False
# self.cha_active=False
else:
if self.score_active:
self.cha_time=time.time()
self.gou_region = region_id
self.score_active=False
self.cha_active=True
# self.gou_active=False
self.feedback_time = time.time()
self.score -= 1 # 错误扣分
if time.time()-self.feedback_time>0.6:
self.score_active=True
if cv.waitKey(1) == ord('q'):
break
if __name__ =="__main__":
try:
game = Game()
game.run()
except Exception as e:
print(f"[ERROR] 程序异常: {e}")
input("按任意键退出...")
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)