1. 程式人生 > >一起學python-opencv十三(直方圖反向投影和模板匹配)

一起學python-opencv十三(直方圖反向投影和模板匹配)

2D直方圖

https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_histograms/py_2d_histogram/py_2d_histogram.html#twod-histogram

為什麼只考慮h,s就夠了呢?

因為其實亮度是很容易受外界影響的,我們認為一個顏色的本質特徵是h和s。計算2D直方圖,我們用的還是calcHist函式,不過引數得輸入兩個通道的了。H原來是0-360,為了讓8位能存下,就對應到了0-180。第四個引數是[xmin,xmax,ymin,ymax]這種形式的。x指的是行,y指的是列。

bins上面有很多規則,請大家大概看一下,說的是如果是一個int,那麼兩個維度區間的數目都是int,如果是[int,int]這個兩個分別是兩個維度的bins個數,還有其它規則請自行檢視。上面給出了range的格式[[xmin,xmax],[ymin,ymax]]。

返回的H是二維的直方圖分佈,xedges是第一個維度的區間邊界組成的陣列,yedges是第二個維度的。

上面應該是少了一句h,s,v=cv2.split(hsv)。下面是如何畫直方圖:

這個是可以大概看一下,越白的地方數值越高。其實有點像等高圖,不過我們這裡最低是黑色,而不是藍色。還有就是用matpltlib畫圖了:

還參考了https://blog.csdn.net/goldxwang/article/details/76855200

imshow裡的interpolation引數是插值的意思,為什麼需要插值呢?因為我們的hist只提供了整數點的值而沒有提供中間的值,那麼這些值該怎麼補充呢?這時候就需要用到插值。

插值方式有很多種,上面有列出來。blinear是雙線性插值。這個我們前面介紹過,還有一個nearest也很常用。參考了https://blog.csdn.net/ccblogger/article/details/72918354

這裡是舉了一個例子:

預設用的應該就是這種插值,需要注意的是,最近鄰插值是還要四捨五入的。

當然計算機也不可能一個點一個點去算,那也是無數個點,肯定有一個最小單位。旁邊的這個類似於等高圖高度顏色帶的這個東西是colorbar生成的,plt.colorbar()要寫在plt.show()之前。

這裡需要注意一下:

596*300=238400。

去掉中括號以後就不對了。和不知道為什麼是800。

用np生成二維直方圖也是可以的,結果是一樣的。

減小分的區間數的話。圖看起來更明顯一些。

需要提醒的是左下角是可以看到對應的高度值的:

[4.02e+04]就是滑鼠所在處的高度。

直方圖反投影

原理介紹:參考了https://blog.csdn.net/zyzhangyue/article/details/45827261

這個是一個滑動視窗,每次移動一個畫素,然後計算視窗中影象的直方圖和模板直方圖的相似度。

最後肯定是要閾值化的,相似度大於某一個值才認為是我們要的結果。

函式實現:

中間最重要的函式當然是calcBackProject了。

scale是比例。我來選一個模板,就是圈起來的那朵花。

大概在[34:73,234:263]這個範圍。

出來的dst就是如果和模板直方圖越接近(這個接近可能就是用的直方圖比較中的某一種距離來度量)結果數值越大,那麼用灰度顯示也就會越白,當然我猜測這個函式裡面是還有一些處理的,因為滑動視窗每次只滑動一個畫素嘛,有很多畫素就重合了,那麼這些畫素點的輸出值怎麼辦呢?我猜想可能就是取平均了。注意中括號。

模板直方圖分的區間越少,也就是分的越粗,直方圖反投影得到的結果越連續,但是也能太大。20的效果還算不錯。

下面就分得太粗略了。

分得太細比較難匹配到。

我有點不懂為什麼上面要對模板直方圖歸一化。alpha是歸一化範圍的下限,而beta是上限,normal_type是歸一化方法,有很多種,參考了:https://www.cnblogs.com/sddai/p/6250094.html

我們常用的是:

我試了一下,好像有點知道是個怎麼回事了。

這個模板直方圖的歸一化的最大值好像和輸出的dst的最大值是一樣的,當300的話就是飽和的,會被認為是255,雖然不知道為什麼有這樣的設定,這個得深究到下面的c++程式碼了。我前面不加為什麼可以呢?因為我選擇的模板直方圖的最大值271大於255。這算是一個巧合,是因為我選擇的模板影象比較大,然後顏色又都比較偏白色。那麼這個歸一化的語句最好還是加上去吧。

596*400和我們的原圖形狀是一樣的。

最終輸出的影象其實有點不太理想,我們把它再閾值化一下,當然這個閾值該怎麼選,這個需要自己測試。中間的那個cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(5,5))是一種形態學處理,我們先不管它。閾值操作:

0其實就代表的是表格中的第一種閾值化方式:

效果還不錯。把這個和原圖與一下。

我選的這個模板有點太白了,不太明顯。

我前面也說過,直方圖不能代表影象的全部資訊,因為它只包含強度資訊,缺少了位置資訊,所以上面匹配到的結果其實還是有很多不是我們想要的。下面是一種考慮了位置資訊的匹配方式。

模板匹配

原理:模板匹配的原理其實很簡單,還是基於滑動視窗,一個畫素一個畫素的去移動,只不過上面我們計算的是影象直方圖的相似度,我們這裡是直接計算影象的相似度,方法有好多種,參考了

https://blog.csdn.net/tsvico/article/details/78817096

計算起來其實挺麻煩的,有的時候我們就需要用到一些比較巧的辦法,比如說對於一些相乘項的求和,我們或許可以把其中一個調轉180,這個是為了湊成卷積的形式,然後根據卷積定理轉到頻域去計算離散的傅立葉變換相乘,然後再傅立葉反變換回來,這樣是會減小計算量的。

