部落冲突辅助 数字识别

最近在重新开始玩部落冲突这款游戏。在高中时期,我非常喜欢这款游戏。当时我就想写一个脚本帮我自动搜鱼,搜到鱼后提示我,我就在一旁写作业。或者甚至能自动打鱼。当时技术有限,做了一个凑活能用的。现在想再次尝试实现一下。先从数字识别做起。

Airtest

在高中时期遇到的第一个难题就是:捏合缩放,用于把地图缩小。当时是使用ADB控制手机,研究了很久发现无法实现,最后只能在每次脚本运行前,手动将地图缩小。

现在了解到Airtest可以轻松地做到。此外,Airtest还提供了其他丰富的API,使得连接设备、截图等操作能轻松实现。

数字识别

高中时期遇到的第二个难题是:数字识别。事实上,当时我已经实现了将图片二值化,然后裁剪出每一个数字。只要将裁剪出的数字和模板计算相似度,就能够完成数字识别的任务了。不记得当时遇到了什么阻碍,最后是调用百度文字识别OCR的API完成的。

现在学习了深度学习,对于这样的图像分类任务,简直是小菜一碟。

二值化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cv2
import numpy

# 图片裁剪
ResourceArea = (126, 100, 286, 165)

im = cv2.imread("snapshot.png")
im = im[ResourceArea[1]:ResourceArea[3], ResourceArea[0]:ResourceArea[2]]

# 二值化
im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
_, im = cv2.threshold(im, 195, 255, cv2.THRESH_BINARY)

kernel = numpy.ones((3, 3), numpy.uint8)
im = cv2.erode(im, kernel) # 腐蚀
im = cv2.dilate(im, kernel) # 膨胀

一般情况,二值化先用cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)将图片转为灰度图像,再用cv2.threshold(im, threshould, 255, cv2.THRESH_BINARY)就完成了二值化。

但是在本例中,将阈值设得比较低,数字以外得区域还会有白点;将阈值设得比较高,数字变得非常残缺。所以设置了一个居中的阈值,然后进行腐蚀后膨胀的操作,消除小的白点。

结果如下:

二值化结果

裁剪单个数字

1
2
3
4
5
6
7
8
9
gold_im = im[0: im.shape[0]//2, 0: im.shape[1]] # 取第一行(金币)

contours, _ = cv2.findContours(im, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=lambda item: cv2.boundingRect(item)[0]) # 按从左到右的顺序

for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
single_num = gold_im[y:y + h, x:x + w]
single_num = cv2.resize(single_num, (16, 16))

高中时,是自己实现了这一算法的。现在,通过ChatGPT,了解到了cv2.findContours这一函数,是用于在图像中查找轮廓的函数。

然后就是不断搜鱼、截图,制作用于训练神经网络的数据集,每个数字收集20张左右已经足够了。

数字

(因为resize操作,数字1变形比较严重,不过没关系)

神经网络

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
import torch.nn as nn
import torch


class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(32, 32, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),

nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(64, 64, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),

nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(128, 128, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
)

self.classifier = nn.Sequential(
nn.Linear(512, 128),
nn.ReLU(inplace=True),

nn.Linear(128, 32),
nn.ReLU(inplace=True),

nn.Linear(32, 10),
)

def forward(self, inputs):
outputs = self.features(inputs)
outputs = torch.flatten(outputs, 1)
outputs = self.classifier(outputs)

return outputs

因为任务比较简单,这种级别的神经网络已经能完美地完成任务了。

训练

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
from torchvision.datasets import ImageFolder
from torchvision import transforms
from torch.utils.data import DataLoader
import torch
import torch.nn as nn
import torch.optim as optim


dataset = ImageFolder('dataset', transforms.ToTensor())
data_loader = DataLoader(dataset=dataset, batch_size=16, shuffle=True)

model = Net()
optimizer = optim.AdamW(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()

num_epochs = 25
for epoch in range(num_epochs):
model.train()
total_loss = 0
for inputs, labels in data_loader:
optimizer.zero_grad()

outputs = model(inputs)
loss = criterion(outputs, labels)
total_loss += loss.item()
loss.backward()
optimizer.step()

print(f'Epoch [{epoch+1:02d}/{num_epochs}], Loss: {total_loss/len(data_loader):.8f}')

torch.save(model.state_dict(), 'weight.pth')

小神经网络+小数据集,在我的小破笔记本上1分钟不到就训练好了。

推理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
model = Net()
model.load_state_dict(torch.load("weight.pth"))

contours, _ = cv2.findContours(gold_im, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=lambda item: cv2.boundingRect(item)[0])

transform = transforms.Compose([
transforms.ToPILImage(),
transforms.ToTensor()
])

nums = []
for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
num = gold_im[y:y + h, x:x + w]
num = cv2.resize(num, (16, 16))
num = cv2.cvtColor(num, cv2.COLOR_GRAY2RGB)
num = transform(num)
texts.append(num)

nums = torch.stack(nums)
res = model(nums)
res = numpy.argmax(res.detach().numpy(), 1)
res = numpy.sum(res*10**numpy.arange(len(res)-1, -1, -1))

搜了几条鱼试验了一下,目前准确率100%,从截完图到识别出结果,耗时在50ms以内,后期资源数上升,再加上黑油,应该也能在200ms内完成推理。


部落冲突辅助 数字识别
https://zuoguan.netlify.app/2024/02/03/部落冲突辅助-数字识别/
作者
坐观是只皮卡丘
发布于
2024年2月3日
许可协议