二維平面中,圖像的幾何變換有等距、相似、仿射、投影等,如下所示:
1 圖像幾何變換
1.1 等距變換
等距變換 (Isometric Transformation),是一種二維的剛體變換,可理解為旋轉和平移的組合
$\quad \begin{bmatrix} x^{\prime} \\ y^{\prime} \\ 1 \end{bmatrix} = \begin{bmatrix} \cos \theta & -\sin \theta & t_x \\ \sin \theta & \cos \theta & t_y \\ 0&0&1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1\end{bmatrix} =\begin{bmatrix} R_{2 \times 2} & T_{2 \times 1} \\ 0_{1 \times 2} & 1_{1 \times 1} \end{bmatrix} \begin{bmatrix} x \\ y \\1 \end{bmatrix}$
其中, $R=\begin{bmatrix} \cos \theta &-\sin\theta \\ \sin \theta & \cos \theta \end{bmatrix}$ 為旋轉矩陣, $T=\begin{bmatrix}t_x \\ t_y \end{bmatrix}$ 為平移矩陣
想象一個無限大的平面上,放一張極薄的圖片,讓它只能在平面內做旋轉和平移運動,則這樣的運動就是等距變換
1.2 相似變換
相似變換 (Similarity Transformation),是一個等距變換和各向均勻縮放的組合
$\quad \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} s \cos \theta & -s\sin \theta & t_x \\ s \sin \theta & s \cos \theta & t_y \\ 0&0&1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1\end{bmatrix} = \begin{bmatrix} sR_{2 \times 2} & T_{2 \times 1} \\ 0_{1 \times 2} & 1_{1 \times 1} \end{bmatrix} \begin{bmatrix} x \\ y \\1 \end{bmatrix}$,其中 $s$ 為縮放系數
想象平面內的一張圖片,在旋轉和平移的過程中,其大小也會均勻縮放(各個方向),則這樣的變換就是相似變換
1.3 仿射變換
1.3.1 定義
仿射變換(Affine Transformation),是一個非奇異線性變變換 (矩陣乘法) 和 平移變換 (向量加法) 的組合
矩陣表達式為 $\quad \begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \begin{bmatrix} a_{11} & a_{12} & t_x \\ a_{21} & a_{22} & t_y \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix} =\begin{bmatrix} A_{2 \times 2} & T_{2 \times 1} \\ 0_{1 \times 2} & 1_{1 \times 1} \end{bmatrix} \begin{bmatrix} x \\ y \\1 \end{bmatrix}$
其中,當 $A = \begin{bmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{bmatrix}$ 是非奇異時,稱 $A$ 為仿射矩陣
1.3.2 分解
仿射矩陣 $A$ 可分解為:旋轉和各向 (正交) 非均勻縮放
$\quad A = R(\theta) R(-\phi) D R(\phi)$,其中 $D = \begin{bmatrix} \lambda_1 & 0 \\ 0 & \lambda_2 \end{bmatrix}$是一個對角矩陣
首先,旋轉角度 $\phi$;然后在 $x$ 和 $y$ 方向上 (其中 $x\perp y$) 分別縮放 $\lambda_1$ 和 $\lambda_2$;再旋轉角度 $-\phi$,也即回轉 $\phi$;最后旋轉角度 $\theta$
本質上,平面中的仿射變換,就是奇異值分解的過程:$A=UDV^T$
想象無限大光滑平面內的一張圖片,在旋轉和平移的過程中,其大小在正交方向上非均勻縮放,則這樣的變換就是仿射變換
1.3.3 不變量
仿射變換的過程中,有三個重要的不變量,分別是:平行線,平行線段長度比,面積比
2 OpenCV 函數
2.1 矩陣 - 相似變換
對於相似變換,有 4 個未知數 ($s, \theta, t_x, t_y$),對應 OpenCV 中的 getRotationMatrix2D() 函數
Mat getRotationMatrix2D ( Point2f center, // 原圖像中的旋轉中心點 double angle, // 旋轉角度(正值代表逆時針旋轉) double scale // 均勻縮放系數 )
該函數可得到如下矩陣:
$\begin{bmatrix} \alpha & \beta & (1- \alpha ) \cdot \texttt{center.x} - \beta \cdot \texttt{center.y} \\ - \beta & \alpha & \beta \cdot \texttt{center.x} + (1- \alpha ) \cdot \texttt{center.y} \end{bmatrix}$
其中, $\alpha=scale \cdot \cos angle$
$\beta=scale \cdot \sin angle$
2.2 矩陣 - 仿射變換
仿射變換有 6 個未知數 ($\phi, \theta, \lambda_1, \lambda_2, t_x, t_y$),需列 6 組方程,而一組對應特征點 $(x,y)$ -> $(x′,y′)$ 可構造 2 個方程,因此,求解 6 個未知數,需要 3 組對應特征點
OpenCV 中 getAffineTransform() 可求解 2x3 矩陣 $\begin{bmatrix} a_{11} & a_{12} & t_{x} \\ a_{21} & a_{22} & t_y \end{bmatrix}$
Mat getAffineTransform ( const Point2f src[], // 原圖像的三角頂點坐標 const Point2f dst[] // 目標圖像的三角頂點坐標 )
其代碼實現比較簡單,先構建方程組,再利用 solve() 求解 $Ax=b$
Mat getAffineTransform(const Point2f src[], const Point2f dst[]) { Mat M(2, 3, CV_64F), X(6, 1, CV_64F, M.ptr()); double a[6 * 6], b[6]; Mat A(6, 6, CV_64F, a), B(6, 1, CV_64F, b); for (int i = 0; i < 3; i++) { int j = i * 12; int k = i * 12 + 6; a[j] = a[k + 3] = src[i].x; a[j + 1] = a[k + 4] = src[i].y; a[j + 2] = a[k + 5] = 1; a[j + 3] = a[j + 4] = a[j + 5] = 0; a[k] = a[k + 1] = a[k + 2] = 0; b[i * 2] = dst[i].x; b[i * 2 + 1] = dst[i].y; } solve(A, B, X); return M; }
2.3 變換后圖象
已知仿射變換矩陣 M ,利用 warpAffine() 函數,可得變換后的圖像:$\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})$
void warpAffine( InputArray src, // 輸入圖象 OutputArray dst, // 輸出圖像(大小為 dsize,類型同 src) InputArray M, // 2x3 矩陣 Size dsize, // 輸出圖像的大小 int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar() )
3 代碼示例
首先構造3組三角頂點坐標,代入 getAffineTransform() 得到仿射變換的矩陣;再用 getRotationMatrix2D() 構造相似變換的矩陣;
然后,warpAffine() 求解經過相似變換和仿射變換的圖像;最后,顯示對比變換后的目標圖像
#include "opencv2/imgproc.hpp" #include "opencv2/imgcodecs.hpp" #include "opencv2/highgui.hpp" using namespace cv; int main() { // 1) read image Mat src = imread("horse.jpg"); // 2) triangle vertices Point2f srcTri[3]; srcTri[0] = Point2f(0.f, 0.f); srcTri[1] = Point2f(src.cols - 1.f, 0.f); srcTri[2] = Point2f(0.f, src.rows - 1.f); Point2f dstTri[3]; dstTri[0] = Point2f(0.f, src.rows * 0.33f); dstTri[1] = Point2f(src.cols * 0.85f, src.rows * 0.25f); dstTri[2] = Point2f(src.cols * 0.15f, src.rows * 0.7f); // 3-1) getAffineTransform Mat warp_mat1 = getAffineTransform(srcTri, dstTri); // 3-2) getRotationMatrix2D Mat warp_mat2 = getRotationMatrix2D(Point2f(0.5*src.cols, 0.5*src.rows), 45, 0.5); // 4) warpAffine image Mat dst1,dst2; warpAffine(src, dst1, warp_mat1, Size(src.cols, src.rows)); warpAffine(src, dst2, warp_mat2, Size(src.cols, src.rows)); // 5) show image imshow("image", src); imshow("warp affine 1", dst1); imshow("warp affine 2", dst2); waitKey(); }
檢測結果對比如下:
參考資料
《Computer Vision: Algorithms and Applications》 Chapter 2 Image Formation
《Multiple View Geometry in Computer Vision》 2.4 A hierarchy of transformations
OpenCV Tutorials / Image Processing (imgproc module) / Affine Transformations
OpenCV-Python Tutorials / Image Processing in OpenCV / Geometric Transformations of Images