從零手擼一個車牌識別(2)-圖像幾何變換

這篇文章主要涉及圖像的幾何變換,也就是矩陣乘法。

這篇文章要提到的內容應該有:

1.圖像的(平移 轉制 切割) 啥的啥的

2.圖像的旋轉

3.插值演算法,圖像縮放拉伸

本文中所有的原來的點的表示為D(x,y) 變換之後的點為D(x,y)

--------------------我們先來看平移--------------------

----------------今天順序是簡單=>難---------------

D = (Tx,Ty)+D

為了其他操作的方便我們把它寫成一個三維的向量而不是二維的。這樣我們可以用乘法來代替加法。矩陣的乘法的普適性明顯強於加法很多。

left[begin{array}{ccc}x y 1end{array}right]=left[begin{array}{ccc}1 & 0 & T_{x} 0 & 1 & T_{y} 0 &0 & 1end{array}right]left[begin{array}{ccc}x y 1end{array}right]

CImg<BYTE> get_translation(const CImg<BYTE>& target_img, int Tx, int Ty)n{ntCImg<BYTE> temp;nttemp.resize(target_img).fill(255);ntcimg_forXY(target_img, x, y)nt{nttif (x + Tx >= 0 && x + Tx<target_img.width()nttt&& y + Ty >= 0 && y + Ty<target_img.height())nttttemp(x + Tx, y + Ty, 0, 0) = *target_img.RED;nt}ntreturn CImg<BYTE>(temp);n}n

轉制和切割我就不發了代碼和矩陣表達式了,比較簡單有需要自己隨便擼一個出來就好;

--------------------旋轉---------------------------------

