http://blog.csdn.net/zhaocj/article/details/400473
标准霍夫变换本质上是把图像映射到它的参数空间上,它需要计算所有的M个边缘点,这样它的运算量和所需内存空间都会很大。如果在输入图像中只是处理mm<M)个边缘点,则这m个边缘点的选取是具有一定概率性的,因此该方法被称为概率霍夫变换(Probabilistic Hough Transform)。该方法还有一个重要的特点就是能够检测出线端,即能够检测出图像中直线的两个端点,确切地定位图像中的直线。

HoughLinesP函数就是利用概率霍夫变换来检测直线的。它的一般步骤为:

1、随机抽取图像中的一个特征点,即边缘点,如果该点已经被标定为是某一条直线上的点,则继续在剩下的边缘点中随机抽取一个边缘点,直到所有边缘点都抽取完了为止;

2、对该点进行霍夫变换,并进行累加和计算;

3、选取在霍夫空间内值最大的点,如果该点大于阈值的,则进行步骤4,否则回到步骤1;

4、根据霍夫变换得到的最大值,从该点出发,沿着直线的方向位移,从而找到直线的两个端点;

5、计算直线的长度,如果大于某个阈值,则被认为是好的直线输出,回到步骤1。

HoughLinesP函数的原型为:

void HoughLinesP(InputArray image,OutputArray lines, double rho, double theta, int threshold, double minLineLength=0,double maxLineGap=0 )

image为输入图像,要求是8位单通道图像

lines为输出的直线向量,每条线用4个元素表示,即直线的两个端点的4个坐标值

rho和theta分别为距离和角度的分辨率

threshold为阈值,即步骤3中的阈值

minLineLength为最小直线长度,在步骤5中要用到,即如果小于该值,则不被认为是一条直线

maxLineGap为最大直线间隙,在步骤4中要用到,即如果有两条线段是在一条直线上,但它们之间因为有间隙,所以被认为是两个线段,如果这个间隙大于该值,则被认为是两条线段,否则是一条。

HoughLinesP函数是在sources/modules/imgproc/src/hough.cpp文件中被定义的:

[cpp] view plaincopy
  1. void cv::HoughLinesP( InputArray _image, OutputArray _lines,
  2. double rho, double theta, int threshold,
  3. double minLineLength, double maxGap )
  4. {
  5. Ptr<CvMemStorage> storage = cvCreateMemStorage(STORAGE_SIZE);
  6. Mat image = _image.getMat();
  7. CvMat c_image = image;
  8. CvSeq* seq = cvHoughLines2( &c_image, storage, CV_HOUGH_PROBABILISTIC,
  9. rho, theta, threshold, minLineLength, maxGap );
  10. seqToMat(seq, _lines);
  11. }

从HoughLinesP函数可以看出,该函数会调用cvHoughLines2函数。它通过参数CV_HOUGH_PROBABILISTIC,最终调用了icvHoughLinesProbabilistic函数:

