霍夫变换与距离之和最小拟合圆方法对比

1、**霍夫变换**
其实霍夫变换理论和opencv中HoughCircles的实现是有根本的不同的,霍夫变换基于像素对自己所属于的直线或曲线参数方程参数进行投票,最终得票高的就是大概率在图像中存在的曲线。而HoughCircles则是根据像素(可能的圆周边缘)的梯度、边缘、边缘方向信息进行圆心定位,然后结合圆周信息和其他约束(如半径大小)进行最终圆的位置的确定。

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

\left ( x-a \right )^{2}+\left ( y-b \right )^{2}=\left ( r \right )^{2}

其中a和b表示圆心坐标,r表示圆半径,因此霍夫变换的圆检测就是在这三个参数组成的三维空间内进行检测。

原则上,霍夫变换可以检测任何形状。但复杂的形状需要的参数就多,霍夫空间的维数就多,因此在程序实现上所需的内存空间以及运行效率上都不利于把标准霍夫变换应用于实际复杂图形的检测中。所以一些改进的霍夫变换就相继提出,它们的基本原理就是尽可能减小霍夫空间的维数。

HoughCircles函数实现了圆形检测,它使用的算法也是改进的霍夫变换——2-1霍夫变换(21HT)。也就是把霍夫变换分为两个阶段,从而减小了霍夫空间的维数。第一阶段用于检测圆心,第二阶段从圆心推导出圆半径。检测圆心的原理是圆心是它所在圆周所有法线的交汇处,因此只要找到这个交点,即可确定圆心,该方法所用的霍夫空间与图像空间的性质相同,因此它仅仅是二维空间。检测圆半径的方法是从圆心到圆周上的任意一点的距离(即半径)是相同,只要确定一个阈值,只要相同距离的数量大于该阈值,我们就认为该距离就是该圆心所对应的圆半径,该方法只需要计算半径直方图,不使用霍夫空间。圆心和圆半径都得到了,那么通过公式1一个圆形就得到了。从上面的分析可以看出,2-1霍夫变换把标准霍夫变换的三维霍夫空间缩小为二维霍夫空间,因此无论在内存的使用上还是在运行效率上,2-1霍夫变换都远远优于标准霍夫变换。但该算法有一个不足之处就是由于圆半径的检测完全取决于圆心的检测,因此如果圆心检测出现偏差,那么圆半径的检测肯定也是错误的。2-1霍夫变换的具体步骤为:

第一阶段:检测圆心

1.1、对输入图像边缘检测;

1.2、计算图形的梯度,并确定圆周线,其中圆周的梯度就是它的法线;

1.3、在二维霍夫空间内,绘出所有图形的梯度直线,某坐标点上累加和的值越大,说明在该点上直线相交的次数越多,也就是越有可能是圆心;

1.4、在霍夫空间的4邻域内进行非最大值抑制;

1.5、设定一个阈值,霍夫空间内累加和大于该阈值的点就对应于圆心。

第二阶段:检测圆半径

2.1、计算某一个圆心到所有圆周线的距离,这些距离中就有该圆心所对应的圆的半径的值,这些半径值当然是相等的,并且这些圆半径的数量要远远大于其他距离值相等的数量;

2.2、设定两个阈值,定义为最大半径和最小半径,保留距离在这两个半径之间的值,这意味着我们检测的圆不能太大,也不能太小;

2.3、对保留下来的距离进行排序;

2.4、找到距离相同的那些值,并计算相同值的数量;

2.5、设定一个阈值,只有相同值的数量大于该阈值,才认为该值是该圆心对应的圆半径;

2.6、对每一个圆心,完成上面的2.1~2.5步骤,得到所有的圆半径。

HoughCircles函数的原型为:

void HoughCircles(InputArray image,OutputArray circles, int method, double dp, double minDist, double param1=100, double param2=100, int minRadius=0,int maxRadius=0 )

image为输入图像,要求是灰度图像

circles为输出圆向量,每个向量包括三个浮点型的元素——圆心横坐标,圆心纵坐标和圆半径

method为使用霍夫变换圆检测的算法,Opencv2.4.9只实现了2-1霍夫变换,它的参数是CV_HOUGH_GRADIENT

dp为第一阶段所使用的霍夫空间的分辨率,dp=1时表示霍夫空间与输入图像空间的大小一致,dp=2时霍夫空间是输入图像空间的一半,以此类推

minDist为圆心之间的最小距离,如果检测到的两个圆心之间距离小于该值,则认为它们是同一个圆心

param1为边缘检测时使用Canny算子的高阈值

param2为步骤1.5和步骤2.5中所共有的阈值

minRadius和maxRadius为所检测到的圆半径的最小值和最大值。
霍夫变换适合检测同一幅图像上多个圆的情况,但是拟合的不是很准确,同时在对单个圆经行拟合时,可能拟合出多个圆。

#include "stdafx.h"
#include "ArcDetect.h"
#include<iostream>
#include <stdio.h>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include<fstream>
#include "cxcore.hpp"
#include "cv.hpp"
#include <map>
#include <cmath>
#define pi 3.14159265;
#ifdef _DEBUG
#pragma comment(lib,"opencv_world320d.lib")
#else
#pragma comment(lib,"opencv_world320.lib")
#endif

