图像处理 直线检测方法

2019-02-14 17:56:28 zfjBIT 阅读数 1180

P.V.C.Hough[1]第一次提出用霍夫变换(Hough Transform) 检测二值图中的直线和曲线, R.D.Duda和P.E.Hart[2]根据霍夫变换提出了一种更有效的直线检测方法, 这就是通常所称的标准霍夫直线检测。 下面详细介绍它的原理。 在xoy平面内的一条直线大致分为如图9-5所示的四种情况:

通过观察可以发现, 计算完成四个象限的直线方程, 最后的表示结果是一样的, 即如果知道原点到一条直线的代数距离ρ和与x轴的夹角θ, 则直线方程可由以下方式表示:
p = x\cos \theta + y\sin \theta

当然, 反过来也可以, 如果知道平面内的一条直线, 那么可以计算出唯一的ρ和θ,即xoy平面内的任意一条直线对应参数空间(或称霍夫空间) θoρ中的一点(ρ, θ) 。 如图9-6所示, 对于xoy平面内的直线y =10-x, 因为原点到该直线的垂线在第一象限, 垂线与x轴正方向的夹角为 \pi /4, 原点到该直线的代数距离为 10/\sqrt{}2, 所以该直线对应到θoρ中的点\left ( \pi /4, 10/\sqrt{2}\right ):

从另一个角度考虑, 过xoy平面内的一点(x1, y1) 有无数条直线, 则对应霍夫空间中的无数个点, 这无数个点连接起来就是θoρ平面内的曲线ρ=x1 cosθ+y1 sinθ。 如图9-7所示, 过xoy平面内的点(5, 5) 有无数条直线, 则这个点对应到霍夫空间中的曲线ρ=5cosθ+5 sinθ, 其中因为过(5, 5) 有一条直线是y=10-x, 如图9-6所示, 该直线对应坐标点\left ( \pi /4, 10/\sqrt{2}\right ) , 所以ρ=5 cosθ+5 sinθ过点 \left ( \pi /4, 10/\sqrt{2}\right )。 对其他直线的讨论与之类似。


通过上述讨论可以解答一个问题: 如何验证xoy平面内的(x1, y1) , (x2,y2) , …是否共线? 只要曲线ρ=xi cosθ+yi sinθ, i=1, 2, …在θoρ平面内相交于一个点就可以了。 举例: 在xoy平面内有四个点1、 2、 3、 4, 坐标依次为(2, 8) 、 (3, 7) 、(5, 5) 和(6, 4) , 根据这四个点可以在θoρ平面内画出对应的四条曲线, 这四条曲线相交于一个点, 所以这四个点是共线的, 如图9-8所示。


过这四个点的直线是y=10-x, 所以相交点为 \left ( \pi /4, 10/\sqrt{2}\right ), 如图9-9所示:

推广以上结论, 讨论一个稍微复杂的问题: 已知平面内的一些点, 如何找出哪些点在同一条直线上? 解答过程如图9-10所示, xoy平面内有5个点, 对应到θoρ平面内的5条曲线, 可以看出1、 2、 3、 4点对应的曲线是相交于一个点的, 所以xoy平面内的1、 2、3、 4点是共线的; 同样, 5点和4点对应的曲线也相交于一个点, 所以xoy平面内的5点和4点是共线的(两点决定一条曲线) ; 其他的与之类似.


图9-10 验证笛卡儿坐标系中的哪些点在同一条直线上


结论: 判断xoy平面内哪些点是共线的, 首先求出每一个点对应到霍夫空间的曲线, 然后判断哪几条曲线相交于一点, 最后将相交于一点的曲线反过来对应到xoy平面内的点, 这些点就是共线的, 这就是在图像中进行标准霍夫直线检测的核心思想。
 

2019-08-08 22:18:45 woshiyigehaowawa 阅读数 489

图像的直线检测

Hough变换
采用参数空间变换的方法,对噪声和不间断直线的检测具有鲁棒性。
核心思想 y=kx+b
可以记为xcosθ+ysinθ=ρ
每一条直线对应一个k,b,参数方程下对应一个点(ρ,θ)
如下图
在这里插入图片描述
将ρ,θ当做一个新的坐标系,横坐标为θ,纵坐标为ρ,则上面的直线则为当前坐标系中的一个点。
在这里插入图片描述
直角坐标系的一个点(x,y),对应极坐标系下的一条正弦曲线ρ=xcosθ+ysinθ = √(x2+y2 )cos(θ+φ)
同一条直线下的多个点,再极坐标系下比相交于一点
在这里插入图片描述
b中的一个点就是在c中的一个余弦曲线,c为极坐标系,a中的一条直线对应c中的一个点,b为a的点集合,c中越多的曲线相交于一点的说明就越有可能是直线的点。