[cpp] view plaincopy
  1. static void
  2. icvHoughLinesProbabilistic( CvMat* image,
  3. float rho, float theta, int threshold,
  4. int lineLength, int lineGap,
  5. CvSeq *lines, int linesMax )
  6. {
  7. //accum为累加器矩阵,mask为掩码矩阵
  8. cv::Mat accum, mask;
  9. cv::vector<float> trigtab;    //用于存储事先计算好的正弦和余弦值
  10. //开辟一段内存空间
  11. cv::MemStorage storage(cvCreateMemStorage(0));
  12. //用于存储特征点坐标,即边缘像素的位置
  13. CvSeq* seq;
  14. CvSeqWriter writer;
  15. int width, height;    //图像的宽和高
  16. int numangle, numrho;    //角度和距离的离散数量
  17. float ang;
  18. int r, n, count;
  19. CvPoint pt;
  20. float irho = 1 / rho;    //距离分辨率的倒数
  21. CvRNG rng = cvRNG(-1);    //随机数
  22. const float* ttab;    //向量trigtab的地址指针
  23. uchar* mdata0;    //矩阵mask的地址指针
  24. //确保输入图像的正确性
  25. CV_Assert( CV_IS_MAT(image) && CV_MAT_TYPE(image->type) == CV_8UC1 );
  26. width = image->cols;    //提取出输入图像的宽
  27. height = image->rows;    //提取出输入图像的高
  28. //由角度和距离分辨率,得到角度和距离的离散数量
  29. numangle = cvRound(CV_PI / theta);
  30. numrho = cvRound(((width + height) * 2 + 1) / rho);
  31. //创建累加器矩阵,即霍夫空间
  32. accum.create( numangle, numrho, CV_32SC1 );
  33. //创建掩码矩阵,大小与输入图像相同
  34. mask.create( height, width, CV_8UC1 );
  35. //定义trigtab的大小,因为要存储正弦和余弦值,所以长度为角度离散数的2倍
  36. trigtab.resize(numangle*2);
  37. //累加器矩阵清零
  38. accum = cv::Scalar(0);
  39. //避免重复计算,事先计算好所需的所有正弦和余弦值
  40. for( ang = 0, n = 0; n < numangle; ang += theta, n++ )
  41. {
  42. trigtab[n*2] = (float)(cos(ang) * irho);
  43. trigtab[n*2+1] = (float)(sin(ang) * irho);
  44. }
  45. //赋值首地址
  46. ttab = &trigtab[0];
  47. mdata0 = mask.data;
  48. //开始写入序列
  49. cvStartWriteSeq( CV_32SC2, sizeof(CvSeq), sizeof(CvPoint), storage, &writer );
  50. // stage 1. collect non-zero image points
  51. //收集图像中的所有非零点,因为输入图像是边缘图像,所以非零点就是边缘点
  52. for( pt.y = 0, count = 0; pt.y < height; pt.y++ )
  53. {
  54. //提取出输入图像和掩码矩阵的每行地址指针
  55. const uchar* data = image->data.ptr + pt.y*image->step;
  56. uchar* mdata = mdata0 + pt.y*width;
  57. for( pt.x = 0; pt.x < width; pt.x++ )
  58. {
  59. if( data[pt.x] )    //是边缘点
  60. {
  61. mdata[pt.x] = (uchar)1;    //掩码的相应位置置1
  62. CV_WRITE_SEQ_ELEM( pt, writer );    把该坐标位置写入序列
  63. }
  64. else    //不是边缘点
  65. mdata[pt.x] = 0;    //掩码的相应位置清0
  66. }
  67. }
  68. //终止写序列,seq为所有边缘点坐标位置的序列
  69. seq = cvEndWriteSeq( &writer );
  70. count = seq->total;    //得到边缘点的数量
  71. // stage 2. process all the points in random order
  72. //随机处理所有的边缘点
  73. for( ; count > 0; count-- )
  74. {
  75. // choose random point out of the remaining ones
  76. //步骤1,在剩下的边缘点中随机选择一个点,idx为不大于count的随机数
  77. int idx = cvRandInt(&rng) % count;
  78. //max_val为累加器的最大值,max_n为最大值所对应的角度
  79. int max_val = threshold-1, max_n = 0;
  80. //由随机数idx在序列中提取出所对应的坐标点
  81. CvPoint* point = (CvPoint*)cvGetSeqElem( seq, idx );
  82. //定义直线的两个端点
  83. CvPoint line_end[2] = {{0,0}, {0,0}};
  84. float a, b;
  85. //累加器的地址指针,也就是霍夫空间的地址指针
  86. int* adata = (int*)accum.data;
  87. int i, j, k, x0, y0, dx0, dy0, xflag;
  88. int good_line;
  89. const int shift = 16;
  90. //提取出坐标点的横、纵坐标
  91. i = point->y;
  92. j = point->x;
  93. // "remove" it by overriding it with the last element
  94. //用序列中的最后一个元素覆盖掉刚才提取出来的随机坐标点
  95. *point = *(CvPoint*)cvGetSeqElem( seq, count-1 );
  96. // check if it has been excluded already (i.e. belongs to some other line)
  97. //检测这个坐标点是否已经计算过,也就是它已经属于其他直线
  98. //因为计算过的坐标点会在掩码矩阵mask的相对应位置清零
  99. if( !mdata0[i*width + j] )    //该坐标点被处理过
  100. continue;    //不做任何处理,继续主循环
  101. // update accumulator, find the most probable line
  102. //步骤2,更新累加器矩阵,找到最有可能的直线
  103. for( n = 0; n < numangle; n++, adata += numrho )
  104. {
  105. //由角度计算距离
  106. r = cvRound( j * ttab[n*2] + i * ttab[n*2+1] );
  107. r += (numrho - 1) / 2;
  108. //在累加器矩阵的相应位置上数值加1,并赋值给val
  109. int val = ++adata[r];
  110. //更新最大值,并得到它的角度
  111. if( max_val < val )
  112. {
  113. max_val = val;
  114. max_n = n;
  115. }
  116. }
  117. // if it is too "weak" candidate, continue with another point
  118. //步骤3,如果上面得到的最大值小于阈值,则放弃该点,继续下一个点的计算
  119. if( max_val < threshold )
  120. continue;
  121. // from the current point walk in each direction
  122. // along the found line and extract the line segment
  123. //步骤4,从当前点出发,沿着它所在直线的方向前进,直到达到端点为止
  124. a = -ttab[max_n*2+1];    //a=-sinθ
  125. b = ttab[max_n*2];    //b=cosθ
  126. //当前点的横、纵坐标值
  127. x0 = j;
  128. y0 = i;
  129. //确定当前点所在直线的角度是在45度~135度之间,还是在0~45或135度~180度之间
  130. if( fabs(a) > fabs(b) )    //在45度~135度之间
  131. {
  132. xflag = 1;    //置标识位,标识直线的粗略方向
  133. //确定横、纵坐标的位移量
  134. dx0 = a > 0 ? 1 : -1;
  135. dy0 = cvRound( b*(1 << shift)/fabs(a) );
  136. //确定纵坐标
  137. y0 = (y0 << shift) + (1 << (shift-1));
  138. }
  139. else    //在0~45或135度~180度之间
  140. {
  141. xflag = 0;   //清标识位
  142. //确定横、纵坐标的位移量
  143. dy0 = b > 0 ? 1 : -1;
  144. dx0 = cvRound( a*(1 << shift)/fabs(b) );
  145. //确定横坐标
  146. x0 = (x0 << shift) + (1 << (shift-1));
  147. }
  148. //搜索直线的两个端点
  149. for( k = 0; k < 2; k++ )
  150. {
  151. //gap表示两条直线的间隙,x和y为搜索位置,dx和dy为位移量
  152. int gap = 0, x = x0, y = y0, dx = dx0, dy = dy0;
  153. //搜索第二个端点的时候,反方向位移
  154. if( k > 0 )
  155. dx = -dx, dy = -dy;
  156. // walk along the line using fixed-point arithmetics,
  157. // stop at the image border or in case of too big gap
  158. //沿着直线的方向位移,直到到达图像的边界或大的间隙为止
  159. for( ;; x += dx, y += dy )
  160. {
  161. uchar* mdata;
  162. int i1, j1;
  163. //确定新的位移后的坐标位置
  164. if( xflag )
  165. {
  166. j1 = x;
  167. i1 = y >> shift;
  168. }
  169. else
  170. {
  171. j1 = x >> shift;
  172. i1 = y;
  173. }
  174. //如果到达了图像的边界,停止位移,退出循环
  175. if( j1 < 0 || j1 >= width || i1 < 0 || i1 >= height )
  176. break;
  177. //定位位移后掩码矩阵位置
  178. mdata = mdata0 + i1*width + j1;
  179. // for each non-zero point:
  180. //    update line end,
  181. //    clear the mask element
  182. //    reset the gap
  183. //该掩码不为0,说明该点可能是在直线上
  184. if( *mdata )
  185. {
  186. gap = 0;    //设置间隙为0
  187. //更新直线的端点位置
  188. line_end[k].y = i1;
  189. line_end[k].x = j1;
  190. }
  191. //掩码为0,说明不是直线,但仍继续位移,直到间隙大于所设置的阈值为止
  192. else if( ++gap > lineGap )    //间隙加1
  193. break;
  194. }
  195. }
  196. //步骤5,由检测到的直线的两个端点粗略计算直线的长度
  197. //当直线长度大于所设置的阈值时,good_line为1,否则为0
  198. good_line = abs(line_end[1].x - line_end[0].x) >= lineLength ||
  199. abs(line_end[1].y - line_end[0].y) >= lineLength;
  200. //再次搜索端点,目的是更新累加器矩阵和更新掩码矩阵,以备下一次循环使用
  201. for( k = 0; k < 2; k++ )
  202. {
  203. int x = x0, y = y0, dx = dx0, dy = dy0;
  204. if( k > 0 )
  205. dx = -dx, dy = -dy;
  206. // walk along the line using fixed-point arithmetics,
  207. // stop at the image border or in case of too big gap
  208. for( ;; x += dx, y += dy )
  209. {
  210. uchar* mdata;
  211. int i1, j1;
  212. if( xflag )
  213. {
  214. j1 = x;
  215. i1 = y >> shift;
  216. }
  217. else
  218. {
  219. j1 = x >> shift;
  220. i1 = y;
  221. }
  222. mdata = mdata0 + i1*width + j1;
  223. // for each non-zero point:
  224. //    update line end,
  225. //    clear the mask element
  226. //    reset the gap
  227. if( *mdata )
  228. {
  229. //if语句的作用是清除那些已经判定是好的直线上的点对应的累加器的值,避免再次利用这些累加值
  230. if( good_line )    //在第一次搜索中已经确定是好的直线
  231. {
  232. //得到累加器矩阵地址指针
  233. adata = (int*)accum.data;
  234. for( n = 0; n < numangle; n++, adata += numrho )
  235. {
  236. r = cvRound( j1 * ttab[n*2] + i1 * ttab[n*2+1] );
  237. r += (numrho - 1) / 2;
  238. adata[r]--;    //相应的累加器减1
  239. }
  240. }
  241. //搜索过的位置,不管是好的直线,还是坏的直线,掩码相应位置都清0,这样下次就不会再重复搜索这些位置了,从而达到减小计算边缘点的目的
  242. *mdata = 0;
  243. }
  244. //如果已经到达了直线的端点,则退出循环
  245. if( i1 == line_end[k].y && j1 == line_end[k].x )
  246. break;
  247. }
  248. }
  249. //如果是好的直线
  250. if( good_line )
  251. {
  252. CvRect lr = { line_end[0].x, line_end[0].y, line_end[1].x, line_end[1].y };
  253. //把两个端点压入序列中
  254. cvSeqPush( lines, &lr );
  255. //如果检测到的直线数量大于阈值,则退出该函数
  256. if( lines->total >= linesMax )
  257. return;
  258. }
  259. }
  260. }