using namespace cv;
using namespace std;

//=======函数实现=====================================================================

//=======调用函数=====================================================================

int main()
{

    Mat src_color = imread("C:\\Users\\wang\\Desktop\\4.jpg");//读取原彩色图
    imshow("原图-彩色", src_color);

    //声明一个三通道图像,像素值全为0,用来将霍夫变换检测出的圆画在上面
    Mat dst(src_color.size(), src_color.type());
    dst = Scalar::all(0);

    Mat src_gray;//彩色图像转化成灰度图
    cvtColor(src_color, src_gray, COLOR_BGR2GRAY);
    imshow("原图-灰度", src_gray);
    imwrite("src_gray.png", src_gray);

    //对灰度图像进行双边滤波
    Mat BilateralFilterImg;
    bilateralFilter(src_gray, BilateralFilterImg, kvalue, kvalue * 2, kvalue / 2);
    vector<Vec3f> circles;//声明一个向量,保存检测出的圆的圆心坐标和半径
    HoughCircles(BilateralFilterImg, circles, CV_HOUGH_GRADIENT, 1.5,50, 130,36, 10, 30);//霍夫变换检测圆
    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]);
        circle(dst, center, 0, Scalar(0, 255, 0), -1, 8, 0);
        circle(dst, center, radius, Scalar(0, 0, 255), 1, 18, 0);
        cout << cvRound(circles[i][0]) << "\t" << cvRound(circles[i][1]) << "\t"
            << cvRound(circles[i][2]) << endl;//在控制台输出圆心坐标和半径                
    }
    imshow("特征提取", dst);
    imwrite("dst.png", dst);
    waitKey();
}

 原图

拟合后图

**2、最小距离**
        这种方法对误差符合正态分布的数据点很有效。但是在机器视觉应用中经常会碰到一些干扰点。这些干扰点多数时候是偏向某一个方向的。这时要是用最小二乘法拟合,拟合出的圆会偏很多。因此,有必要研究更有效的拟合算法。

这里介绍一个我常用的拟合算法,根据数据点到圆的距离绝对值的和来确定圆的参数,也就是下面这个式子:

f=\sum \left | \sqrt{\left ( x_{i}-x_{c}\right )^{^{2}}+\left ( y_{i}+y_{c} \right )^{2}}-R \right |

       最小距离法,适合拟合图像上只有单个圆的图像,抗干扰能力强,拟合准确。

#include "stdafx.h"
#include "ArcDetect.h"
#include<iostream>
#include <stdio.h>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include<fstream>
#include "cxcore.hpp"
#include "cv.hpp"
#include <map>
#include <cmath>
#define pi 3.14159265;
#ifdef _DEBUG
#pragma comment(lib,"opencv_world320d.lib")
#else
#pragma comment(lib,"opencv_world320.lib")
#endif

using namespace cv;
using namespace std;

//=======函数实现=====================================================================

//=======调用函数=====================================================================
 

const int kvalue = 15;
int main()
{

    Mat src_color = imread("C:\\Users\\wang\\Desktop\\5.png");//读取原彩色图
    imshow("原图-彩色", src_color);

    //声明一个三通道图像,像素值全为0,用来将霍夫变换检测出的圆画在上面
    Mat dst(src_color.size(), src_color.type());
    dst = Scalar::all(0);

    Mat src_gray;//彩色图像转化成灰度图
    cvtColor(src_color, src_gray, COLOR_BGR2GRAY);
    imshow("原图-灰度", src_gray);
    imwrite("src_gray.png", src_gray);

    //对灰度图像进行双边滤波
    Mat BilateralFilterImg;
    bilateralFilter(src_gray, BilateralFilterImg, kvalue, kvalue * 2, kvalue / 2);
    Mat EdgeImg;
    Canny(BilateralFilterImg, EdgeImg,200, 220);
    vector<vector<Point>>contours;
    Mat IfOffsetRoi;
    double maxareaRoi = 0;
    int maxindex = 0;
    cv::findContours(EdgeImg, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
    for (int index = 0; index < contours.size(); index++)
    {

        double tmparea = contours[index].size() ;
        if (tmparea > maxareaRoi)
        {
            maxareaRoi = tmparea;
            maxindex = index;//记录最大轮廓的索引号
        }
    }
    Mat image2 = src_color.clone();
    Mat RoiLocate1 = Mat::zeros(src_gray.rows, src_gray.cols, CV_8UC1);

    cv::drawContours(RoiLocate1, contours, maxindex, Scalar(255), CV_FILLED);

    std::vector<cv::Point2f> points;
    cv::Point2f center;
    double radius;
    for (int i = 0;i < contours[maxindex].size();i++)
    {
        points.push_back(contours[maxindex].at(i));
    }
    circleLeastFit(points, center, radius);
 

    circle(src_color, center, 0, Scalar(0, 255, 0), -1, 8, 0);
    circle(src_color, center, radius, Scalar(0, 0, 255), 1, 18, 0);

    imshow("特征提取", dst);
    imwrite("dst.png", dst);

    waitKey();

}

原图

拟合后的图

猜你喜欢

转载自blog.csdn.net/qq_37211994/article/details/83178693