介绍
各位同学好,今天和大家分享一下如何使用 mediapipe+opencv 自制贪吃蛇小游戏。先放张图看效果。
规则:食指指尖控制蛇头,指尖每接触到黄色方块,计数加一,蛇身变长,方块随机切换位置。如果指尖停止移动,或者移动过程中蛇头撞到蛇身,那么游戏结束。点击键盘上的R键重新开始游戏。
游戏进行时:
游戏结束界面:
1. 安装工具包
1
2
3
4
5
6
7
8
9
10
11
12
|
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
import numpy as np
from cvzone.HandTrackingModule import HandDetector # 导入手部检测模块
import math
import random
|
21个手部关键点信息如下,本节我们主要研究食指指尖'8'的坐标(x,y)信息。
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()
MULTI_HAND_LANDMARKS: 被检测/跟踪的手的集合,其中每只手被表示为21个手部地标的列表,每个地标由x, y, z组成。x和y分别由图像的宽度和高度归一化为[0,1]。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: 返回绘制了关键点及连线后的图像
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
import cv2
import cvzone
import numpy as np
from cvzone.HandTrackingModule import HandDetector # 导入手部检测模块
#(1)获取摄像头
cap = cv2.VideoCapture(0) # 0代表电脑自带的摄像头
# 设置显示窗口的size
cap.set(3, 1280) # 窗口宽1280
cap.set(4, 720) # 窗口高720
#(2)模型配置
detector = HandDetector(maxHands=1, # 最多检测1只手
detectionCon=0.8) # 最小检测置信度0.8
#(3)图像处理
while True:
# 每次读取一帧相机图像,返回是否读取成功success,读取的帧图像img
success, img = cap.read()
# 图像翻转,使图像和自己呈镜像关系
img = cv2.flip(img, 1) # 0代表上下翻转,1代表左右翻转
# 检测手部关键点。返回手部信息hands,绘制关键点后的图像img
hands, img = detector.findHands(img, flipType=False) # 由于上一行翻转过图像了,这里就不用翻转了
# 查看关键点信息
print(hands)
#(4)关键点处理
if hands: # 如果检测到手了,那就处理关键点
# 获得食指指尖坐标(x,y)
hand = hands[0] # 获取一只手的全部信息
lmList = hand['lmList'] # 获得这只手的21个关键点的坐标(x,y,z)
pointIndex = lmList[8][0:2] # 只获取食指指尖关键点的(x,y)坐标
# 以食指指尖为圆心画圈(圆心坐标是元组类型),半径为15,青色填充
cv2.circle(img, tuple(pointIndex), 15, (255,0,0), cv2.FILLED)
#(5)显示图像
cv2.imshow('img', img) # 输入图像显示窗口的名称及图像
# 每帧滞留1毫秒后消失,并且按下ESC键退出
if cv2.waitKey(1) & 0xFF == 27:
break
# 释放视频资源
cap.release()
cv2.destroyAllWindows()
|
效果图如下:
打印手部关键点信息如下:
1
2
3
4
|
[{'lmList': [[1152, 675, 0], [1085, 693, -37], [1030, 698, -68], [1003, 698, -97], [1003, 679, -122], [1001, 511, -48], [1041, 546, -81], [1093, 608, -102], [1134, 652, -110], [1075, 484, -46], [1119, 534, -84], [1171, 605, -101], [1217, 659, -103], [1141, 481, -45], [1177, 529, -83], [1219, 590, -84], [1253, 642, -73], [1195, 494, -47], [1221, 521, -73], [1245, 566, -65], [1267, 602, -49]],
'bbox': (1001, 481, 266, 217),
'center': (1134, 589),
'type': 'Right'}]
|
3. 蛇身移动
构造一个处理蛇身移动的类,要求在没吃食物时,蛇身保持固定的长度跟随食指指尖移动。
举个例子,如果当前的蛇身节点列表 self.points 包含 [a, b, c, d] 这四个节点,a节点代表蛇尾,d节点代表蛇头。在下一帧,食指指尖移动到 e 点,将 e 节点追加到蛇身节点列表中,那么现在的列表包含 [a, b, c, d, e] 节点,其中 e 节点为新的蛇头。
此时判断当前蛇身总长度 self.currentLength(列表中所有节点之间的长度之和)是否大于蛇身固定长度 self.allowedLength,保证在移动过程中蛇身长度不变。
如果当前蛇身总长度 self.currentLength 大于固定长度 self.allowedLength,那么在节点列表中从尾到头依次删除节点,列表 [a, b, c, d, e] 中 a 表示蛇尾节点,先删除,判断列表 [b, c, d, e] 的节点之间的总长度是否满足要求。若仍大于固定长度,那么就再删除 b 节点,再判断。
如果当前蛇身总长度 self.currentLength 小于固定长度 self.allowedLength,那么就不做任何处理。
在上述代码中补充:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
import cv2
import cvzone
import numpy as np
from cvzone.HandTrackingModule import HandDetector # 导入手部检测模块
import math
# 构造一个贪吃蛇移动的类
class SnakeGameClass:
#(一)初始化
def __init__(self):
self.points = [] # 蛇的身体的节点坐标
self.lengths = [] # 蛇身各个节点之间的坐标
self.currentLength = 0 # 当前蛇身长度
self.allowedLength = 150 # 没吃东西时,蛇的总长度
self.previousHead = (0,0) # 前一个蛇头节点的坐标
#(二)更新增加蛇身长度
def update(self, imgMain, currentHead): # 输入图像,当前蛇头的坐标
px, py = self.previousHead # 获得前一个蛇头的x和y坐标
cx, cy = currentHead # 当前蛇头节点的x和y坐标
# 添加当前蛇头的坐标到蛇身节点坐标列表中
self.points.append([cx,cy])
# 计算两个节点之间的距离
distance = math.hypot(cx-px, cy-py) # 计算平方和开根
# 将节点之间的距离添加到蛇身节点距离列表中
self.lengths.append(distance)
# 增加当前蛇身长度
self.currentLength += distance
# 更新蛇头坐标
self.previousHead = (cx,cy)
#(三)减少蛇尾长度,即移动过程中蛇头到蛇尾的长度不大于150
if self.currentLength > self.allowedLength:
# 遍历所有的节点线段长度。新更新的蛇头索引在列表后面,蛇尾的索引在列表前面
for i, length in enumerate(self.lengths):
# 从蛇尾到蛇头依次减线段长度,得到的长度是否满足要求
self.currentLength -= length
# 从列表中删除蛇尾端的线段长度,以及蛇尾节点
self.lengths.pop(i)
self.points.pop(i)
# 如果当前蛇身长度小于规定长度,满足要求,退出循环
if self.currentLength < self.allowedLength:
break
#(四)绘制蛇
# 当节点列表中有值了,才能绘制
if self.points:
# 遍历蛇身节点坐标
for i, point in enumerate(self.points):
# 绘制前后两个节点之间的连线
if i != 0:
cv2.line(imgMain, tuple(self.points[i-1]), tuple(self.points[i]), (0,255,0), 20)
# 在蛇头的位置画个圆
cv2.circle(imgMain, tuple(self.points[-1]), 20, (255,0,0), cv2.FILLED)
# 返回更新后的图像
return imgMain
#(1)获取摄像头
cap = cv2.VideoCapture(0) # 0代表电脑自带的摄像头
# 设置显示窗口的size
cap.set(3, 1280) # 窗口宽1280
cap.set(4, 720) # 窗口高720
#(2)模型配置
detector = HandDetector(maxHands=1, # 最多检测1只手
detectionCon=0.8) # 最小检测置信度0.8
# 接收创建贪吃蛇的类
game = SnakeGameClass()
#(3)图像处理
while True:
# 每次读取一帧相机图像,返回是否读取成功success,读取的帧图像img
success, img = cap.read()
# 图像翻转,使图像和自己呈镜像关系
img = cv2.flip(img, 1) # 0代表上下翻转,1代表左右翻转
# 检测手部关键点。返回手部信息hands,绘制关键点后的图像img
hands, img = detector.findHands(img, flipType=False) # 由于上一行翻转过图像了,这里就不用翻转了
# 查看关键点信息
print(hands)
#(4)关键点处理
if hands: # 如果检测到手了,那就处理关键点
# 获得食指指尖坐标(x,y)
hand = hands[0] # 获取一只手的全部信息
lmList = hand['lmList'] # 获得这只手的21个关键点的坐标(x,y,z)
pointIndex = lmList[8][0:2] # 只获取食指指尖关键点的(x,y)坐标
# 更新贪吃蛇的节点,给出蛇头节点坐标。返回更新后的图像
img = game.update(img, pointIndex)
#(5)显示图像
cv2.imshow('img', img) # 输入图像显示窗口的名称及图像
# 每帧滞留1毫秒后消失,并且按下ESC键退出
if cv2.waitKey(1) & 0xFF == 27:
break
# 释放视频资源
cap.release()
cv2.destroyAllWindows()
|
效果图如下,蛇身保持默认固定长度随着指尖而移动。
4. 蛇进食增加身体长度
先看下面代码 SnakeGameClass 类中的第(五)步。给食物(即绘制的矩形)随机给出一个中心点坐标,自定义的类方法 randomFoodLocation(),执行该方法则食物的中心点坐标的x在[100,1000]中随机取一个数,y在[100,600]中随机取一个数。
下面代码定义的类中的第(七)步。判断食指指尖(即蛇头节点坐标)是否在矩形内部,如果在内部,那么蛇身移动过程中的固定长度 self.allowedLength 增加50个像素值。得分 self.score 加一。并在下一帧随机改变食物的位置。
在上述代码中补充:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
import cv2
import cvzone
import numpy as np
from cvzone.HandTrackingModule import HandDetector # 导入手部检测模块
import math
import random
# 构造一个贪吃蛇移动的类
class SnakeGameClass:
#(一)初始化
def __init__(self):
self.score = 0 # 积分器
self.points = [] # 蛇的身体的节点坐标
self.lengths = [] # 蛇身各个节点之间的坐标
self.currentLength = 0 # 当前蛇身长度
self.allowedLength = 150 # 没吃东西时,蛇的总长度
self.previousHead = (0,0) # 前一个蛇头节点的坐标
self.foodPoint = (0,0) # 食物的起始位置
self.randomFoodLocation() # 随机改变食物的位置
#(五)食物随机出现的位置
def randomFoodLocation(self):
# x在100至1000之间,y在100至600之间,随机取一个整数
self.foodPoint = random.randint(100, 1000), random.randint(100, 600)
#(二)更新增加蛇身长度
def update(self, imgMain, currentHead): # 输入图像,当前蛇头的坐标
px, py = self.previousHead # 获得前一个蛇头的x和y坐标
cx, cy = currentHead # 当前蛇头节点的x和y坐标
# 添加当前蛇头的坐标到蛇身节点坐标列表中
self.points.append([cx,cy])
# 计算两个节点之间的距离
distance = math.hypot(cx-px, cy-py) # 计算平方和开根
# 将节点之间的距离添加到蛇身节点距离列表中
self.lengths.append(distance)
# 增加当前蛇身长度
self.currentLength += distance
# 更新蛇头坐标
self.previousHead = (cx,cy)
#(三)减少蛇尾长度,即移动过程中蛇头到蛇尾的长度不大于150
if self.currentLength > self.allowedLength:
# 遍历所有的节点线段长度。新更新的蛇头索引在列表后面,蛇尾的索引在列表前面
for i, length in enumerate(self.lengths):
# 从蛇尾到蛇头依次减线段长度,得到的长度是否满足要求
self.currentLength -= length
# 从列表中删除蛇尾端的线段长度,以及蛇尾节点
self.lengths.pop(i)
self.points.pop(i)
# 如果当前蛇身长度小于规定长度,满足要求,退出循环
if self.currentLength < self.allowedLength:
break
#(七)检查蛇是否吃了食物
rx, ry = self.foodPoint # 得到食物的中心点坐标位置
# 绘制矩形作为蛇的食物
cv2.rectangle(imgMain, (rx-20, ry-20), (rx+20, ry+20), (255,255,0), cv2.FILLED)
cv2.rectangle(imgMain, (rx-20, ry-20), (rx+20, ry+20), (0,255,255), 5)
cv2.rectangle(imgMain, (rx-5, ry-5), (rx+5, ry+5), (0,0,255), cv2.FILLED)
# 检查指尖(即蛇头cx,cy)是否在矩形内部
if rx-20 < cx < rx+20 and ry-20< cy < ry+20:
# 随机更换食物的位置
self.randomFoodLocation()
# 增加蛇身的限制长度,每吃1个食物就能变长50
self.allowedLength += 50
# 吃食物的计数加一
self.score += 1
print('eat!', f'score:{self.score}')
#(四)绘制蛇
# 当节点列表中有值了,才能绘制
if self.points:
# 遍历蛇身节点坐标
for i, point in enumerate(self.points):
# 绘制前后两个节点之间的连线
if i != 0:
cv2.line(imgMain, tuple(self.points[i-1]), tuple(self.points[i]), (0,255,0), 20)
cv2.line(imgMain, tuple(self.points[i-1]), tuple(self.points[i]), (0,0,255), 15)
# 在蛇头的位置画个圆
cv2.circle(imgMain, tuple(self.points[-1]), 20, (255,255,0), cv2.FILLED)
cv2.circle(imgMain, tuple(self.points[-1]), 18, (255,0,0), 3)
cv2.circle(imgMain, tuple(self.points[-1]), 5, (0,0,0), cv2.FILLED)
# 返回更新后的图像
return imgMain
#(1)获取摄像头
cap = cv2.VideoCapture(0) # 0代表电脑自带的摄像头
# 设置显示窗口的size
cap.set(3, 1280) # 窗口宽1280
cap.set(4, 720) # 窗口高720
#(2)模型配置
detector = HandDetector(maxHands=1, # 最多检测1只手
detectionCon=0.8) # 最小检测置信度0.8
# 接收创建贪吃蛇的类
game = SnakeGameClass()
#(3)图像处理
while True:
# 每次读取一帧相机图像,返回是否读取成功success,读取的帧图像img
success, img = cap.read()
# 图像翻转,使图像和自己呈镜像关系
img = cv2.flip(img, 1) # 0代表上下翻转,1代表左右翻转
# 检测手部关键点。返回手部信息hands,绘制关键点后的图像img
hands, img = detector.findHands(img, flipType=False) # 由于上一行翻转过图像了,这里就不用翻转了
#(4)关键点处理
if hands: # 如果检测到手了,那就处理关键点
# 获得食指指尖坐标(x,y)
hand = hands[0] # 获取一只手的全部信息
lmList = hand['lmList'] # 获得这只手的21个关键点的坐标(x,y,z)
pointIndex = lmList[8][0:2] # 只获取食指指尖关键点的(x,y)坐标
# 更新贪吃蛇的节点,给出蛇头节点坐标。返回更新后的图像
img = game.update(img, pointIndex)
#(5)显示图像
cv2.imshow('img', img) # 输入图像显示窗口的名称及图像
# 每帧滞留1毫秒后消失,并且按下ESC键退出
if cv2.waitKey(1) & 0xFF == 27:
break
# 释放视频资源
cap.release()
cv2.destroyAllWindows()
|
效果图如下:
5. 自身碰撞及界面的处理
先看到自定义类 SnakeGameClass 中的第(八)步,蛇身节点列表 self.points 中包含从蛇头到蛇尾的所有节点。如 [a, b, c, d, e] 节点,a 节点代表蛇尾,e 节点代表蛇头。这里我就粗糙地判断一下是否碰撞,如果大家有更好的判断方法可以改动这第(八)步。计算蛇头 e 节点到所有节点之间的距离,如果小于某个值就代表碰撞了,游戏结束 self.gameover = True
再看到自定义类中的第(三)步。如果游戏结束 self.gameover = True,那就在下一帧中绘制结算界面,不再执行蛇身移动程序。
再看到主程序中的第(5)步。其中 k == ord('r'),按下键盘上的 r 键来重新游戏。将所有蛇身变量初始化,并将 self.gameover = False,退出结算界面,使得下一帧能执行蛇身移动操作。
在上述代码中补充:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
|
import cv2
import cvzone
from matplotlib.cbook import pts_to_midstep
import numpy as np
from cvzone.HandTrackingModule import HandDetector # 导入手部检测模块
import math
import random
# 构造一个贪吃蛇移动的类
class SnakeGameClass:
#(一)初始化
def __init__(self):
self.score = 0 # 积分器
self.points = [] # 蛇的身体的节点坐标
self.lengths = [] # 蛇身各个节点之间的坐标
self.currentLength = 0 # 当前蛇身长度
self.allowedLength = 150 # 没吃东西时,蛇的总长度
self.previousHead = (0,0) # 前一个蛇头节点的坐标
self.foodPoint = (0,0) # 食物的起始位置
self.randomFoodLocation() # 随机改变食物的位置
self.gameover = False # 蛇头撞到蛇身,变成True,游戏结束
#(二)食物随机出现的位置
def randomFoodLocation(self):
# x在100至1000之间,y在100至600之间,随机取一个整数
self.foodPoint = random.randint(100, 1000), random.randint(100, 600)
#(三)更新增加蛇身长度
def update(self, imgMain, currentHead): # 输入图像,当前蛇头的坐标
# 游戏结束,显示文本
if self.gameover:
cvzone.putTextRect(imgMain, 'GameOver', [400,300], 5, 3, colorR=(0,255,255), colorT=(0,0,255))
cvzone.putTextRect(imgMain, f'Score:{self.score}', [450,400], 5, 3, colorR=(255,255,0))
cvzone.putTextRect(imgMain, f"Press Key 'R' to Restart", [230,500], 4, 3, colorR=(0,255,0), colorT=(255,0,0))
else:
px, py = self.previousHead # 获得前一个蛇头的x和y坐标
cx, cy = currentHead # 当前蛇头节点的x和y坐标
# 添加当前蛇头的坐标到蛇身节点坐标列表中
self.points.append([cx,cy])
# 计算两个节点之间的距离
distance = math.hypot(cx-px, cy-py) # 计算平方和开根
# 将节点之间的距离添加到蛇身节点距离列表中
self.lengths.append(distance)
# 增加当前蛇身长度
self.currentLength += distance
# 更新蛇头坐标
self.previousHead = (cx,cy)
#(四)减少蛇尾长度,即移动过程中蛇头到蛇尾的长度不大于150
if self.currentLength > self.allowedLength:
# 遍历所有的节点线段长度。新更新的蛇头索引在列表后面,蛇尾的索引在列表前面
for i, length in enumerate(self.lengths):
# 从蛇尾到蛇头依次减线段长度,得到的长度是否满足要求
self.currentLength -= length
# 从列表中删除蛇尾端的线段长度,以及蛇尾节点
self.lengths.pop(i)
self.points.pop(i)
# 如果当前蛇身长度小于规定长度,满足要求,退出循环
if self.currentLength < self.allowedLength:
break
#(五)绘制得分板
cvzone.putTextRect(imgMain, f'Score:{self.score}', [50,80], 4, 3, colorR=(255,255,0))
#(六)检查蛇是否吃了食物
rx, ry = self.foodPoint # 得到食物的中心点坐标位置
# 绘制矩形作为蛇的食物
cv2.rectangle(imgMain, (rx-20, ry-20), (rx+20, ry+20), (255,255,0), cv2.FILLED)
cv2.rectangle(imgMain, (rx-20, ry-20), (rx+20, ry+20), (0,255,255), 5)
cv2.rectangle(imgMain, (rx-5, ry-5), (rx+5, ry+5), (0,0,255), cv2.FILLED)
# 检查指尖(即蛇头cx,cy)是否在矩形内部
if rx-20 < cx < rx+20 and ry-20< cy < ry+20:
# 随机更换食物的位置
self.randomFoodLocation()
# 增加蛇身的限制长度,每吃1个食物就能变长50
self.allowedLength += 50
# 吃食物的计数加一
self.score += 1
print('eat!', f'score:{self.score}')
#(七)绘制蛇
# 当节点列表中有值了,才能绘制
if self.points:
# 遍历蛇身节点坐标
for i, point in enumerate(self.points):
# 绘制前后两个节点之间的连线
if i != 0:
cv2.line(imgMain, tuple(self.points[i-1]), tuple(self.points[i]), (0,255,0), 20)
cv2.line(imgMain, tuple(self.points[i-1]), tuple(self.points[i]), (0,0,255), 15)
# 在蛇头的位置画个圆
cv2.circle(imgMain, tuple(self.points[-1]), 20, (255,255,0), cv2.FILLED)
cv2.circle(imgMain, tuple(self.points[-1]), 18, (255,0,0), 3)
cv2.circle(imgMain, tuple(self.points[-1]), 5, (0,0,0), cv2.FILLED)
#(八)检查蛇头碰撞到自身
for point in self.points[:-2]: # 不算蛇头到自身的距离
# 计算蛇头和每个节点之间的距离
dist = math.hypot(cx-point[0], cy-point[1])
# 如果距离小于1.8,那么就证明碰撞了
if dist < 1.8:
# 游戏结束
self.gameover = True
# 返回更新后的图像
return imgMain
#(1)获取摄像头
cap = cv2.VideoCapture(0) # 0代表电脑自带的摄像头
# 设置显示窗口的size
cap.set(3, 1280) # 窗口宽1280
cap.set(4, 720) # 窗口高720
#(2)模型配置
detector = HandDetector(maxHands=1, # 最多检测1只手
detectionCon=0.8) # 最小检测置信度0.8
# 接收创建贪吃蛇的类
game = SnakeGameClass()
#(3)图像处理
while True:
# 每次读取一帧相机图像,返回是否读取成功success,读取的帧图像img
success, img = cap.read()
# 图像翻转,使图像和自己呈镜像关系
img = cv2.flip(img, 1) # 0代表上下翻转,1代表左右翻转
# 检测手部关键点。返回手部信息hands,绘制关键点后的图像img
hands, img = detector.findHands(img, flipType=False) # 由于上一行翻转过图像了,这里就不用翻转了
#(4)关键点处理
if hands: # 如果检测到手了,那就处理关键点
# 获得食指指尖坐标(x,y)
hand = hands[0] # 获取一只手的全部信息
lmList = hand['lmList'] # 获得这只手的21个关键点的坐标(x,y,z)
pointIndex = lmList[8][0:2] # 只获取食指指尖关键点的(x,y)坐标
# 更新贪吃蛇的节点,给出蛇头节点坐标。返回更新后的图像
img = game.update(img, pointIndex)
#(5)显示图像
cv2.imshow('img', img) # 输入图像显示窗口的名称及图像
# 重新开始游戏
k = cv2.waitKey(1) # 每帧滞留1毫秒后消失
if k == ord('r'): # 键盘'r'键代表重新开始游戏
game.gameover = False
game.score = 0 # 积分器
game.points = [] # 蛇的身体的节点坐标
game.lengths = [] # 蛇身各个节点之间的坐标
game.currentLength = 0 # 当前蛇身长度
game.allowedLength = 150 # 没吃东西时,蛇的总长度
game.previousHead = (0,0) # 前一个蛇头节点的坐标
game.randomFoodLocation() # 随机改变食物的位置
if k & 0xFF == 27: # 键盘ESC键退出程序
break
# 释放视频资源
cap.release()
cv2.destroyAllWindows()
|
效果图如下,在移动过程中,蛇头每碰到一个食物,蛇身就会变长,如果 停止移动 或 蛇头节点距离蛇身节点过近 就会结束游戏。
|