仿射變化的原理,使用及相關拓展的總結
前言
看了下原理計劃上榜的文章,沒錯,我也會寫標題了,不過本文內容無愧於題目。給大家詳細講一講opencv裏的仿射變換,也就是cv2.getAffineTransform和cv2.warpAffine這兩個函數。
原本我通常會先寫原理,然後再舉個簡單的例子,之後再舉一個複雜點的拓展的例子。爲了防止大家一看原理或者數學公式這類東西就跑,我先舉個例子,大家理解了就能用,想提升的再往下看就好。
簡單的例子
這個例子看懂了,遇到圖像仿射變換的需求直接套就行。
我們來變下面這張圖:
其中三個圓圈的中心點大概是,紅[316,76],黃[215,369],藍[413,371]。
那麼我們把紅點往左移,黃和藍不變。移動後,紅[215,76],黃[215,369],藍[413,371]。
代碼如下
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Mar 9 13:53:38 2020
@author: phoenix
"""
import cv2
import numpy as np
img=cv2.imread('/Users/phoenix/Documents/A.png')
rows,cols,ch=img.shape
pts1=np.float32([[316,76],[215,369],[413,371]])
pts2=np.float32([[215,76],[215,369],[413,371]])
M=cv2.getAffineTransform(pts1,pts2)
dst=cv2.warpAffine(img,M,(cols,rows))
cv2.imshow("dst",dst)
cv2.waitKey(0)
結果如下
再舉個例子,如果保持紅點不變,黃左移,藍右移。
那麼只要把代碼中的pts2改一下,改成
pts2=np.float32([[316,76],[115,369],[513,371]])
結果如圖
原理
簡單理解就是:仿射變化的關鍵在於仿射變化矩陣M。
M可以通過cv2.getAffineTransform這個函數得到。
M是一個簡單的2*3的矩陣。
假設一個點A座標爲(x1,y1),經過仿射變換矩陣M,在另一個圖像上爲A1點座標爲(x2,y2)。剛剛有說過M是一個2*3的矩陣,這裏設
那麼
也就是說
x2=a * x1 + b * y1 + c
y2=d * x1 + e * y1 + f
如果想做更深入的瞭解的話,可以看這個網址。
仿射變換
上面👆這個網址介紹的很詳細
提升拓展
cv2.getAffineTransform這個函數沒什麼好說的,是單純用來求仿射變換矩陣M
下面說說cv2.getAffineTransform這個函數
先看下它的help。
Help on built-in function warpAffine:
warpAffine(...)
warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) -> dst
. @brief Applies an affine transformation to an image.
.
. The function warpAffine transforms the source image using the specified matrix:
.
. \f[\texttt{dst} (x,y) = \texttt{src} ( \texttt{M} _{11} x + \texttt{M} _{12} y + \texttt{M} _{13}, \texttt{M} _{21} x + \texttt{M} _{22} y + \texttt{M} _{23})\f]
.
. when the flag #WARP_INVERSE_MAP is set. Otherwise, the transformation is first inverted
. with #invertAffineTransform and then put in the formula above instead of M. The function cannot
. operate in-place.
.
. @param src input image.
. @param dst output image that has the size dsize and the same type as src .
. @param M \f$2\times 3\f$ transformation matrix.
. @param dsize size of the output image.
. @param flags combination of interpolation methods (see #InterpolationFlags) and the optional
. flag #WARP_INVERSE_MAP that means that M is the inverse transformation (
. \f$\texttt{dst}\rightarrow\texttt{src}\f$ ).
. @param borderMode pixel extrapolation method (see #BorderTypes); when
. borderMode=#BORDER_TRANSPARENT, it means that the pixels in the destination image corresponding to
. the "outliers" in the source image are not modified by the function.
. @param borderValue value used in case of a constant border; by default, it is 0.
.
. @sa warpPerspective, resize, remap, getRectSubPix, transform
總結下就是
1.這個方程是用來做圖像仿射變化的,且是通過指定的矩陣來進行轉換。
2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) -> dst
其中
src:輸入圖像。
dst:輸出圖像,其大小爲dsize,並且與src類型相同。
M: 轉換矩陣。
dsize:輸出圖像的大小。
flags:插值方法(請參閱#InterpolationFlags)和可選方法的組合
borderMode:像素外推方法(不過看別的博客上寫的邊界像素模式?)(請參閱#BorderTypes);當borderMode =#BORDER_TRANSPARENT,表示目標圖像中的像素對應於源圖像中的“異常值”不會被該功能修改。(這裏我覺得我的翻譯沒有問題,但我實在不知道他想表達什麼)
borderValue:邊界不變時使用的值;默認情況下爲0。也就是黑色。
src,dst,M,dsize:輸入圖像,輸出圖像,轉換矩陣,輸出圖像的大小。這四個比較基礎,沒什麼好說的。
後面的**flags,borderMode,borderValue,這個三個變量很容易被大家忽略,其實還是非常非常有用**的。可以區別你對這個函數的理解深度。
flags:插值方法
flages表示插值方式,默認爲flags=cv2.INTER_LINEAR(雙線性插值)。
做個彙總給大家
INTER_NEAREST-最近鄰插值
INTER_LINEAR-雙線性插值(默認使用)
INTER_AREA-使用像素面積關係進行重採樣。 這可能是首選的圖像抽取方法,因爲它可以提供無波紋的結果。 但是當圖像放大時,它類似於INTER_NEAREST方法。
INTER_CUBIC-在4x4像素鄰域上的雙三次插值
INTER_LANCZOS4-在8x8像素鄰域上的Lanczos插值
這麼多插值方法怎麼選擇,我給大家推薦這篇博客OpenCV圖像插值方法的比較
如果你看不太懂那篇博客。那麼我給個不負責任的個人建議。
不在意圖片質量:使用默認的就OK。
在意圖片質量不在意代碼的運行時間:使用最後一個。
既在意質量又在意時間:看到前面寫的原理了嗎,你可以利用矩陣反推回去,因爲原圖的質量是最高的
borderMode:像素外推方法(邊界像素模式)
這個推薦大家看這個網站
BorderTypes
這是google翻譯後的網頁截圖。
簡單些解釋呢,borderType是指要添加在圖像周圍的某種類型的邊界,就像在圖像周圍創造一個邊框(給你的圖像加個相框的感覺)。
borderMode和borderValue加在一起,默認就是黑的。如果不理解往下看。
borderValue:邊界不變時使用的值
這裏的邊界不變指的是borderMode=cv2.BORDER_CONSTANT。
舉個例子這裏有一張圖片
當我用
dst=cv2.warpAffine(img,M,(cols,rows))
,borderMode和borderValue不選擇,即爲默認時。
仿射變化的結果是
記住這張圖
接着我用
dst=cv2.warpAffine(img,M,(cols,rows),borderMode=cv2.BORDER_CONSTANT ,borderValue=(0,255,255))
或者
dst=cv2.warpAffine(img,M,(cols,rows) ,borderValue=(0,255,255))
結果是
沒錯原本黑色的地方被黃色(0,255,255)填滿了。
而我如果更改borderMode,我使用
dst=cv2.warpAffine(img,M,(cols,rows),borderMode=cv2.BORDER_REFLECT,borderValue=(0,255,255))
結果是如圖,右邊填充的是h的鏡像。
cv2.BORDER_REFLECT的效果就是fedcba|abcdefgh|hgfedcb
此時borderValue的值也就沒什麼用了。
結尾
這篇文章花了我好長時間啊,翻了很多資料和博客,力求做到詳細正確。如果你看到了這裏,麻煩給我點了贊且留言支持一下。如果你還有關於仿射變換的問題沒有解決,麻煩發在評論裏,我會回覆幫你解決的。謝謝各位!