圖像處理的仿射變換與透視變換
引言
這一周主要在研究圖像的放射變換與透視變換,目前出現的主要問題是需要正確識別如下圖中的編碼標誌點圓心。
1.當傾斜角較小時:傾斜角較小
2.傾斜角較大時:
傾斜角較大
由上面兩幅圖可以看出,當傾斜角較大時,中間的圓斑將變為橢圓,四周的圓環帶之間的面積比關係也將出現變化,影響識別演算法的正確判斷。
那麼如何將傾斜角如此大的編碼標誌點進行校正呢?這便是本篇文章需要解決的問題。一 仿射變換與透視變換
其實一直有點沒太理解「放射」倆字是啥意思,但是大家都這麼叫,其實仿射變換和透視變換更直觀的叫法可以叫做「平面變換」和「空間變換」或者「二維坐標變換」和「三維坐標變換」。如果這麼命名的話,其實很顯然,這倆是一回事,只不過一個是二維坐標(x,y),一個是三維坐標(x,y,z)。也就是:
仿射變換:透視變換:
從另一個角度也能說明三維變換和二維變換的意思,仿射變換的方程組有6個未知數,所以要求解就需要找到3組映射點,三個點剛好確定一個平面。透視變換的方程組有8個未知數,所以要求解就需要找到4組映射點,四個點就剛好確定了一個三維空間。
仿射變換和透視變換的數學原理也不需要深究,其計算方法為坐標向量和變換矩陣的乘積,換言之就是矩陣運算。在應用層面,放射變換是圖像基於3個固定頂點的變換,如圖1.1:所示:圖中紅點即為固定頂點,在變換先後固定頂點的像素值不變,圖像整體則根據變換規則進行變換同理,透視變換是圖像基於4個固定頂點的變換,如圖1.2所示:
在OpenCV中,放射變換和透視變換均有封裝好的函數,分別為:
void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
與
void warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
兩種變換函數形式完全相同,因此以仿射變換為例:
void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())參數InputArray src:輸入變換前的圖像;參數OutputArray dst:輸出變換後圖像,需要初始化一個空矩陣用來保存結果,不用設定矩陣尺寸;參數Size dsize:設置輸出圖像大小;參數int flags=INTER_LINEAR:設置插值方式,默認方式為線性插值;後兩個參數不常用,在此不贅述。
關於生成變換矩陣InputArray M的函數getAffineTransform():
Mat getAffineTransform(const Point2f* src, const Point2f* dst)參數const Point2f* src:原圖的三個固定頂點參數const Point2f* dst:目標圖像的三個固定頂點返回值:Mat型變換矩陣,可直接用於warpAffine()函數注意,頂點數組長度超過3個,則會自動以前3個為變換頂點;數組可用Point2f[]或Point2f*表示
示例代碼如下:
//讀取原圖 Mat I = imread("..//img.jpg"); //設置空矩陣用於保存目標圖像 Mat dst; //設置原圖變換頂點 Point2f AffinePoints0[3] = { Point2f(100, 50), Point2f(100, 390), Point2f(600, 50) }; //設置目標圖像變換頂點 Point2f AffinePoints1[3] = { Point2f(200, 100), Point2f(200, 330), Point2f(500, 50) }; //計算變換矩陣 Mat Trans = getAffineTransform(AffinePoints0, AffinePoints1); //矩陣仿射變換 warpAffine(I, dst, Trans, Size(I.cols, I.rows)); //分別顯示變換先後圖像進行對比 imshow("src", I); imshow("dst", dst); waitKey();
同理,透視變換與仿射變換函數類似:
void warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
生成變換矩陣函數為:
Mat getPerspectiveTransform(const Point2f* src, const Point2f* dst)
注意透視變換頂點為4個。
兩種變換完整代碼及結果比較:
#include <iostream>#include <opencv.hpp>using namespace std;using namespace cv;Mat AffineTrans(Mat src, Point2f* scrPoints, Point2f* dstPoints){ Mat dst; Mat Trans = getAffineTransform(scrPoints, dstPoints); warpAffine(src, dst, Trans, Size(src.cols, src.rows), CV_INTER_CUBIC); return dst;}Mat PerspectiveTrans(Mat src, Point2f* scrPoints, Point2f* dstPoints){ Mat dst; Mat Trans = getPerspectiveTransform(scrPoints, dstPoints); warpPerspective(src, dst, Trans, Size(src.cols, src.rows), CV_INTER_CUBIC); return dst;}void main(){ Mat I = imread("..//img.jpg"); //700*438 Point2f AffinePoints0[4] = { Point2f(100, 50), Point2f(100, 390), Point2f(600, 50), Point2f(600, 390) }; Point2f AffinePoints1[4] = { Point2f(200, 100), Point2f(200, 330), Point2f(500, 50), Point2f(600, 390) }; Mat dst_affine = AffineTrans(I, AffinePoints0, AffinePoints1); Mat dst_perspective = PerspectiveTrans(I, AffinePoints0, AffinePoints1); for (int i = 0; i < 4; i++) { circle(I, AffinePoints0[i], 2, Scalar(0, 0, 255), 2); circle(dst_affine, AffinePoints1[i], 2, Scalar(0, 0, 255), 2); circle(dst_perspective, AffinePoints1[i], 2, Scalar(0, 0, 255), 2); } imshow("origin", I); imshow("affine", dst_affine); imshow("perspective", dst_perspective); waitKey();}
可以看出,仿射變換以3個點為基準點,即使數組長度為4也僅取前3個點作為基準點;透視變換以4個點為基準點,兩種變換結果不相同。應根據實際情況判斷使用哪種變換方式更佳。
二 編碼標誌點透視變換矯正
回到引言部分的問題,對於編碼標誌點中,我們可以以中心橢圓與坐標軸的四個交點為檢測點,以橢圓的長軸為半徑繪製一個理想圓,理想圓與坐標軸的交點為目標點。運用上面介紹的透視變換知識,便可以很容易的解決問題,如圖2.1所示。
三 跋
文章的最後,單純地需要感謝一下高靜小朋友提供的測試樣圖,才得以文章正式成文。
文章始發於我的個人公眾號:視覺IMAX。
推薦閱讀:
※Matlab圖像頻域濾波器的生成
※CVPR 2018 論文概述:有損壓縮視頻的多幀質量增強方法
※OTSU閾值分割
※基於Substance Designer 的2D/3D Perlin Noise (I)
※HDR技術從入門到放棄