有关opencv的学习(22)—霍夫变换及应用

一、霍夫变换及应用

       霍夫变换(Hough Transform)是图像处理中的一种特征提取技术, 该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换的结果。
       霍夫变换在OpenCV中主要分两种:
                  霍夫线变换---检测直线(线段)
                  霍夫圆变换---检测圆
       用到的函数:
              HoughLines()---标准霍夫变换、多尺度霍夫变换
              HoughLinesP()---累计概率霍夫变换
              HoughCricles()---霍夫圆变换


二、霍夫线变换

       霍夫线变换是一种寻找直线的方法, 一般在使用霍夫变换前首先将图像进行边缘检测处理,一般霍夫变换的输入为边缘二值图。OpenCV支持三种不同的霍夫线变换, 包括:
        标准霍夫变换(SHT)
多尺度霍夫变换(MSHT)------是SHT在多尺度下的一个变种
累计概率霍夫变换(PPHT)------是SHT的改进, 在一定范围内进行霍夫变换(减少 计算时间和运算量)


使用函数对应关系:
标准霍夫变换(SHT)------HoughLines()函数
多尺度霍夫变换(MSHT)------HoughLines()函数
累计概率霍夫变换(PPHT)------HoughLinesP()函数


霍夫线变换原理说明:








标准霍夫变换----HoughLines( )

函数原型:

CV_EXPORTS_Wvoid HoughLines(InputArray image,OutputArray lines,

                                   double rho,double theta, int threshold,

                                   double srn=0,double stn=0 );

src:输入原图像(一般为8位单通道二值图像);
lines: 经过霍夫变换后检测线条的输出矢量, 每一条线由两个元素的矢量(ρ, Θ)表示, 其中ρ是离座标原点的距离, Θ是弧度线条旋转角度(0表示垂直线, π/2度表示水平线);
rho: 以像素为单位的距离精度, 另一种表述方式是直线搜索时的进步尺寸的单位半径;
theta: 以弧度为单位的角度精度, 另一种表述方式是直线搜索时的进步尺寸的角度单位;
threshold: 累加平面的阈值参数, 即识别某部分为一条直线时它在累加平面中必须达到的值, 大于阈值threshold的线段才可以被检测通过并返回到结果中;
srn: 默认值0, 对于多尺度的霍夫变换, 这是第三个参数进步尺寸rho的除数距离;
stn: 默认值0, 对于多尺度霍夫变换, 表示单位角度theta;


代码如下:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
    
    Mat image=imread("/Users/zhangxiaoyu/Desktop/1.png",0);//读取灰度图像
    if(image.empty())
    {
        cout<<"Error!/n";
        return -1;
    }
    
    //cv::imshow("original image", image);
    
    //应用Canny算法检测边缘
    cv::Mat cannyImg;
    cv::Canny(image, cannyImg, 50, 100);
    cv::imshow("canny image", cannyImg);
    
    //用霍夫变换检测直线
    std::vector<cv::Vec2f>lines;
    cv::HoughLines(cannyImg, lines, 1, CV_PI/180, 180);
    
    std::vector<cv::Vec2f>::const_iterator it=lines.begin();
    
    while(it!=lines.end())
    {
        float rho=(*it)[0];// 第一个元素是距离rho
        float theta=(*it)[1];// 第二个元素是角度theta
        
        
        if (theta < CV_PI/4.|| theta > 3.*CV_PI/4.)
        {
            // 垂直线(大致)
            // 直线与第一行的交叉点
            cv::Point pt1(rho/cos(theta),0);
            // 直线与最后一行的交叉点
            cv::Point pt2((rho-cannyImg.rows*sin(theta))/
                          cos(theta),cannyImg.rows);
            // 画白色的线
            cv::line( image, pt1, pt2, cv::Scalar(255), 1);
        }
        
        
        else
        {
            // 水平线(大致)
            // 直线与第一列的交叉点
            cv::Point pt1(0,rho/sin(theta));
            // 直线与最后一列的交叉点
            cv::Point pt2(cannyImg.cols,
                          (rho-cannyImg.cols*cos(theta))/sin(theta));
            // 画白色的线
            cv::line(image, pt1, pt2, cv::Scalar(255), 1);
        }
        ++it;
        
    }
    
    cv::imshow("hough-transform image", image);
    waitKey(0);
    
}

运行结果如下:

原图像:


检测结果:



        可以看出,霍夫变换只是寻找图像中边缘像素的对齐区域。因为有些像素只是碰巧排成了直线,这种方式可能产生错误的检测结果。也可能因为多条直线穿过了同一个像素对齐区域,而检测出重复的结果。

        

       为解决上述问题并检测到线段(即包含端点的直线),人们提出了霍夫变换的改进版。这就是概率霍夫变换,在OpenCV中通过cv::HoughLinesP函数实现。


累积概率霍夫变换-----HoughLinesP()

函数原型:

CV_EXPORTS_W void HoughLinesP( InputArray image, OutputArray lines,

                                       double rho, double theta, int threshold,

                                       double minLineLength=0, double maxLineGap=0 );

src: 输入原图像(一般为8位单通道二值图像);
lines: 经过霍夫变换后检测线条的输出矢量, 每一条线由4个元素矢量(x_1,y_1,x_2,y_2)表示, 其中(x_1,y_1)和(x_2,y_2)是检测到线段的结束点;
rho: 以像素为单位的距离精度, 另一种表述方式是直线搜索时的进步尺寸的单位半径;
theta: 以弧度为单位的角度精度, 另一种表述方式是直线搜索时的进步尺寸的角度单位;
threshold: 累加平面的阈值参数, 即识别某部分为一条直线时它在累加平面中必须达到的值, 大于阈值threshold的线段才可以被检测通过并返回到结果中;
minLineLength: 默认值0, 表示最低线段的长度, 小於则不显示;
maxLineGap: 默认值0, 允许将同一行点与点之间连接起来的最大距离.


