圖像的幾何變換是在不改變圖像內容的前提下對圖像像素的進行空間幾何變換,主要包括了圖像的平移變換、鏡像變換、縮放和旋轉等。本文首先介紹了圖像幾何變換的一些基本概念,而後再OpenCV2下實現了圖像的平移變換、鏡像變換、縮放以及旋轉,最後介紹幾何的組合變換(平移+縮放+旋轉)。算法
圖像的幾何變換改變了像素的空間位置,創建一種原圖像像素與變換後圖像像素之間的映射關係,經過這種映射關係可以實現下面兩種計算:spa
對於第一種計算,只要給出原圖像上的任意像素座標,都能經過對應的映射關係得到到該像素在變換後圖像的座標位置。將這種輸入圖像座標映射到輸出的過程稱爲「向前映射」。反過來,知道任意變換後圖像上的像素座標,計算其在原圖像的像素座標,將輸出圖像映射到輸入的過程稱爲「向後映射」。可是,在使用向前映射處理幾何變換時卻有一些不足,一般會產生兩個問題:映射不徹底,映射重疊3d
要解決上述兩個問題可使用「向後映射」,使用輸出圖像的座標反過來推算改座標對應於原圖像中的座標位置。這樣,輸出圖像的每一個像素均可以經過映射關係在原圖像找到惟一對應的像素,而不會出現映射不徹底和映射重疊。因此,通常使用向後映射來處理圖像的幾何變換。從上面也能夠看出,向前映射之因此會出現問題,主要是因爲圖像像素的總數發生了變化,也就是圖像的大小改變了。在一些圖像大小不會發生變化的變換中,向前映射仍是頗有效的。code
對於數字圖像而言,像素的座標是離散型非負整數,可是在進行變換的過程當中有可能產生浮點座標值。例如,原圖像座標(9,9)在縮小一倍時會變成(4.5,4.5),這顯然是一個無效的座標。插值算法就是用來處理這些浮點座標的。常見的插值算法有最鄰近插值法、雙線性插值法,二次立方插值法,三次立方插值法等。本文主要介紹最鄰近插值和雙線性插值,其餘一些高階的插值算法,之後再作研究。orm
圖像的平移變換就是將圖像全部的像素座標分別加上指定的水平偏移量和垂直偏移量。平移變換根據是否改變圖像大小分爲兩種
左邊平移圖像的大小發生了,在保證圖像平移的同時,也保存了完整的圖像信息。右邊的平移圖像大小沒有變化,故圖像右下角的部分被截除了。blog
設dx爲水平偏移量,dy爲垂直偏移量,(x0,y0)爲原圖像座標,(x,y)爲變換後圖像座標,則平移變換的座標映射爲
get
這是向前映射,即將原圖像的座標映射到變換後的圖像上。
其逆變換爲
,向後映射,即將變換後的圖像座標映射到原圖像上。在圖像的幾何變換中,通常使用向後映射。it
圖像的平移變換實現仍是很簡單的,這裏再也不贅述.form
平移後圖像的大小不變
class
void GeometricTrans::translateTransform(cv::Mat const& src, cv::Mat& dst, int dx, int dy) { CV_Assert(src.depth() == CV_8U); const int rows = src.rows; const int cols = src.cols; dst.create(rows, cols, src.type()); Vec3b *p; for (int i = 0; i < rows; i++) { p = dst.ptr<Vec3b>(i); for (int j = 0; j < cols; j++) { //平移後坐標映射到原圖像 int x = j - dx; int y = i - dy; //保證映射後的座標在原圖像範圍內 if (x >= 0 && y >= 0 && x < cols && y < rows) p[j] = src.ptr<Vec3b>(y)[x]; } } }
平移後圖像的大小變化
void GeometricTrans::translateTransformSize(cv::Mat const& src, cv::Mat& dst, int dx, int dy) { CV_Assert(src.depth() == CV_8U); const int rows = src.rows + abs(dy); //輸出圖像的大小 const int cols = src.cols + abs(dx); dst.create(rows, cols, src.type()); Vec3b *p; for (int i = 0; i < rows; i++) { p = dst.ptr<Vec3b>(i); for (int j = 0; j < cols; j++) { int x = j - dx; int y = i - dy; if (x >= 0 && y >= 0 && x < src.cols && y < src.rows) p[j] = src.ptr<Vec3b>(y)[x]; } } }
ps:這裏圖像變換的代碼以三通道圖像爲例,單通道的於此相似,代碼中沒有作處理。
圖像的鏡像變換分爲兩種:水平鏡像和垂直鏡像。水平鏡像以圖像垂直中線爲軸,將圖像的像素進行對換,也就是將圖像的左半部和右半部對調。垂直鏡像則是以圖像的水平中線爲軸,將圖像的上半部分和下班部分對調。效果以下:
設圖像的寬度爲width,長度爲height。(x,y)爲變換後的座標,(x0,y0)爲原圖像的座標
水平鏡像的實現
void GeometricTrans::hMirrorTrans(const Mat &src, Mat &dst) { CV_Assert(src.depth() == CV_8U); dst.create(src.rows, src.cols, src.type()); int rows = src.rows; int cols = src.cols; switch (src.channels()) { case 1: const uchar *origal; uchar *p; for (int i = 0; i < rows; i++){ origal = src.ptr<uchar>(i); p = dst.ptr<uchar>(i); for (int j = 0; j < cols; j++){ p[j] = origal[cols - 1 - j]; } } break; case 3: const Vec3b *origal3; Vec3b *p3; for (int i = 0; i < rows; i++) { origal3 = src.ptr<Vec3b>(i); p3 = dst.ptr<Vec3b>(i); for(int j = 0; j < cols; j++){ p3[j] = origal3[cols - 1 - j]; } } break; default: break; } }
分別對三通道圖像和單通道圖像作了處理,因爲比較相似之後的代碼只處理三通道圖像,再也不作特別說明。
在水平鏡像變換時,遍歷了整個圖像,而後根據映射關係對每一個像素都作了處理。實際上,水平鏡像變換就是將圖像座標的列換到右邊,右邊的列換到左邊,是能夠以列爲單位作變換的。一樣垂直鏡像變換也如此,能夠以行爲單位進行變換。
垂直鏡像變換
void GeometricTrans::vMirrorTrans(const Mat &src, Mat &dst) { CV_Assert(src.depth() == CV_8U); dst.create(src.rows, src.cols, src.type()); int rows = src.rows; for (int i = 0; i < rows; i++) src.row(rows - i - 1).copyTo(dst.row(i)); }
src.row(rows - i - 1).copyTo(dst.row(i));
上面一行代碼是變換的核心代碼,從原圖像中取出第i行,並將其複製到目標圖像。
頂不住了啊,寫理論部分太痛苦了啊,明天繼續幾何變換的後續幾種:轉置、縮放、旋轉以及組合變換。