实时读取屏幕识别指定颜色
背景
在GBA测评中有一个关卡是解锁(如下图所示),需要在转盘到指定区间时按下空格键,后期会越来快,难度比较高,因此想要提出一种能够识别屏幕、当转盘到达指定区间时自动按空格的方法。
思路
首先想到的思路就是通过颜色来进行区分,明显看到当轮盘转到指定区间会出现一种不同的颜色。如果能够识别这种颜色大概的数量,当超过一定阈值时就可以认为处于正确状态(即轮盘转到指定位置)。具体流程如下:
- 截取正确状态下特殊的颜色,识别该特殊颜色。
- 截取正确状态与错误状态图片,分别识别两种状态下该颜色的数量,计算得到一个特殊颜色数量的阈值,当大于该阈值时就认为处于正确状态。
- 捕获屏幕中的当前图片。
- 识别当前图片中特殊的数量,与阈值做比较,判断是否属于正确状态。
方法
-
cv2提供有方法可以进行指定颜色提取,参照这篇文章使用OpenCV和Python检测特定颜色,关键代码如下:
import cv2 # 读取图片 cv2img = cv2.imread(true_case_dir) # 格式转换 hsv = cv2.cvtColor(cv2img, cv2.COLOR_BGR2HSV) # 颜色区间选择 mask = cv2.inRange(hsv, lower_range, upper_range) # 通过按位与筛选得到指定颜色部分的图像 res = cv2.bitwise_and(cv2img, cv2img, mask=mask)
-
ImageGrab提供有方法可以进行屏幕捕获,参照这篇文章用python进行屏幕截图,只用两行代码搞定,关键代码如下:
from PIL import ImageGrab img = ImageGrab.grab()
-
pyautogui提供有方法可以进行模拟键入空格,关键代码如下
import pyautogui as pg pg.press('space')
-
tips
- 使用屏幕截取的图像,颜色格式为RGB;
- cv2在读取图像时默认格式为BGR读入,参照这篇文章关于opencv读取图片时,颜色发生变化后,如何修改
结果
使用目标颜色进行遮罩,可以得到以下结果。
代码
from PIL import ImageGrab
import pyautogui as pg
import numpy as np
import cv2
import time
def get_color_range(target_color_dir):
"""
获取指定图片的HSV颜色区间
"""
target_img = cv2.imread(target_color_dir)
target_color_hsv = cv2.cvtColor(target_img, cv2.COLOR_BGR2HSV)
target_color_hsv = target_color_hsv.reshape(-1, 3)
lower_range = np.min(target_color_hsv, axis=0)
upper_range = np.max(target_color_hsv, axis=0)
return lower_range, upper_range
def case_test(true_case_dir, false_case_dir, target_color_dir):
"""
根据正确、错误样本、目标颜色获取颜色区间以及目标区域大小
"""
lower_range, upper_range = get_color_range(target_color_dir)
# good case
print("========good case=======")
cv2img = cv2.imread(true_case_dir)
hsv = cv2.cvtColor(cv2img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, lower_range, upper_range)
max_area = np.sum(mask)
res = cv2.bitwise_and(cv2img, cv2img, mask=mask)
cv2.imwrite(true_case_dir[:-4]+"-result.png", res)
cv2.imwrite(true_case_dir[:-4]+"-mask.png", mask)
print("good case area is %d" % max_area)
# bad case
print("========bad case=======")
cv2img = cv2.imread(false_case_dir)
hsv = cv2.cvtColor(cv2img, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, lower_range, upper_range)
min_area = np.sum(mask)
res = cv2.bitwise_and(cv2img, cv2img, mask=mask)
cv2.imwrite(false_case_dir[:-4]+"-result.png", res)
cv2.imwrite(false_case_dir[:-4]+"-mask.png", mask)
print("bad case area is %d" % min_area)
# compute area limit
limit_area = min_area + int((max_area-min_area)/10)
return lower_range, upper_range, limit_area
# 封装成一个主方法
if __name__=="__main__":
# 获取目标颜色范围及目标区域大小
lower_range, upper_range, limit_area = case_test("file/2-true.png", "file/2-false.png", "file/2-target_color.png")
# 程序休眠5s此时切换到游戏视图
time.sleep(5)
# 开始循环处理
time_limit = 20
flag = True
while flag:
# 截取屏幕图像
img = ImageGrab.grab()
hsv = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2HSV)
mask = cv2.inRange(hsv, lower_range, upper_range)
area = np.sum(mask)
# print(area)
if area > limit_area:
pg.press('space')
print('space')
time_limit -= 1
if time_limit%5 == 0:
print("该关卡结束, 下一个!")
time.sleep(5)
else:
print("第%d位密码已解锁" % (5 - (time_limit)%5))
time.sleep(2)
if time_limit == 0:
flag = False