代码如下:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
        
    Mat image=imread("/Users/zhangxiaoyu/Desktop/6.png",0);//读取灰度图像
    if(image.empty())
    {
        cout<<"Error!/n";
        return -1;
    }
    
    cv::Mat dstimg=image.clone();
    cv::imshow("original image", dstimg);
    
    //应用Canny算法检测边缘
    cv::Mat cannyImg;
    cv::Canny(image, cannyImg, 50, 100);
    //cv::imshow("canny image", cannyImg);
    //waitKey(0);

    
    //用霍夫变换检测直线
    
    std::vector<cv::Vec4i>lines;
    cv::HoughLinesP(cannyImg, lines, 1, CV_PI/180, 150);
    
    for(size_t i=0;i<lines.size();i++)
    {
        Vec4i l=lines[i];
        line(dstimg, Point(l[0],l[1]), Point(l[2],l[3]), Scalar(255),1,CV_AA);
    }
    
    cv::imshow("hough-transform P image", dstimg);
    waitKey(0);
    
}
运行结果如下:


      概率霍夫变换对基本算法做了一些修正。首先,概率霍夫变换在二值分布图上随机选择像素点,而不是系统性地逐行扫描图像。一旦累加器的某个入口达到了预设的最小值,就沿着对应的直线扫描图像,并移除所有在这条直线上的像素点(包括还没投票的像素点)。这个扫描过程还检测可以接受的线段长度。为此,算法定义了两个额外的参数:一个是允许的线段最小长度,另一个是组成连续线段时允许的最大像素间距。这个额外的步骤增加了算法的复杂度,但也得到了一定的补偿,即由于在扫描直线过程中已经清除了部分像素点,因此减少了投票过程中用到的像素点。

三、霍夫圆变换

         霍夫圆变换的基本原理和霍夫线变换大体上类似, 只是点对应的二维极径极角空间被三维的圆心点x, y和半径r空间取代, 如果用完全一样的方法运算量较大, 运行速度较慢, 所以采用“霍夫梯度法”来做圆变换。

霍夫梯度法原理:



     简单来说,即为选择两轮筛选:

     第一轮筛选使用一个二维累加器,找出可能是圆的位置。因为圆周上像素点的梯度方向与半径的方向是一致的,对于每个像素点,累加器中只对沿着梯度方向的入口增加计数(根据预先定义的最小和最大半径值)。一旦检测到可能的圆心(即收到了预定数量的投票),就在第二轮筛选中建立半径值范围的一维直方图。这个直方图的尖峰值就是被检测圆的半径。

“霍夫梯度法”的缺点:
          (1)可能在输出结果中产生噪声;
          (2)如果累加器阈值设置较低, 算法需要较长时间, 每一个中心只选择一个圆, 对于同心圆只选择一个;
          (3)如果新的中心很接近已接受的中心, 则不会被保留下来。


霍夫圆变换---HoughCircles()

函数原型:

CV_EXPORTS_W void HoughCircles( InputArray image, OutputArray circles,

                                          int method, double dp, double minDist,

                                     double param1=100, double param2=100,

                                      int minRadius=0, int maxRadius=0 );

src: 输入原图像(一般为8位单通道图像);
circles: 经过霍夫变换后检测圆的输出矢量, 每个矢量包含三个元素的浮点矢量(x, y, radius);
method: 使用的检测方法, 默认只有一种CV_HOUGH_GRADIENT;
dp: 用来检测圆心的累加器图像的分辨率与输入图像之比的倒数, 且此参数允许创建一个比输入图像分辨率低的累加器。如dp=1, 累加器和输入如下具有相同分辨率; dp=2, 累加器只有输入图像一半大的宽度和高度
minDist: 霍夫变换检测到圆的圆心之间的最小距离, 分辨两个不同圆;
param1: 默认值100, 它是第三个参数method设置的对应参数, 表示传递给Canny边缘算子的高阈值, 低阈值是高阈值的一半
param2: 默认值100,它是第三个参数method设置的对应参数, 表示检测阶段圆心累加器阈值, 越小, 则会检测到越多不存在的圆, 越大, 更多真正的圆被检测到;
minRadius/maxRadius: 表示圆半径的最小值/最大值;


代码如下:

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
        
    Mat image=imread("/Users/zhangxiaoyu/Desktop/7.png",0);//读取灰度图像
    if(image.empty())
    {
        cout<<"Error!/n";
        return -1;
    }
    
    cv::Mat dstimg=image.clone();
    cv::imshow("original image", dstimg);
    
    cv::GaussianBlur(image, image, Size(3,3),1.5);
    
    //用霍夫变换检测
    std::vector<cv::Vec3f>circles;
    cv::HoughCircles(image, circles, CV_HOUGH_GRADIENT, 1,5,200,50,5,200);
    
    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(dstimg, center, 3,Scalar(0,0,255),-1,8,0);//画圆心
        circle(dstimg, center, radius, Scalar(0,255,0),3,8,0);
        
    }
    cv::imshow("Hough circle image", dstimg);
    waitKey(0);
    
}
运行结果如下:

原图:


检测结果:







































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章