步骤:
1将(ρ,θ)空间量化成许多小格
2根据x-y平面每一个直线点带入θ的量化值,算出各个ρ,将对应格计数累加
3当全部点变换后,对小格进行校验。设置累计阈值T,计数器大于T的小格对应于共线点,其可以用作直线拟合参数。小于T的反映非共线点,丢弃不用。

k,b的值是负无穷到正无穷
而θ是0到2π是个有限的数,坐标系是有限的,ρ最大不会超过原始直线对角线的长度,所以在有界的坐标系中更利于做格子的划分。可用于圆等的检测。类似有投票

2017-07-26 13:35:29 walilk 阅读数 12440

前言

  [图像处理] 实验笔记系列是以图像处理算法为主的文章专栏,以我在算法研究中的实验笔记资料为基础加以整理推出的。该系列内容涉及常见的图像处理算法理论以及常见的算法应用,每篇博客都会介绍相关的算法原理,代码实现和算法在实际应用中的技巧。
  本文主要整理自笔者在一项图像处理任务中的直线检测(line detection)部分的笔记资料,采用了基于霍夫变换(Hough Transform)的直线检测算法。文中给出了直线检测常用的算法介绍,论文资料等,以及笔者的实验笔记和实验结果。
  
  文章小节安排如下:
  1)直线检测相关算法
  2)霍夫直线检测的基本原理
  3)霍夫直线检测的OpenCV实现
  4)直线检测的应用
  
  

一、直线检测相关算法  

1.1 霍夫变换(Hough Transform) 

  霍夫变换(Hough Transform)换于1962年由Paul Hough 首次提出,后于1972年由Richard Duda和Peter Hart推广使用,是图像处理中从图像中检测几何形状的基本方法之一。经典霍夫变换用来检测图像中的直线,后来霍夫变换经过扩展可以进行任意形状物体的识别,例如圆和椭圆。
  
  霍夫变换运用两个坐标空间之间的变换将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化为统计峰值问题。
  
  参考论文:
  [1] P.V.C. Hough,Machine Analysis of Bubble Chamber Pictures, Proc. Int. Conf. High Energy Accelerators and Instrumentation, 1959.
  [2] Duda, R. O. and P. E. Hart, “Use of the Hough Transformation to Detect Lines and Curves in Pictures,”Comm. ACM, Vol. 15, pp. 11–15 (January, 1972).
  
  

1.2 霍夫直线检测(Hough Line Detection) 

  Hough直线检测的基本原理在于利用点与线的对偶性,在我们的直线检测任务中,即图像空间中的直线与参数空间中的点是一一对应的,参数空间中的直线与图像空间中的点也是一一对应的。这意味着我们可以得出两个非常有用的结论:
  1)图像空间中的每条直线在参数空间中都对应着单独一个点来表示;
  2)图像空间中的直线上任何一部分线段在参数空间对应的是同一个点。
  
  因此Hough直线检测算法就是把在图像空间中的直线检测问题转换到参数空间中对点的检测问题,通过在参数空间里寻找峰值来完成直线检测任务。
  

1.3 LSD 

  -待续-
  

二、霍夫直线检测的基本原理

2.1 关于对偶性

  首先,我们通过实例来解释一下对偶性的意义。
  1)图像空间中的点与参数空间中的直线一一对应
  在图像空间x-y中一条直线在直角坐标系下可以表示为:
  

直线方程

  其中k和b是参数,对应表示斜率和截距。
  
图像空间的直线

  过某一点A(x0, y0)的所有直线的参数均满足方程y0=k*x0+b,即点A(x0, y0)确定了一族直线。
  如果我们将方程改写为:
  
直接方程形式改写

  那么该方程在参数空间k-b中就对应了一条直线:
  
参数空间的直线

  也就是说,图像空间x-y中的点(x0,y0)对应了参数空间k-b中的直线b=-k*x0+y0。因此可以得到结论,图像空间中的点与参数空间中的直线一一对应。
  
  2)图像空间中的直线与参数空间中的点一一对应
  我们在直线y=k*x+b上再增加一个点B(x1, y1),如下图所示:
  
图像空间的直线

  那么点B(x1, y1)在参数空间同样对应了一条直线:
  
参数空间的直线
  
  可以看到,图像空间x-y中的点A和点B在参数空间k-b中对应的直线相交于一点,这也就是说AB所确定的直线,在参数空间中对应着唯一一个点,这个点的坐标值(k0, b0)也就是直线AB的参数。
  
  以上就是在直线检测任务中关于对偶性的直观解释。这个性质也为我们解决直线检测任务提供了方法,也就是把图像空间中的直线对应到参数空间中的点,最后通过统计特性来解决问题。假如图像空间中有两条直线,那么最终在参数空间中就会对应到两个峰值点,依此类推。
  

