头图

aircv是网易放出的小开源项目,应该也是现在做简单图像匹配被引用最多的项目了。上一篇做了如何使用aircv之find_template的描述,然而,它并不算是一个成熟的项目,里面的小坑不少,有待改进。今天先做个代码逻辑的解析。

核心函数find_template 与 find_all_template

find_template函数是返回第一个最匹配的结果(位置未必在最上面),而find_all_template是返回所有大于指定置信度的结果。

比如要在思否页面截图中找
image.png
结果如如下图所示:
find_template.png

find_all_template.png

我们深入进去看一下代码,就会发现find_template是这样写的:

def find_template(im_source, im_search, threshold=0.5, rgb=False, bgremove=False):
    '''
    @return find location
    if not found; return None
    '''
    result = find_all_template(im_source, im_search, threshold, 1, rgb, bgremove)
    return result[0] if result else None

好家伙! 直接调用find_all_template,然后取返回值的第一个。。。

所以find_all_template才是真正的核心,我们排除掉无关代码,来看一下最关键的部分:

def find_all_template(im_source, im_search, threshold=0.5, maxcnt=0, rgb=False, bgremove=False):
    # 匹配算法,aircv实际上在代码里写死了用CCOEFF_NORMED,大部分测试的效果,也确实是这个算法更好
    method = cv2.TM_CCOEFF_NORMED
    # 获取匹配矩阵
    res = cv2.matchTemplate(im_source, im_search, method)
    w, h = im_search.shape[1], im_search.shape[0]
    result = []
    while True:
        # 找到匹配最大最小值
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
        top_left = max_loc
        if max_val < threshold:
            break
        # 计算中心点
        middle_point = (top_left[0]+w/2, top_left[1]+h/2)
        # 计算四个角的点,存入结果集
        result.append(dict(
            result=middle_point,
            rectangle=(top_left, (top_left[0], top_left[1] + h), (top_left[0] + w, top_left[1]), (top_left[0] + w, top_left[1] + h)),
            confidence=max_val
        ))
        # 把最匹配区域填充掉,再继续查找下一个
        cv2.floodFill(res, None, max_loc, (-1000,), max_val-threshold+0.1, 1, flags=cv2.FLOODFILL_FIXED_RANGE)
    return result

其中cv2.matchTemplate是opencv的方法,它的返回值是个矩阵,相当于用小图在大图上滑动,从左上角开始,每次移动一个像素,然后计算一个匹配结果,最终形成结果矩阵。
image.png

结果矩阵大小应该是: (W - w + 1) x (H - h + 1),其中W,H是大图的宽高, w和h是小图的宽高。

这个矩阵中最大值的那个点,就表示小图的左上角对在这个位置时,匹配度最高,由此得到第一个匹配结果。

最后cv2.floodFill的作用,是把结果矩阵最大值这块区域用别的数字填充掉,这样就可以查找下一个最大值了,而且也避免了区域重叠的现象(要不然下一个最大值,有可能在刚刚找到的区域里面)。

几个小问题

  • 不支持灰度图和带透明通道的图
    其实opencv的matchTemplate本来只支持灰度图,但大多数情况下,我们都是查找彩色图,所以aircv封装的时候,把bgr三个彩色通道做了分离,分别调用matchTemplate,然后再合并结果。

但这个封装没有兼容本来就是灰度图的情况,竟然会出错,而且如果源图带透明通道也会出错,为此,我专门提交了一个PR,但一直没有处理,看来项目已经没人维护了。

  • floodFill貌似有点兴师动众了,numpy的切片应该可以实现
    这个回头写段小代码验证一下
  • 不能处理图片的缩放
    模板图片和原图中的内容,并不一定大小严格一致的,那么就需要做一些缩放处理,重复尝试。
  • 图像匹配效果不理想
    如果是完全一致的图,find_template的效果非常好,但当需要模糊匹配时,某些人眼一看就相同的图像,是无法被识别出来的,有点时候又会误识别,这可能需要从两方面改进:
    -- 调整算法:采用SIFT之类的特征点检测算法,可以解决大小和角度都不一致的图像匹配问题;Halcon等商业软件,采用了shape based matching,匹配效果更佳。
    -- 加入信息:有时候除了模板图像本身,我们也许知道更多的信息,比如图像可能出现的位置范围,图像周边区域的样式等等,来帮助提升识别准确度,减少误识和漏识。

songofhawk
303 声望24 粉丝