图像匹配(大图中找小图)之find_template源码解析

aircv是网易放出的小开源项目,应该也是现在做简单图像匹配被引用最多的项目了。上一篇做了如何使用aircv之find_template的描述,然而,它并不算是一个成熟的项目,里面的小坑不少,有待改进。今天先做个代码逻辑的解析。
核心函数find_template 与 find_all_template find_template函数是返回第一个最匹配的结果(位置未必在最上面),而find_all_template是返回所有大于指定置信度的结果。
比如要在思否页面截图中找
图像匹配(大图中找小图)之find_template源码解析
文章图片

结果如如下图所示:
图像匹配(大图中找小图)之find_template源码解析
文章图片

图像匹配(大图中找小图)之find_template源码解析
文章图片

我们深入进去看一下代码,就会发现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的方法,它的返回值是个矩阵,相当于用小图在大图上滑动,从左上角开始,每次移动一个像素,然后计算一个匹配结果,最终形成结果矩阵。
图像匹配(大图中找小图)之find_template源码解析
文章图片

结果矩阵大小应该是: (W - w + 1) x (H - h + 1),其中W,H是大图的宽高, w和h是小图的宽高。
这个矩阵中最大值的那个点,就表示小图的左上角对在这个位置时,匹配度最高,由此得到第一个匹配结果。
最后cv2.floodFill的作用,是把结果矩阵最大值这块区域用别的数字填充掉,这样就可以查找下一个最大值了,而且也避免了区域重叠的现象(要不然下一个最大值,有可能在刚刚找到的区域里面)。
几个小问题
  • 不支持灰度图和带透明通道的图
    其实opencv的matchTemplate本来只支持灰度图,但大多数情况下,我们都是查找彩色图,所以aircv封装的时候,把bgr三个彩色通道做了分离,分别调用matchTemplate,然后再合并结果。
【图像匹配(大图中找小图)之find_template源码解析】但这个封装没有兼容本来就是灰度图的情况,竟然会出错,而且如果源图带透明通道也会出错,为此,我专门提交了一个PR,但一直没有处理,看来项目已经没人维护了。
  • floodFill貌似有点兴师动众了,numpy的切片应该可以实现
    这个回头写段小代码验证一下
  • 不能处理图片的缩放
    模板图片和原图中的内容,并不一定大小严格一致的,那么就需要做一些缩放处理,重复尝试。
  • 图像匹配效果不理想
    如果是完全一致的图,find_template的效果非常好,但当需要模糊匹配时,某些人眼一看就相同的图像,是无法被识别出来的,有点时候又会误识别,这可能需要从两方面改进:
    -- 调整算法:采用SIFT之类的特征点检测算法,可以解决大小和角度都不一致的图像匹配问题;Halcon等商业软件,采用了shape based matching,匹配效果更佳。
    -- 加入信息:有时候除了模板图像本身,我们也许知道更多的信息,比如图像可能出现的位置范围,图像周边区域的样式等等,来帮助提升识别准确度,减少误识和漏识。

    推荐阅读