首页 > 其他 > 详细

HoughCircle内容整编(HoughTransform和CircleHoughTransform)

时间:2020-06-25 10:32:23      阅读:215      评论:0      收藏:0      [点我收藏+]

一、霍夫(圆)变换的广泛使用和简要历史

霍夫变换是一特征提取方法,被广泛应用在图像处理和计算机视觉应用中。霍夫变换是用来辨别找出物件中的特征,例如:线条。他的算法流程大致如下,给定一个物件、要辨别的形状的种类,算法会在参数空间中执行投票来决定物体的形状,而这是由累加空间(accumulator space)里的局部最大值来决定。

现在广泛使用的霍夫变换是由 Richard Duda 和 Peter Hart 在公元1972年发明,并称之为广义霍夫变换,广义霍夫变换和更早前1962年的Paul Hough 的专利有关,这也是其名称的又来 。 经典的霍夫变换是侦测图片中的直线,之后,霍夫变换不仅能识别直线,也能够识别任何形状,常见的有圆形、椭圆形。霍夫变换在1962年申请为专利U.S. Patent 3,069,654,其专利名为"辨识复杂图案的方法及手段"(Method and Means for Recognizing Complex Patterns)。 任一条直线可以由斜率和截距来表示,在该专利中,利用斜率和截距来将一条直线参数化,然而这会导致无界的转换空间(unbounded transform space),因为斜率有可能是无限大。1981年,因为Dana H. Ballard 的一篇期刊论文 "Generalizing the Hough transform to detect arbitrary shapes",让霍夫变换开始流行于电脑视觉界。

“Hough transform”即使在今天进行搜索,其引用次数也远大于其他经典计算机视觉发现运算符,例如Sobel filtercanny edge detection
技术分享图片技术分享图片技术分享图片

二、调用方法

2.1标准霍夫线变换和统计概率霍夫线变换

OpenCV实现了以下两种霍夫线变换:

  1. 标准霍夫线变换
  • 原理在上面的部分已经说明了. 它能给我们提供一组参数对 技术分享图片 的集合来表示检测到的直线
  • 在OpenCV 中通过函数 HoughLines 来实现
  1. 统计概率霍夫线变换
  • 这是执行起来效率更高的霍夫线变换. 它输出检测到的直线的端点 技术分享图片
  • 在OpenCV 中它通过函数 HoughLinesP 来实现

代码

  1. 这个程序是用来做什么的?
    • 加载一幅图片
    • 对图片进行 标准霍夫线变换 或是 统计概率霍夫线变换.
    • 分别在两个窗口显示原图像和绘出检测到直线的图像.
  2. 我们将要说明的例程能从 这里 下载。 一个更高级的版本 (能同时演示标准霍夫线变换和统计概率霍夫线变换并带有活动条来改变变换的阈值) 能从 这里 下载。
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
void help(){
cout << "\nThis program demonstrates line finding with the Hough transform.\n"
"Usage:\n"
"./houghlines <image_name>, Default is pic1.jpg\n" << endl;}
int main(int argc, char** argv){
const char* filename = argc >= 2 ? argv[1: "pic1.jpg";
Mat src = imread(filename, 0);
if(src.empty())
{
help();
cout << "can not open " << filename << endl;
return -1;
}
Mat dst, cdst;
Canny(src, dst, 502003);
cvtColor(dst, cdst, CV_GRAY2BGR);
#if 0
  vector<Vec2f> lines;
  HoughLines(dst, lines, 1, CV_PI/18010000 );
  for( size_t i = 0; i < lines.size(); i++ )
  {
     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( cdst, pt1, pt2, Scalar(0,0,255), 3, CV_AA);
  }
 #else
  vector<Vec4i> lines;
  HoughLinesP(dst, lines, 1, CV_PI/180505010 );
  for( size_t i = 0; i < lines.size(); i++ )
  {
    Vec4i l = lines[i];
    line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, CV_AA);
  }
 #endif
imshow("source", src);
imshow("detected lines", cdst);
waitKey();
return 0;}

代码说明

  1. 加载图片

    Mat src = imread(filename, 0);if(src.empty()){
    help();
    cout << "can not open " << filename << endl;
    return -1;}
  2. 用Canny算子对图像进行边缘检测

    Canny(src, dst, 502003);

    现在我们将要执行霍夫线变换. 我们将会说明怎样使用OpenCV的函数做到这一点:

  3. 标准霍夫线变换

    1. 首先, 你要执行变换:

      vector<Vec2f> lines;
      HoughLines(dst, lines, 1, CV_PI/18010000 );

      带有以下自变量:

      • dst: 边缘检测的输出图像. 它应该是个灰度图 (但事实上是个二值化图)
      • lines: 储存着检测到的直线的参数对 技术分享图片 的容器 * rho : 参数极径 技术分享图片 以像素值为单位的分辨率. 我们使用 1 像素.
      • theta: 参数极角 技术分享图片 以弧度为单位的分辨率. 我们使用 1度 (即CV_PI/180)
      • threshold: 要”检测” 一条直线所需最少的的曲线交点
      • srn and stn: 参数默认为0. 查缺OpenCV参考文献来获取更多信息.
    2. 通过画出检测到的直线来显示结果.

      for( size_t i = 0; i < lines.size(); i++ ){
      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( cdst, pt1, pt2, Scalar(0,0,255), 3, CV_AA);}
  4. 统计概率霍夫线变换

    1. 首先, 你要执行变换:

      vector<Vec4i> lines;
      HoughLinesP(dst, lines, 1, CV_PI/180505010 );

      带有以下自变量:

      • dst: 边缘检测的输出图像. 它应该是个灰度图 (但事实上是个二值化图) * lines: 储存着检测到的直线的参数对 技术分享图片 的容器
      • rho : 参数极径 技术分享图片 以像素值为单位的分辨率. 我们使用 1 像素.
      • theta: 参数极角 技术分享图片 以弧度为单位的分辨率. 我们使用 1度 (即CV_PI/180)
      • threshold: 要”检测” 一条直线所需最少的的曲线交点 * minLinLength: 能组成一条直线的最少点的数量. 点数量不足的直线将被抛弃.
      • maxLineGap: 能被认为在一条直线上的亮点的最大距离.
    2. 通过画出检测到的直线来显示结果.

      for( size_t i = 0; i < lines.size(); i++ ){
      Vec4i l = lines[i];
      line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, CV_AA);}
  5. 显示原始图像和检测到的直线:

    imshow("source", src);imshow("detected lines", cdst);
  6. 等待用户按键推出程序

    waitKey();