首先是喜聞樂見的三角換元

  begin{equation}nleft{nbegin{array}{l}nx=rsintheta ny=rcosthetanend{array}nright.nend{equation}n

然後我萌順時針旋轉一個Delta theta

然後是高中的那個兩角和公式吧,帶進去得到了

  begin{equation}nleft{nbegin{array}{l}nx=rsin(theta +Delta theta)=x cosDelta theta+ysinDeltathetany=rcos(theta+Deltatheta)=-x sinDelta theta+ycosDeltathetanend{array}nright.nend{equation}

這樣我們就得到了...我要被TeX弄死了...我還是手寫吧

我感覺應該把他們二值化一下但是還是懶得弄...

然後問題來了如果我們想要的是將圖像按(a,b)旋轉(eg.繞圖像中心旋轉)那怎麼辦呢

接下來是複習矩陣乘法的時間了

step1.首先進行坐標變換:

我們得到了在新坐標系中的D(x,y)

step2.然後我們開始旋轉Delta theta角度

我們得到了新坐標系中旋轉完的坐標D(x,y)

step3.我們再放回到原來的坐標系

step4.帶進去乘起來啊

漏了一個Delta不過無所謂啦看得懂就好...

step5.寫成一個表達式方便待會碼代碼

  begin{equation}nleft{nbegin{array}{l}nx=xcosDeltatheta+ysinDeltatheta-acosDeltatheta-bsinDeltatheta ny=-xsinDeltatheta+ycosDeltatheta+asinDeltatheta-bcosDeltathetanend{array}nright.nend{equation}n

啊然後我們就要開始碼代碼了

但是

很慘的事情是好像算出來的值都不是整數啊摔

怎麼辦

嗯...

----------------分割線出來----------------------

於是我們遇到了插值的問題

這就很簡單粗暴了

四捨五入不行就隨便賦值啊

這就是第一種方法

1.最鄰近插值法

直接給代碼好了...

#define ROUNDF(s) ((int)((s)+0.5))nint _zoom_with_nearest_neiberbour_interpolation (CImg<BYTE>& target_img, int parameter)n{//this function shouldnt be used.ntint _new_width(ROUNDF(target_img.width()*parameter)),ntt_new_height(ROUNDF(target_img.height()*parameter));ntCImg<BYTE> temp(_new_width, _new_height, 1, 3);ntcimg_forXY(temp, x, y)nt{/*get the original point in the target_imgnt and copy from it directly*/nttint x_origin = ROUNDF(x/2),nttty_origin = ROUNDF(y/2);nttif (x_origin >= 0 && x_origin < target_img.width()nttt&& y_origin >= 0 && y_origin < target_img.height()) {nttttemp(x, y, 0, 0) = *target_img.data(x_origin, y_origin, 0, 0);nttttemp(x, y, 0, 1) = *target_img.data(x_origin, y_origin, 0, 1);nttttemp(x, y, 0, 2) = *target_img.data(x_origin, y_origin, 0, 2);ntt}nttelse/*else it makes me unhappy and I dont give a ntttshit so I would give a white*/ntttBW(temp) = 255;nt}nttarget_img = temp;ntreturn 0;n}n

//ROUNDF()是一個浮點四捨五入到整數的宏函數,如果你們看見了這個宏函數的什麼坑請務必告訴我

//別看注釋

整個過程中我一直保持著不停的不停地四捨五入你就知道這個演算法有多隨意了...

我們來看看效果,今天好久沒有看到那輛可愛的賓利了...

昨天處理完的原圖,我用今天剛提到的剪切處理了之後

是這樣的

然後我們兩倍試試:

十倍的

是不是很粗糙

//我的皮膚(大概)都比這個十倍放大里的邊緣要光滑

2.雙線性插值法 bilinear interpolation

雙線性插值法是這樣的

step1. 我們把這個點的精確坐標算出來坐標是這樣的 P(浮點數x,還是浮點數y)

(c)這圖顯然是wiki的 wiki bilinear interpolation

step2.

//公式是(c)wiki的(我實在不想敲TeX)

f(x,y)代表的就是點(x,y)的灰度值

所以我們可以看到上述的兩個公式乾的事情只不過是將P點灰度對於y的加權平均而已

step3.然後接下來要乾的事情就是

(c)wiki again

對x加權平均一下

把這個正方形的四個頂點當作是相鄰的四個像素得到:

V_{1}=((x_{2}-x)f(Q_{11})+(x-x_{1})f(Q_{21}),(x_{2}-x)f(Q_{12})+(x-x_{1})f(Q_{22}))

f(x,y)=V_{1}cdot (y_{2}-y,y-y_{1})

step4.然後我們可以用代碼實現了

計算的介面怎麼設計真是非常頭疼啊...難不成在函數內部實現嗎...

最後我腦海中浮現出我從維基上拔下來的那張圖,那就破罐子破摔,傳6個值過去好了

template<typename T>nT _get_bilinear_interpolation_valn(float Px,float Py,T Q11,T Q12, T Q21, T Q22)n{/*see what P&Q means by going ton https://en.wikipedia.org/wiki/Bilinear_interpolationn Thank wiki and whats more if you see what V1&V2 meansn go to JamesFreemans ZhuanlanZhihu*/ntdouble V1 = (1 - Px)*Q11 + Px*Q21,nttV2 = (1 - Px)*Q12 + Px*Q22;ntdouble result = V1*(1 - Py) + Py*V2;ntreturn T(result);n}n

這就是那個計算函數了...是不是應該寫個struct來實現傳遞什麼的,但是本著能用就是王道的...我也沒什麼太多想法

CImg<BYTE> get_zoom(const CImg<BYTE>&target_img, int parameter)n{/*see what P&Q means by going tonthttps://en.wikipedia.org/wiki/Bilinear_interpolationntThank wiki and whats more if you see what V1&V2 meansntgo to JamesFreemans ZhuanlanZhihu*/ntint _new_width(std::round(target_img.width()*parameter)),ntt_new_height(std::round(target_img.height()*parameter));ntCImg<BYTE> temp(_new_width, _new_height, 1, 3);ntcimg_forXY(temp, x, y)nt{nttfloat Px = ((float)x) / parameter,ntttPy = ((float)y) / parameter;nttif (Px >= 0 && Px < target_img.width()nttt&& Py >= 0 && Py < target_img.height()) {nntttBYTE Q11R = *target_img.data(Px, Py, 0, 0);ntttBYTE Q11G = *target_img.data(Px, Py, 0, 1);ntttBYTE Q11B = *target_img.data(Px, Py, 0, 2);nntttBYTE Q12R = *target_img.data(Px, Py + 1, 0, 0);ntttBYTE Q12G = *target_img.data(Px, Py + 1, 0, 1);ntttBYTE Q12B = *target_img.data(Px, Py + 1, 0, 2);nntttBYTE Q21R = *target_img.data(Px + 1, Py, 0, 0);ntttBYTE Q21G = *target_img.data(Px + 1, Py, 0, 1);ntttBYTE Q21B = *target_img.data(Px + 1, Py, 0, 2);nntttBYTE Q22R = *target_img.data(Px + 1, Py + 1, 0, 0);ntttBYTE Q22G = *target_img.data(Px + 1, Py + 1, 0, 1);ntttBYTE Q22B = *target_img.data(Px + 1, Py + 1, 0, 2);nntttfloat Py_decimal = Py - (int)Py,nttttPx_decimal = Px - (int)Px;nttttemp(x, y, 0, 0) = _get_bilinear_interpolation_valntttt(Px_decimal, Py_decimal, Q11R, Q12R, Q21R, Q22R);nttttemp(x, y, 0, 1) = _get_bilinear_interpolation_valntttt(Px_decimal, Py_decimal, Q11G, Q12G, Q21G, Q22G);nttttemp(x, y, 0, 2) = _get_bilinear_interpolation_valntttt(Px_decimal, Py_decimal, Q11B, Q12B, Q21B, Q22B);ntt}nttelsentttBW(temp) = 255;nt}ntreturn temp;n}n

然後這就是非常醜陋的C++實現

晚上太晚了懶得寫注釋了

我們來看看效果:

依舊是十倍的放大,可以看到圖像變得圓滑(看不出就仔細看還是有區別的我不說我已經找不同五分鐘了)的同時變得有一些模糊了。

------------------------雙線性插值法以上--------------------

PS:有一些變換如把一個平行四邊形拉成正方形之類的.具體下來也是一些矩陣的變化,因為時間原因我就不寫了...(今天寫代碼寫了9個小時感覺要飛起來了...

主要就是1.把圖像的變化看成矩陣乘法

2.雙線性插值法(以及思想啊,加權的思想啊


推薦閱讀:

無痛理解Local binary pattern(LBP)
OTB-2015 database與OpenCV320 tracking API

TAG:C | 图形识别 | 计算机视觉 |