還有一種加快運算速度的方法叫做積分圖法。參考了

https://blog.csdn.net/yuan1125/article/details/70274515

就是從左上角開始累積畫素值,最後生成一個圖,就叫積分圖。

一個區域的特徵值就是這個區域裡所有畫素點對應強度的和。

上面原理和如何加快計算的方法,下面就是程式碼的實驗了。

為什麼輸出影象的大小會變小呢?這是因為輸出的影象其實是由上面的方法計算出來的相似度,輸出的結果其實只需要取從左上角開始取(W-w+1,H-h+1)就夠了,因為剩下的區域是不可能作為匹配區域的左上角的,因為不夠啊。為什麼取左上角,因為底層的c++是這麼寫的。

紅色代表目標圖片,藍色是模板圖片,紅色是輸出區域。

官方例子是個灰度影象,shape[::-1是步長為-1,為什麼要為-1呢?這是因為w寬度,也就是列數和h高度,行數的位置是反過來的,我們完全可以寫h,w=template.shape。官方也是有點皮。我們還是匹配那朵白玫瑰。我先來介紹幾個函式,首先是模板匹配的函式:

這裡面的引數的英文單詞很常見了。templ是模板的意思。

這個函式是找出最大值,最小值以及對應的位置。上面這些比較方法應該都在c++裡面由巨集定義的。eval就是為了去掉引號,那一開始在method列表裡面別加引號不就行了嗎?搞不懂。

plt.xticks是選x軸座標刻度的,他這樣寫就是不要刻度。plt.suptitle是大標題,因為有幾個子圖嘛,所以會有一個大標題咯。我就改了四處。

400-29+1=372,598-38+1=559。

如果把xTicks和yTick裡刪掉,那麼就會有刻度了。這個第一種方法結果稍微有點不對。

這其實和模板匹配演算法有關係,下面其實寫錯了,我畫紅線的話都是錯的,不用在意。

這種方法取的是應該是左圖中最亮的地方。那麼這樣的演算法為什麼不是一樣的最大呢?

設想黑線是均值,然後紅色是模板,藍色是我設定的影象,那麼你說按照上面的公式是紅色和紅色的值大,還是紅色和藍色的值大呢?毫無疑問是紅色和藍色。上面的方法是判斷相關性的而不是相似性,所謂相關性就是你增大,我也增大,你減小我也減小,那麼我們就叫做正相關,反之叫做負相關,和增大和減小前的值和增大或者減小了多少都無關,所以說這個模板匹配方法很不好。

這個匹配的是對的。也是取左圖中最亮的地方。這種方法才是1代表正相關,-1代表負相關,0代表線性無關。

為什麼標準相關匹配結果就可以呢?我想用柯西不等式來說明問題

-1<=R(x,y)<=1,這個取等條件比較苛刻,是所有位置對應畫素的強度成比例才可以,一般這種條件在影象中也就是同一張圖才能達到這樣的條件了,至少在我們這張圖上是的。

這個也是取最亮的地方,也是稍微有一點偏差的。

這個匹配的結果也還好。

上面兩種方法的差別和上面的相似,只不過上面是減去了絕對值,這裡沒有減去絕對值而已,還是用相關性不是相似度和柯西不等式來解釋。

這個是找最暗的。匹配的也不錯。

上面兩個是做差後平方和的結果,當然是同一張圖片的輸出區域最暗了。對於我們這張圖,六種裡面四種匹配的都還不錯,但是都是有一些條件的,不過我覺得沒有標準化的那兩個相關性方法就不要用了,比較差。

一個模板匹配多個物件

這個不只是取一個最大的或者最小的了,而是規定一個閾值,這個閾值時自己設定的,可以找到多個物件。我們還是用上面的來做實驗。

閾值設為0.8也還是隻能匹配到我們的原圖。

0.5的時候又出現一個新的區域。

0.45的時候就比較多了。

這裡還要提醒一點,plt.imshow單通道的時候,選擇cmap='gray',也就是顏色圖取的是灰度圖才會和cv2顯示的一致。不然用的時它使用的不是灰度系統。

用的時什麼image.cmap。不知道這是個什麼東西,但是絕對不是灰度。如果時3維就沒事,因為它會直接用RGB。

沒使用灰度之前。

之後:

雖然不知道為什麼顯示的一個黑一個白。

不過好歹時回到了灰度空間。我有點理解為什麼全是黑的了。首先colorbar會讓plt.imshow按照我們的灰度值來。註釋了colorbar後:

不註釋:

感覺其實挺奇怪的,我的理解是plt.imshow單通道的時候必須要有一個對比才行,如果全是一種顏色,colorbar也算是一種對比的手段,這個時候顯示的是完美的按照灰度。如果不是一個值,顯示的是相對灰度,也就是裡面的最大值就是白色,最小值是黑色,線性插值來顯示灰度值。

前面又出現了變數前加*號的操作。

這個以前其實也說過的,不過相信很多人都忘了,其實也包括我,233,這很符合遺忘規律嘛,忘了不要緊,複習一下:https://zhidao.baidu.com/question/2140001532025683868.html

那這裡為什麼還要反著來呢?其實很簡單了,這個其實是用rectangle畫畫的一個特性:

這個實驗我們可以看到,rectangle裡面傳遞引數的時候是列在前的,行在後的,這可能是為了適應我們的x軸水平,y軸垂直,一般寫座標都是(x,y)這樣的習慣。不過這和np的格式就反過來了,因為np是行在前,列在後。還有一點需要注意:頂點座標必須是元組。

好的,馬上是十一了,但是休息是不可能休息的。