2.2 参数空间的选择

  上述为了方便讲解对偶性和霍夫变换的基本原理,我们的参数空间也选择了笛卡尔直角坐标系。但在实际应用中,参数空间是不能选择直角坐标系的,因为原始图像直角坐标空间中的特殊直线x=c(垂直x轴,直线的斜率为无穷大)是没办法在基于直角坐标系的参数空间中表示的。
  
  所以在实际应用中,参数空间采用极坐标系ρ-θ,图示如下:
  

参数空间选择极坐标系时的直线示意

  直线的表达式为:
  
直线表达式

  化简便可得到:
  

化简的直线表达式

  对于直线上的点(x0, y0),可以将通过该点的直线族定义为:
  
经过指定点的直线族表达式

  
  
  这就回到我们刚才的结论,参数空间的每个点(ρ,θ)都对应了图像空间的一条直线,或者说图像空间的一个点在参数空间中就对应为一条曲线。参数空间采用极坐标系,这样就可以在参数空间表示原始空间中的所有直线了。
  注意,此时图像空间(直角坐标系x-y)上的一个点对应到参数空间(极坐标系ρ-θ)上是一条曲线,确切的说是一条正弦曲线。
  
参数空间的曲线

  

2.3 利用霍夫变换检测直线

  如前所述,霍夫直线检测就是把图像空间中的直线变换到参数空间中的点,通过统计特性来解决检测问题。具体来说,如果一幅图像中的像素构成一条直线,那么这些像素坐标值(x, y)在参数空间对应的曲线一定相交于一个点,所以我们只需要将图像中的所有像素点(坐标值)变换成参数空间的曲线,并在参数空间检测曲线交点就可以确定直线了。
  
  在理论上,一个点对应无数条直线或者说任意方向的直线,但在实际应用中,我们必须限定直线的数量(即有限数量的方向)才能够进行计算。
  
  因此,我们将直线的方向θ离散化为有限个等间距的离散值,参数ρ也就对应离散化为有限个值,于是参数空间不再是连续的,而是被离散量化为一个个等大小网格单元。将图像空间(直角坐标系)中每个像素点坐标值变换到参数空间(极坐标系)后,所得值会落在某个网格内,使该网格单元的累加计数器加1。当图像空间中所有的像素都经过霍夫变换后,对网格单元进行检查,累加计数值最大的网格,其坐标值(ρ0, θ0)就对应图像空间中所求的直线。
  

参数空间的量化

  
  以上就是霍夫直线检测算法要做的,它检测图像中每个像素点在参数空间对应曲线之间的交点,如果交于一点的曲线的数量超过了阈值,那就可以认为这个交点(ρ,θ)在图像空间中对应一条直线。
    
    

2.4 霍夫直线检测的优缺点

  优点:
  Hough直线检测的优点是抗干扰能力强,对图像中直线的殘缺部分、噪声以及其它共存的非直线结构不敏感。
  缺点:
  Hough变换算法的特点导致其时间复杂度和空间复杂度都很高,并且在检测过程中只能确定直线方向,丢失了线段的长度信息。
  
  

三、霍夫直线检测的OpenCV实现  

  OpenCV支持三种霍夫直线检测算法:
  1)Standard Hough Transform(SHT,标准霍夫变换)
  2)Multiscale Hough Transform(MSHT,多尺度霍夫变换)
  3)Progressive Probability Houth Transform(PPHT,渐进概率式霍夫变换)
  

3.1 霍夫直线检测函数定义

  在OpenCV2.1之前的版本,霍夫直线检测函数如下:
  
  函数原型:

CVAPI(CvSeq*) cvHoughLines2( CvArr* image, void* line_storage, int method,
  double rho, double theta, int threshold,
  double param1 CV_DEFAULT(0), double param2 CV_DEFAULT(0),
  double min_theta CV_DEFAULT(0), double max_theta CV_DEFAULT(CV_PI));

  函数说明:
  cvHoughLines2老版OpenCV的霍夫直线检测函数,通过method参数可以支持三种霍夫直线检测算法,分别是CV_HOUGH_STANDARD、CV_HOUGH_PROBABILISTIC =1、CV_HOUGH_MULTI_SCALE。
  
  
  在OpenCV新版本下,霍夫直线检测算法定义了两个函数:HoughLines、HoughLinesP
  1)HoughLines:标准霍夫变换、多尺度霍夫变换
  函数原型:

CV_EXPORTS_W void 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 );
  

  参数说明:
  InputArray image:输入图像,必须是8位单通道图像。
  OutputArray lines:检测到的线条参数集合。
  double rho:以像素为单位的距离步长。
  double theta:以弧度为单位的角度步长。
  int threshold:累加计数值的阈值参数,当参数空间某个交点的累加计数的值超过该阈值,则认为该交点对应了图像空间的一条直线。
  double srn:默认值为0,用于在多尺度霍夫变换中作为参数rho的除数,rho=rho/srn。
  double stn:默认值为0,用于在多尺度霍夫变换中作为参数theta的除数,theta=theta/stn。
  
  函数说明:
  HoughLines函数输出检测到直线的矢量表示集合,每一条直线由具有两个元素的矢量(ρ, θ)表示,其中ρ表示直线距离原点(0, 0)的长度,θ表示直线的角度(以弧度为单位)。
HoughLines函数无法输出图像空间中线段的长度,这也是霍夫变换本身的弱点。

  
  备注说明:
  如果srn和stn同时为0,就表示HoughLines函数执行标准霍夫变换,否则就是执行多尺度霍夫变换。
  
  
  2)HoughLinesP:渐进概率式霍夫变换
  函数原型:

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

  参数说明:
  InputArray image:输入图像,必须是8位单通道图像。
  OutputArray lines:检测到的线条参数集合。
  double rho:直线搜索时的距离步长,以像素为单位。
  double theta:直线搜索时的角度步长,以弧度为单位。
  int threshold:累加计数值的阈值参数,当参数空间某个交点的累加计数的值超过该阈值,则认为该交点对应了图像空间的一条直线。
  double minLineLength:默认值为0,表示最小线段长度阈值(像素)。
  double maxLineGap:默认值为0,表示直线断裂的最大间隔距离阈值。即如果有两条线段是在一条直线上,但它们之间有间隙,那么如果这个间隔距离大于该值,则被认为是一条线段,否则认为是两条线段。
  
  函数说明:
  HoughLinesP函数输出检测到直线的矢量表示集合,每一条直线由具有四个元素的矢量(x1, y1, x2, y2)表示,其中(x1, y1)表示线段的起点,(x2, y2)表示线段的终点。
  HoughLinesP函数可以检测出图像空间中线段的长度。

  
  

3.2 霍夫直线检测函数使用

  霍夫直线变换是一种用来在图像空间寻找直线的方法,输入图像要求是二值图像,同时为了提高检测直线的效率和准确率,在使用霍夫线变换之前,最好对图像进行边缘检测生成边缘二值图像,这样的检测效果是最好的。
  
  1)HoughLines函数
  代码:

std::string img_path;
cv::Mat mat_color;
cv::Mat mat_gray;
cv::Mat mat_binary;
cv::Mat mat_canny;
cv::Mat mat_board;

img_path = "line.png";
mat_color = cv::imread(img_path, 1);
mat_gray = cv::imread(img_path, 0);
mat_board = cv::Mat(mat_color.size(), mat_color.type(), Scalar::all(255));

// binary
cv::threshold(mat_gray, mat_binary, 0.0, 255.0, cv::THRESH_OTSU);
// invert color
cv::bitwise_not(mat_binary, mat_binary);

// detect edge
Canny(mat_binary, mat_canny, 50, 200, 3);

// detect line
vector<Vec2f> lines;
HoughLines(mat_canny, lines, 1, CV_PI / 180, 150, 0, 0);

// draw line
cout << "line number: " << lines.size() << endl;
for (size_t i = 0; i < lines.size(); i++)
{
    Vec2f linex = lines[i];
    cout << "radius: " << linex[0] << ", radian: "<< linex[1] << ", angle: " << 180 / CV_PI * linex[1] << endl;
    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(mat_board, pt1, pt2, Scalar(255, 0, 0), 1);
}
cv::imshow("gray", mat_gray);
cv::imshow("binary", mat_binary);
cv::imshow("canny", mat_canny);
cv::imshow("color", mat_board);
cv::waitKey();

  原图:
  

原图

  二值图:
  
二值图

  边缘图:
  
边缘图

  检测效果:
  
HoughLines的检测效果

  
  2)HoughLinesP函数
  代码:

std::string img_path;
cv::Mat mat_color;
cv::Mat mat_gray;
cv::Mat mat_binary;
cv::Mat mat_canny;
cv::Mat mat_board;

img_path = "line.png";
mat_color = cv::imread(img_path, 1);
mat_gray  = cv::imread(img_path, 0);
mat_board = cv::Mat(mat_color.size(), mat_color.type(), Scalar::all(255));