下面就给出应用HoughLinesP函数检测直线段的应用程序:

[cpp] view plaincopy
  1. #include "opencv2/core/core.hpp"
  2. #include "opencv2/highgui/highgui.hpp"
  3. #include "opencv2/imgproc/imgproc.hpp"
  4. #include <iostream>
  5. using namespace cv;
  6. using namespace std;
  7. int main( int argc, char** argv )
  8. {
  9. Mat src, edge,color_edge;
  10. src=imread("building.jpg");
  11. if( !src.data )
  12. return -1;
  13. Canny(src,edge,50,200,3);
  14. cvtColor( edge, color_edge, CV_GRAY2BGR );
  15. vector<Vec4i> lines;
  16. HoughLinesP(edge, lines, 1, CV_PI/180, 80, 30, 10 );
  17. for( size_t i = 0; i < lines.size(); i++ )
  18. {
  19. Vec4i l = lines[i];
  20. line( color_edge, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 2);
  21. }
  22. namedWindow( "lines", CV_WINDOW_AUTOSIZE );
  23. imshow( "lines", color_edge );
  24. waitKey(0);
  25. return 0;
  26. }

下图为输出的图像:

转载于:https://www.cnblogs.com/fag888/p/5789167.html

Opencv2.4.9源码分析——HoughLinesP相关推荐

  1. Opencv2.4.9源码分析要点摘录

    以下摘录自 http://blog.csdn.net/zhaocj?viewmode=contents Opencv2.4.9源码分析要点摘录 Boosting AdaBoost的计算步骤: 1.设有 ...

  2. Opencv2.4.9源码分析——SURF

     SURF (Speeded Up Robust Features)是一种具有鲁棒性的局部特征检测算法,它首先由Herbert Bay等人于2006年提出,并在2008年进行了完善.其实该算法是H ...

  3. OpenCV2.4.9源码分析——Support Vector Machines

    引言 本文共分为三个部分,第一个部分介绍SVM的原理,我们全面介绍了5中常用的SVM算法:C-SVC.ν-SVC.单类SVM.ε-SVR和ν-SVR,其中C-SVC和ν-SVC不仅介绍了处理两类分类问 ...

  4. Opencv2.4.9源码分析——Stitching(四)

    4.图像投影变换 4.1 原理 前文我们已经说过,每幅图像是相机在不同角度下拍摄得到的,它们并不在同一个投影平面上,如果对重叠部分直接进行拼接,则会破坏实际场景的视觉一致性.所以我们需要在拼接之前,对 ...

  5. Opencv2.4.9源码分析——Stitching(二)

    2.计算单应矩阵 2.1 原理 在得到了图像特征点以后,我们就可以根据这些特征点,实现图像匹配,即得到重叠区域.而要把多幅图像拼接成一幅图像,就需要以某幅图像为基准,把其他图像映射到该图像所在的平面. ...

  6. Opencv2.4.9源码分析——Stitching(五)

    5.曝光补偿 5.1 原理 即使通过几何投影,图像之间可以做到很好的拼接,但如果不同图像之间有不同的曝光程度,那么拼接图像中的重叠部分也会出现明显的边缘,这样就使图像看起来十分不自然.因此,我们还需要 ...

  7. Opencv2.4.9源码分析——Stitching(一)

    相机镜头所呈现出的景物要比人类的视觉系统所看到的景物要狭小得多,因此一幅图像不可能捕获到我们所看到的整个景物.全景图像拼接给出了这个问题的解决办法,它是把图像间重叠部分拿出来拼接起来,从而得到一幅更大 ...

  8. Opencv2.4.9源码分析——HoughCircles

    图形可以用一些参数进行表示,标准霍夫变换的原理就是把图像空间转换成参数空间(即霍夫空间),例如霍夫变换的直线检测就是在距离-角度空间内进行检测.圆可以表示成: (x-a)2+(y-b)2=r2     ...

  9. SVM算法及OpenCV源码分析

    关于SVM原理,请参看: 系统学习机器学习之SVM(一) 系统学习机器学习之SVM(二) 系统学习机器学习之SVM(三)--Liblinear,LibSVM使用整理,总结 系统学习机器学习之SVM(四 ...

最新文章

  1. OCR引擎Tesseract以及pytesseract详解及实例
  2. 精通python-精通Python设计模式
  3. elasticsearch 常用命令
  4. Git本地仓库与远程仓库关联
  5. hitchhiker部署_《 Hitchhiker的React Router v4指南》:路由配置的隐藏值
  6. Cloud一分钟 |格力电器营收比去年增长500亿元; 红黄蓝加盟停不下来;中美双方同意停止相互加征新的关税...
  7. 诗与远方:无题(六十四)- 杂诗
  8. openssl pkeyutl执行SM2椭圆曲线数字签名
  9. cmd 修改ie快捷方式_windows使用技巧之Win + R 与 CMD 的不同
  10. mysql 改变 执行计划_诡异的MySql执行计划的更改
  11. Zookeeper C API 指南二(监视(Wathes), 基本常量和结构体介绍)
  12. C# SOCKE通信
  13. 黑客防线、黑客X档案专辑 NPM、PYPI、DockerHub 备份
  14. 内网穿透Natapp
  15. 测试raid10下的服务器性能,Raid5 Raid10性能测试
  16. [职场人生]求职信常用语句 Useful wording in application letters
  17. requests的Proxy-SSL错误
  18. 《matlab科研绘图系列》之小提琴图绘制
  19. 洛谷【T279725】搬砖
  20. 税点怎么用计算机算出来,2017个税计算器具体要怎么算

热门文章

  1. 人人都能做游戏!3D次世代CE云端引擎发布
  2. ASP.NET MVC (五、HttpClient接口解析)
  3. Java的二十三种设计模式(建造者模式(Builder))
  4. css : 使用浮动实现左右各放一个元素时很容易犯的错误
  5. Jenkins 中如何一次构建多个项目
  6. angular_ui-router ——依赖注入
  7. Objective-C:OC内部可变对象和不可变对象的深(复制)拷贝问题思考:
  8. Xamarin简介与Xamarin支持MVC设计模式
  9. Shell编程之变量赋值和引用
  10. asp.net中关于静态页面生成的代码实例