OpenCV之imgproc 模块. 图像处理(3)霍夫线变换 霍夫圆变换 Remapping 重映射 仿射变换
霍夫線變換
目標(biāo)
在這個部分您將學(xué)習(xí)到:
- 使用OpenCV的以下函數(shù)?HoughLines?和?HoughLinesP?來檢測圖像中的直線.
原理
Note
?以下原理的說明來自書籍?學(xué)習(xí)OpenCV?作者Bradski和Kaehler.
霍夫線變換
它是如何實現(xiàn)的?
眾所周知, 一條直線在圖像二維空間可由兩個變量表示. 例如:
對于霍夫變換, 我們將用?極坐標(biāo)系?來表示直線. 因此, 直線的表達(dá)式可為:
化簡得:?
一般來說對于點?, 我們可以將通過這個點的一族直線統(tǒng)一定義為:
這就意味著每一對??代表一條通過點??的直線.
如果對于一個給定點??我們在極坐標(biāo)對極徑極角平面繪出所有通過它的直線, 將得到一條正弦曲線. 例如, 對于給定點?and??我們可以繪出下圖 (在平面??-?):
只繪出滿足下列條件的點??and?.
我們可以對圖像中所有的點進(jìn)行上述操作. 如果兩個不同點進(jìn)行上述操作后得到的曲線在平面??-??相交, 這就意味著它們通過同一條直線. 例如, 接上面的例子我們繼續(xù)對點:?,??和點?,??繪圖, 得到下圖:
這三條曲線在??-??平面相交于點?, 坐標(biāo)表示的是參數(shù)對 () 或者是說點?, 點??和點??組成的平面內(nèi)的的直線.
那么以上的材料要說明什么呢? 這意味著一般來說, 一條直線能夠通過在平面??-??尋找交于一點的曲線數(shù)量來?檢測. 越多曲線交于一點也就意味著這個交點表示的直線由更多的點組成. 一般來說我們可以通過設(shè)置直線上點的?閾值?來定義多少條曲線交于一點我們才認(rèn)為?檢測?到了一條直線.
這就是霍夫線變換要做的. 它追蹤圖像中每個點對應(yīng)曲線間的交點. 如果交于一點的曲線的數(shù)量超過了?閾值, 那么可以認(rèn)為這個交點所代表的參數(shù)對??在原圖像中為一條直線.
標(biāo)準(zhǔn)霍夫線變換和統(tǒng)計概率霍夫線變換
OpenCV實現(xiàn)了以下兩種霍夫線變換:
- 原理在上面的部分已經(jīng)說明了. 它能給我們提供一組參數(shù)對??的集合來表示檢測到的直線
- 在OpenCV 中通過函數(shù)?HoughLines?來實現(xiàn)
- 這是執(zhí)行起來效率更高的霍夫線變換. 它輸出檢測到的直線的端點?
- 在OpenCV 中它通過函數(shù)?HoughLinesP?來實現(xiàn)
代碼
- 加載一幅圖片
- 對圖片進(jìn)行?標(biāo)準(zhǔn)霍夫線變換?或是?統(tǒng)計概率霍夫線變換.
- 分別在兩個窗口顯示原圖像和繪出檢測到直線的圖像.
代碼說明
加載圖片
Mat src = imread(filename, 0); if(src.empty()) {help();cout << "can not open " << filename << endl;return -1; }用Canny算子對圖像進(jìn)行邊緣檢測
Canny(src, dst, 50, 200, 3);現(xiàn)在我們將要執(zhí)行霍夫線變換. 我們將會說明怎樣使用OpenCV的函數(shù)做到這一點:
標(biāo)準(zhǔn)霍夫線變換
首先, 你要執(zhí)行變換:
vector<Vec2f> lines; HoughLines(dst, lines, 1, CV_PI/180, 100, 0, 0 );帶有以下自變量:
- dst: 邊緣檢測的輸出圖像. 它應(yīng)該是個灰度圖 (但事實上是個二值化圖)
- lines: 儲存著檢測到的直線的參數(shù)對??的容器 *?rho?: 參數(shù)極徑??以像素值為單位的分辨率. 我們使用?1像素.
- theta: 參數(shù)極角??以弧度為單位的分辨率. 我們使用?1度?(即CV_PI/180)
- threshold: 要”檢測” 一條直線所需最少的的曲線交點
- srn?and?stn: 參數(shù)默認(rèn)為0. 查缺O(jiān)penCV參考文獻(xiàn)來獲取更多信息.
通過畫出檢測到的直線來顯示結(jié)果.
for( size_t i = 0; i < lines.size(); i++ ) {float rho = lines[i][0], theta = lines[i][1];Point pt1, pt2;double a = cos(theta), b = sin(theta);double x0 = a*rho, y0 = b*rho;pt1.x = cvRound(x0 + 1000*(-b));pt1.y = cvRound(y0 + 1000*(a));pt2.x = cvRound(x0 - 1000*(-b));pt2.y = cvRound(y0 - 1000*(a));line( cdst, pt1, pt2, Scalar(0,0,255), 3, CV_AA); }統(tǒng)計概率霍夫線變換
首先, 你要執(zhí)行變換:
vector<Vec4i> lines; HoughLinesP(dst, lines, 1, CV_PI/180, 50, 50, 10 );帶有以下自變量:
- dst: 邊緣檢測的輸出圖像. 它應(yīng)該是個灰度圖 (但事實上是個二值化圖) *?lines: 儲存著檢測到的直線的參數(shù)對??的容器
- rho?: 參數(shù)極徑??以像素值為單位的分辨率. 我們使用?1?像素.
- theta: 參數(shù)極角??以弧度為單位的分辨率. 我們使用?1度?(即CV_PI/180)
- threshold: 要”檢測” 一條直線所需最少的的曲線交點 *?minLinLength: 能組成一條直線的最少點的數(shù)量. 點數(shù)量不足的直線將被拋棄.
- maxLineGap: 能被認(rèn)為在一條直線上的亮點的最大距離.
通過畫出檢測到的直線來顯示結(jié)果.
for( size_t i = 0; i < lines.size(); i++ ) {Vec4i l = lines[i];line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, CV_AA); }顯示原始圖像和檢測到的直線:
imshow("source", src); imshow("detected lines", cdst);等待用戶按鍵推出程序
waitKey();結(jié)果
Note
?得到的結(jié)果使用的是在上面?代碼?部分提到的更高級版代碼. 霍夫線變換的代碼沒有改變, 唯一不同的是在GUI的部分加入了活動條可動態(tài)改變閾值.輸入圖像為:
通過執(zhí)行統(tǒng)計概率霍夫線變換我們能得到下面的結(jié)果:
當(dāng)你使用滑動條來改變?閾值?的時候會觀察到檢測到線的數(shù)目的改變. 這是因為: 如果你設(shè)置了一個更大的閾值, 能檢測到的線的數(shù)目將更少 (你需要更多的點來表示一條能檢測到的直線).
霍夫圓變換
目標(biāo)
在這個教程中你將學(xué)習(xí)如何:
- 使用OpenCV函數(shù)?HoughCircles?在圖像中檢測圓.
原理
霍夫圓變換
-
霍夫圓變換的基本原理和上個教程中提到的霍夫線變換類似, 只是點對應(yīng)的二維極徑極角空間被三維的圓心點x, y還有半徑r空間取代.
-
對直線來說, 一條直線能由參數(shù)極徑極角??表示. 而對圓來說, 我們需要三個參數(shù)來表示一個圓, 如上文所說現(xiàn)在原圖像的邊緣圖像的任意點對應(yīng)的經(jīng)過這個點的所有可能圓是在三維空間有下面這三個參數(shù)來表示了,其對應(yīng)一條三維空間的曲線. 那么與二維的霍夫線變換同樣的道理, 對于多個邊緣點越多這些點對應(yīng)的三維空間曲線交于一點那么他們經(jīng)過的共同圓上的點就越多,類似的我們也就可以用同樣的閾值的方法來判斷一個圓是否被檢測到, 這就是標(biāo)準(zhǔn)霍夫圓變換的原理, 但也正是在三維空間的計算量大大增加的原因, 標(biāo)準(zhǔn)霍夫圓變化很難被應(yīng)用到實際中:
這里的??表示圓心的位置 (下圖中的綠點) 而??表示半徑, 這樣我們就能唯一的定義一個圓了, 見下圖:
-
出于上面提到的對運算效率的考慮, OpenCV實現(xiàn)的是一個比標(biāo)準(zhǔn)霍夫圓變換更為靈活的檢測方法:?霍夫梯度法, 也叫2-1霍夫變換(21HT), 它的原理依據(jù)是圓心一定是在圓上的每個點的模向量上, 這些圓上點模向量的交點就是圓心, 霍夫梯度法的第一步就是找到這些圓心, 這樣三維的累加平面就又轉(zhuǎn)化為二維累加平面. 第二部根據(jù)所有候選中心的邊緣非0像素對其的支持程度來確定半徑. 21HT方法最早在Illingworth的論文The Adaptive Hough Transform中提出并詳細(xì)描述, 也可參照Yuen在1990年發(fā)表的A Comparative Study of Hough Transform Methods for Circle Finding, Bradski的《學(xué)習(xí)OpenCV》一書則對OpenCV中具體對算法的具體實現(xiàn)有詳細(xì)描述并討論了霍夫梯度法的局限性.
例程
- 加載一幅圖像并對其模糊化以降噪
- 對模糊化后的圖像執(zhí)行霍夫圓變換 .
- 在窗體中顯示檢測到的圓.
說明
加載一幅圖像
src = imread( argv[1], 1 );if( !src.data ){ return -1; }轉(zhuǎn)成灰度圖:
cvtColor( src, src_gray, CV_BGR2GRAY );執(zhí)行高斯模糊以降低噪聲:
GaussianBlur( src_gray, src_gray, Size(9, 9), 2, 2 );執(zhí)行霍夫圓變換:
vector<Vec3f> circles;HoughCircles( src_gray, circles, CV_HOUGH_GRADIENT, 1, src_gray.rows/8, 200, 100, 0, 0 );函數(shù)帶有以下自變量:
- src_gray: 輸入圖像 (灰度圖)
- circles: 存儲下面三個參數(shù):??集合的容器來表示每個檢測到的圓.
- CV_HOUGH_GRADIENT: 指定檢測方法. 現(xiàn)在OpenCV中只有霍夫梯度法
- dp = 1: 累加器圖像的反比分辨率
- min_dist = src_gray.rows/8: 檢測到圓心之間的最小距離
- param_1 = 200: Canny邊緣函數(shù)的高閾值
- param_2 = 100: 圓心檢測閾值.
- min_radius = 0: 能檢測到的最小圓半徑, 默認(rèn)為0.
- max_radius = 0: 能檢測到的最大圓半徑, 默認(rèn)為0
繪出檢測到的圓:
for( size_t i = 0; i < circles.size(); i++ ) {Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));int radius = cvRound(circles[i][2]);// circle centercircle( src, center, 3, Scalar(0,255,0), -1, 8, 0 );// circle outlinecircle( src, center, radius, Scalar(0,0,255), 3, 8, 0 );}你將會看到圓用紅色繪出而圓心用小綠點表示
顯示檢測到的圓:
namedWindow( "Hough Circle Transform Demo", CV_WINDOW_AUTOSIZE ); imshow( "Hough Circle Transform Demo", src );等待用戶按鍵結(jié)束程序
waitKey(0);結(jié)果
上面例程輸入一張圖例得出的運行結(jié)果如下:
Remapping 重映射
目標(biāo)
本教程向你展示如何使用OpenCV函數(shù)?remap?來實現(xiàn)簡單重映射.
理論
重映射是什么意思?
-
把一個圖像中一個位置的像素放置到另一個圖片指定位置的過程.
-
為了完成映射過程, 有必要獲得一些插值為非整數(shù)像素坐標(biāo),因為源圖像與目標(biāo)圖像的像素坐標(biāo)不是一一對應(yīng)的.
-
我們通過重映射來表達(dá)每個像素的位置??:
這里??是目標(biāo)圖像,??是源圖像,??是作用于??的映射方法函數(shù).
-
讓我們來思考一個快速的例子. 想象一下我們有一個圖像??, 我們想滿足下面的條件作重映射:
會發(fā)生什么? 圖像會按照??軸方向發(fā)生翻轉(zhuǎn). 例如, 源圖像如下:
看到紅色圈關(guān)于 x 的位置改變(??軸水平翻轉(zhuǎn)):
-
通過 OpenCV 的函數(shù)?remap?提供一個簡單的重映射實現(xiàn).
代碼
- 裝載一幅圖像.
- 程序按秒循環(huán), 在一個窗口中順序出現(xiàn)4種重映射過程對相同的圖像.
- 等待用戶按 ‘ESC’ 鍵退出程序。
說明
首先準(zhǔn)備程序用到的變量:
Mat src, dst; Mat map_x, map_y; char* remap_window = "Remap demo"; int ind = 0;加載一幅圖像:
src = imread( argv[1], 1 );創(chuàng)建目標(biāo)圖像和兩個映射矩陣.( x 和 y )
dst.create( src.size(), src.type() ); map_x.create( src.size(), CV_32FC1 ); map_y.create( src.size(), CV_32FC1 );創(chuàng)建一個窗口用于展示結(jié)果.
namedWindow( remap_window, CV_WINDOW_AUTOSIZE );建立一個間隔1000毫秒的循環(huán),每次循環(huán)執(zhí)行更新映射矩陣參數(shù)并對源圖像進(jìn)行重映射處理(使用?mat_x?和?mat_y),然后把更新后的目標(biāo)圖像顯示出來:
while( true ) {/// Each 1 sec. Press ESC to exit the programint c = waitKey( 1000 );if( (char)c == 27 ){ break; }/// Update map_x & map_y. Then apply remapupdate_map();remap( src, dst, map_x, map_y, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0, 0) );/// Display resultsimshow( remap_window, dst ); }上面用到的重映射函數(shù)?remap. 參數(shù)說明:
- src: 源圖像
- dst: 目標(biāo)圖像,與?src?相同大小
- map_x: x方向的映射參數(shù). 它相當(dāng)于方法??的第一個參數(shù)
- map_y: y方向的映射參數(shù). 注意?map_y?和?map_x?與?src?的大小一致。
- CV_INTER_LINEAR: 非整數(shù)像素坐標(biāo)插值標(biāo)志. 這里給出的是默認(rèn)值(雙線性插值).
- BORDER_CONSTANT: 默認(rèn)
如何更新重映射矩陣?mat_x?和?mat_y? 請繼續(xù)看:
更新重映射矩陣:?我們將分別使用4種不同的映射:
圖像寬高縮小一半,并顯示在中間:
所有成對的參數(shù)??處理后都符合:??和?
圖像上下顛倒:?
圖像左右顛倒:?
同時執(zhí)行b和c的操作:?
下面的代碼片段說明上述的映射過程. 在這里?map_x?代表第一個坐標(biāo)?h(i,j)?,?map_y?是第二個坐標(biāo).
for( int j = 0; j < src.rows; j++ ) { for( int i = 0; i < src.cols; i++ ){switch( ind ){case 0:if( i > src.cols*0.25 && i < src.cols*0.75 && j > src.rows*0.25 && j < src.rows*0.75 ){map_x.at<float>(j,i) = 2*( i - src.cols*0.25 ) + 0.5 ;map_y.at<float>(j,i) = 2*( j - src.rows*0.25 ) + 0.5 ;}else{ map_x.at<float>(j,i) = 0 ;map_y.at<float>(j,i) = 0 ;}break;case 1:map_x.at<float>(j,i) = i ;map_y.at<float>(j,i) = src.rows - j ;break;case 2:map_x.at<float>(j,i) = src.cols - i ;map_y.at<float>(j,i) = j ;break;case 3:map_x.at<float>(j,i) = src.cols - i ;map_y.at<float>(j,i) = src.rows - j ;break;} // end of switch}}ind++; }結(jié)果
上面的代碼編譯后, 運行時給一個圖片路徑參數(shù). 例如,使用下面的圖片:
圖像寬高縮小一半,并顯示在中間:
圖像上下顛倒:
圖像左右顛倒:
兩個方向同時顛倒:
仿射變換
目標(biāo)
在這個教程中你將學(xué)習(xí)到如何:
原理
什么是仿射變換?
一個任意的仿射變換都能表示為?乘以一個矩陣?(線性變換) 接著再?加上一個向量?(平移).
綜上所述, 我們能夠用仿射變換來表示:
你現(xiàn)在可以知道, 事實上, 仿射變換代表的是兩幅圖之間的?關(guān)系?.
我們通常使用??矩陣來表示仿射變換.
考慮到我們要使用矩陣??和??對二維向量??做變換, 所以也能表示為下列形式:
?or?
怎樣才能求得一個仿射變換?
好問題. 我們在上文有提到過仿射變換基本表示的就是兩幅圖片之間的?聯(lián)系?. 關(guān)于這種聯(lián)系的信息大致可從以下兩種場景獲得:
讓我們形象地說明一下. 因為矩陣??聯(lián)系著兩幅圖片, 我們以其表示兩圖中各三點直接的聯(lián)系為例. 見下圖:
點1, 2 和 3 (在圖一中形成一個三角形) 與圖二中三個點一一映射, 仍然形成三角形, 但形狀已經(jīng)大大改變. 如果我們能通過這樣兩組三點求出仿射變換 (你能選擇自己喜歡的點), 接下來我們就能把仿射變換應(yīng)用到圖像中所有的點.
例程
- 加載一幅圖片
- 對這幅圖片應(yīng)用仿射變換. 這個變換是從源圖像和目標(biāo)圖像的兩組三點之間的聯(lián)系獲得的. 這一步我們使用函數(shù)warpAffine?來實現(xiàn).
- 仿射變換結(jié)束后再對圖像應(yīng)用旋轉(zhuǎn). 這里的旋轉(zhuǎn)繞圖像中點
- 等待用戶退出程序
說明
定義一些需要用到的變量, 比如需要用來儲存中間和目標(biāo)圖像的Mat和兩個需要用來定義仿射變換的二維點數(shù)組.
Point2f srcTri[3]; Point2f dstTri[3];Mat rot_mat( 2, 3, CV_32FC1 ); Mat warp_mat( 2, 3, CV_32FC1 ); Mat src, warp_dst, warp_rotate_dst;加載源圖像:
src = imread( argv[1], 1 );以與源圖像同樣的類型和大小來對目標(biāo)圖像初始化:
warp_dst = Mat::zeros( src.rows, src.cols, src.type() );仿射變換:?正如上文所說, 我們需要源圖像和目標(biāo)圖像上分別一一映射的三個點來定義仿射變換:
srcTri[0] = Point2f( 0,0 ); srcTri[1] = Point2f( src.cols - 1, 0 ); srcTri[2] = Point2f( 0, src.rows - 1 );dstTri[0] = Point2f( src.cols*0.0, src.rows*0.33 ); dstTri[1] = Point2f( src.cols*0.85, src.rows*0.25 ); dstTri[2] = Point2f( src.cols*0.15, src.rows*0.7 );你可能想把這些點繪出來以獲得對變換的更直觀感受. 他們的位置大概就是在上面圖例中的點的位置 (原理部分). 你會注意到由三點定義的三角形的大小和方向改變了.
通過這兩組點, 我們能夠使用OpenCV函數(shù)?getAffineTransform?來求出仿射變換:
warp_mat = getAffineTransform( srcTri, dstTri );我們獲得了用以描述仿射變換的??矩陣 (在這里是?warp_mat)
將剛剛求得的仿射變換應(yīng)用到源圖像
warpAffine( src, warp_dst, warp_mat, warp_dst.size() );函數(shù)有以下參數(shù):
- src: 輸入源圖像
- warp_dst: 輸出圖像
- warp_mat: 仿射變換矩陣
- warp_dst.size(): 輸出圖像的尺寸
這樣我們就獲得了變換后的圖像! 我們將會把它顯示出來. 在此之前, 我們還想要旋轉(zhuǎn)它...
旋轉(zhuǎn):?想要旋轉(zhuǎn)一幅圖像, 你需要兩個參數(shù):
我們通過下面的代碼來定義這些參數(shù):
Point center = Point( warp_dst.cols/2, warp_dst.rows/2 ); double angle = -50.0; double scale = 0.6;我們利用OpenCV函數(shù)?getRotationMatrix2D?來獲得旋轉(zhuǎn)矩陣, 這個函數(shù)返回一個??矩陣 (這里是?rot_mat)
rot_mat = getRotationMatrix2D( center, angle, scale );現(xiàn)在把旋轉(zhuǎn)應(yīng)用到仿射變換的輸出.
warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );最后我們把仿射變換和旋轉(zhuǎn)的結(jié)果繪制在窗體中,源圖像也繪制出來以作參照:
namedWindow( source_window, CV_WINDOW_AUTOSIZE ); imshow( source_window, src );namedWindow( warp_window, CV_WINDOW_AUTOSIZE ); imshow( warp_window, warp_dst );namedWindow( warp_rotate_window, CV_WINDOW_AUTOSIZE ); imshow( warp_rotate_window, warp_rotate_dst );等待用戶退出程序
waitKey(0);結(jié)果
編譯上述例程之后, 我們給出一個圖像的路徑作為參數(shù). 比如這樣一幅圖片:
仿射變換后我們獲得:
最后, 應(yīng)用了一個負(fù)角度旋轉(zhuǎn) (記住負(fù)角度指的是順時針) 和基于一個縮放因子的縮放之后, 我們得到:
from: http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/table_of_content_imgproc/table_of_content_imgproc.html#table-of-content-imgproc
總結(jié)
以上是生活随笔為你收集整理的OpenCV之imgproc 模块. 图像处理(3)霍夫线变换 霍夫圆变换 Remapping 重映射 仿射变换的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenCV之imgproc 模块. 图
- 下一篇: OpenCV之imgproc 模块. 图