背景
在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










