python openCV学习——快速的图像匹配

文章目录

      • 我的学习背景
      • 图像相似度计算
        • 感知哈希算法
      • 局部匹配

由于最近工作中需要用到图像快速图像匹配的事情,在此做一下学习记录。

主要是两个,一个是图像相似度计算,一个是图像模板匹配。

我的学习背景

之前的博客介绍过关于GAutomator的应用。但是GA只是提供一些基于游戏控件的基础逻辑。比如给一个控件全路径查找坐标,控件长宽;根据坐标/控件模拟点击;查找控件上的图片和文字,等等。

当作一个游戏自动化的时候,只有这些手段有时候会显得很无能为力。比如:

  • 点击一个匹配按钮,有可能因为卡顿等原因,我没有点到。这种时候,我如何在脚本中获悉我是否真的成功点击了该按钮?
  • 我如何知道是否匹配成功?
  • 曾经尝试过使用判断一个某个场景当中的特殊控件是否被加载出来来判断当前处于哪个场景,然而有些场景是在loading界面预加载的。

等等诸多问题。后来我想明白了,不管游戏控件如何变动,一切以画面加载为准。

图像相似度计算

我的第一个尝试就是截取当前屏幕,然后跟点击按钮之前的画面相比,如果相似度过低,就认为转场了。

先说结论,这种方法也是行不通的。由于很难找到一个几乎静止的场景,就算不是战斗场景,普通界面上的一些红点,通告走马灯或特效都会对图像相似度产生很大的影响。

关于图像相似度,我用的是著名的感知哈希算法

感知哈希算法

看到哈希就知道其实这个算法并没有什么神秘的。带哈希的算法绝大部分就是为了生成对应物体的指纹,所谓指纹,其实就是一串乱七八糟的数字。如何将一个物体映射成一串数字,就是各个哈希算法的工作了。那么感知哈希的映射规则如下(来自网上各种资料):

  1. step1:缩小图片尺寸
    将图片缩小到8x8的尺寸, 总共64个像素. 这一步的作用是去除各种图片尺寸和图片比例的差异, 只保留结构、明暗等基本信息。

  2. step2:转为灰度图片
    将缩小后的图片, 转为64级灰度图片。

  3. step3:计算灰度平均值
    计算图片中所有像素的灰度平均值

  4. step4:比较像素的灰度
    将每个像素的灰度与平均值进行比较, 如果大于或等于平均值记为1, 小于平均值记为0。

  5. step5:计算哈希值
    将上一步的比较结果, 组合在一起, 就构成了一个64位的二进制整数, 这就是这张图片的指纹。

  6. step6:对比图片指纹
    得到图片的指纹后, 就可以对比不同的图片的指纹, 计算出64位中有多少位是不一样的. 如果不相同的数据位数不超过5, 就说明两张图片很相似, 如果大于10, 说明它们是两张不同的图片。

下面看一下python实现

import cv2
import numpy as np

def pHash(imgfile):
    # step 2 & step 1
    img=cv2.imread(imgfile, cv2.IMREAD_GRAYSCALE) # 这里是读图的时候就顺便转化成灰度图了
    img=cv2.resize(img,(64,64),interpolation=cv2.INTER_LINEAR) # 这里才缩小尺寸,但是这里是选择缩小到64*64,保留更多的细节

    # step 3
    h, w = img.shape[:2]
    vis0 = np.zeros((h,w), np.float32)
    vis0[:h,:w] = img
    vis1 = cv2.dct(cv2.dct(vis0)) # 二维的DCT变换
    vis1.resize(32,32) # 再次压缩到 32*32
    img_list = list(vis1.flatten()) # 二维数组的一维展开
    avg = sum(img_list)*1./len(img_list) # 计算平均值
    avg_list = ['0' if i<avg else '1' for i in img_list] # 0-1化

    # step 4
    return ''.join(['%x' % int(''.join(avg_list[x:x+4]),2) for x in range(0,32*32,4)]) # 生成指纹,这句代码其实就是每次取四位bit(前面0-1化的数据),然后转化成一位十六进制的数字)

现在用这个方法可以得到每个图片的指纹(一串十六进制数字),拿到指纹之后,用以下方法计算相似度。首先计算两串指纹的汉明距离:

def hammingDist(s1, s2):
    assert len(s1) == len(s2)
    return sum([ch1 != ch2 for ch1, ch2 in zip(s1, s2)])

然后将汉明距离归一化,以此将相似度表示在[0,1]范围内。最终相似度的对外接口如下:

def similarity(img1, img2):
    hash_val1 = pHash(img1) # 计算图一指纹
    hash_val2 = pHash(img2) # 计算图二指纹
    sim = 1 - hammingDist(hash_val1, hash_val2) * 1. / (32 * 32 / 4) # 计算汉明距离并归一化
    return sim

这种算法用来判断游戏专场是行不通的。因为一个场景的特征往往是静止不动的UI控件,而不是3D场景。但是,感知哈希算法并不关心两个画面有没有一些不动的相同点,它只关心整体有多少差距,而不管差距都是哪些地方体现。这种算法对于“请你来找茬”倒是异常高效。

局部匹配

为了能够利用不动的元素来表征一个场景,只能对某个场景中的部分图进行匹配,亦即模板匹配。

它给定一个模板,然后在一张图中寻找与该模板最相似的部分。注意这个最相似的部分大小是跟模板相同的,术语上说就是感兴趣区域(ROI)。你不能拿一张放大了的图标当模板,尽管图里可能有一模一样的图标(除了大小),但是模板匹配仍然不能成功匹配。

opencv-python给了一个现成的模板匹配的接口:

cv2.matchTemplate(img_src, img_temp, method[, mask])

img_src即要进行匹配的图片,img_temp即模板。method是匹配方法,它用来决定用什么算法来计算模板与当前感兴趣区域的匹配度。该方法的返回值可以用cv2自带的接口进行解析。示例如下:

res = cv2.matchTemplate(img, temp_img, cv2.TM_CCOFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)

min_val和max_val表示最差匹配和最优匹配的匹配值。min_loc和max_loc分别是一个二维元组,表示最差匹配和最优匹配的感兴趣区域在图中的坐标。

需要注意的是,不同的method,匹配值的范围和坐标表示的点是不同的。有些匹配值是[-1,1],有些是[0,1]。有些坐标对应的是感兴趣区域的左上角,有些又是左下角。

在这里,cv2.TM_CCOFF_NORMED方法用0表示最差匹配,1表示最优匹配。坐标对应左上角。此时,最优匹配的坐标原点计算如下:

top_left = max_loc
center_pos = (top_left[0] + w / 2, top_left[1] + h / 2) # 因为cv2中(0,0)对应图像的左上角。

你可能感兴趣的:(openCV,python,测试开发)