影像匹配(大圖中找小圖)之find_template原始碼解析

songofhawk發表於2021-12-25

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,匹配效果更佳。
    -- 加入資訊:有時候除了模板影像本身,我們也許知道更多的資訊,比如影像可能出現的位置範圍,影像周邊區域的樣式等等,來幫助提升識別準確度,減少誤識和漏識。

相關文章