// binary
cv::threshold(mat_gray, mat_binary, 0.0, 255.0, cv::THRESH_OTSU);
// invert color
cv::bitwise_not(mat_binary, mat_binary);

// detect edge
Canny(mat_binary, mat_canny, 50, 200, 3);

// detect line
vector<Vec4i> lines;
HoughLinesP(mat_canny, lines, 1, CV_PI / 180, 150, 50, 50);

// draw line
cout << "line number: " << lines.size() << endl;
for (size_t i = 0; i < lines.size(); i++)
{
    Vec4i linex = lines[i];
    line(mat_board, Point(linex[0], linex[1]), Point(linex[2], linex[3]), Scalar(255, 0, 0), 1);
}

cv::imshow("gray",   mat_gray);
cv::imshow("binary", mat_binary);
cv::imshow("canny", mat_canny);
cv::imshow("color", mat_board);
cv::waitKey();

  检测效果:
  

HoughLinesP的检测效果

  
  

四、直线检测的应用

4.1 直线检测的实际应用

  直线检测是机器视觉和模式识别中最重要的任务之一,对图像理解/分析等有重要的意义。在实际应用中,直线检测可用于机器人定位中的网格识别,板材的裂纹检测,表单票据的格式识别,零件纹路的检测,自动驾驶中的车道检测等等,可以看出,在工业领域中,直线检测以及各种图像处理技术应用是非常丰富的。
  

4.2 直线检测在图像矫正方面的应用

  笔者最近在一个OCR项目也使用了Hough Line Detection算法。在该项目中,待识别文本图像的内容是倾斜的(即文字是倾斜的),我们采用的测略就是通过直线检测确定图像内容的倾斜程度并进行旋转纠正,这样得到的无倾斜图像更有利于OCR任务。
  原图:
  

原图

  矫正效果:
  
矫正效果

  

五、参考资料

参考论文与书目:
[1] Duda, R. O. and P. E. Hart, “Use of the Hough Transformation to Detect Lines and Curves in Pictures,”Comm. ACM, Vol. 15, pp. 11–15 (January, 1972).
[2] 顾思妍. 机器视觉的直线检测技术及应用研究[D].广东工业大学,2011.
[3] 数字图像处理[M]. 电子工业出版社 , (美)RafaelC.Gonzalez,(美)RichardE.Woods,(美)StevenL.Eddins著, 2005

参考博客:
Hough变换-理解篇
http://blog.csdn.net/abcjennifer/article/details/7448513
Hough transform(霍夫变换)
http://www.cnblogs.com/AndyJee/p/3805594.html
霍夫变换概述和标准霍夫变换
http://www.jianshu.com/p/55eabb42c6c2

2012-07-07 14:42:21 jia20003 阅读数 109359

图像处理之霍夫变换(直线检测算法)

霍夫变换是图像变换中的经典手段之一,主要用来从图像中分离出具有某种相同特征的几何

形状(如,直线,圆等)。霍夫变换寻找直线与圆的方法相比与其它方法可以更好的减少噪

声干扰。经典的霍夫变换常用来检测直线,圆,椭圆等。

 

霍夫变换算法思想:

以直线检测为例,每个像素坐标点经过变换都变成都直线特质有贡献的统一度量,一个简单

的例子如下:一条直线在图像中是一系列离散点的集合,通过一个直线的离散极坐标公式,

可以表达出直线的离散点几何等式如下:

X *cos(theta) + y * sin(theta)  = r 其中角度theta指r与X轴之间的夹角,r为到直线几何垂

直距离。任何在直线上点,x, y都可以表达,其中 r, theta是常量。该公式图形表示如下:

然而在实现的图像处理领域,图像的像素坐标P(x, y)是已知的,而r, theta则是我们要寻找

的变量。如果我们能绘制每个(r, theta)值根据像素点坐标P(x, y)值的话,那么就从图像笛卡

尔坐标系统转换到极坐标霍夫空间系统,这种从点到曲线的变换称为直线的霍夫变换。变换

通过量化霍夫参数空间为有限个值间隔等分或者累加格子。当霍夫变换算法开始,每个像素

坐标点P(x, y)被转换到(r, theta)的曲线点上面,累加到对应的格子数据点,当一个波峰出现

时候,说明有直线存在。同样的原理,我们可以用来检测圆,只是对于圆的参数方程变为如

下等式:

(x –a ) ^2 + (y-b) ^ 2 = r^2其中(a, b)为圆的中心点坐标,r圆的半径。这样霍夫的参数空间就

变成一个三维参数空间。给定圆半径转为二维霍夫参数空间,变换相对简单,也比较常用。

 

编程思路解析:

1.      读取一幅带处理二值图像,最好背景为黑色。

2.      取得源像素数据

3.      根据直线的霍夫变换公式完成霍夫变换,预览霍夫空间结果

4.       寻找最大霍夫值,设置阈值,反变换到图像RGB值空间(程序难点之一)

5.      越界处理,显示霍夫变换处理以后的图像

 

关键代码解析:

直线的变换角度为[0 ~ PI]之间,设置等份为500为PI/500,同时根据参数直线参数方程的取值

范围为[-r, r]有如下霍夫参数定义:

 // prepare for hough transform
 int centerX = width / 2;
 int centerY = height / 2;
 double hough_interval = PI_VALUE/(double)hough_space;
	    
 int max = Math.max(width, height);
 int max_length = (int)(Math.sqrt(2.0D) * max);
 hough_1d = new int[2 * hough_space * max_length];

实现从像素RGB空间到霍夫空间变换的代码为:

// start hough transform now....
int[][] image_2d = convert1Dto2D(inPixels);
for (int row = 0; row < height; row++) {
	for (int col = 0; col < width; col++) {
    	int p = image_2d[row][col] & 0xff;
    	if(p == 0) continue; // which means background color
    	
    	// since we does not know the theta angle and r value, 
    	// we have to calculate all hough space for each pixel point
    	// then we got the max possible theta and r pair.
    	// r = x * cos(theta) + y * sin(theta)
    	for(int cell=0; cell < hough_space; cell++ ) {
    		max = (int)((col - centerX) * Math.cos(cell * hough_interval) + (row - centerY) * Math.sin(cell * hough_interval));
    		max += max_length; // start from zero, not (-max_length)
    		if (max < 0 || (max >= 2 * max_length)) {// make sure r did not out of scope[0, 2*max_lenght]
                continue;
            }
    		hough_2d[cell][max] +=1;
    	}
    }
}

寻找最大霍夫值计算霍夫阈值的代码如下:

// find the max hough value
int max_hough = 0;
for(int i=0; i<hough_space; i++) {
	for(int j=0; j<2*max_length; j++) {
		hough_1d[(i + j * hough_space)] = hough_2d[i][j];
		if(hough_2d[i][j] > max_hough) {
			max_hough = hough_2d[i][j];
		}
	}
}
System.out.println("MAX HOUGH VALUE = " + max_hough);

// transfer back to image pixels space from hough parameter space
int hough_threshold = (int)(threshold * max_hough);

从霍夫空间反变换回像素数据空间代码如下:

	    // transfer back to image pixels space from hough parameter space
	    int hough_threshold = (int)(threshold * max_hough);
	    for(int row = 0; row < hough_space; row++) {
	    	for(int col = 0; col < 2*max_length; col++) {
	    		if(hough_2d[row][col] < hough_threshold) // discard it
	    			continue;
	    		int hough_value = hough_2d[row][col];
	    		boolean isLine = true;
	    		for(int i=-1; i<2; i++) {
	    			for(int j=-1; j<2; j++) {
	    				if(i != 0 || j != 0) {
    		              int yf = row + i;
    		              int xf = col + j;
    		              if(xf < 0) continue;
    		              if(xf < 2*max_length) {
    		            	  if (yf < 0) {
    		            		  yf += hough_space;
    		            	  }
    		                  if (yf >= hough_space) {
    		                	  yf -= hough_space;
    		                  }
    		                  if(hough_2d[yf][xf] <= hough_value) {
    		                	  continue;
    		                  }
    		                  isLine = false;
    		                  break;
    		              }
	    				}
	    			}
	    		}
	    		if(!isLine) continue;
	    		
	    		// transform back to pixel data now...
	            double dy = Math.sin(row * hough_interval);
	            double dx = Math.cos(row * hough_interval);
	            if ((row <= hough_space / 4) || (row >= 3 * hough_space / 4)) {
	                for (int subrow = 0; subrow < height; ++subrow) {
	                  int subcol = (int)((col - max_length - ((subrow - centerY) * dy)) / dx) + centerX;
	                  if ((subcol < width) && (subcol >= 0)) {
	                	  image_2d[subrow][subcol] = -16776961;
	                  }
	                }
	              } else {
	                for (int subcol = 0; subcol < width; ++subcol) {
	                  int subrow = (int)((col - max_length - ((subcol - centerX) * dx)) / dy) + centerY;
	                  if ((subrow < height) && (subrow >= 0)) {
	                	  image_2d[subrow][subcol] = -16776961;
	                  }
	                }
	              }
	    	}
	    }
霍夫变换源图如下:

霍夫变换以后,在霍夫空间显示如下:(白色表示已经找到直线信号)


最终反变换回到像素空间效果如下:


