C++版本OpenCv教程(四十二)霍夫变换原理及直线检测
霍夫變換(Hough Transform)是圖像處理中檢測是否存在直線的重要算法,該算法是由Paul Hough在1962年首次提出,最開始只能檢測圖像中的直線,但是霍夫變換經過不斷的擴展和完善已經可以檢測多種規則形狀,例如圓形、橢圓等。霍夫變換通過將圖像中的像素在一個空間坐標系中變換到另一個坐標空間坐標系中,使得在原空間中具有形同特性的曲線或者直線映射到另一個空間中形成峰值,從而把檢測任意形狀的問題轉化為統計峰值的問題。
霍夫變換通過構建檢測形狀的數學解析式將圖像中像素點映射到參數空間中,例如我們想檢測兩個像素點所在的直線,需要構建直線的數學解析式。在圖像空間x-y直角坐標系中,對于直線可以用式(7.1)所示的解析式來表示。
其中k是直線的斜率,b是直線的截距。假設圖像中存在一像素點A(x0,y0),所有經過這個像素點直線可以用式(7.2)表示。
在圖像空間x-y直角坐標系中,由于變量是x和y,因此式(7.2)表示的是經過點像素點A(x0,y0)的直線,但是經過一點的直線有無數條,因此式(7.2)中的 和 具有無數個可以選擇的值,如果將x0和y0看作是變量, k和 b表示定值,那么式(7.2)可以表示在k-b空間的一條直線,映射過程示意圖如圖7-1所示。用式(7.1)的形式表示映射的結果如式(7.3)所示,即霍夫變換將x-y直角坐標系中經過一點的所有直線映射成了k-b空間中的一條直線,直線上的每個點都對應著x-y直角坐標系中的一條直線。
當圖像中存在另一個像素點B(x1,y1)時,在圖像空間x-y直角坐標系中所有經過像素點B(x1,y1)的直線也會在參數空間中映射出一條直線。由于參數空間中每一個點都表示圖像空間x-y直角坐標系中直線的斜率和截距,因此如果有一條直線經過像素點A(x0,y0)和像素點B(x1,y1)時,這條直線所映射在參數空間中的坐標點應該既在像素點A(x0,y0)映射的直線上又在像素點B(x1,y1)映射的直線上。在平面內一個點同時在兩條直線上,那么這個點一定是兩條直線的交點,因此這條同時經過A(x0,y0)和B(x1,y1)的直線所對應的斜率和截距就是參數空間中兩條直線的交點。
根據前面的分析可以得到霍夫變換中存在兩個重要的結論:(1)圖像空間中的每條直線在參數空間中都對應著單獨一個點來表示;(2)圖像空間中的直線上任何像素點在參數空間對應的直線相交于同一個點。圖7-2給出了第二條結論的示意圖。因此通過霍夫變換尋找圖像中的直線就是尋找參數空間中大量直線相交的一點。
利用式(7.1)形式進行霍夫變換可以尋找到圖像中絕大多數直線,但是當圖像中存在垂直直線時,即所有的像素點的x坐標相同時,直線上的像素點利用上述霍夫變換方法得到的參數空間中多條直線互相平行,無法相交于一點。例如在圖像上存在3個像素點(2,1)、(2,2)和(2,3) ,利用式(7.3)可以求得參數空間中3條直線解析式如式中所示,這些直線具有相同的斜率,因此無法交于一點,具體形式如圖7-3所示。
為了解決垂直直線在參數空間沒有交點的問題,一般采用極坐標方式表示圖像空間x-y直角坐標系中的直線,具體形式如式(7.5)所示。
其中 r為坐標原點到直線的距離, Θ為坐標原點到直線的垂線與x軸的夾角,這兩個參數的含義如圖7-4所示。
根據霍夫變換原理,利用極坐標形式表示直線時,在圖像空間中經過某一點的所有直線映射到參數空間中是一個正弦曲線。圖像空間中直線上的兩個點在參數空間中映射的兩條正弦曲線相交于一點,圖7-5中給出了用極坐標形式表示直線的霍夫變換的示意圖。
通過上述的變換過程,將圖像中的直線檢測轉換成了在參數空間中尋找某個點 通過的正線曲線最多的問題。由于在參數空間內的曲線是連續的,而在實際情況中圖像的像素是離散的,因此我們需要將參數空間的r軸和Θ軸進行離散化,用離散后的方格表示每一條正弦曲線。首先尋找符合條件的網格,之后尋找該網格對應的圖像空間中所有的點,這些點共同組成了原圖像中的直線。
總結上面所有的原理和步驟,霍夫變換算法檢測圖像中的直線主要分為4個步驟:
霍夫檢測具有抗干擾能力強,對圖像中直線的殘缺部分、噪聲以及其它共存的非直線結構不敏感,能容忍特征邊界描述中的間隙,并且相對不受圖像噪聲影響等優點,但是霍夫變換的時間復雜度和空間復雜度都很高,并且檢測精度受參數離散間隔制約。離散間隔較大時會降低檢測精度,離散間隔較小時雖然能提高精度,但是會增加計算負擔,導致計算時間邊長。
OpenCV 4提供了兩種用于檢測圖像中直線的相關函數,分別是標準霍夫變換和多尺度霍夫變換函數HoughLins()和漸進概率式霍夫變換函數HoughLinesP()。首先將介紹標準霍夫變換函數HoughLins(),該函數的函數原型在代碼清單7-1中給出。
void cv::HoughLines(InputArray image,OutputArray lines,double rho,double theta,int threshold,double srn = 0,double stn = 0,double min_theta = 0,double max_theta = CV_PI )- image:待檢測直線的原圖像,必須是CV_8U的單通道二值圖像。
- lines:霍夫變換檢測到的直線輸出量,每一條直線都由兩個參數表示,分別表示直線距離坐標原點的距離 和坐標原點到直線的垂線與x軸的夾角 。
- rho:以像素為單位的距離分辨率,即距離 離散化時的單位長度。
- theta:以弧度為單位的角度分辨率,即夾角 離散化時的單位角度。
- threshold:累加器的閾值,即參數空間中離散化后每個方格被通過的累計次數大于該閾值時將被識別為直線,否則不被識別為直線。
- srn:對于多尺度霍夫變換算法中,該參數表示距離分辨率的除數,粗略的累加器距離分辨率是第三個參數rho,精確的累加器分辨率是rho/srn。這個參數必須是非負數,默認參數為0。
- stn:對于多尺度霍夫變換算法中,該參數表示角度分辨率的除數,粗略的累加器距離分辨率是第四個參數rho,精確的累加器分辨率是rho/stn。這個參數必須是非負數,默認參數為0。當這個參數與第六個參數srn同時為0時,此函數表示的是標準霍夫變換。
- min_theta:檢測直線的最小角度,默認參數為0。
- max_theta:檢測直線的最大角度,默認參數為CV_PI,是OpenCV
4中的默認數值具體為3.1415926535897932384626433832795。
該函數用于尋找圖像中的直線,并以極坐標的形式將圖像中直線的極坐標參數輸出。該函數的第一個參數為輸入圖像,必須是CV_8U的單通道二值圖像,如果需要檢測彩色圖像或者灰度圖像中是否存在直線,可以通過Canny()函數計算圖像的邊緣,并將邊緣檢測結果二值化后的圖像作為輸入圖像賦值給該參數。函數的第二個參數是霍夫變換檢測到的圖像中直線極坐標描述的系數,是一個N×2的vector矩陣,每一行中的第一個元素是直線距離坐標原點的距離,第二個元素是該直線過坐標原點的垂線與x軸的夾角,這里需要注意的是圖像中的坐標原點在圖像的左上角。函數第三個和第四個參數是霍夫變換中對參數空間坐標軸進行離散化后單位長度,這兩個參數的大小直接影響到檢測圖像中直線的精度,數值越小精度越高。第三個參數表示參數空間 軸的單位長度,單位為像素,該參數常設置為1;第四個參數表示參數空間 軸的單位長度,單位為弧度,該函數常設置為CV_PI/180。函數第五個參數是累加器的閾值,表示參數空間中某個方格是否被認定為直線的判定標準,這個數值越大,對應在原圖像中構成直線的像素點越多,反之則越少。第六個和第七個參數起到選擇標準霍夫變換和多尺度霍夫變換的作用,當兩個參數全為0時,該函數使用標準霍夫變換算法,否則該函數使用多尺度霍夫變換算法,當函數使用多尺度霍夫變換算法時,這兩個函數分別表示第三個參數單位距離長度的除數和第四個參數角度單位角度的除數。函數最后兩個參數是檢測直線的最小角度和最大角度,兩個參數必須大于等于0小于等于CV_PI(3.1415926535897932384626433832795),并且最小角度的數值要小于最大角度的數值。
該函數只能輸出直線的極坐標表示形式的參數,如果想在圖像中繪制該直線需要進一步得到直線兩端的坐標,通過line()函數在原圖像中繪制直線,由于該函數只能判斷圖像中是否有直線,而不能判斷直線的起始位置,因此使用line()函數繪制直線時常繪制盡可能長的直線。在代碼清單7-2中給出了利用HoughLines()函數檢測圖像中直線的示例程序,程序中根據直線的參數計算出直線與經過坐標原點的垂線的交點的坐標,之后利用直線的線性關系計算出直線兩端盡可能遠的端點坐標,最后利用line()函數在原圖像中繪制直線。程序首先利用Canny()函數對灰度圖像進行邊緣提取,然后對邊緣經過進行二值化處理,之后檢測圖像中的直線,為了驗證第五個參數累加器閾值對檢測直線長短的影響,分別設置較小和較大的兩個累加器,程序運行結果在圖7-6、圖7-7給出,通過結果可以看出累加器較小時較短的直線也可以被檢測出來,累加器較大時只能檢測出圖像中較長的直線。
#include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui.hpp> using namespace cv; using namespace std;void drawLIne(Mat &img,//要標記的圖像vector<Vec2f>lines,//檢測的直線數據double rows,//原圖像的行數double cols,//原圖像的列數Scalar scalar,//繪制直線的顏色int n//繪制直線的線寬) {Point pt1,pt2;for(size_t i=0;i<lines.size();++i){float rho=lines[i][0];//直線距離坐標原點的距離float theta=lines[i][1];//直線過坐標原點垂線與x軸夾角double a=cos(theta);//夾角的余弦值double b=sin(theta);//夾角的正弦值double x0=a*rho,y0=b*rho;//直線與坐標原點垂線的交點double length=max(rows,cols);//圖像高寬的最大值//計算直線上的一點pt1.x=cvRound(x0+length*(-b));pt1.y=cvRound(y0+length*(a));//計算直線上另一點pt2.x=cvRound(x0-length*(-b));pt2.y=cvRound(y0-length*(a));//兩點繪制一條直線line(img,pt1,pt2,scalar,n);} }int main(){Mat img=imread("HoughLines.jpg");if(img.empty()){cout<<"請確認輸入的圖片路徑是否正確"<<endl;return -1;}Mat edge;//檢測邊緣圖像,病二值化Canny(img,edge,80,100,3, false);threshold(edge,edge,170,255,THRESH_BINARY);//用不同的累加器進行檢測直線vector<Vec2f>lines1,lines2;HoughLines(edge,lines1,1,CV_PI/100,50,0,0);HoughLines(edge,lines2,1,CV_PI/100,150,0,0);//在原圖像中繪制直線Mat img1,img2;img.copyTo(img1);img.copyTo(img2);drawLIne(img1,lines1,edge.rows,edge.cols,Scalar(255),2);drawLIne(img2,lines2,edge.rows,edge.cols,Scalar(255),2);//顯示圖像imshow("edge",edge);imshow("img",img);imshow("img1",img1);imshow("img2",img2);waitKey(0);return 0; }
使用標準霍夫變換和多尺度霍夫變換函數HoughLins()提取直線時無法準確知道圖像中直線或者線段的長度,只能得到圖像中是否存在符合要求的直線以及直線的極坐標解析式。如果需要準確的定位圖像中線段的位置,HoughLins()函數便無法滿足需求,但是OpenCV 4提供的漸進概率式霍夫變換函數HoughLinesP()可以得到圖像中滿足條件的直線或者線段兩個端點的坐標,進而確定直線或者線段的位置,該函數的函數原型在代碼清單7-3中給出。
- image:待檢測直線的原圖像,必須是CV_8C的單通道二值圖像。
- lines:霍夫變換檢測到的直線輸出量,每一條直線都由4個參數進行描述,分別是直線兩個端點的坐標
- rho:以像素為單位的距離分辨率,即距離 離散化時的單位長度。
- theta:以弧度為單位的角度分辨率,即夾角 離散化時的單位角度。
- threshold:累加器的閾值,即參數空間中離散化后每個方格被通過的累計次數大于閾值時則被識別為直線,否則不被識別為直線。
- minLineLength:直線的最小長度,當檢測直線的長度小于該數值時將會被剔除。
- maxLineGap:允許將同一行兩個點連接起來的最大距離。
該函數用于尋找圖像中滿足條件的直線或者線段兩個端點的坐標。該函數的第一個參數為輸入圖像,必須是CV_8U的單通道二值圖像,如果需要檢測彩色圖像或者灰度圖像中是否存在直線,可以通過Canny()函數計算圖像的邊緣,并將邊緣檢測結果二值化后的圖像作為輸入圖像賦值給該參數。函數的第二個參數是圖像中直線或者線段兩個端點的坐標,是一個N×4的vector矩陣。Vec4i中前兩個元素分別是直線或者線段一個端點的x坐標和y坐標,后兩個元素分別是直線或者線段另一個端點的x坐標和y坐標。函數第三個和第四個參數含義與HoughLines()函數的參數含義相同,都是霍夫變換中對參數空間坐標軸進行離散化后的單位長度,這兩個參數的大小直接影響到檢測圖像中直線的精度,數值越小精度越高。第三個參數表示參數空間 軸的單位長度,單位為像素,該參數常設置為1;第四個參數表示參數空間 軸的單位角度,單位為弧度,該函數常設置為CV_PI/180。函數第五個參數是累加器的閾值,表示參數空間中某個方格是否被認定為直線的判定標準,這個數值越大,對應在原圖像中的直線越長,反之則越短。第六個參數是檢測直線或者線段的長度,如果圖像中直線的長度小于這個閾值,即使是直線也不會作為最終結果輸出。函數最后一個參數是鄰近兩個點連接的最大距離,這個參數主要能夠控制傾斜直線的檢測長度,當提取較長的傾斜直線時該參數應該具有較大取值。
該函數的最大特點是能夠直接給出圖像中直線或者線段兩個端點的像素坐標,因此可較精確的定位到圖像中直線的位置。為了了解該函數的使用方式,在代碼清單7-4中給出了利用HoughLinesP()函數提取圖像直線的示例程序,程序中使用的原圖像與代碼清單7-2中相同,程序的輸出結果在圖7-8給出,程序結果說明HoughLinesP()函數確實可以實現圖像中直線或者線段的定位任務,并且結果也說明函數最后一個參數較大時傾斜直線檢測的完整度較高。
#include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui.hpp> using namespace cv; using namespace std;int main(){Mat img=imread("HoughLines.jpg");if(img.empty()){cout<<"請確認輸入的圖片路徑是否正確"<<endl;return -1;}Mat edge;//檢測邊緣圖像,病二值化Canny(img,edge,80,100,3, false);threshold(edge,edge,170,255,THRESH_BINARY);//利用漸進概率式霍夫變換提取直線vector<Vec2f>linesP1,linesP2;HoughLinesP(edge,linesP1,1,CV_PI/100,150,30,10);HoughLinesP(edge,linesP2,1,CV_PI/100,150,30,30);//在原圖像中繪制直線Mat img1,img2;img.copyTo(img1);for(size_t i=0;i<linesP1.size();++i){line(img1,Point(linesP1[i][0],linesP1[i][1]),Point(linesP1[i][2],linesP1[i][3]),Scalar(255),3);}img.copyTo(img2);for(size_t i=0;i<linesP1.size();++i){line(img2,Point(linesP2[i][0],linesP2[i][1]),Point(linesP2[i][2],linesP2[i][3]),Scalar(255),3);}//顯示圖像imshow("img1",img1);imshow("img2",img2);waitKey(0);return 0; }
前面兩個函數都是檢測圖像中是否存在直線,但是在實際工程或者任務需求中我們可能得到的是圖像中一些點的坐標而不是一副完整的圖像,因此OpenCV 4中提供了能夠在含有坐標的眾多點中尋找是否存在直線的**HoughLinesPointSet()**函數,該函數的函數原型在代碼清單7-5中給出。
- _point:輸入點的集合,必須是平面內的2D坐標,數據類型必須是CV_32FC2或CV_32SC2。
- _lines:在輸入點集合中可能存在的直線,每一條直線都具有三個參數,分別是權重、直線距離坐標原點的距離 和坐標原點到直線的垂線與x軸的夾角 。
- lines_max:檢測直線的最大數目。
- threshold:累加器的閾值,即參數空間中離散化后每個方格被通過的累計次數大于閾值時則被識別為直線,否則不被識別為直線。
- min_rho:檢測直線長度的最小距離,以像素為單位。
- max_rho:檢測直線長度的最大距離,以像素為單位。
- rho_step::以像素為單位的距離分辨率,即距離 離散化時的單位長度。
- min_theta:檢測直線的最小角度值,以弧度為單位。
- max_theta:檢測直線的最大角度值,以弧度為單位。
- theta_step:以弧度為單位的角度分辨率,即夾角 離散化時的單位角度。
該函數用于在含有坐標的2D點的集合中尋找直線,函數檢測直線使用的方法是標準霍夫變換法。函數第一個參數是2D點集合中每個點的坐標,由于坐標必須是CV_32F或者CV_32S類型,因此可以將點集定義成vector< Point2f>或者vector< Point2f>類型。函數的第二個參數是檢測到的輸入點集合中可能存在的直線,是一個1×N的矩陣,數據類型為CV_64FC3,其中第1個數據表示該直線的權重,權重越大表示是直線的可靠性越高,第2個數據和第3個數據分別表示直線距離坐標原點的距離 和坐標原點到直線的垂線與x軸的夾角 ,矩陣中數據的順序是按照權重由大到小依次存放。函數第三個參數是檢測直線的數目,如果數目過大,檢測到的直線可能存在權重較小的情況。函數第四個參數是累加器的閾值,表示參數空間中某個方格是否被認定為直線的判定標準,這個數值越大,表示檢測的直線需要通過的點的數目越多。函數第五個、第六個參數是檢測直線長度的取值范圍,單位為像素。函數第七個參數是霍夫變換算法中離散化時距離分辨率的大小,單位為像素。函數第八個、第九個參數是檢測直線經過坐標原點的垂線與x軸夾角的范圍,單位為弧度。函數第七個參數是霍夫變換算法中離散化時角度分辨率的大小,單位為弧度。
為了了解該函數的使用方法,在代碼清單7-6中給出了利用該函數檢測2D點集合中直線的示例程序。程序中首先生成2D點集,之后利用HoughLinesPointSet()函數檢測其中可能存在的直線,并將檢測的直線權重和距離坐標原點的距離 和坐標原點到直線的垂線與x軸的夾角 輸出,程序的輸出結果在圖7-9給出。
#include <opencv2/opencv.hpp> #include <iostream> #include <vector>using namespace cv; using namespace std;int main() {system("color F0"); //更改輸出界面顏色Mat lines; //存放檢測直線結果的矩陣vector<Vec3d> line3d; //換一種結果存放形式vector<Point2f> point; //待檢測是否存在直線的所有點const static float Points[20][2] = {{ 0.0f, 369.0f },{ 10.0f, 364.0f },{ 20.0f, 358.0f },{ 30.0f, 352.0f },{ 40.0f, 346.0f },{ 50.0f, 341.0f },{ 60.0f, 335.0f },{ 70.0f, 329.0f },{ 80.0f, 323.0f },{ 90.0f, 318.0f },{ 100.0f, 312.0f },{ 110.0f, 306.0f },{ 120.0f, 300.0f },{ 130.0f, 295.0f },{ 140.0f, 289.0f },{ 150.0f, 284.0f },{ 160.0f, 277.0f },{ 170.0f, 271.0f },{ 180.0f, 266.0f },{ 190.0f, 260.0f }};//將所有點存放在vector中,用于輸入函數中for (int i = 0; i < 20; i++){point.push_back(Point2f(Points[i][0], Points[i][1]));}//參數設置double rhoMin = 0.0f; //最小長度double rhoMax = 360.0f; //最大長度double rhoStep = 1; //離散化單位距離長度double thetaMin = 0.0f; //最小角度double thetaMax = CV_PI / 2.0f; //最大角度double thetaStep = CV_PI / 180.0f; 離散化單位角度弧度HoughLinesPointSet(point, lines, 20, 1, rhoMin, rhoMax, rhoStep,thetaMin, thetaMax, thetaStep);lines.copyTo(line3d);//輸出結果for (int i = 0; i < line3d.size(); i++){cout << "votes:" << (int)line3d.at(i).val[0] << ", "<< "rho:" << line3d.at(i).val[1] << ", "<< "theta:" << line3d.at(i).val[2] << endl;}return 0; }總結
以上是生活随笔為你收集整理的C++版本OpenCv教程(四十二)霍夫变换原理及直线检测的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 可能是史上最适合入门SQL语句的教程——
- 下一篇: 大数据在人际关系管理领域,主要有哪些价值