y=kx+b
對於直線上一個確定的點(x_0,y_0),有:y_0=kx_0+b
這表示參數平面(k-b)中的一條直線。因此,圖像中的一個點對應參數平面中的一條直線,圖像中的一條直線對應參數平面中的一個點。對圖像上所有的點作霍夫變換,最終所要檢測的直線對應的一定是參數平面中直線相交最多的那個點。這樣就在圖像中檢測出了直線。在實際應用中,直線通常採用參數方程
Opencv裏有以下函數檢測直線(最基本的霍夫變換):
void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )
具體用法看代碼就知道了:
#include "opencv2/opencv.hpp"
#define PI 3.1415926
int main(int argc, char *argv[])
{
cv::Mat image = cv::imread ("road.jpg");
cv::Mat result;
cv::cvtColor (image,result,CV_BGRA2GRAY);
cv::Mat contours;
//邊緣檢測
cv::Canny (result,contours,125,350);
std::vector<cv::Vec2f> lines;
//霍夫變換,獲得一組極座標參數(rho,theta),每一對對應一條直線,保存到lines
//第3,4個參數表示在(rho,theta)座標系裏橫縱座標的最小單位,即步長
cv::HoughLines (contours,lines,1,PI/180,80);
std::vector<cv::Vec2f>::const_iterator it = lines.begin ();
std::cout<<lines.size ()<<std::endl;
while(it != lines.end()){
float rho = (*it)[0];
float theta = (*it)[1];
if(theta<PI/4.||theta>3.*PI/4){
//畫交點在上下兩邊的直線
cv::Point pt1(rho/cos(theta),0);
cv::Point pt2((rho-result.rows*sin(theta))/cos(theta),result.rows);
cv::line(image,pt1,pt2,cv::Scalar(255),1);
}
else {
//畫交點在左右兩邊的直線
cv::Point pt1(0,rho/sin(theta));
cv::Point pt2(result.cols,(rho-result.cols*cos(theta)/sin(theta)));
cv::line(image,pt1,pt2,cv::Scalar(255),1);
}
++it;
}
cv::namedWindow ("hough");
cv::imshow("hough",image);
cv::waitKey (0);
}
檢測結果示例:
另外,可以看出,上面的直線檢測存在以下問題:
1)只能檢測出線段所在的直線,而不知道具體線段位置,也不知道線段長度;
2)同一直線可能檢測出多條直線;
3)偶然地也可能誤判段直線。
針對這些問題,opencv有那麼一個函數:
void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, dou-
ble minLineLength=0, double maxLineGap=0 )
這個方法是通過概率霍夫變換實現的:
1)隨機獲取邊緣圖片上的前景點,映射到級座標系畫曲線;
2)當極座標系裏面有交點達到最小投票數,將該點對應x-y座標系的直線L找出來;
3)搜索邊緣圖片上前景點,在直線L上的點(且點與點之間距離小於maxLineGap的)連成線段,然後這些點全部刪除,並且記錄該線段的參數,就是起始點和終止點啦~~~~~~~~~~~~~~~~~~~(當然這裏線段長度要滿足最小長度的,否則就不用記錄了)
4)重複1),2),3)
其使用方法見代碼:
#include "opencv2/opencv.hpp"
#define PI 3.1415926
class LineFinder{
private:
// 直線對應的點參數向量
std::vector<cv::Vec4i> lines;
//步長
double deltaRho;
double deltaTheta;
// 判斷是直線的最小投票數
int minVote;
// 判斷是直線的最小長度
double minLength;
// 同一條直線上點之間的距離容忍度
double maxGap;
public:
//初始化
LineFinder() : deltaRho(1), deltaTheta(PI/180),
minVote(10), minLength(0.), maxGap(0.) {}
// 設置步長
void setAccResolution(double dRho, double dTheta) {
deltaRho= dRho;
deltaTheta= dTheta;
}
// 設置最小投票數
void setMinVote(int minv) {
minVote= minv;
}
// 設置最小線段長度和線段間距容忍度
void setLineLengthAndGap(double length, double gap) {
minLength= length;
maxGap= gap;
}
//尋找線段
std::vector<cv::Vec4i> findLines(cv::Mat& binary) {
lines.clear();
cv::HoughLinesP(binary,lines, deltaRho, deltaTheta, minVote,minLength, maxGap);
// return lines;
}
// 畫線段
void drawDetectedLines(cv::Mat &image, cv::Scalar color=cv::Scalar(255,255,255)) {
std::vector<cv::Vec4i>::const_iterator it2=lines.begin();
while (it2!=lines.end()) {
cv::Point pt1((*it2)[0],(*it2)[1]);
cv::Point pt2((*it2)[2],(*it2)[3]);
cv::line( image, pt1, pt2, color);
++it2;
}
}
};
int main(int argc, char *argv[])
{
cv::Mat image = cv::imread ("road.jpg");
cv::Mat result;
cv::cvtColor (image,result,CV_BGRA2GRAY);
cv::Mat contours;
//邊緣檢測
cv::Canny (result,contours,125,350);
LineFinder finder;
finder.setMinVote (80);
finder.setLineLengthAndGap (100,20);
finder.findLines (contours);
finder.drawDetectedLines (image);
/*
std::vector<cv::Vec2f> lines;
//霍夫變換,獲得一組極座標參數(rho,theta),每一對對應一條直線,保存到lines
//第3,4個參數表示在(rho,theta)座標系裏橫縱座標的最小單位,即步長
cv::HoughLines (contours,lines,1,PI/180,80);
std::vector<cv::Vec2f>::const_iterator it = lines.begin ();
std::cout<<lines.size ()<<std::endl;
while(it != lines.end()){
float rho = (*it)[0];
float theta = (*it)[1];
if(theta<PI/4.||theta>3.*PI/4){
//畫交點在上下兩邊的直線
cv::Point pt1(rho/cos(theta),0);
cv::Point pt2((rho-result.rows*sin(theta))/cos(theta),result.rows);
cv::line(image,pt1,pt2,cv::Scalar(255),1);
}
else {
//畫交點在左右兩邊的直線
cv::Point pt1(0,rho/sin(theta));
cv::Point pt2(result.cols,(rho-result.cols*cos(theta)/sin(theta)));
cv::line(image,pt1,pt2,cv::Scalar(255),1);
}
++it;
}
*/
cv::namedWindow ("hough");
cv::imshow("hough",image);
cv::waitKey (0);
}
測試結果如下: