在上一章,我们已经成功训练了属于自己的yolo模型,接下来,我们将利用这个模型来识别jj象棋中的棋子并生成棋盘数据

模型测试

再次截图,获得新的图片数据,并存在 src.png 下,用于测试

1
2
3
4
5
6
from ultralytics import YOLO

model = YOLO("训练完成的模型文件")
results = model("./src.png")
for result in results:
result.show()

运行以上的代码后我们获得了一张带有标注的图像文件

接下来我们就可以实时识别手机/模拟器屏幕来生成棋盘了

实时追踪

这里我们可以使用两种不同的追踪方法:

  • 使用 cv2.VideoCapture("") 对实时直播流进行识别
  • 循环 访问DroidCast_raw的地址进行识别

实机追踪

这里第一种适用于实机识别,我开发了一款录屏并进行HLS流直播的软件 ASteam 来实现此功能

现在,我们可以利用 cv2.VideoCapture("") 来进行实时追踪了

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
from ultralytics import YOLO
import cv2
import numpy as np
import requests

def check_status():
try:
requests.get("http://127.0.0.1:13254/screen.m3u8")
return True
except requests.RequestException:
return False

def get_loc_by_point(x,y):
x = int(x / 120)
y = int(y / 108)
return x,y

def printBoard(board,names):
text = np.char.asarray(board) # 创建一个字符数组
# 便遍历取到id
for x in range(10):
for y in range(9):
if board[x,y] != -1:
text[x,y] = names[board[x,y]]
else:
text[x,y] = "empty"
print(board)
print(text)

if check_status():# 检查状态
print("AStream is running")
else:
print("Please start AStream")

# 加载模型
model = YOLO("jjchess.pt")
# 开始读取流
cap = cv2.VideoCapture("http://127.0.0.1:13254/screen.m3u8")
# 因为opencv每一帧都解码效率较低,因此我们需要跳过一些帧来提升速度
count = 5# 跳帧个数
type = -1# 我方类型,0红1黑-1未定义
board = np.zeros([10,9],dtype=int)# 9x10的棋盘
board.fill(-1) # 初始化棋盘数据
while cap.isOpened():
for _ in range(count):
cap.grab()# 跳帧
ret, frame = cap.read()# 读帧
if not ret:
break
# 切分处理图片
# 根据屏幕宽度来切除屏幕的上下部分,保持宽高比
height,width = frame.shape[:2]
clip = (height - width) / 2
img = frame[int(clip-50):int(height-clip+50),0:int(width)]
img = cv2.resize(img, (1080, 1080))
# 开始调用yolo识别
results = model(img, stream=True)
# 遍历识别到的所有结果
for result in results:
boxes = result.boxes# 识别框
names = result.names# 识别标签
for box in boxes:# 遍历识别框
id = box.cls.cpu().numpy()[0]
name = f"{names[id]}"
# 排除除了棋子之外的所有标签
if not name.startswith("black") and not name.startswith("red"):
continue
x1, y1, x2, y2 = map(int, box.xyxy.cpu().numpy()[0])# 矩形框左上点与右下点
cx,cy = int((x1 + x2) / 2),int((y1 + y2) / 2)# 中心点
x,y = get_loc_by_point(cx,cy)# 象棋的位置
board[y,x] = id# 更新棋盘
# 判断是红方/黑方
if x == 4 and y == 9:
if name == "red_boss":
type = 0
if name == "black_boss":
type = 1

cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)# 绘制矩形框
cv2.circle(img, (cx, cy), 10, (0, 0, 255), -1)# 绘制中心点
cv2.putText(img, f"{name}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)# 绘制标签
cv2.putText(img, f"({x},{y})", (x1, y2), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)# 绘制象棋位置
printBoard(board,names)# 打印棋盘
if type != -1 and type == 0:
print("AI red")
else:
print("AI black")
board.fill(-1) # 清空棋盘数据

img = cv2.resize(img, (640, 640))# 缩放以让电脑可以正常显示
cv2.imshow("Frame", img)# 显示识别结果
if cv2.waitKey(1) & 0xFF == ord('q'):
break

测试结果:

模拟器追踪

现在,我们将尝试使用 DroidCast_raw 进行连续截图来实现实时追踪

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
from ultralytics import YOLO
import cv2
import numpy as np
import os
import requests
import subprocess

def check_status():
try:
requests.get("http://127.0.0.1:53516/preview")
return True
except requests.RequestException:
return False

def get_clipped_img():
img = requests.get("http://127.0.0.1:53516/preview")
img = cv2.imdecode(np.array(bytearray(img.content)),-1)
height,width = img.shape[:2]
clip = (height - width) / 2
img = img[int(clip-50):int(height-clip+50),0:int(width)]
img = cv2.resize(img, (1080, 1080))
cv2.imwrite("./tmp.png", img)
img = cv2.imread("./tmp.png")
return img

def get_loc_by_point(x,y):
x = int(x / 120)
y = int(y / 108)
return x,y

def printBoard(board,names):
text = np.char.asarray(board) # 创建一个字符数组
# 便遍历取到id
for x in range(10):
for y in range(9):
if board[x,y] != -1:
text[x,y] = names[board[x,y]]
else:
text[x,y] = "empty"
print(board)
print(text)
device = "127.0.0.1:16416"# mumu模拟器adb地址
os.system("adb connect %s" % device)

if check_status():
print("DroidCast_raw is running")
else:
print("try start DroidCast_raw")
os.system("adb -s %s push DroidCast_raw.apk /data/local/tmp" % device)
os.system("adb -s %s forward tcp:53516 tcp:53516" % device)
subprocess.Popen(["adb","-s",device,"shell","CLASSPATH=/data/local/tmp/DroidCast_raw.apk nohup app_process / ink.mol.droidcast_raw.Main --port=53516 &"],shell=True)

model = YOLO("jjchess.pt")
type = -1# 我方类型,0红1黑-1未定义
board = np.zeros([10,9],dtype=int)
board.fill(-1) # 初始化棋盘数据
while True:
img = get_clipped_img()# 读帧
results = model(img, stream=True)
# 遍历识别到的所有结果
for result in results:
boxes = result.boxes# 识别框
names = result.names# 识别标签
for box in boxes:# 遍历识别框
id = box.cls.cpu().numpy()[0]
name = f"{names[id]}"
# 排除除了棋子之外的所有标签
if not name.startswith("black") and not name.startswith("red"):
continue
x1, y1, x2, y2 = map(int, box.xyxy.cpu().numpy()[0])# 矩形框左上点与右下点
cx,cy = int((x1 + x2) / 2),int((y1 + y2) / 2)# 中心点
x,y = get_loc_by_point(cx,cy)# 象棋的位置
board[y,x] = id# 更新棋盘
# 判断是红方/黑方
if x == 4 and y == 9:
if name == "red_boss":
type = 0
if name == "black_boss":
type = 1

cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)# 绘制矩形框
cv2.circle(img, (cx, cy), 10, (0, 0, 255), -1)# 绘制中心点
cv2.putText(img, f"{name}", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)# 绘制标签
cv2.putText(img, f"({x},{y})", (x1, y2), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2)# 绘制象棋位置
printBoard(board,names)# 打印棋盘
if type != -1 and type == 0:
print("AI red")
else:
print("AI black")
board.fill(-1) # 清空棋盘数据

img = cv2.resize(img, (640, 640))
cv2.imshow("Frame", img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break

测试结果: