摘要
圖像幾何變換又稱為圖像空間變換, 它將一幅圖像中的坐標位置映射到另一幅圖像中的新坐標位置。幾何變換不改變圖像的像素值, 只是在圖像平面上進行像素的重新安排。
幾何變換大致分為仿射變換、投影變換、極坐標變換,完成幾何變換需要兩個獨立的算法過程:
1、一個用來實現空間坐標變換的算法,用它描述每個像素如何從初始位置移動到終止位置
2、一個插值算法完成輸出圖像的每個像素的灰度值
放射變換
😀首先,先來分析一下放射變換的原理:
-
什么是放射變換?
仿射變換是從一個二維坐標系變換到另一個二維坐標系,屬於線性變換。通過已知3對坐標點可以求得變換矩陣。(不共線的三對對應點,決定了唯一的變換矩陣)
其中
就是仿射變換矩陣一般形式,根據不同的變換,比如平移、縮放、旋轉等等,仿射變換矩陣的值是不一樣的。
1、平移
假設空間坐標 (x,y)先沿 x軸平移tx ,在沿 y軸平移 ty,則變換后的坐標為(x+tx,y+ty) ,此時平移變換為:
2、縮放
二維空間坐標 (x,y) 以任意一點 (x0,y0) 為中心在水平方向和垂直方向上分別縮放 sx和 sy倍,縮放后坐標為 (x0+sx (x-x0),y0+sy(y-y0)) ,通俗來講就是縮放后的坐標離中心點的水平距離變為原坐標離中心點水平距離的 sx 倍。
當 (x0,y0) 為原點 (0,0) 時,縮放變換可以表示為:
當 (x0,y0) 為任意點 時,變換過程理解為,先將中心點平移到原點,再以原點為中心進行縮放,然后移回到原來的中心點。縮放變換表示為:
注意:等式右邊的運算應該從右往左看
3、旋轉
順時針繞原點(0,0)旋轉變換的矩陣表示為:
若以任意一點 (x0,y0) 為中心旋轉,相當於先將原點移動到旋轉中心,然后繞原點旋轉,最后移回坐標原點,用矩陣表示為:
注意:上面的運算順序是從右向左的。
OpenCV提供的旋轉函數,實現順時針90°、180°、270°的旋轉
rotate(InputArray src, Output dst, int rotateCode) rotateCode有以下取值: ROTATE_90_CLOCKWISE //順時針旋轉90度 ROTATE_180 //順時針旋轉180度 ROTATE_90_COUNTERCLOCKWISE //逆時針旋轉90度
flip(src, dst, int flipCode)
實現了圖像的水平鏡像、垂直鏡像和逆時針旋轉180°,不過並不是通過仿射變換實現的,而是通過行列互換,它與
rotate()
、
transpose()
函數一樣都在core.hpp頭文件中。
-
求解放射變換矩陣
以上都是知道變換前坐標求變換后的坐標,如果我們已經知道了變換前的坐標和變換后的坐標,想求出仿射變換矩陣,可以通過解方程法或矩陣法。
🤨解方程法
由於仿射變換矩陣
有6個未知數,所以我們只需三組坐標列出六個方程即可。OpenCV提供函數getAffineTransform(src, dst)
通過方程法求解,其中src和dst分別為前后坐標。
對於C++來說,一種方式是將坐標存在Point2f數組中,另一種方法是保存在Mat中:
// 第一種方法 Point2f src1[] = {Pointy2f(0, 0), Point2f(200, 0), Point2f(0, 200)}; Point2f dst1[] = {Pointy2f(0, 0), Point2f(100, 0), Point2f(0, 100)}; // 第二種方法 Mat src2 = (Mat_<float>(3, 2) << 0, 0, 200, 0, 0, 200); Mat dst2 = (Mat_<float>(3, 2) << 0, 0, 100, 0, 0, 100); Mat A = getAffineTransform(src1, dst1);
😮矩陣法
對於等比例縮放的仿射變換,OpenCV提供函數getRotationMatrix2D
(center, angle, scale)
來計算矩陣,center是變換中心;angle是逆時針旋轉的角度,(opencv中正角度代表逆時針旋轉);scale是等比例縮放的系數。
我們通過下面的代碼來定義這些參數,例如:
Point center = Point( src.cols/2, src.rows/2 ); //中心點 double angle = -50.0; double scale = 0.6;
-
插值算法分析
在運算中,我們可能會遇到目標坐標有小數的情況,比如將坐標(3,3)縮放2倍變為了(1.5,1.5),但是對於圖像來說並沒有這個點,這時候我們就要用周圍坐標的值來估算此位置的顏色,也就是插值。
(1)最近鄰插值(INTER_NEAREST)
最近鄰插值就是從(x,y)的四個相鄰坐標中找到最近的那個來當作它的值,如(2.3,4.7),它的相鄰坐標分別為(2,4)、(3,4)、(2,5)、(3,5),計算這幾個相鄰坐標與(2.3,4.7)坐標的距離,若最近的為(2,5),則取(2,5)的顏色值為的(2.3,4.7)值。
此種方法得到的圖像會出現鋸齒狀外觀,對於放大圖像則更明顯。
(2)雙線性插值(INTER_LINEAR)(最常用)
要估計輸入圖像非整數坐標 的值,分為三個步驟:
第一步:先用線性關系估計輸入圖像中 的值
第二步:同理用線性關系估計輸入圖像中 的值
第三步:根據前兩步得到的值,用線性關系估計輸入圖像中 的值
-
進行放射變換
將剛剛求得的仿射變換矩陣應用到原圖像,OpenCV提供函數warpAffine進行放射變換。
warpAffine(src,dst,M,dsize,flags,bordMode, borderValue) //src:輸入圖像 //dst:輸出變換后圖像,需要初始化一個空矩陣用來保存結果,不用設定矩陣尺寸 //M:2行3列的仿射變換矩陣 //dsize: 二元元組(寬,高),代表輸出圖像大小 //flags: 插值法 INTER_NEAREST、INTER_LINEAR(默認線性插值) //bordMode: 邊界像素模式,默認值BORDER_CONSTANT //bordValue: 當填充模式為BORDER_CONSTANT時的填充值(默認為Scalar(),即0)
-
opencv實現放射變換
1️⃣第一種仿射變換的調用方式:三點法
說明:當得到變換前的三點坐標以及變化后的三點坐標就可以用該方法
Point2f srcPoints[3];//原圖中的三點 (一個包含三維點(x,y)的數組,其中x、y是浮點型數) Point2f dstPoints[3];//目標圖中的三點 Mat dst_warp1, dst_warp2; Mat src = imread("D:/opencv練習圖片/薛之謙.jpg"); imshow("輸入圖像", src); //第一種仿射變換的調用方式:三點法 //三個點對的值,上面也說了,只要知道你想要變換后圖的三個點的坐標,就可以實現仿射變換 srcPoints[0] = Point2f(0, 0); srcPoints[1] = Point2f(0, src.rows); srcPoints[2] = Point2f(src.cols, 0); //映射后的三個坐標值 dstPoints[0] = Point2f(0, src.rows*0.3); dstPoints[1] = Point2f(src.cols*0.25, src.rows*0.75); dstPoints[2] = Point2f(src.cols*0.75, src.rows*0.25); Mat M1 = getAffineTransform(srcPoints, dstPoints);//由三個點對計算變換矩陣 warpAffine(src, dst_warp1, M1, src.size());//仿射變換 imshow("放射變換", dst_warp1);
2️⃣第二種仿射變換的調用方式:自定義旋轉角度和縮放比例(最常用)
說明:angle(旋轉角度:正數為逆時針旋轉,負數為順時針旋轉);scale(縮放因子)
Mat src = imread("D:/opencv練習圖片/薛之謙.jpg"); imshow("輸入圖像", src); /// 計算關於圖像中心的旋轉矩陣 Point center = Point(src.cols / 2, src.rows / 2); double angle = 35.0; //旋轉的同時也可以放縮 double scale = 0.8; /// 根據以上參數得到旋轉矩陣 Mat M = getRotationMatrix2D(center, angle, scale); //計算旋轉后的畫布大小,並將旋轉中心平移到新的旋轉中心 Rect bbox = RotatedRect(center, Size(src.cols*scale, src.rows*scale), angle).boundingRect(); M.at<double>(0, 2) += bbox.width / 2.0 - center.x; M.at<double>(1, 2) += bbox.height / 2.0 - center.y; /// 旋轉圖像 Mat rotate_dst; warpAffine(src, rotate_dst, M, bbox.size()); imshow("放射變化", rotate_dst); waitKey(0); return 0;
💖分析:
對於該句的理解:
//計算旋轉后的畫布大小,並將旋轉中心平移到新的旋轉中心 Rect bbox = RotatedRect(center, Size(src.cols*scale, src.rows*scale), angle).boundingRect(); M.at<double>(0, 2) += bbox.width / 2.0 - center.x; M.at<double>(1, 2) += bbox.height / 2.0 - center.y;
是由於在圖像旋轉之后,部分數據就會超出原來圖像的位置,出現截斷,為了避免上面的問題,引入類RotatedRect。並計算它的外接矩形(使用成員函數boundingRect()返回)
最后計算新的中心點
格外注意:size(src.cols*scale,src.rows*scale)對於中心點坐標(而不是像素坐標)
小結(對於opencv中“旋轉”的思考)
很多人都對opencv中的旋轉吐糟,意思是它自己沒有很好的關於選擇的算法。那么今天就來分析一下OpenCV中旋轉是如何定量的,什么是正方向?什么是負方向?什么時候用角度?什么時候用弧度?
一、OpenCV中旋轉式如何定量的
- void rotate(InputArray src, OutputArray dst, int rotateCode);
- getRotationMatrix2D函數
- 通過pca獲得圖像的弧度(這個函數的目的,在於通過PCA方法,獲得當前輪廓的主要方向。)
投影變換(透視變換)

OpenCV提供函數getPerspectiveTransform(src, dst)
實現求解投影矩陣,需要輸入四組對應的坐標。
getPerspectiveTransform(const Point2f* src, const Point2f* dst) //參數const Point2f* src:原圖的四個固定頂點 //參數const Point2f* dst:目標圖像的四個固定頂點 //返回值:Mat型變換矩陣,可直接用於warpAffine()函數 //注意,頂點數組長度超4個,則會自動以前4個為變換頂點;數組可用Point2f[]或Point2f*表示
OpenCV提供函數warpPerspective
實現投影變換,參數說明和仿射變化類似。
warpPerspective(src,dst,M,dsize,flags,bordMode, borderValue) //src:輸入圖像 //dst:輸出變換后圖像,需要初始化一個空矩陣用來保存結果,不用設定矩陣尺寸 //M:2行3列的仿射變換矩陣 //dsize: 二元元組(寬,高),代表輸出圖像大小 //flags: 插值法 INTER_NEAREST、INTER_LINEAR(默認線性插值) //bordMode: 邊界像素模式,默認值BORDER_CONSTANT //bordValue: 當填充模式為BORDER_CONSTANT時的填充值(默認為Scalar(),即0)
😛opencv實現投影變換:
Mat src, dst_warp1; Point2f srcPoints[4];//原圖中的三點 (一個包含三維點(x,y)的數組,其中x、y是浮點型數) Point2f dstPoints[4];//目標圖中的三點 int main(int argc, char** argv) { src = imread("D:/opencv練習圖片/薛之謙.jpg"); namedWindow("SrcImage"); imshow("SrcImage", src); //變換前四點坐標 srcPoints[0] = Point2f(0, 0); srcPoints[1] = Point2f(0, src.rows); srcPoints[2] = Point2f(src.cols, 0); srcPoints[3] = Point2f(src.cols, src.rows); //變換后的四點坐標 dstPoints[0] = Point2f(src.cols*0.1, src.rows*0.1); dstPoints[1] = Point2f(0, src.rows); dstPoints[2] = Point2f(src.cols, 0); dstPoints[3] = Point2f(src.cols*0.7, src.rows*0.8); Mat M1 = getPerspectiveTransform(srcPoints, dstPoints);//由四個點對計算透視變換矩陣 warpPerspective(src, dst_warp1, M1, src.size());//投影變換 imshow("投影變換(四點法)", dst_warp1); waitKey(0); return 0; }
極坐標變換
通常通過極坐標變化校正圖像中的圓形物體或包含在圓環中的物體。
OpenCV 中提供了warpPolar()函數用於實現圖像的極坐標變換,該函數的函數原型如下:
warpPolar( src, dst, Size dsize, Point2f center, double maxRadius, int flags )
- src:原圖像,可以是灰度圖像或者彩色圖像
-
dst:極坐標變換后輸出圖像(與原圖像具有相同的數據類型和通道數)。
-
dsize:輸出圖像大小。(自行決定)
-
center:極坐標變換時極坐標原點在原圖像中的位置。
-
maxRadius:變換時邊界圓的半徑,它也決定了逆變換時的比例參數。
-
flags: 插值方法與極坐標映射方法標志,兩個方法之間通過“+”或者“|”號進行連接。
😄warpPolar()函數極坐標映射方法標志:
WARP_POLAR_LINEAR //極坐標正變換(直角坐標變換到極坐標) WARP_POLAR_LOG //半對數極坐標變換 WARP_INVERSE_MAP //逆變換(極坐標變換到直角坐標)
😛opencv實現極坐標變換:
Mat src, dst; int main(int argc, char** argv) { src = imread("D:/opencv練習圖片/環形字符.jpg"); namedWindow("SrcImage"); imshow("SrcImage", src); Point2f center = Point2f(src.cols / 2, src.rows / 2); //極坐標在圖像中的原點 // 圓的半徑 double maxRadius = min(center.y, center.x); //正極坐標變換 warpPolar(src, dst, Size(200, 500), center, maxRadius, INTER_LINEAR | WARP_POLAR_LINEAR); // 改變結果方向 rotate(dst, dst, ROTATE_90_COUNTERCLOCKWISE); imshow("極坐標變換", dst); waitKey(0); return 0; }
參考鏈接:(8條消息) 【OpenCV學習筆記】之仿射變換(Affine Transformation)_zhu_hongji的博客-CSDN博客_仿射變換
【從零學習OpenCV 4】圖像極坐標變換 - 知乎 (zhihu.com)
OpenCV算法學習筆記之幾何變換 - 簡書 (jianshu.com)