圖像的旋轉
- 1 單點旋轉
- 2. 圖片旋轉(cv2.getRotationMatrix2D)
- 3. 插值方法
- 3.1 最近鄰插值(cv2.INTER_NEAREST)
- 3.2 雙線性插值(cv2.INTER_LINEAR)
- 3.3 像素區域插值(cv2.INTER_AREA)
- 3.4 雙三次插值(cv2.INTER_CUBIC)
- 3.5 Lanczos插值(cv2.INTER_LANCZOS4)
- 3.6 小結
- 4. 邊緣填充方式(cv2.warpAffine的屬性borderMode)
- 4.1 邊界復制(BODER_REPLICATE)
- 4.2 邊界反射(BORDER_REFLECT)
- 4.3 邊界反射101(BORDER_REFLECT_101)
- 4.4 邊界常數(BORDER_CONSTANT)
- 4.5 邊界包裹(BORDER_WRAP)
圖像旋轉是指圖像以某一點為旋轉中心,將圖像中的所有像素點都圍繞該點旋轉一定的角度,并且旋轉后的像素點組成的圖像與原圖像相同。
1 單點旋轉
首先我們以最簡單的一個點的旋轉為例子,且以最簡單的情況舉例,令旋轉中心為坐標系中心O(0,0),假設有一點 P 0 ( x 0 , y 0 ) P_{0}(x_{0},y_{0}) P0?(x0?,y0?), P 0 P_{0} P0?離旋轉中心O的距離為r, O P 0 OP_{0} OP0?與坐標軸x軸的夾角為 α \alpha α, P 0 P_{0} P0?繞O順時針旋轉 θ \theta θ角后對應的點為 P ( x , y ) P(x,y) P(x,y),如下圖所示:
單點旋轉的原理
2. 圖片旋轉(cv2.getRotationMatrix2D)
明白了單個點的旋轉過程之后,其實圖像旋轉也很好理解,就是將圖像里的每個像素點都帶入仿射變換矩陣里,從而得到旋轉后的新坐標。在OpenCV中,要得到仿射變換矩陣可以使用cv2.getRotationMatrix2D(),通過這個函數即可直接獲取到上面的旋轉矩陣,
格式如下:
cv2.RotationMatrix2D(Center,Angle,Scale)
該函數需要接收的參數為:
- Center:表示旋轉的中心點,是一個二維的坐標點(x,y)
- Angle:表示旋轉的角度
- Scale:表示縮放比例,可以通過該參數調整圖像相對于原始圖像的大小變化
因此,在本實驗中只需要在組件中填好圖片要旋轉的角度與縮放的比例即可。
代碼如下:
'''圖片的旋轉'''img=cv2.imread(r"../15day4.10/src/1.jpg")# 求出圖片的高寬和顏色h,w,c=img.shape# 給出一個旋轉角度angle=45# 給出縮放倍數sacle=1# 以圖片的中心作為旋轉中心的仿射變換矩陣m=cv2.getRotationMatrix2D((h/2,w/2),angle=angle,scale=sacle)img_rotate=cv2.warpAffine(img,m,(w,h))cv2.imshow("img",img)cv2.imshow("img_rotate",img_rotate)cv2.waitKey(0)
但是這里會有一個問題:
- 由于三角函數的值是小數,那么其乘積也會是小數,雖然OpenCV中會對其進行取整操作,但是像素點旋轉之后的取整結果也有可能重合,這樣就會導致可能會在旋轉的過程中丟失一部分原始的像素信息。
- 并且如果使用了scale參數進行圖像的縮放的話,當圖像放大時,比如一個10*10的圖像放大成20*20,圖像由100個像素點變成400個像素點,那么多余的300個像素點是怎么來的?而當圖像縮小時,比如一個20*20的圖像縮小為10*10的圖像,需要丟掉300個像素點,那到底要怎么丟才能保證圖像還能是一個正常的圖像?
- 因此我們需要一種方法來幫我們計算旋轉后的圖像中每一個像素點所對應的像素值,從而保證圖像的完整性,這種方法就叫做插值法。
3. 插值方法
在圖像處理和計算機圖形學中,插值(Interpolation)是一種通過已知數據點之間的推斷或估計來獲取新數據點的方法。它在圖像處理中常用于處理圖像的放大、縮小、旋轉、變形等操作,以及處理圖像中的像素值。
圖像插值算法是為了解決圖像縮放或者旋轉等操作時,由于像素之間的間隔不一致而導致的信息丟失和圖像質量下降的問題。當我們對圖像進行縮放或旋轉等操作時,需要在新的像素位置上計算出對應的像素值,而插值算法的作用就是根據已知的像素值來推測未知位置的像素值。本實驗提供了五種常見的插值算法,下面一一介紹。
3.1 最近鄰插值(cv2.INTER_NEAREST)
最近鄰插值的原理
代碼如下:
'''最近鄰插值法'''img=cv2.imread(r"../15day4.10/src/1.jpg")# 求出圖片的高寬和顏色h,w,c=img.shape# 給出一個旋轉角度angle=45# 給出縮放倍數sacle=1.5#給出縮放后的圖像的窗口大小frame=(2*w,2*h)# 以圖片的中心作為旋轉中心的仿射變換矩陣m=cv2.getRotationMatrix2D((h/2,w/2),0,sacle)img_rotate=cv2.warpAffine(img,m,frame,flags=cv2.INTER_NEAREST)cv2.imshow("img",img)cv2.imshow("img_rotate",img_rotate)cv2.waitKey(0)
3.2 雙線性插值(cv2.INTER_LINEAR)
雙線性插值的原理
代碼如下:
'''雙線性插值'''img=cv2.imread(r"../15day4.10/src/1.jpg")# 求出圖片的高寬和顏色h,w,c=img.shape# 給出一個旋轉角度angle=45# 給出縮放倍數sacle=1.5#給出縮放后的圖像的窗口大小frame=(2*w,2*h)# 以圖片的中心作為旋轉中心的仿射變換矩陣m=cv2.getRotationMatrix2D((h/2,w/2),0,sacle)img_rotate=cv2.warpAffine(img,m,frame,flags=cv2.INTER_LINEAR)cv2.imshow("img",img)cv2.imshow("img_rotate",img_rotate)cv2.waitKey(0)
注意:
- 雙線性插值和最近鄰插值的區別就,當放大倍數為小數時,最近鄰插值(cv2.INTER_NEAREST)是對結果下取整,而雙線性插值(cv2.INTER_LINEAR)根據距離哪個像素點近就將取大的權值
3.3 像素區域插值(cv2.INTER_AREA)
像素區域插值主要分兩種情況,縮小圖像和放大圖像的工作原理并不相同。
當使用像素區域插值方法進行縮小圖像時,它就會變成一個均值濾波器(濾波器其實就是一個核,這里只做簡單了解,后面實驗中會介紹),其工作原理可以理解為對一個區域內(均值濾波器/核)的像素值取平均值。
當使用像素區域插值方法進行放大圖像時,如果圖像放大的比例是整數倍,那么其工作原理與最近鄰插值類似;如果放大的比例不是整數倍,那么就會調用雙線性插值進行放大。
其中目標像素點與原圖像的像素點的對應公式如下所示:
s r c X = d s t X ? s r c W i d t h d s t W i d t h s r c X=d s t X*{\frac{s r c W i d t h}{d s t W i d t h}} srcX=dstX?dstWidthsrcWidth?
s r c Y = d s t Y ? s r c H e i g h t d s t H e i g h t s r c Y=d s t Y*{\frac{s r c H e i g h t}{d s t H e i g h t}} srcY=dstY?dstHeightsrcHeight?
代碼如下:
'''像素區域插值'''img=cv2.imread(r"../15day4.10/src/1.jpg")# 求出圖片的高寬和顏色h,w,c=img.shape# 給出一個旋轉角度angle=45# 給出縮放倍數sacle=1.5#給出縮放后的圖像的窗口大小frame=(2*w,2*h)# 以圖片的中心作為旋轉中心的仿射變換矩陣m=cv2.getRotationMatrix2D((h/2,w/2),0,sacle)img_rotate=cv2.warpAffine(img,m,frame,flags=cv2.INTER_AREA)cv2.imshow("img",img)cv2.imshow("img_rotate",img_rotate)cv2.waitKey(0)
像素區域插值(cv2.INTER_AREA)就是最近鄰插值和雙線性插值的結合
3.4 雙三次插值(cv2.INTER_CUBIC)
與雙線性插值法相同,該方法也是通過映射,在映射點的鄰域內通過加權來得到放大圖像中的像素值。不同的是,雙三次插值法需要原圖像中近鄰的16個點來加權。
目標像素點與原圖像的像素點的對應公式如下所示:
s r c X = d s t X ? s r c W i d t h d s t W i d t h s r c X=d s t X*{\frac{s r c W i d t h}{d s t W i d t h}} srcX=dstX?dstWidthsrcWidth?
s r c Y = d s t Y ? s r c H e i g h t d s t H e i g h t s r c Y=d s t Y*{\frac{s r c H e i g h t}{d s t H e i g h t}} srcY=dstY?dstHeightsrcHeight?
下面我們舉例說明,假設原圖像A大小為m*n,縮放后的目標圖像B的大小為M*N。其中A的每一個像素點是已知的,B是未知的,我們想要求出目標圖像B中每一個像素點(X,Y)的值,必須先找出像素(X,Y)在原圖像A中對應的像素(x,y),再根據原圖像A距離像素(x,y)最近的16個像素點作為計算目標圖像B(X,Y)處像素值的參數,利用BiCubic基函數求出16個像素點的權重,圖B像素(x,y)的值就等于16個像素點的加權疊加。
假如下圖中的P點就是目標圖像B在(X,Y)處根據上述公式計算出的對應于原圖像A中的位置,P的坐標位置會出現小數部分,所以我們假設P點的坐標為(x+u,y+v),其中x、y表示整數部分,u、v表示小數部分,那么我們就可以得到其周圍的最近的16個像素的位置,我們用a(i,j)(i,j=0,1,2,3)來表示,如下圖所示。
然后給出BiCubic函數:
其中,a一般取-0.5或-0.75。
我們要做的就是將上面的16個點的坐標帶入函數中,獲取16像素所對應的權重 W ( x ) W(x) W(x)。然而BiCubic函數是一維的,所以我們需要將像素點的行與列分開計算,比如a00這個點,我們需要將x=0帶入BiCubic函數中,計算a00點對于P點的x方向的權重,然后將y=0帶入BiCubic函數中,計算a00點對于P點的y方向的權重,其他像素點也是這樣的計算過程,最終我們就可以得到P所對應的目標圖像B在(X,Y)處的像素值為:
B ( X , Y ) = ∑ i = 0 3 ∑ j = 0 3 a i j × W ( i ) × W ( j ) B(X,Y)=\sum_{i=0}^{3}\sum_{j=0}^{3}a_{i j}\times W_{(i)}\times W_{(j)} B(X,Y)=i=0∑3?j=0∑3?aij?×W(i)?×W(j)?
依此辦法我們就可以得到目標圖像中所有的像素點的像素值。
代碼如下:
'''雙三次插值'''img=cv2.imread(r"../15day4.10/src/1.jpg")# 求出圖片的高寬和顏色h,w,c=img.shape# 給出一個旋轉角度angle=45# 給出縮放倍數sacle=1.5#給出縮放后的圖像的窗口大小frame=(2*w,2*h)# 以圖片的中心作為旋轉中心的仿射變換矩陣m=cv2.getRotationMatrix2D((h/2,w/2),0,sacle)img_rotate=cv2.warpAffine(img,m,frame,flags=cv2.INTER_CUBIC)cv2.imshow("img",img)cv2.imshow("img_rotate",img_rotate)cv2.waitKey(0)
雙三次插值(cv2.INTER_CUBIC)比雙線性插值(cv2.INTER_LINEAR)對圖片處理更精細,但是執行效率比較低
3.5 Lanczos插值(cv2.INTER_LANCZOS4)
Lanczos插值方法與雙三次插值的思想是一樣的,不同的就是其需要的原圖像周圍的像素點的范圍變成了8*8,并且不再使用BiCubic函數來計算權重,而是換了一個公式計算權重。
首先還是目標像素點與原圖像的像素點的對應公式如下所示:
s r c X = d s t X ? s r c W i d t h d s t W i d t h s r c X=d s t X*{\frac{s r c W i d t h}{d s t W i d t h}} srcX=dstX?dstWidthsrcWidth?
s r c Y = d s t Y ? s r c H e i g h t d s t H e i g h t s r c Y=d s t Y*{\frac{s r c H e i g h t}{d s t H e i g h t}} srcY=dstY?dstHeightsrcHeight?
下面我們舉例說明,假設原圖像A大小為m*n,縮放后的目標圖像B的大小為M*N。其中A的每一個像素點是已知的,B是未知的,我們想要求出目標圖像B中每一個像素點(X,Y)的值,必須先找出像素(X,Y)在原圖像A中對應的像素(x,y),再根據原圖像A距離像素(x,y)最近的64個像素點作為計算目標圖像B(X,Y)處像素值的參數,利用權重函數求出64個像素點的權重,圖B像素(x,y)的值就等于64個像素點的加權疊加。
假如下圖中的P點就是目標圖像B在(X,Y)處根據上述公式計算出的對應于原圖像A中的位置,P的坐標位置會出現小數部分,所以我們假設P點的坐標為(x+u,y+v),其中x、y表示整數部分,u、v表示小數部分,那么我們就可以得到其周圍的最近的64個像素的位置,我們用a(i,j)(i,j=0,1,2,3,4,5,6,7)來表示,如下圖所示。
然后給出權重公式:
其中a通常取2或者3,當a=2時,該算法適用于圖像縮小。a=3時,該算法適用于圖像放大。
與雙三次插值一樣,這里也需要將像素點分行和列分別帶入計算權重值,其他像素點也是這樣的計算過程,最終我們就可以得到P所對應的目標圖像B在(X,Y)處的像素值為:
S ( x , y ) = ∑ i = [ x ] ? a + 1 [ x ] + a ∑ j = [ y ] ? a + 1 [ y ] + a s i j L ( x ? i ) L ( y ? j ) S(x,y)=\sum_{i=[x]-a+1}^{[x]+a}\sum_{j=[y]-a+1}^{[y]+a}s_{i j}L(x-i)L(y-j) S(x,y)=i=[x]?a+1∑[x]+a?j=[y]?a+1∑[y]+a?sij?L(x?i)L(y?j)
其中 [ x ] [x] [x]、 [ y ] [y] [y]表示對坐標值向下取整,通過該方法就可以計算出新的圖像中所有的像素點的像素值。
代碼如下:
'''lanczos'''img=cv2.imread(r"../15day4.10/src/1.jpg")# 求出圖片的高寬和顏色h,w,c=img.shape# 給出一個旋轉角度angle=45# 給出縮放倍數sacle=1.5#給出縮放后的圖像的窗口大小frame=(2*w,2*h)# 以圖片的中心作為旋轉中心的仿射變換矩陣m=cv2.getRotationMatrix2D((h/2,w/2),0,sacle)img_rotate=cv2.warpAffine(img,m,frame,flags=cv2.INTER_LANCZOS4)cv2.imshow("img",img)cv2.imshow("img_rotate",img_rotate)cv2.waitKey(0)
3.6 小結
最近鄰插值的計算速度最快,但是可能會導致圖像出現鋸齒狀邊緣和失真,效果較差。雙線性插值的計算速度慢一點,但效果有了大幅度的提高,適用于大多數場景。雙三次插值、Lanczos插值的計算速度都很慢,但是效果都很好。
在OpenCV中,關于插值方法默認選擇的都是雙線性插值,且一般情況下雙線性插值已經能滿足大部分需求。
4. 邊緣填充方式(cv2.warpAffine的屬性borderMode)
可以看到,左圖在逆時針旋轉45度之后原圖的四個頂點在右圖中已經看不到了,同時,右圖的四個頂點區域其實是什么都沒有的,因此我們需要對空出來的區域進行一個填充。右圖就是對空出來的區域進行了像素值為(0,0,0)的填充,也就是黑色像素值的填充。除此之外,后續的一些圖像處理方式也會用到邊緣填充,這里介紹五個常用的邊緣填充方法。
4.1 邊界復制(BODER_REPLICATE)
邊界復制會將邊界處的像素值進行復制,然后作為邊界填充的像素值,如下圖所示,可以看到四周的像素值都一樣。
代碼如下:
'''邊界復制'''img=cv2.imread(r"../15day4.10/src/face.png")# 求出圖片的高寬和顏色h,w,c=img.shape# 給出一個旋轉角度angle=45# 給出縮放倍數sacle=1#給出縮放后的圖像的窗口大小frame=(2*w,2*h)# 以圖片的中心作為旋轉中心的仿射變換矩陣m=cv2.getRotationMatrix2D((h/2,w/2),angle,sacle)img_rotate=cv2.warpAffine(img,m,frame,borderMode=cv2.BORDER_REPLICATE)cv2.imshow("img",img)cv2.imshow("img_rotate",img_rotate)cv2.waitKey(0)
4.2 邊界反射(BORDER_REFLECT)
如下圖所示,會根據原圖的邊緣進行反射。
代碼如下:
'''邊界反射'''img=cv2.imread(r"../15day4.10/src/face.png")# 求出圖片的高寬和顏色h,w,c=img.shape# 給出一個旋轉角度angle=45# 給出縮放倍數sacle=1#給出縮放后的圖像的窗口大小frame=(2*w,2*h)# 以圖片的中心作為旋轉中心的仿射變換矩陣m=cv2.getRotationMatrix2D((h/2,w/2),angle,sacle)img_rotate=cv2.warpAffine(img,m,frame,borderMode=cv2.BORDER_REFLECT)cv2.imshow("img",img)cv2.imshow("img_rotate",img_rotate)cv2.waitKey(0)
邊界反射是將原圖沿著原圖的邊界鏡像復制填充整個邊框
4.3 邊界反射101(BORDER_REFLECT_101)
與邊界反射不同的是,不再反射邊緣的像素點,如下圖所示。
代碼如下:
'''邊界反射_101'''img=cv2.imread(r"../15day4.10/src/face.png")# 求出圖片的高寬和顏色h,w,c=img.shape# 給出一個旋轉角度angle=45# 給出縮放倍數sacle=1#給出縮放后的圖像的窗口大小frame=(2*w,2*h)# 以圖片的中心作為旋轉中心的仿射變換矩陣m=cv2.getRotationMatrix2D((h/2,w/2),angle,sacle)img_rotate=cv2.warpAffine(img,m,frame,borderMode=cv2.BORDER_REFLECT_101)cv2.imshow("img",img)cv2.imshow("img_rotate",img_rotate)cv2.waitKey(0)
邊界反射101是將原圖以原圖的邊界為軸鏡像復制填充整個邊框
4.4 邊界常數(BORDER_CONSTANT)
當選擇邊界常數時,還要指定常數值是多少,默認的填充常數值為0,如下圖所示。
img2=cv2.warpAffine(img,M,(shape[1],shape[0]),flags=cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT,borderValue=100)
代碼如下:
'''邊界常數'''img=cv2.imread(r"../15day4.10/src/face.png")# 求出圖片的高寬和顏色h,w,c=img.shape# 給出一個旋轉角度angle=45# 給出縮放倍數sacle=1#給出縮放后的圖像的窗口大小frame=(2*w,2*h)# 以圖片的中心作為旋轉中心的仿射變換矩陣m=cv2.getRotationMatrix2D((h/2,w/2),angle,sacle)#邊界常數(borderMode=cv2.BORDER_CONSTANT)的邊界值(borderVlue)為0則是黑色,如果就是一個值的話是將bgr這三個值都賦值為這個值img_rotate=cv2.warpAffine(img,m,frame,borderMode=cv2.BORDER_CONSTANT,borderValue=(0,0,255))cv2.imshow("img",img)cv2.imshow("img_rotate",img_rotate)cv2.waitKey(0)
邊界常數(borderMode=cv2.BORDER_CONSTANT)的邊界值(borderVlue)為0則是黑色,如果就是一個值的話是將bgr這三個值都賦值為這個值
4.5 邊界包裹(BORDER_WRAP)
如下圖所示。
代碼如下:
'''邊界包裹'''img=cv2.imread(r"../15day4.10/src/face.png")# 求出圖片的高寬和顏色h,w,c=img.shape# 給出一個旋轉角度angle=45# 給出縮放倍數sacle=1#給出縮放后的圖像的窗口大小frame=(2*w,2*h)# 以圖片的中心作為旋轉中心的仿射變換矩陣m=cv2.getRotationMatrix2D((h/2,w/2),angle,sacle)img_rotate=cv2.warpAffine(img,m,frame,borderMode=cv2.BORDER_WRAP)cv2.imshow("img",img)cv2.imshow("img_rotate",img_rotate)cv2.waitKey(0)
邊界包裹(cv2.BORDER_WRAP)的含義就是將圖片平鋪填充窗口