2017-06-22 229 views
3

灰度圖像我有一個項目,我想定位圖像一堆箭頭看起來像這樣:ibb.co/dSCAYQ 與下面的模板:ibb.co/jpRUtQPython的OpenCV的matchTemplate與蒙

我在Python中使用cv2的模板匹配功能。我的算法是將模板旋轉360度並匹配每次旋轉。我得到以下結果:ibb.co/kDFB7k

正如你所看到的,它運作良好,除2米的箭都非常接近,使得另一支箭是在模板中的黑色區域。

我正在嘗試使用掩碼,但似乎cv2並未在所有應用程序中使用我的掩碼,即無論掩碼數組具有什麼值,匹配都是相同的。已經嘗試了兩天,但cv2的有限文檔沒有幫助。

這裏是我的代碼:

import numpy as np 
import cv2 
import os 
from scipy import misc, ndimage 

STRIPPED_DIR = #Image dir 
TMPL_DIR = #Template dir 
MATCH_THRESH = 0.9 
MATCH_RES = 1 #specifies degree-interval at which to match 

def make_templates(): 
    base = misc.imread(os.path.join(TMPL_DIR,'base.jpg')) # The templ that I rotate to make 360 templates 
    for deg in range(360): 
     print('making template: ' + str(deg)) 
     tmpl = ndimage.rotate(base, deg) 
     misc.imsave(os.path.join(TMPL_DIR, 'tmp' + str(deg) + '.jpg'), tmpl) 

def make_masks(): 
    for deg in range(360): 
     tmpl = cv2.imread(os.path.join(TMPL_DIR, 'tmp' + str(deg) + '.jpg'), 0) 
     ret2, mask = cv2.threshold(tmpl, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU) 
     cv2.imwrite(os.path.join(TMPL_DIR, 'mask' + str(deg) + '.jpg'), mask) 

def match(img_name): 
    img_rgb = cv2.imread(os.path.join(STRIPPED_DIR, img_name)) 
    img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY) 

    for deg in range(0, 360, MATCH_RES): 
     tmpl = cv2.imread(os.path.join(TMPL_DIR, 'tmp' + str(deg) + '.jpg'), 0) 
     mask = cv2.imread(os.path.join(TMPL_DIR, 'mask' + str(deg) + '.jpg'), 0) 
     w, h = tmpl.shape[::-1] 
     res = cv2.matchTemplate(img_gray, tmpl, cv2.TM_CCORR_NORMED, mask=mask) 
     loc = np.where(res >= MATCH_THRESH) 

     for pt in zip(*loc[::-1]): 
      cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2) 
     cv2.imwrite('res.png',img_rgb) 

有些事情,我認爲可能是錯的,但不知道如何解決:

  1. 通道的數量面膜/ TMPL/IMG應該有。我嘗試了一個彩色4通道PNG stackoverflow eg.的例子,但不知道它如何轉換爲灰度或3通道JPEG。
  2. 掩碼數組的值。例如應掩蓋像素是1還是255?

任何幫助,非常感謝。

UPDATE 我在我的代碼中修復了一個微不足道的錯誤; mask = mask必須用於matchTemplate()的參數中。這與使用255的掩碼值相結合產生了不同。但是,現在我得到了大量的假陽性,如下所示: http://ibb.co/esfTnk請注意,假陽性與真陽性相關性更強。 任何關於如何解決我的面具來解決這個問題的指針?現在我只是使用我的模板的黑白轉換。

+0

嘗試使用包含'0'和'255'強度值的單個通道掩碼。 – ZdaR

+0

@ZdaR我試過了,但不起作用。看起來即使使用零矩陣或255矩陣的掩碼也不會影響匹配。 – TonyZ

+0

原來這是個伎倆,我犯了一個微不足道的錯誤。我需要在matchTemplate()中使用參數mask = mask。 雖然有進一步的複雜性。我已經更新了這個問題。謝謝 – TonyZ

回答

2

你已經想通了的第一個問題,但我會擴大他們一點:

對於二進制面具,它應該是uint8類型:其中,值只是零或非零。具有零的位置將被忽略,並且如果它們不爲零,則包含在掩碼中。你可以通過一個float32來代替作爲掩碼,在這種情況下,它可以讓你重量的像素爲;所以0的值被忽略,1是包括的,並且.5是包括的,但是隻給它一半的權重作爲另一個像素。請注意,只有TM_SQDIFFTM_CCORR_NORMED支持掩碼,但這很好,因爲您正在使用後者。 matchTemplate的掩碼僅爲單通道。當你發現,mask不是位置參數,所以它必須用參數mask=your_mask中的鍵調用。所有這些在this page on the OpenCV docs中都很明確。

我們新的問題:

它關係到你使用的方法和你使用jpg S中的事實。看看formulas for the normed methods。在圖像完全爲零的情況下,您將得到錯誤的結果,因爲您將被零除。但這不是確切的問題---因爲返回nannp.nan > value總是返回false,所以你永遠不會從nan值中繪製一個正方形。