结果

得到的结果使用的是在上面 代码 部分提到的更高级版代码. 霍夫线变换的代码没有改变, 唯一不同的是在GUI的部分加入了活动条可动态改变阈值.输入图像为:

技术分享图片

通过执行统计概率霍夫线变换我们能得到下面的结果:

技术分享图片

当你使用滑动条来改变 阈值 的时候会观察到检测到线的数目的改变. 这是因为: 如果你设置了一个更大的阈值, 能检测到的线的数目将更少 (你需要更多的点来表示一条能检测到的直线).

2.2霍夫圆变换  技术分享图片

#include <opencv2/imgproc.hpp>

Finds circles in a grayscale image using the Hough transform.

The function finds circles in a grayscale image using a modification of the Hough transform.

Example: 

#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <math.h>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
    Mat img, gray;
    if( argc != 2 || !(img=imread(argv[1], 1)).data)
        return -1;
    cvtColor(img, gray, COLOR_BGR2GRAY);
    // smooth it, otherwise a lot of false circles may be detected
    GaussianBlur( gray, gray, Size(9, 9), 2, 2 );
    vector<Vec3f> circles;
    HoughCircles(gray, circles, HOUGH_GRADIENT,2, gray.rows/4, 200, 100 );
    for( size_t i = 0; i < circles.size(); i++ )
    {
         Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
         int radius = cvRound(circles[i][2]);
         // draw the circle center
         circle( img, center, 3, Scalar(0,255,0), -1, 8, 0 );
         // draw the circle outline
         circle( img, center, radius, Scalar(0,0,255), 3, 8, 0 );
    }
    namedWindow( "circles", 1 );
    imshow( "circles", img );
    waitKey(0);
    return 0;
}

Note

Usually the function detects the centers of circles well. However, it may fail to find correct radii. You can assist to the function by specifying the radius range ( minRadius and maxRadius ) if you know it. Or, in the case of HOUGH_GRADIENT method you may set maxRadius to a negative number to return centers only without radius search, and find the correct radius using an additional procedure.

It also helps to smooth image a bit unless it‘s already soft. For example, GaussianBlur() with 7x7 kernel and 1.5x1.5 sigma or similar blurring may help.

Parameters
image8-bit, single-channel, grayscale input image.
circlesOutput vector of found circles. Each vector is encoded as 3 or 4 element floating-point vector (x,y,radius) or (x,y,radius,votes) .
methodDetection method, see HoughModes. The available methods are HOUGH_GRADIENT and HOUGH_GRADIENT_ALT.
dpInverse ratio of the accumulator resolution to the image resolution. For example, if dp=1 , the accumulator has the same resolution as the input image. If dp=2 , the accumulator has half as big width and height. For HOUGH_GRADIENT_ALT the recommended value is dp=1.5, unless some small very circles need to be detected.
minDistMinimum distance between the centers of the detected circles. If the parameter is too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is too large, some circles may be missed.
param1First method-specific parameter. In case of HOUGH_GRADIENT and HOUGH_GRADIENT_ALT, it is the higher threshold of the two passed to the Canny edge detector (the lower one is twice smaller). Note that HOUGH_GRADIENT_ALT uses Scharr algorithm to compute image derivatives, so the threshold value shough normally be higher, such as 300 or normally exposed and contrasty images.
param2Second method-specific parameter. In case of HOUGH_GRADIENT, it is the accumulator threshold for the circle centers at the detection stage. The smaller it is, the more false circles may be detected. Circles, corresponding to the larger accumulator values, will be returned first. In the case of HOUGH_GRADIENT_ALT algorithm, this is the circle "perfectness" measure. The closer it to 1, the better shaped circles algorithm selects. In most cases 0.9 should be fine. If you want get better detection of small circles, you may decrease it to 0.85, 0.8 or even less. But then also try to limit the search range [minRadius, maxRadius] to avoid many false circles.
minRadiusMinimum circle radius.
maxRadiusMaximum circle radius. If <= 0, uses the maximum image dimension. If < 0, HOUGH_GRADIENT returns centers without finding the radius. HOUGH_GRADIENT_ALT always computes circle radiuses.

HOUGH圆检测,得益于一个新算法HOUGH_GRADIENT_ALT支持,比以前有很大程度的精度提升,测试对比如下:

技术分享图片

对于这个算法,我的评测是使用它来处理“不知道半径”的情况,效果要比原算法好;但是目前我们只采用原算法。

三、理论

