一、 初始化方块所有类型并处理图像
注意方块类型数量
建议大小为 50 * 50 的方块。
# 获取所有的方块类型
def getAllSquareTypes():
print("将图像矩阵按类型归入类型列表...")
types = []
for i in range(0, 8):
empty_img = cv2.imread("ss/" + str(i) + ".png")
empty_img = np.array(empty_img)
empty_gray = cv2.cvtColor(empty_img, cv2.COLOR_BGR2GRAY)
empty_GB = cv2.GaussianBlur(empty_gray, (3, 3), 0)
empty_GB = cv2.threshold(empty_GB, 100, 255, cv2.THRESH_BINARY_INV)[1]
empty_GB = cv2.dilate(empty_GB, None, iterations=2)
types.append(empty_GB)
return types
二、 获取窗口在屏幕上位置
返回窗口左上角坐标。
# 获取窗体坐标位置(左上)
def getGameWindowPosition():
# FindWindow(lpClassName=None, lpWindowName=None) 窗口类名 窗口标题名
window = win32gui.FindWindow(None,WINDOW_TITLE)
# 没有定位到游戏窗体
while not window:
print('定位游戏窗体失败,5秒后重试...')
time.sleep(5)
window = win32gui.FindWindow(None,WINDOW_TITLE)
# 定位到游戏窗体
win32gui.SetForegroundWindow(window) # 将窗体顶置
pos = win32gui.GetWindowRect(window)
print("定位到游戏窗体:" + str(pos))
return (pos[0],pos[1])
三、截取屏幕并保存为 png 格式
输入你想要的名字(英文)即可保存截屏。
def window_capture(filename):
hwnd = 0 # 窗口的编号,0号表示当前活跃窗口
# 根据窗口句柄获取窗口的设备上下文DC(Divice Context)
hwndDC = win32gui.GetWindowDC(hwnd)
# 根据窗口的DC获取mfcDC
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
# mfcDC创建可兼容的DC
saveDC = mfcDC.CreateCompatibleDC()
# 创建bigmap准备保存图片
saveBitMap = win32ui.CreateBitmap()
# 获取监控器信息
MoniterDev = win32api.EnumDisplayMonitors(None, None)
w = MoniterDev[0][2][2]
h = MoniterDev[0][2][3]
# print w,h #图片大小
# 为bitmap开辟空间
saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
# 高度saveDC,将截图保存到saveBitmap中
saveDC.SelectObject(saveBitMap)
# 截取从左上角(0,0)长宽为(w,h)的图片
saveDC.BitBlt((0, 0), (w, h), mfcDC, (0, 0), win32con.SRCCOPY)
saveBitMap.SaveBitmapFile(saveDC, filename)
四、读取图片并切割需要识别的部分
注意:此处需要修改的部分为:
MARGIN_LEFT、MARGIN_HEIGHT为
窗口左上角坐标系到左上角第一个方块的宽和高
SQUARE_HEIGHT、 SQUARE_WIDTH为
方块左上角到下一个方块的左上角(即包含方块与方块之间的间隙)
# 从屏幕截图中切割
def getAllSquare(screen_image,game_pos):
print('图像切片处理...')
# 通过游戏窗体,找到连连看连接的区域:
game_x = game_pos[0] + MARGIN_LEFT
game_y = game_pos[1] + MARGIN_HEIGHT
# 从连接区域左上开始,把图像切割成一个一个的小块,切割标准是按照小块的横纵坐标。
all_square = []
for x in range(0,H_NUM):
# line_square = []
for y in range(0,V_NUM):
# ndarray的切片方法,[纵坐标起始位置:纵坐标结束为止,横坐标起始位置:横坐标结束位置]
square = screen_image[game_y + y * SQUARE_HEIGHT :game_y + (y+1) * SQUARE_HEIGHT,game_x + x * SQUARE_WIDTH:game_x + (x+1) * SQUARE_WIDTH]
# cv2.rectangle(screen_image, (game_x + x * SQUARE_WIDTH , game_y + y * SQUARE_HEIGHT),
# (game_x + (x+1) * SQUARE_WIDTH, game_y + (y+1) * SQUARE_HEIGHT),
# (0, 0, 255),
# 3,
# 8)
# cv2.imshow("ss", screen_image)
# cv2.waitKey(0)
all_square.append(square)
# 因为有些图片的边缘不一致造成干扰(主要是空白区域的切图),所以把每张小方块向内缩小一部分再
# 对所有的方块进行处理屏蔽掉外边缘 然后返回
# return list(map(lambda square : square[SUB_LT_Y:SUB_RB_Y,SUB_LT_X:SUB_RB_X],all_square))
return [square[SUB_LT_Y:SUB_RB_Y, SUB_LT_X:SUB_RB_X] for square in all_square]
五、讲切片后的图像和第一步初始化的图像进行对比
对比原理:将图片都处理成二值化图后,利用 absdiff (做差求绝对值) 比较两个图片的差异,差异越多,图片二值图显示出来白色部分越多。再选取最小差异,即可判断出图片类型。
# 将所有的方块与类型进行比较,转置成数字
def getAllSquareRecord(all_square_list,types):
print("将所有的方块与类型进行比较,转置成数字矩阵...")
record = [] # 整个记录的二维数组
line = [] # 记录一行
for square in all_square_list: # 把所有的方块和保存起来的所有类型做对比
square = cv2.resize(square, (50, 50))
square = np.array(square)
square_gray = cv2.cvtColor(square, cv2.COLOR_BGR2GRAY)
square_GB = cv2.GaussianBlur(square_gray, (3, 3), 0)
thresh = cv2.threshold(square_GB, 100, 255, cv2.THRESH_BINARY_INV)[1]
thresh = cv2.dilate(thresh, None, iterations=2)
num = 0
j = 0
min = 10000
for type in types: # 所有类型
edg = cv2.absdiff(type, thresh)
contours, hierarchy = cv2.findContours(edg.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
area = 0
for c in contours:
area += cv2.contourArea(c)
if area < min:
min = area
j = num
num += 1 # 如果没有匹配上,则类型数加1
line.append(j) # 将类型的数字记录进这一行
if len(line) == V_NUM: # 如果校验完这一行已经有了 V_NUM 个数据,则另起一行
record.append(line)
line = []
return np.transpose(record)
六、循环遍历消除方块
def autoRemove(squares,game_pos):
# 每次消除一对儿,QQ的连连看最多105对儿
game_x = game_pos[0] + MARGIN_LEFT
game_y = game_pos[1] + MARGIN_HEIGHT
# 判断是否消除完了?如果没有的话,点击重列后继续消除
for i in range(0,108):
autoRelease(squares,game_x,game_y)
# 自动消除
def autoRelease(result,game_x,game_y):
for i in range(0,len(result)):
for j in range(0,len(result[0])):
# 以上两个for循环,定位第一个选中点
if result[i][j] != 0:
for m in range(0,len(result)):
for n in range(0,len(result[0])):
if result[m][n] != 0:
# 后两个for循环定位第二个选中点
if matching.canConnect(i,j,m,n,result):
# 执行消除算法并返回
result[i][j] = 0
result[m][n] = 0
# print('可消除点:'+ str(i+1) + ',' + str(j+1) + '和' + str(m+1) + ',' + str(n+1))
x1 = game_x + j * SQUARE_WIDTH
y1 = game_y + i * SQUARE_HEIGHT
x2 = game_x + n * SQUARE_WIDTH
y2 = game_y + m * SQUARE_HEIGHT
win32api.SetCursorPos((x1 + 15,y1 + 18))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x1+15, y1+18, 0, 0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x1+15, y1+18, 0, 0)
time.sleep(0.15)
win32api.SetCursorPos((x2 + 15, y2 + 18))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x2 + 15, y2 + 18, 0, 0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x2 + 15, y2 + 18, 0, 0)
time.sleep(0.2)
return True
return False
七、最终效果视频
完结撒花