一个更好的运行监测直线的结果(输入为二值图像):


完整的霍夫变换源代码如下:

package com.gloomyfish.image.transform;

import java.awt.image.BufferedImage;

import com.process.blur.study.AbstractBufferedImageOp;

public class HoughLineFilter extends AbstractBufferedImageOp {
	public final static double PI_VALUE = Math.PI;
	private int hough_space = 500;
	private int[] hough_1d;
	private int[][] hough_2d;
	private int width;
	private int height;
	
	private float threshold;
	private float scale;
	private float offset;
	
	public HoughLineFilter() {
		// default hough transform parameters
		//	scale = 1.0f;
		//	offset = 0.0f;
		threshold = 0.5f;
		scale = 1.0f;
		offset = 0.0f;
	}
	
	public void setHoughSpace(int space) {
		this.hough_space = space;
	}
	
	public float getThreshold() {
		return threshold;
	}

	public void setThreshold(float threshold) {
		this.threshold = threshold;
	}

	public float getScale() {
		return scale;
	}

	public void setScale(float scale) {
		this.scale = scale;
	}

	public float getOffset() {
		return offset;
	}

	public void setOffset(float offset) {
		this.offset = offset;
	}

	@Override
	public BufferedImage filter(BufferedImage src, BufferedImage dest) {
		width = src.getWidth();
        height = src.getHeight();

        if ( dest == null )
            dest = createCompatibleDestImage( src, null );

        int[] inPixels = new int[width*height];
        int[] outPixels = new int[width*height];
        getRGB( src, 0, 0, width, height, inPixels );
        houghTransform(inPixels, outPixels);
        setRGB( dest, 0, 0, width, height, outPixels );
        return dest;
	}

	private void houghTransform(int[] inPixels, int[] outPixels) {
        // prepare for hough transform
	    int centerX = width / 2;
	    int centerY = height / 2;
	    double hough_interval = PI_VALUE/(double)hough_space;
	    
	    int max = Math.max(width, height);
	    int max_length = (int)(Math.sqrt(2.0D) * max);
	    hough_1d = new int[2 * hough_space * max_length];
	    
	    // define temp hough 2D array and initialize the hough 2D
	    hough_2d = new int[hough_space][2*max_length];
	    for(int i=0; i<hough_space; i++) {
	    	for(int j=0; j<2*max_length; j++) {
	    		hough_2d[i][j] = 0;
	    	}
	    }
	    
	    // start hough transform now....
	    int[][] image_2d = convert1Dto2D(inPixels);
	    for (int row = 0; row < height; row++) {
	    	for (int col = 0; col < width; col++) {
	        	int p = image_2d[row][col] & 0xff;
	        	if(p == 0) continue; // which means background color
	        	
	        	// since we does not know the theta angle and r value, 
	        	// we have to calculate all hough space for each pixel point
	        	// then we got the max possible theta and r pair.
	        	// r = x * cos(theta) + y * sin(theta)
	        	for(int cell=0; cell < hough_space; cell++ ) {
	        		max = (int)((col - centerX) * Math.cos(cell * hough_interval) + (row - centerY) * Math.sin(cell * hough_interval));
	        		max += max_length; // start from zero, not (-max_length)
	        		if (max < 0 || (max >= 2 * max_length)) {// make sure r did not out of scope[0, 2*max_lenght]
	                    continue;
	                }
	        		hough_2d[cell][max] +=1;
	        	}
	        }
	    }
	    
		// find the max hough value
		int max_hough = 0;
		for(int i=0; i<hough_space; i++) {
			for(int j=0; j<2*max_length; j++) {
				hough_1d[(i + j * hough_space)] = hough_2d[i][j];
				if(hough_2d[i][j] > max_hough) {
					max_hough = hough_2d[i][j];
				}
			}
		}
		System.out.println("MAX HOUGH VALUE = " + max_hough);
		
		// transfer back to image pixels space from hough parameter space
		int hough_threshold = (int)(threshold * max_hough);
	    for(int row = 0; row < hough_space; row++) {
	    	for(int col = 0; col < 2*max_length; col++) {
	    		if(hough_2d[row][col] < hough_threshold) // discard it
	    			continue;
	    		int hough_value = hough_2d[row][col];
	    		boolean isLine = true;
	    		for(int i=-1; i<2; i++) {
	    			for(int j=-1; j<2; j++) {
	    				if(i != 0 || j != 0) {
    		              int yf = row + i;
    		              int xf = col + j;
    		              if(xf < 0) continue;
    		              if(xf < 2*max_length) {
    		            	  if (yf < 0) {
    		            		  yf += hough_space;
    		            	  }
    		                  if (yf >= hough_space) {
    		                	  yf -= hough_space;
    		                  }
    		                  if(hough_2d[yf][xf] <= hough_value) {
    		                	  continue;
    		                  }
    		                  isLine = false;
    		                  break;
    		              }
	    				}
	    			}
	    		}
	    		if(!isLine) continue;
	    		
	    		// transform back to pixel data now...
	            double dy = Math.sin(row * hough_interval);
	            double dx = Math.cos(row * hough_interval);
	            if ((row <= hough_space / 4) || (row >= 3 * hough_space / 4)) {
	                for (int subrow = 0; subrow < height; ++subrow) {
	                  int subcol = (int)((col - max_length - ((subrow - centerY) * dy)) / dx) + centerX;
	                  if ((subcol < width) && (subcol >= 0)) {
	                	  image_2d[subrow][subcol] = -16776961;
	                  }
	                }
	              } else {
	                for (int subcol = 0; subcol < width; ++subcol) {
	                  int subrow = (int)((col - max_length - ((subcol - centerX) * dx)) / dy) + centerY;
	                  if ((subrow < height) && (subrow >= 0)) {
	                	  image_2d[subrow][subcol] = -16776961;
	                  }
	                }
	              }
	    	}
	    }
	    
	    // convert to hough 1D and return result
	    for (int i = 0; i < this.hough_1d.length; i++)
	    {
	      int value = clamp((int)(scale * this.hough_1d[i] + offset)); // scale always equals 1
	      this.hough_1d[i] = (0xFF000000 | value + (value << 16) + (value << 8));
	    }
	    
	    // convert to image 1D and return
	    for (int row = 0; row < height; row++) {
	    	for (int col = 0; col < width; col++) {
	        	outPixels[(col + row * width)] = image_2d[row][col];
	        }
	    }
	}
	
