OpenCV2马拉松第22圈——Hough变换直线检测原理与实现
来源:互联网 发布:软件已过期怎么办 编辑:程序博客网 时间:2024/05/17 00:52
计算机视觉讨论群162501053
转载请注明:http://blog.csdn.net/abcd1992719g/article/details/27220445
收入囊中
- Hough变换
- 概率Hough变换
- 自己实现Hough变换直线检测
葵花宝典
先看一下我实现的效果图
下面,我们进入Hough变换的原理讲解。
看上图,我们知道,经过一点(x0,y0)的直线可以表示成y0 = mox + b0
反过来看方程,b = –x0m + y0 ,于是我们从原来的坐标系转移到了Hough空间,m是横坐标,b是纵坐标
刚才提到,经过(x0,y0)的直线具有的特征是b = –x0m + y0,在Hough空间下也是一条直线,
那么经过(x1,y1)的直线具有的特征是b = -x1m + y1,在Hough空间下是另一条直线。
两条直线的相交点的(m,b)就是经过(x0,y0)(x1,y1)的直线,这个应该可以理解吧。
于是就有了一个简单的想法,对于每一个点,在Hough空间中都画出一条直线,对于每条直线经过的点,都填充在如下的 Hough空间中,看哪交点多,就能确定。我们用一个二维数组表示Hough空间,如下。最后就变成数哪些格子的值比较高。
但是,用m和b有局限性。因为m是可以取到无穷大的,所以这个特征只在理论上可行...实际上我们不可能申请一个无限大的二维数组。
自然而然,我们想到了极坐标,在极坐标下,就没有这个限制了。
在极坐标下,我们的直线可以写成:
也就是:
经过点(x0,y0)的直线:
当x0 = 8, y0 = 6,我们有这样的图
我们在下面只考虑 并且 .
我们还有2个点,, , ,就可以绘制出下面的图形
这3条直线相交于, 也就是说 () = 是这3个点 , 共同经过的直线!
因此,我们有了算法雏形
• 初始化H( Hough空间的二维数组)全为0
• 遍历图片的 (x,y)
For θ = 0 to 360
ρ = xcos θ + y sin θ
H(θ, ρ) = H(θ,ρ) + 1
end
end
ρ = xcos θ + y sin θ
H(θ, ρ) = H(θ,ρ) + 1
end
end
• Find the value(s) of (θ, ρ)where H(θ, ρ)is a local maximum
• Thedetected line in the image is given by ρ = xcos θ + y sin θ
看下面的图片,当都一条直线时,Hough空间的某个区域就会很亮,取局部极大值就可以
一张更复杂的图片
初识API
- C++: void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )
- image – 8-bit,单通道二值图(有可能被函数改变)
- lines – 输出vector,是 的vector. 是距离原点距离 (图片左上角[0,0]处). ( ).
- rho – 累加器的半径resolution
- theta – 累加器的theta resulution
- threshold – 返回Hough空间中 ( ).的点
- srn – For the multi-scale Hough transform, it is a divisor for the distance resolution rho . The coarse accumulator distance resolution is rho and the accurate accumulator resolution is rho/srn . If both srn=0 and stn=0 , the classical Hough transform is used. Otherwise, both these parameters should be positive.
- stn – For the multi-scale Hough transform, it is a divisor for the distance resolution theta.
- C++: void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, doublemaxLineGap=0 )
- image –8-bit,单通道二值图(有可能被函数改变)
- lines – 输出向量是4-element vector , 是起点 是终点
- rho – Distance resolution of the accumulator in pixels.
- theta – Angle resolution of the accumulator in radians.
- threshold – Accumulator threshold parameter. Only those lines are returned that get enough votes ( ).
- minLineLength – 最小长度,小于这个值不被认为是线段
- maxLineGap – 两个点之间最大的gap,当小于这个值两个点就被认为是同一线段的点
荷枪实弹
还是先贴出官方sample
#include <cv.h>#include <highgui.h>#include <math.h>using namespace cv;int main(int argc, char** argv){ Mat src, dst, color_dst; if( argc != 2 || !(src=imread(argv[1], 0)).data) return -1; Canny( src, dst, 50, 200, 3 ); cvtColor( dst, color_dst, CV_GRAY2BGR );#if 0 vector<Vec2f> lines; HoughLines( dst, lines, 1, CV_PI/180, 100 ); for( size_t i = 0; i < lines.size(); i++ ) { float rho = lines[i][0]; float theta = lines[i][1]; double a = cos(theta), b = sin(theta); double x0 = a*rho, y0 = b*rho; Point pt1(cvRound(x0 + 1000*(-b)), cvRound(y0 + 1000*(a))); Point pt2(cvRound(x0 - 1000*(-b)), cvRound(y0 - 1000*(a))); line( color_dst, pt1, pt2, Scalar(0,0,255), 3, 8 ); }#else vector<Vec4i> lines; HoughLinesP( dst, lines, 1, CV_PI/180, 80, 30, 10 ); for( size_t i = 0; i < lines.size(); i++ ) { line( color_dst, Point(lines[i][0], lines[i][1]), Point(lines[i][2], lines[i][3]), Scalar(0,0,255), 3, 8 ); }#endif namedWindow( "Source", 1 ); imshow( "Source", src ); namedWindow( "Detected Lines", 1 ); imshow( "Detected Lines", color_dst ); waitKey(0); return 0;}
假如我们想检测直线,就可以用第一个API,因为这个API返回的是直线的两个参数
如果想检测图片中的线段,就用第二个API,因为这个 API返回的是起点和终点
下面看下我自己的实现,首先是弧度及结构体的定义
const double pi = 3.1415926f;const double RADIAN = 180.0/pi; struct line{int theta;int r;};
vector<struct line> houghLine(Mat &img, int threshold){vector<struct line> lines;int diagonal = floor(sqrt(img.rows*img.rows + img.cols*img.cols));vector< vector<int> >p(360 ,vector<int>(diagonal));for( int j = 0; j < img.rows ; j++ ) { for( int i = 0; i < img.cols; i++ ) { if( img.at<unsigned char>(j,i) > 0){for(int theta = 0;theta < 360;theta++){int r = floor(i*cos(theta/RADIAN) + j*sin(theta/RADIAN));if(r < 0)continue;p[theta][r]++;}}}}//get local maximumfor( int theta = 0;theta < 360;theta++){for( int r = 0;r < diagonal;r++){int thetaLeft = max(0,theta-1);int thetaRight = min(359,theta+1);int rLeft = max(0,r-1);int rRight = min(diagonal-1,r+1);int tmp = p[theta][r];if( tmp > threshold && tmp > p[thetaLeft][rLeft] && tmp > p[thetaLeft][r] && tmp > p[thetaLeft][rRight]&& tmp > p[theta][rLeft] && tmp > p[theta][rRight]&& tmp > p[thetaRight][rLeft] && tmp > p[thetaRight][r] && tmp > p[thetaRight][rRight]){struct line newline;newline.theta = theta;newline.r = r;lines.push_back(newline);}}}return lines;}
最后是画直线的函数
void drawLines(Mat &img, const vector<struct line> &lines){for(int i = 0;i < lines.size();i++){vector<Point> points;int theta = lines[i].theta;int r = lines[i].r;double ct = cos(theta/RADIAN);double st = sin(theta/RADIAN);//r = x*ct + y*st//leftint y = int(r/st);if(y >= 0 && y < img.rows){Point p(0, y);points.push_back(p);}//righty = int((r-ct*(img.cols-1))/st);if(y >= 0 && y < img.rows){Point p(img.cols-1, y);points.push_back(p);}//topint x = int(r/ct);if(x >= 0 && x < img.cols){Point p(x, 0);points.push_back(p);}//downx = int((r-st*(img.rows-1))/ct);if(x >= 0 && x < img.cols){Point p(x, img.rows-1);points.push_back(p);}cv::line( img, points[0], points[1], Scalar(0,0,255), 1, CV_AA);}}
完整代码
#include "opencv2/imgproc/imgproc.hpp"#include "opencv2/highgui/highgui.hpp"#include <iostream>#include <vector>#include <cmath>using namespace cv;using namespace std;const double pi = 3.1415926f;const double RADIAN = 180.0/pi; struct line{int theta;int r;};/* * r = xcos(theta) + ysin(theta) */vector<struct line> houghLine(Mat &img, int threshold){vector<struct line> lines;int diagonal = floor(sqrt(img.rows*img.rows + img.cols*img.cols));vector< vector<int> >p(360 ,vector<int>(diagonal));for( int j = 0; j < img.rows ; j++ ) { for( int i = 0; i < img.cols; i++ ) { if( img.at<unsigned char>(j,i) > 0){for(int theta = 0;theta < 360;theta++){int r = floor(i*cos(theta/RADIAN) + j*sin(theta/RADIAN));if(r < 0)continue;p[theta][r]++;}}}}//get local maximumfor( int theta = 0;theta < 360;theta++){for( int r = 0;r < diagonal;r++){int thetaLeft = max(0,theta-1);int thetaRight = min(359,theta+1);int rLeft = max(0,r-1);int rRight = min(diagonal-1,r+1);int tmp = p[theta][r];if( tmp > threshold && tmp > p[thetaLeft][rLeft] && tmp > p[thetaLeft][r] && tmp > p[thetaLeft][rRight]&& tmp > p[theta][rLeft] && tmp > p[theta][rRight]&& tmp > p[thetaRight][rLeft] && tmp > p[thetaRight][r] && tmp > p[thetaRight][rRight]){struct line newline;newline.theta = theta;newline.r = r;lines.push_back(newline);}}}return lines;}void drawLines(Mat &img, const vector<struct line> &lines){for(int i = 0;i < lines.size();i++){vector<Point> points;int theta = lines[i].theta;int r = lines[i].r;double ct = cos(theta/RADIAN);double st = sin(theta/RADIAN);//r = x*ct + y*st//leftint y = int(r/st);if(y >= 0 && y < img.rows){Point p(0, y);points.push_back(p);}//righty = int((r-ct*(img.cols-1))/st);if(y >= 0 && y < img.rows){Point p(img.cols-1, y);points.push_back(p);}//topint x = int(r/ct);if(x >= 0 && x < img.cols){Point p(x, 0);points.push_back(p);}//downx = int((r-st*(img.rows-1))/ct);if(x >= 0 && x < img.cols){Point p(x, img.rows-1);points.push_back(p);}cv::line( img, points[0], points[1], Scalar(0,0,255), 1, CV_AA);}}int main( int, char** argv ){Mat src,src_gray,edge; src = imread( argv[1] ); cvtColor( src, src_gray, CV_BGR2GRAY ); blur( src_gray, src_gray, Size(3,3) ); Canny( src_gray, edge, 50, 200);vector<struct line> lines = houghLine(edge, 90);drawLines(src, lines); namedWindow("result", 1); imshow("result", src); waitKey(); return 0;}
举一反三
概率Hough变换在基本算法上增加了比较少的修改。之前是逐行扫描,现在则是随机选点。每当累加器的一个条目达到指定的最小值,就沿这条直线的方向扫描,并且将通过它的所有点删除(即使它们还没有参与投票)。而且该扫描还确定被接受的线段的长度。为此,该算法定义了两个附加参数。一个是被接受线段的最小长度,而另一个是被允许以形成连续的段的最大距离。这个附加步骤增加了算法的复杂性,但是复杂性带来的效率损失被较少的点会参与投票过程补偿。
我们再来看一看其他形状在二维 Hough空间的样子
我们再考虑一下噪声的影响
噪声使得峰值定位很难
噪声程度越厉害,结果越不准确
解决噪声问题的算法:
这个算法也不复杂,对于一个点,我们以前要遍历[0,360]的角度,但是现在,这个角度就直接被我们取出来了,速度也有很大的提升,非常不错的算法。
6 1
- OpenCV2马拉松第22圈——Hough变换直线检测原理与实现
- OpenCV2马拉松第23圈——圆检测与通用Hough变换
- OpenCV2马拉松第20圈——blob特征检测原理与实现
- openCV2马拉松第18圈——坐标变换
- OpenCV2马拉松第25圈——直线拟合与RANSAC算法
- openCV2马拉松第19圈——Harris角点检测(自己实现)
- OpenCV2马拉松第21圈——形态学操作实现角点检测
- 图像中直线的检测——Hough变换原理
- Hough变换——直线检测(投票方法实现)
- Hough直线检测的原理与实现
- 【opencv2】直线hough变换
- OpenCV2马拉松第17圈——边缘检测(Canny边缘检测)
- Hough变换 直线检测原理及其Matlab实现
- OpenCV2马拉松第14圈——边缘检测(Sobel,prewitt,roberts)
- OpenCV2马拉松第15圈——边缘检测(Laplace算子,LOG算子)
- OpenCV2马拉松第16圈——边缘检测(形态学梯度)
- OpenCV2马拉松第26圈——FAST特征点检测
- OpenCV2马拉松第27圈——SIFT论文,原理及源码解读
- 网站功能
- Android中的Gallery的使用
- 脱离舒适圈,才能加快成长速度
- Java 模拟form表单提交
- 你说是有钱不顾家好 还是顾家没钱好 这真是一个问题
- OpenCV2马拉松第22圈——Hough变换直线检测原理与实现
- Mac OS X 创新卡关三年,唯一看得出版本不同之处是「预设桌布」
- 在VS2013中强制IIS Express应用程序池使用经典模式
- [C/C++]static关键字用法总结
- 22种代码的坏味道,一句话概括
- Android app内 语言切换
- 你是人间的四月天
- 齐次坐标的理解
- POJ-1005-I Think I Need a Houseboat