在自动化分析数位图片的问题里,其中一个常有的子问题是侦测某些简单的直线圆形椭圆形。在多数情况下,边缘侦测器(edge detector)会先用来做图片前处理,将原本的图片变成只含有边缘的图片。 因为图片的不完美或是边缘侦测的不完美,导致有些点(point)或像素(pixel)缺漏,或是有噪声使得边缘侦测器所得的边界偏离了实际的边界。所以无法直观的将检测出的边缘分成直线、圆形、椭圆形的集合, 而霍夫变换解决上述问题,借由霍夫变换算法中的投票步骤,在复杂的参数空间中找到图形的参数,电脑可以由参数得知该边缘(edge)是哪种形状。

2.1 最简单的霍夫变换是在图像中识别直线。

2.1.1 在平面直角坐标系(x-y)中,一条直线可以用方程式

技术分享图片

表示,技术分享图片技术分享图片可以视为参数空间技术分享图片中的一点。当直线垂直于轴时,斜率为无限大, 若用电脑数值计算时会很不方便。

2.1.2 Duda 和 Hart 提出使用Hesse normal form来表示直线的参数,但是这个本身的原理比较繁琐,我们可以通过图像和极坐标的方法来进行简化。

技术分享图片
一方面,x=0的时候y的值,这个点是可以确定的;r/sin(Θ)      f(0) =  r/sin(Θ

另一方面,这条直线的斜率(红色)是tan(PI-Θ) = tan(PI/2 + Θ),根据诱导公式: tan(π/2+α)=-cotα ,也就是斜率为:-cot(Θ)

根据2.1.1中得到的定义,这条直线可以表示为:


技术分享图片

进行化简便可得到(简单变换):

技术分享图片

2.1.3 对于一个给定点我们在极坐标对极径极角平面绘出所有通过它的直线, 将得到一条正弦曲线. 例如, 对于给定点X_0= 8 和Y_0= 6 我们可以绘出下图 (在平面,  .技术分享图片):
 技术分享图片
这个变化非常有价值,是整个Hough变化的核心!

那么给定一个点技术分享图片,通过该点的所有直线的参数技术分享图片的集合,会在技术分享图片平面上形成一个三角函数,可由下方程式证明技术分享图片

因此,给定很多点,判断这些点是否共线(concurrent lines)的问题,经由霍夫变换之后,变成判断一堆曲线(每一个点在技术分享图片平面上代表一条曲线)是否 在技术分享图片平面上相交于同一点的问题(concurrent curves)。继续上面那张抽象的图,如果两个不同点进行上述操作后得到的曲线在平面相交, 这就意味着它们通过同一条直线. 例如,接上面的例子我们继续绘图, 得到下图:

 
技术分享图片

这三条曲线在平面相交于点 (0.925, 9.6), 坐标表示的是参数对  技术分享图片或者是说点技术分享图片, 点技术分享图片和点技术分享图片组成的平面内的的直线。
以上的说明表明,一般来说, 一条直线能够通过在平面  寻找交于一点的曲线数量来检测。而越多曲线交于一点也就意味着这个交点表示的直线由更多的点组成. 一般来说我们可以通过设置直线上点的阈值来定义多少条曲线交于一点我们才认为检测到了一条直线。

这就是霍夫线变换要做的. 它追踪图像中每个点对应曲线间的交点. 如果交于一点的曲线的数量超过了阈值, 那么可以认为这个交点所代表的参数对在原图像中为一条直线。它的最大特点在于作业域的变化。

2.1.4范例:入的图片中有两条粗直线,经过霍夫变换后的结果得到accumaltor矩阵,右图就是把accumaltor矩阵画出来,越亮值越大,越黑值越小。在右图中,有两个很明显的亮点, 这两个亮点分别代表两条不同参数的直线,与输入的图片(左图)吻合。然后读取矩阵的两个最大值就可以得出这两条线距画面中心距离以及角度。

技术分享图片




霍夫变换的效率取决于输入图片的品质,边缘必须要正确呈现才能让霍夫变换有效率,当图片有噪声的时候,在霍夫变换的前一级要做抑制噪声的动作。

2.2、Circle Hough Transform

圆霍夫变换(CHT)是一种用于数字图像处理中的基本特征提取技术,用于检测不完整图像中的圆。通过在Hough参数空间中进行“投票”,然后在累加器矩阵中选择局部最大值来生成候选圆。 
它是霍夫变换的一种特殊形式。

2.2.1理论

二维图像中,圆可以这样来表示:

技术分享图片
其中(a,b)是圆的中心,r是半径。如果二维点(x,y)是固定的,则可以根据(1)找到参数。参数空间将是三维(a,b,r)。并且所有满足(x,y)的参数都将位于顶点为(x,y,0)的倒置直角锥的表面上。在3D空间中,圆弧参数可以通过2D圆弧上的点定义的许多圆锥曲面的交点来识别。这个过程可以分为两个阶段。第一个阶段是固定半径,然后在2D参数空间中找到最佳的圆心。第二阶段是在一维参数空间中找到最佳半径。

  圆检测是视觉处理的基础应用问题 目前主流的检测算法是基于hough变换的方法 其基本思想是将图像的空间域变换到参数空间 用大多数边界点满足的某种参数形式来描述图像中的边缘曲线 通过累加投票求得峰值对应点即为有效图元信息 该方法具有可靠性高 对噪声 变形部分区域残缺 边缘不连续适应性强等特点 但传统hough变换有如下缺陷 1、一到多的参数映射引起的计算量大;2、内存占用空间大;3、参数量化间隔标准确定难。
技术分享图片
查找已知半径R的参数
如果半径固定,则参数空间将减小为2D(圆心的位置)。对于原始圆上的每个点(x,y),它可以根据(1)定义一个以(x,y)为中心的半径为R的圆。参数空间中所有此类圆的交点将对应于原始圆的中心点。
技术分享图片
考虑原始图像中一个圆上的4个点(左)。圆圈霍夫变换显示在右侧。注意,假定半径是已知的。对于原始图像中四个点(白点)中的每个(x,y),它可以在Hough参数空间中定义一个以半径r为中心的(x,y)圆。累加器矩阵用于跟踪相交点。在参数空间中,圆通过的点的投票数将增加一。然后可以找到局部最大值点(右图中心的红色点)。最大值的位置(a,b)将是原始圆的中心。
 
具有已知半径R的多个圆
用相同的技术可以找到半径相同的多个圆
技术分享图片
 
累加器矩阵和投票
在实践中,引入累加器矩阵以找到参数空间中的交点。首先,我们需要使用网格将参数空间划分为“存储桶”,并根据网格生成累加器矩阵。累加器矩阵中的元素表示参数空间中穿过参数空间中相应网格单元的“圆”数。该号码也称为“投票号码”。最初,矩阵中的每个元素均为零。然后,对于原始空间中的每个“边缘”点,我们可以在参数空间中制定一个圆,并增加圆通过的网格单元的投票数。此过程称为“投票”。
投票后,我们可以在累加器矩阵中找到局部最大值。局部最大值的位置与原始空间中的圆心相对应。【如果半径已经知道的话,将极大程度降低这里的运算量】
查找未知半径的圆参数
由于参数空间是3D,因此累加器矩阵也将是3D。我们可以遍历可能的半径;对于每个半径,我们使用以前的技术。最后,在3D累加器矩阵中找到局部最大值。累加器数组在3D空间中应为A [x,y,r]。投票应针对每个像素,半径和theta A [x,y,r] + = 1。
算法:
对于每个A [a,b,r] = 0;
在图像高斯模糊上处理滤波算法,将图像转换为灰度(grayScaling),使Canny运算符,Canny运算符给出图像的边缘。
表决累加器中所有可能的圆圈。
累加器A的局部最大投票圈子给出了圈子Hough空间。
累加器的最大投票圈子给出了该圈子。
 For each pixel(x,y)
    For each radius r = 10 to r = 60 // the possible radius
      For each theta t = 0 to 360  // the possible  theta 0 to 360
         a = x – r * cos(t * PI / 180); //polar coordinate for center
         b = y – r * sin(t * PI / 180);  //polar coordinate for center
         A[a,b,r] +=1; //voting
      end
    end
  end
技术分享图片

首先使用阈值和高斯滤波器将原始图片(右)转换为二进制图像(左)。然后使用Canny边缘检测从中找到边缘(中间)。此后,圆弧霍夫变换使用所有的边缘点来找到基本的圆弧结构。
局限性
由于CHT的参数空间是三维的,因此可能需要大量的存储和计算。选择更大的网格大小可以缓解此问题。
但是,选择合适的网格尺寸很困难。由于网格太粗,可能导致错误地获得较大的投票值,因为许多完全不同的结构对应于单个存储桶。网格太细会导致找不到结构,因为未完全对齐的令牌产生的投票最终会出现在不同的存储桶中,并且没有存储桶拥有较大的投票权。

三、代码解析:
在OpenCV中,霍夫线变换是一种用来寻找直线的方法. 在使用霍夫线变换之前, 首先要对图像进行边缘检测的处理,也即霍夫线变换的直接输入只能是边缘二值图像.
OpenCV中的霍夫线变换有如下三种:
<1>标准霍夫变换(StandardHough Transform,SHT),由HoughLines函数调用。
<2>多尺度霍夫变换(Multi-ScaleHough Transform,MSHT),由HoughLines函数调用。
<3>累计概率霍夫变换(ProgressiveProbabilistic Hough Transform,PPHT),由HoughLinesP函数调用。
代码位置:

带有以下自变量:

  • dst: 边缘检测的输出图像. 它应该是个灰度图 (但事实上是个二值化图)
  • lines: 储存着检测到的直线的参数对 技术分享图片 的容器 * 
  • rho : 参数极径 技术分享图片 以像素值为单位的分辨率. 我们使用 1 像素.
  • theta: 参数极角 技术分享图片 以弧度为单位的分辨率. 我们使用 1度 (即CV_PI/180)
  • threshold: 要”检测” 一条直线所需最少的的曲线交点
  • srn and stn: 参数默认为0. 查缺OpenCV参考文献来获取更多信息.
技术分享图片

static void HoughLinesStandard( InputArray src, OutputArray lines, int type,float rho, float theta,int threshold, int linesMax,double min_theta, double max_theta )
{
    CV_CheckType(type, type == CV_32FC2 || type == CV_32FC3, "Internal error");
    Mat img = src.getMat();
    int i, j;
    float irho = 1 / rho;
    CV_Assert( img.type() == CV_8UC1 );
    CV_Assert( linesMax > 0 );
    const uchar* image = img.ptr();
    int step = (int)img.step;
    int width = img.cols;
    int height = img.rows;
    int max_rho = width + height;
    int min_rho = -max_rho;
    CV_CheckGE(max_theta, min_theta, "max_theta must be greater than min_theta");
    int numangle = cvRound((max_theta - min_theta) / theta);
    int numrho = cvRound(((max_rho - min_rho) + 1/ rho);
    Mat _accum = Mat::zeros( (numangle+2), (numrho+2), CV_32SC1 );
    std::vector<int> _sort_buf;
    AutoBuffer<float> _tabSin(numangle);
    AutoBuffer<float> _tabCos(numangle);
    int *accum = _accum.ptr<int>();
    float *tabSin = _tabSin.data(), *tabCos = _tabCos.data();
    // create sin and cos table
    createTrigTable( numangle, min_theta, theta, irho, tabSin, tabCos);
    // stage 1. fill accumulator
    for( i = 0; i < height; i++ )
        for( j = 0; j < width; j++ )
        {
            if( image[i * step + j] != 0 )
                for(int n = 0; n < numangle; n++ )
                {
                    int r = cvRound( j * tabCos[n] + i * tabSin[n] );
                    r += (numrho - 1/ 2;
                    accum[(n+1* (numrho+2+ r+1]++;
                }
        }
    // stage 2. find local maximums
    findLocalMaximums( numrho, numangle, threshold, accum, _sort_buf );
    // stage 3. sort the detected lines by accumulator value
    std::sort(_sort_buf.begin(), _sort_buf.end(), hough_cmp_gt(accum));
    // stage 4. store the first min(total,linesMax) lines to the output buffer
    linesMax = std::min(linesMax, (int)_sort_buf.size());
    double scale = 1./(numrho+2);
    lines.create(linesMax, 1, type);
    Mat _lines = lines.getMat();
    for( i = 0; i < linesMax; i++ )
    {
        LinePolar line;
        int idx = _sort_buf[i];
        int n = cvFloor(idx*scale) - 1;
        int r = idx - (n+1)*(numrho+2- 1;
        line.rho = (r - (numrho - 1)*0.5f* rho;
        line.angle = static_cast<float>(min_theta) + n * theta;
        if (type == CV_32FC2)
        {
            _lines.at<Vec2f>(i) = Vec2f(line.rho, line.angle);
        }
        else
        {
            CV_DbgAssert(type == CV_32FC3);
            _lines.at<Vec3f>(i) = Vec3f(line.rho, line.angle, (float)accum[idx]);
        }
    }
}

static void
HoughLinesSDiv( InputArray image, OutputArray lines, int type,
                float rho, float theta, int threshold,
                int srn, int stn, int linesMax,
                double min_theta, double max_theta )
{
    CV_CheckType(type, type == CV_32FC2 || type == CV_32FC3, "Internal error");
    #define _POINT(row, column)\
        (image_src[(row)*step+(column)])
    Mat img = image.getMat();
    int index, i;
    int ri, ti, ti1, ti0;
    int row, col;
    float r, t;                 /* Current rho and theta */
    float rv;                   /* Some temporary rho value */
    int fn = 0;
    float xc, yc;
    const float d2r = (float)(CV_PI / 180);
    int sfn = srn * stn;
    int fi;
    int count;
    int cmax = 0;
    std::vector<hough_index> lst;
    CV_Assert( img.type() == CV_8UC1 );
    CV_Assert( linesMax > 0 );
    threshold = MIN( threshold, 255 );
    const uchar* image_src = img.ptr();
    int step = (int)img.step;
    int w = img.cols;
    int h = img.rows;
    float irho = 1 / rho;
    float itheta = 1 / theta;
    float srho = rho / srn;
    float stheta = theta / stn;
    float isrho = 1 / srho;
    float istheta = 1 / stheta;
    int rn = cvFloor( std::sqrt( (double)w * w + (double)h * h ) * irho );
    int tn = cvFloor( 2 * CV_PI * itheta );
    lst.push_back(hough_index(threshold, -1.f, 0.f));
    // Precalculate sin table
    std::vector<float> _sinTable( 5 * tn * stn );
    float* sinTable = &_sinTable[0];
    for( index = 0; index < 5 * tn * stn; index++ )
        sinTable[index] = (float)cos( stheta * index * 0.2f );
    std::vector<uchar> _caccum(rn * tn, (uchar)0);
    uchar* caccum = &_caccum[0];
    // Counting all feature pixels
    for( row = 0; row < h; row++ )
        for( col = 0; col < w; col++ )
            fn += _POINT( row, col ) != 0;
    std::vector<int> _x(fn), _y(fn);
    int* x = &_x[0], *= &_y[0];
    // Full Hough Transform (it‘s accumulator update part)
    fi = 0;
    for( row = 0; row < h; row++ )
    {
        for( col = 0; col < w; col++ )
        {
            if( _POINT( row, col ))
            {
                int halftn;
                float r0;
                float scale_factor;
                int iprev = -1;
                float phi, phi1;
                float theta_it;     // Value of theta for iterating
                // Remember the feature point
                x[fi] = col;
                y[fi] = row;
                fi++;
                yc = (float) row + 0.5f;
                xc = (float) col + 0.5f;
                /* Update the accumulator */
                t = (float) fabs( cvFastArctan( yc, xc ) * d2r );
                r = (float) std::sqrt( (double)xc * xc + (double)yc * yc );
                r0 = r * irho;
                ti0 = cvFloor( (t + CV_PI*0.5* itheta );
                caccum[ti0]++;
                theta_it = rho / r;
                theta_it = theta_it < theta ? theta_it : theta;
                scale_factor = theta_it * itheta;
                halftn = cvFloor( CV_PI / theta_it );
                for( ti1 = 1, phi = theta_it - (float)(CV_PI*0.5), phi1 = (theta_it + t) * itheta;
                     ti1 < halftn; ti1++, phi += theta_it, phi1 += scale_factor )
                {
                    rv = r0 * std::cos( phi );
                    i = (int)rv * tn;
                    i += cvFloor( phi1 );
                    assert( i >= 0 );
                    assert( i < rn * tn );
                    caccum[i] = (uchar) (caccum[i] + ((i ^ iprev) != 0));
                    iprev = i;
                    if( cmax < caccum[i] )
                        cmax = caccum[i];
                }
            }
        }
    }
    // Starting additional analysis
    count = 0;
    for( ri = 0; ri < rn; ri++ )
    {
        for( ti = 0; ti < tn; ti++ )
        {
            if( caccum[ri * tn + ti] > threshold )
                count++;
        }
    }
    if( count * 100 > rn * tn )
    {
        HoughLinesStandard( image, lines, type, rho, theta, threshold, linesMax, min_theta, max_theta );
        return;
    }
    std::vector<uchar> _buffer(srn * stn + 2);
    uchar* buffer = &_buffer[0];
    uchar* mcaccum = buffer + 1;
    count = 0;
    for( ri = 0; ri < rn; ri++ )
    {
        for( ti = 0; ti < tn; ti++ )
        {
            if( caccum[ri * tn + ti] > threshold )
            {
                count++;
                memset( mcaccum, 0, sfn * sizeof( uchar ));
                for( index = 0; index < fn; index++ )
                {
                    int ti2;
                    float r0;
                    yc = (float) y[index] + 0.5f;
                    xc = (float) x[index] + 0.5f;
                    // Update the accumulator
                    t = (float) fabs( cvFastArctan( yc, xc ) * d2r );
                    r = (float) std::sqrt( (double)xc * xc + (double)yc * yc ) * isrho;
                    ti0 = cvFloor( (t + CV_PI * 0.5* istheta );
                    ti2 = (ti * stn - ti0) * 5;
                    r0 = (float) ri *srn;
                    for( ti1 = 0; ti1 < stn; ti1++, ti2 += 5 )
                    {
                        rv = r * sinTable[(int) (std::abs( ti2 ))] - r0;
                        i = cvFloor( rv ) * stn + ti1;
                        i = CV_IMAX( i, -1 );
                        i = CV_IMIN( i, sfn );
                        mcaccum[i]++;
                        assert( i >= -1 );
                        assert( i <= sfn );
                    }
                }
                // Find peaks in maccum...
                for( index = 0; index < sfn; index++ )
                {
                    int pos = (int)(lst.size() - 1);
                    if( pos < 0 || lst[pos].value < mcaccum[index] )
                    {
                        hough_index vi(mcaccum[index],
                                       index / stn * srho + ri * rho,
                                       index % stn * stheta + ti * theta - (float)(CV_PI*0.5));
                        lst.push_back(vi);
                        for( ; pos >= 0; pos-- )
                        {
                            if( lst[pos].value > vi.value )
                                break;
                            lst[pos+1= lst[pos];
                        }
                        lst[pos+1= vi;
                        if( (int)lst.size() > linesMax )
                            lst.pop_back();
                    }
                }
            }
        }
    }
    lines.create((int)lst.size(), 1, type);
    Mat _lines = lines.getMat();
    for( size_t idx = 0; idx < lst.size(); idx++ )
    {
        if( lst[idx].rho < 0 )
            continue;
        if (type == CV_32FC2)
        {
            _lines.at<Vec2f>((int)idx) = Vec2f(lst[idx].rho, lst[idx].theta);
        }
        else
        {
            CV_DbgAssert(type == CV_32FC3);
            _lines.at<Vec3f>((int)idx) = Vec3f(lst[idx].rho, lst[idx].theta, (float)lst[idx].value);
        }
    }
}
static void
HoughLinesProbabilistic( Mat& image,
                         float rho, float theta, int threshold,
                         int lineLength, int lineGap,
                         std::vector<Vec4i>& lines, int linesMax )
{
    Point pt;
    float irho = 1 / rho;
    RNG rng((uint64)-1);
    CV_Assert( image.type() == CV_8UC1 );
    int width = image.cols;
    int height = image.rows;
    int numangle = cvRound(CV_PI / theta);
    int numrho = cvRound(((width + height) * 2 + 1/ rho);
#if defined HAVE_IPP && IPP_VERSION_X100 >= 810 && !IPP_DISABLE_HOUGH
    CV_IPP_CHECK()
    {
        IppiSize srcSize = { width, height };
        IppPointPolar delta = { rho, theta };
        IppiHoughProbSpec* pSpec;
        int bufferSize, specSize;
        int ipp_linesMax = std::min(linesMax, numangle*numrho);
        int linesCount = 0;
        lines.resize(ipp_linesMax);
        IppStatus ok = ippiHoughProbLineGetSize_8u_C1R(srcSize, delta, &specSize, &bufferSize);
        Ipp8u* buffer = ippsMalloc_8u_L(bufferSize);
        pSpec = (IppiHoughProbSpec*) ippsMalloc_8u_L(specSize);
        if (ok >= 0) ok = ippiHoughProbLineInit_8u32f_C1R(srcSize, delta, ippAlgHintNone, pSpec);
        if (ok >= 0) {ok = CV_INSTRUMENT_FUN_IPP(ippiHoughProbLine_8u32f_C1R, image.data, (int)image.step, srcSize, threshold, lineLength, lineGap, (IppiPoint*&lines[0], ipp_linesMax, &linesCount, buffer, pSpec);};
        ippsFree(pSpec);
        ippsFree(buffer);
        if (ok >= 0)
        {
            lines.resize(linesCount);
            CV_IMPL_ADD(CV_IMPL_IPP);
            return;
        }
        lines.clear();
        setIppErrorStatus();
    }
#endif
    Mat accum = Mat::zeros( numangle, numrho, CV_32SC1 );
    Mat mask( height, width, CV_8UC1 );
    std::vector<float> trigtab(numangle*2);
    forint n = 0; n < numangle; n++ )
    {
        trigtab[n*2= (float)(cos((double)n*theta) * irho);
        trigtab[n*2+1= (float)(sin((double)n*theta) * irho);
    }
    const float* ttab = &trigtab[0];
    uchar* mdata0 = mask.ptr();
    std::vector<Point> nzloc;
    // stage 1. collect non-zero image points
    for( pt.y = 0; pt.y < height; pt.y++ )
    {
        const uchar* data = image.ptr(pt.y);
        uchar* mdata = mask.ptr(pt.y);
        for( pt.x = 0; pt.x < width; pt.x++ )
        {
            if( data[pt.x] )
            {
                mdata[pt.x] = (uchar)1;
                nzloc.push_back(pt);
            }
            else
                mdata[pt.x] = 0;
        }
    }
    int count = (int)nzloc.size();
    // stage 2. process all the points in random order
    for( ; count > 0; count-- )
    {
        // choose random point out of the remaining ones
        int idx = rng.uniform(0, count);
        int max_val = threshold-1, max_n = 0;
        Point point = nzloc[idx];
        Point line_end[2];
        float a, b;
        int* adata = accum.ptr<int>();
        int i = point.y, j = point.x, k, x0, y0, dx0, dy0, xflag;
        int good_line;
        const int shift = 16;
        // "remove" it by overriding it with the last element
        nzloc[idx] = nzloc[count-1];
        // check if it has been excluded already (i.e. belongs to some other line)
        if!mdata0[i*width + j] )
            continue;
        // update accumulator, find the most probable line
        forint n = 0; n < numangle; n++, adata += numrho )
        {
            int r = cvRound( j * ttab[n*2+ i * ttab[n*2+1] );
            r += (numrho - 1/ 2;
            int val = ++adata[r];
            if( max_val < val )
            {
                max_val = val;
                max_n = n;
            }
        }
        // if it is too "weak" candidate, continue with another point
        if( max_val < threshold )
            continue;
        // from the current point walk in each direction
        // along the found line and extract the line segment
        a = -ttab[max_n*2+1];
        b = ttab[max_n*2];
        x0 = j;
        y0 = i;
        if( fabs(a) > fabs(b) )
        {
            xflag = 1;
            dx0 = a > 0 ? 1 : -1;
            dy0 = cvRound( b*(1 << shift)/fabs(a) );
            y0 = (y0 << shift) + (1 << (shift-1));
        }
        else
        {
            xflag = 0;
            dy0 = b > 0 ? 1 : -1;
            dx0 = cvRound( a*(1 << shift)/fabs(b) );
            x0 = (x0 << shift) + (1 << (shift-1));
        }
        for( k = 0; k < 2; k++ )
        {
            int gap = 0, x = x0, y = y0, dx = dx0, dy = dy0;
            if( k > 0 )
                dx = -dx, dy = -dy;
            // walk along the line using fixed-point arithmetic,
            // stop at the image border or in case of too big gap
            for( ;; x += dx, y += dy )
            {
                uchar* mdata;
                int i1, j1;
                if( xflag )
                {
                    j1 = x;
                    i1 = y >> shift;
                }
                else
                {
                    j1 = x >> shift;
                    i1 = y;
                }
                if( j1 < 0 || j1 >= width || i1 < 0 || i1 >= height )
                    break;
                mdata = mdata0 + i1*width + j1;
                // for each non-zero point:
                //    update line end,
                //    clear the mask element
                //    reset the gap
                if*mdata )
                {
                    gap = 0;
                    line_end[k].y = i1;
                    line_end[k].x = j1;
                }
                else if++gap > lineGap )
                    break;
            }
        }
        good_line = std::abs(line_end[1].x - line_end[0].x) >= lineLength ||
                    std::abs(line_end[1].y - line_end[0].y) >= lineLength;
        for( k = 0; k < 2; k++ )
        {
            int x = x0, y = y0, dx = dx0, dy = dy0;
            if( k > 0 )
                dx = -dx, dy = -dy;
            // walk along the line using fixed-point arithmetic,
            // stop at the image border or in case of too big gap
            for( ;; x += dx, y += dy )
            {
                uchar* mdata;
                int i1, j1;
                if( xflag )
                {
                    j1 = x;
                    i1 = y >> shift;
                }
                else
                {
                    j1 = x >> shift;
                    i1 = y;
                }
                mdata = mdata0 + i1*width + j1;
                // for each non-zero point:
                //    update line end,
                //    clear the mask element
                //    reset the gap
                if*mdata )
                {
                    if( good_line )
                    {
                        adata = accum.ptr<int>();
                        forint n = 0; n < numangle; n++, adata += numrho )
                        {
                            int r = cvRound( j1 * ttab[n*2+ i1 * ttab[n*2+1] );
                            r += (numrho - 1/ 2;
                            adata[r]--;
                        }
                    }
                    *mdata = 0;
                }
                if( i1 == line_end[k].y && j1 == line_end[k].x )
                    break;
            }
        }
        if( good_line )
        {
            Vec4i lr(line_end[0].x, line_end[0].y, line_end[1].x, line_end[1].y);
            lines.push_back(lr);
            if( (int)lines.size() >= linesMax )
                return;
        }
    }
}
而且,CHT对噪声不是很鲁棒。
技术分享图片
函数定义,在hough.cpp的结尾的地方:
技术分享图片
static void HoughCircles( InputArray _image, OutputArray _circles,
                          int method, double dp, double minDist,
                          double param1, double param2,
                          int minRadius, int maxRadius,
                          int maxCircles, double param3 )
{
    CV_INSTRUMENT_REGION();
    int type = CV_32FC3;
    if( _circles.fixedType() )
    {
        type = _circles.type();
        CV_CheckType(type, type == CV_32FC3 || type == CV_32FC4, "Wrong type of output circles");
    }
    CV_Assert(!_image.empty() && _image.type() == CV_8UC1 && (_image.isMat() || _image.isUMat()));
    CV_Assert(_circles.isMat() || _circles.isVector());
    if( dp <= 0 || minDist <= 0 || param1 <= 0 || param2 <= 0)
        CV_Error( Error::StsOutOfRange, "dp, min_dist, canny_threshold and acc_threshold must be all positive numbers" );
    int cannyThresh = cvRound(param1), accThresh = cvRound(param2), kernelSize = cvRound(param3);
    minRadius = std::max(0, minRadius);
    if(maxCircles < 0)
        maxCircles = INT_MAX;
    bool centersOnly = (maxRadius < 0);
    if( maxRadius <= 0 )
        maxRadius = std::max( _image.rows(), _image.cols() );
    else if( maxRadius <= minRadius )
        maxRadius = minRadius + 2;
    switch( method )
    {
    case CV_HOUGH_GRADIENT:
        if (type == CV_32FC3)
            HoughCirclesGradient<Vec3f>(_image, _circles, (float)dp, (float)minDist,
                                        minRadius, maxRadius, cannyThresh,
                                        accThresh, maxCircles, kernelSize, centersOnly);
        else if (type == CV_32FC4)
            HoughCirclesGradient<Vec4f>(_image, _circles, (float)dp, (float)minDist,
                                        minRadius, maxRadius, cannyThresh,
                                        accThresh, maxCircles, kernelSize, centersOnly);
        else
            CV_Error(Error::StsError, "Internal error");
        break;
    default:
        CV_Error( Error::StsBadArg, "Unrecognized method id. Actually only CV_HOUGH_GRADIENT is supported." );
    }
}
void HoughCircles( InputArray _image, OutputArray _circles,
                   int method, double dp, double minDist,
                   double param1, double param2,
                   int minRadius, int maxRadius )
{
    HoughCircles(_image, _circles, method, dp, minDist, param1, param2, minRadius, maxRadius, -13);
}
主要是调用了 HoughCirclesGradient【主要函数应该是在这里】

static void HoughCirclesGradient(InputArray _image, OutputArray _circles,
                                 float dp, float minDist,
                                 int minRadius, int maxRadius, int cannyThreshold,
                                 int accThreshold, int maxCircles, int kernelSize, bool centersOnly)
{
    CV_Assert(kernelSize == -1 || kernelSize == 3 || kernelSize == 5 || kernelSize == 7);
    dp = max(dp, 1.f);
    float idp = 1.f/dp;
    Mat edges, dx, dy;
    Sobel(_image, dx, CV_16S, 10, kernelSize, 10, BORDER_REPLICATE);
    Sobel(_image, dy, CV_16S, 01, kernelSize, 10, BORDER_REPLICATE);
    Canny(dx, dy, edges, std::max(1, cannyThreshold / 2), cannyThreshold, false);
    Mutex mtx;
    int numThreads = std::max(1, getNumThreads());
    std::vector<Mat> accumVec;
    NZPointSet nz(_image.rows(), _image.cols());
    parallel_for_(Range(0, edges.rows),
                  HoughCirclesAccumInvoker(edges, dx, dy, minRadius, maxRadius, idp, accumVec, nz, mtx),
                  numThreads);
    int nzSz = cv::countNonZero(nz.positions);
    if(nzSz <= 0)
        return;
    Mat accum = accumVec[0];
    for(size_t i = 1; i < accumVec.size(); i++)
    {
        accum += accumVec[i];
    }
    accumVec.clear();
    std::vector<int> centers;
    // 4 rows when multithreaded because there is a bit overhead
    // and on the other side there are some row ranges where centers are concentrated
    parallel_for_(Range(1, accum.rows - 1),
                  HoughCirclesFindCentersInvoker(accum, centers, accThreshold, mtx),
                  (numThreads > 1? ((accum.rows - 2/ 4: 1);
    int centerCnt = (int)centers.size();
    if(centerCnt == 0)
        return;
    std::sort(centers.begin(), centers.end(), hough_cmp_gt(accum.ptr<int>()));
    std::vector<CircleType> circles;
    circles.reserve(256);
    if (centersOnly)
    {
        // Just get the circle centers
        GetCircleCenters(centers, circles, accum.cols, minDist, dp);
    }
    else
    {
        std::vector<EstimatedCircle> circlesEst;
        if (nzSz < maxRadius * maxRadius)
        {
            // Faster to use a list
            NZPointList nzList(nzSz);
            nz.toList(nzList);
            // One loop iteration per thread if multithreaded.
            parallel_for_(Range(0, centerCnt),
                HoughCircleEstimateRadiusInvoker<NZPointList>(nzList, nzSz, centers, circlesEst, accum.cols,
                    accThreshold, minRadius, maxRadius, dp, mtx),
                numThreads);
        }
        else
        {
            // Faster to use a matrix
            // One loop iteration per thread if multithreaded.
            parallel_for_(Range(0, centerCnt),
                HoughCircleEstimateRadiusInvoker<NZPointSet>(nz, nzSz, centers, circlesEst, accum.cols,
                    accThreshold, minRadius, maxRadius, dp, mtx),
                numThreads);
        }
        // Sort by accumulator value
        std::sort(circlesEst.begin(), circlesEst.end(), cmpAccum);
        // Create Circles
        CreateCircles(circlesEst, circles);
        RemoveOverlaps(circles, minDist);
    }
    if (circles.size() > 0)
    {
        int numCircles = std::min(maxCircles, int(circles.size()));
        Mat(1, numCircles, cv::traits::Type<CircleType>::value, &circles[0]).copyTo(_circles);
        return;
    }
}
 
只能止步于此,这个Hough的代码不是一般复杂。




HoughCircle内容整编(HoughTransform和CircleHoughTransform)

原文:https://www.cnblogs.com/jsxyhelu/p/13191015.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!