相反,問題是在邊緣情況下,你會得到一個非零值的暗示;並且由於您使用的是jpg圖片,因此並非所有黑色值都恰好爲0;其實很多都不是。從公式中可以看出,你正在用平均值進行潛水,當你的圖像窗口中有1,2,5等值時,平均值會非常小,所以它會炸掉相關值。您應該使用TM_SQDIFF代替(因爲它是唯一允許使用掩碼的其他方法)。另外,因爲你使用的是jpg,所以你的大部分口罩都毫無價值,因爲任何非零值(即使是1)都被視爲包含。你應該使用png作爲面具。只要模板具有適當的蒙版,無論使用jpg還是png作爲模板都無關緊要。

隨着TM_SQDIFF,而不是尋找最大值,你正在尋找最低---你想要的最小的差異模板和圖像補丁。你知道差異應該非常小 - 對於像素完美匹配,恰好爲0,你可能不會得到。你可以稍微調整一下閾值。請注意,每次旋轉總會得到非常接近的值,因爲模板的性質 - 小箭頭欄幾乎不會增加許多正值,並且不一定能保證一度離散化完全正確(除非你以這種方式製作圖像)。但即使是面對完全錯誤的方向的箭頭仍然會非常接近,因爲有很多重疊;並且朝向接近正確方向的箭頭將是真的接近具有正確方向的值。

預覽平方的差的結果是什麼,當你運行代碼:

res = cv2.matchTemplate(img_gray, tmpl, cv2.TM_SQDIFF, mask=mask) 
cv2.imshow("result", res.astype(np.uint8)) 
if cv2.waitKey(0) & 0xFF == ord('q'): 
    break 

Square difference image

你可以看到,基本模板的每個方向緊密匹配。

不管怎麼說,這似乎爲8的門檻釘它:

Match locations with square differences and a threshold of 8

我在你的代碼修改唯一的辦法就是改變以png獻給所有圖像,切換到TM_SQDIFF,確保loc查找值小於的閾值而不是大於,並使用8的MATCH_THRESH。至少我認爲這就是我所做的一切。看一下,以防萬一:

import numpy as np 
import cv2 
import os 
from scipy import misc, ndimage 

STRIPPED_DIR = ... 
TMPL_DIR = ... 
MATCH_THRESH = 8 
MATCH_RES = 1 #specifies degree-interval at which to match 

def make_templates(): 
    base = misc.imread(os.path.join(TMPL_DIR,'base.jpg')) # The templ that I rotate to make 360 templates 
    for deg in range(360): 
     print('making template: ' + str(deg)) 
     tmpl = ndimage.rotate(base, deg) 
     misc.imsave(os.path.join(TMPL_DIR, 'tmp' + str(deg) + '.png'), tmpl) 

def make_masks(): 
    for deg in range(360): 
     tmpl = cv2.imread(os.path.join(TMPL_DIR, 'tmp' + str(deg) + '.png'), 0) 
     ret2, mask = cv2.threshold(tmpl, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU) 
     cv2.imwrite(os.path.join(TMPL_DIR, 'mask' + str(deg) + '.png'), mask) 

def match(img_name): 
    img_rgb = cv2.imread(os.path.join(STRIPPED_DIR, img_name)) 
    img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY) 

    for deg in range(0, 360, MATCH_RES): 
     tmpl = cv2.imread(os.path.join(TMPL_DIR, 'tmp' + str(deg) + '.png'), 0) 
     mask = cv2.imread(os.path.join(TMPL_DIR, 'mask' + str(deg) + '.png'), 0) 
     w, h = tmpl.shape[::-1] 
     res = cv2.matchTemplate(img_gray, tmpl, cv2.TM_SQDIFF, mask=mask) 

     loc = np.where(res < MATCH_THRESH) 
     for pt in zip(*loc[::-1]): 
      cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2) 
     cv2.imwrite('res.png',img_rgb) 
+0

@TonyZ看起來像你沒有在那裏完成你的評論,隨時精心製作。 'png'文件是未壓縮的,而'jpg's是。對於'png',矩陣中的內容與寫入文件的內容完全相同; 'jpg'被壓縮,所以這些值與你寫的矩陣不一樣。 –

+0

嗨!謝謝你的回答! 我想通過使用TM_SQDIFF工作,但不知道如何使用PNG作爲面具。我必須用jpeg蒙版使用16個閾值,但8個更好。 我知道它有點笨重匹配每一個旋轉度,但我需要恢復我的程序箭頭的方向。 再次,非常感謝!我現在對引擎蓋下正在發生的事情有了更好的瞭解。 – TonyZ

+0

它並不笨拙,這是非常明確的代碼(這是我完全回答的原因的一半,所以非常感謝),並且考慮發生了什麼並不需要很長時間(部分原因是因爲您的模板足夠小)。 –