	public BufferedImage getHoughImage() {
		BufferedImage houghImage = new BufferedImage(hough_2d[0].length, hough_space, BufferedImage.TYPE_4BYTE_ABGR);
		setRGB(houghImage, 0, 0, hough_2d[0].length, hough_space, hough_1d);
		return houghImage;
	}
	
	public static int clamp(int value) {
	      if (value < 0)
	    	  value = 0;
	      else if (value > 255) {
	    	  value = 255;
	      }
	      return value;
	}
	
	private int[][] convert1Dto2D(int[] pixels) {
		int[][] image_2d = new int[height][width];
		int index = 0;
		for(int row = 0; row < height; row++) {
			for(int col = 0; col < width; col++) {
				index = row * width + col;
				image_2d[row][col] = pixels[index];
			}
		}
		return image_2d;
	}

}
转载文章请务必注明出自本博客!!

学习图像处理,点击视频教程《数字图像处理-基础入门》




2017-07-08 19:03:01 lindamtd 阅读数 1103

上一篇博文介绍了图像的Canny边缘检测,本文主要介绍图像的直线检测部分,主要使用概率霍夫变换来检测直线,调用的函数为HoughLinesP(),下面给出代码部分以及直线检测效果图:

1、代码部分:

// Detect_Lines.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <cv.h>
#include "highgui.h"
using namespace std;
using namespace cv;
void drawDetectLines(Mat& image,const vector<Vec4i>& lines,Scalar & color)
{     
	// 将检测到的直线在图上画出来    
	vector<Vec4i>::const_iterator it=lines.begin();    
	while(it!=lines.end())    
	{        
		Point pt1((*it)[0],(*it)[1]);       
		Point pt2((*it)[2],(*it)[3]);        
		line(image,pt1,pt2,color,2); //线条宽度设置为2    
		++it;   
	}
} 
int _tmain(int argc, _TCHAR* argv[])
{
	Mat src_img=imread("..\\image_norm\\71253.jpg");
	imshow("src_img",src_img);
	Mat I;    
	cvtColor(src_img,I,CV_BGR2GRAY);                            
	Mat contours;    
	Canny(I,contours,125,350);   
	threshold(contours,contours,128,255,THRESH_BINARY);    
	vector<Vec4i> lines;       
	HoughLinesP(contours,lines,1,CV_PI/180,80,50,10);   
	drawDetectLines(src_img,lines,Scalar(0,255,0));     
	imshow("Detect_Lines",src_img);  
	cvWaitKey(0);
	return 0;
}


2、原图以及直线检测效果图:


至此,已经实现了图像的直线检测部分,将检测出来的直线在原图中画了出来,也可以将检测出来的直线在上一篇博文中的边缘图像中画出来,效果如下:


特别说明,HoughLinesP()函数的一般步骤请参考博文:http://blog.csdn.net/zhaocj/article/details/40047397