(openCV)Canny算子加霍夫变换检测直线

  1. canny算子
    在边缘检测中,最常用的就是Canny算子,当然还有Sobel,Laplacian,Scharr等算子,我这里研究的是Canny算子。
    在Canny算子中,进行了以下步骤:
    第一步:消除噪声,采用的是用一个(比如size=5)的孔径的高斯核对图像灰度矩阵的每一点做进行加权求和。
    第二步:计算梯度幅值和方向
    第三步:非极大值抑制,也就是排除非边缘像素。仅仅保留一些细线条(就是候选边缘)
    第四步:采用了两个阀值(也就是滞后阀值)
    两个阀门值作用:检测到某个像素值的幅值高于高的阀门值,该像素保留。低于低的阀门值,被舍弃,当一个像素值处于两个阀门值之间,而且其前面的一个像素值高于高阀门值,就被保留,如果其前面的一个像素值低于低的阀门值,就被舍弃。这就是滞后作用。
    一般高低阀门值的比例都2:1和3:1之间。
void Canny( InputArray image//注意这个输入图像必须是单通道8位图像
, OutputArray edges,//输出的包含边缘的图像,与输入的原图片有相同的尺寸和参数。
   double threshold1, double threshold2,//高低阀门值
    int apertureSize=3,//用于第一步滤波的Sobll算子孔径打字大小。
    bool L2gradient=false );//gradient梯度值标识,有默认值

以上就是Canny调用函数的讲解。

  1. 霍夫变换的实现
    在之前一篇博客中,介绍霍夫变换的具体原理,这里就不做详细介绍。
    在OPENCV中,提供了一个基础函数cv::HoughLines();注意这个函数的输入是一个二值化的图像矩阵。通常这个输入是一个经过Canny算子处理过的图片。值得记住的是,这个函数输出的是一个 cv::Vec2f类型组成的向量,每个元素是一对浮点数,表示检测到的直线的参数,也就是(ρ , θ),下面一段代码说明了这句话使用霍夫变化的具体应用。
//应用Canny算法
cv::Mat contours;
cv::Canny(image,contours,125,350);
//霍夫变换检测直线
std::vector<cv::Vec2f> lines;
cv::HoughLines(test,lines,
1,PI/180, // 搜索图片的步长,也就是((ρ , θ)的步进
80); // minimum number of votes

值得注意的是,直接用直接用HoughLines()检测出来的图像中的直线不是线段,所以直接绘制,会穿透整个图片。所以画线的时候,要找出线和图片边界的交叉点,然后在这两个交叉点之间画线。

std::vector<cv::Vec2f>::const_iterator it= lines.begin();
while (it!=lines.end()) {
float rho= (*it)[0]; // first element is distance rho
float theta= (*it)[1]; // second element is angle theta
if (theta < PI/4.
|| theta > 3.*PI/4.) { // ~vertical line
// point of intersection of the line with first row
cv::Point pt1(rho/cos(theta),0);
// point of intersection of the line with last row
cv::Point pt2((rho-result.rows*sin(theta))/
cos(theta),result.rows);
// draw a white line
cv::line( image, pt1, pt2, cv::Scalar(255), 1);
} else { // ~horizontal line
// point of intersection of the
// line with first column
cv::Point pt1(0,rho/sin(theta));
// point of intersection of the line with last column
cv::Point pt2(result.cols,
(rho-result.cols*cos(theta))/sin(theta));
// draw a white line
cv::line(image, pt1, pt2, cv::Scalar(255), 1);
}
++it;
}

上面的霍夫变换不太好,于是产生概率霍夫变换,也就是函数 cv::HoughLinesP();
下面给出一段霍夫变换的代码

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

using namespace std;
using namespace cv;

class LineFinder
{
private:
    cv::Mat img;
    vector<cv::Vec4i> lines;//包含被检测直线的端点

    //累加器分辨率参数
    double deltaRho;//相当于r
    double deltaTheta;//相当于角度

    int minVote;//确认直线之前必须收到的最小投票数
    double minLength;//直线的最小长度

    double maxGap;//直线上允许的最大空隙

public:
    LineFinder() :deltaRho(1), deltaTheta(CV_PI / 180), minVote(100), minLength(0.), maxGap(0.){}

    //设置累加器的分辨率
    void setAccResolution(double dRho, double dTheta)
    {
        deltaRho = dRho;
        deltaTheta = dTheta;
    }
    //设置最小投票数
    void setMinVote(int minvote)
    {
        minVote = minvote;
    }
    //设置直线长度和空隙
    void setLineLengthAndGap(double length, double gap)
    {
        minLength = length;
        maxGap = gap;
    }
    //参数设置好后,就开始霍夫变换。 
    Vector<cv::Vec4i>findLines(Mat& binary)
    {
        lines.clear();
        HoughLinesP(binary, lines, deltaRho, deltaTheta, minVote, minLength, maxGap);
        return lines;
    }

    //这个函数用于在图像上绘制检测到的Lines
    void drawDetectedLines(Mat& image, Scalar color = Scalar(255,0, 0))
    {
        vector<cv::Vec4i>::const_iterator it2 = lines.begin();
        while (it2 != lines.end())
        {
            cv::Point pt1((*it2)[0], (*it2)[1]);
            cv::Point pt2((*it2)[2], (*it2)[3]);

            line(image, pt1, pt2, color);

            ++it2;
        }
    }

};

int main()
{
    Mat image = imread("2.jpg");
    imshow("灰度图", image);

    Mat contours,grayImage;
    Canny(image, contours, 125, 350,3);
    cvtColor(contours, grayImage, COLOR_GRAY2BGR);
    //contours = 255 - contours;
    cv::Mat contoursInv; // inverted image
    cv::threshold(contours, contoursInv,
        128, // values below this
        255, // becomes this
        cv::THRESH_BINARY_INV);
    imshow("Canny", contoursInv);
    imshow("灰度图", grayImage);

    LineFinder finder;
    finder.setLineLengthAndGap(50,10);
    finder.setMinVote(150);
    Vector<Vec4i> lines = finder.findLines(contours);
    //Mat  B;
    //B.create(grayImage.rows, grayImage.cols, CV_8U, Scalar::all(0));
    cv::Mat o = cv::Mat::zeros(grayImage.rows, grayImage.cols, CV_8UC1);
    finder.drawDetectedLines(o);
    imshow("霍夫变换检测线", o);

    waitKey(0);
    return 0;
}

下面解释一下累计霍夫变换的基本步骤:
1)随机获取边缘图片上的前景点,映射到级坐标系画曲线;
2)当极坐标系里面有交点达到最小投票数,将该点对应x-y坐标系的直线L找出来;
3)搜索边缘图片上前景点,在直线L上的点(且点与点之间距离小于maxLineGap的)连成线段,然后这些点全部删除,并且记录该线段的参数,就是起始点和终止点啦(两个点,4个参数,所以容器为Vec4i)
(当然这里线段长度要满足最小长度的,否则就不用记录了)
4)重复1),2),3)
这里应该理解累加器不同入口就是记录不同直线经过的像素点数,这些像素点进行投票,比如现在有100个像素点投票,表示这条直线上有100个像素点,函数中会设置最低投票数,只有高于这个阀门值的直线被保留下来。
下面讲讲累加器入口:每个(x0,y0)都会满足一个极坐标方程,ρ=x0*cosθ+y0*sinθ,这里如果一(x0,y0)为常数,(ρ,θ)为变量,一共180个,在坐标域就会画出一条曲线(180个点构成),这些不同的(ρ,θ)就是一个累加器入口。在坐标域有两条曲线相交,产生一个点,对应的(ρ,θ)就会加1,再有一条曲线过这个点,对应入口在加一。表示的是(ρ,θ)经过图像上的点数又加1.

  1. 霍夫圆的检测
    由于圆的描述有半径和坐标(3个参数),对应累加器应该是3维的,
    所以openCV的霍夫圆检测用了两轮投票筛选,先把圆心检测出来,再把半径选出来。
    下面是霍夫圆检测代码
#include<opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace std;
using namespace cv;

int main()
{
    Mat srcImage = imread("3.jpg", 1);
    Mat midImage;//临时变量
    Mat dstImage;//最后霍夫圆变换效果图
    imshow("原图", srcImage);

    cvtColor(srcImage, midImage, COLOR_BGR2GRAY);
    GaussianBlur(midImage, midImage, Size(9,9),2,2);
    imshow("滤波后", midImage);
    //开始进行霍夫圆变换
    vector <Vec3f>circles;
    HoughCircles(midImage, circles, CV_HOUGH_GRADIENT ,2, 50, 200, 100,20,200);
    //检测到的霍夫圆参数就在circles中
    //for (size_t i = 0; i < circles.size(); i++)
    //{
    //  cout << circles.size() << endl;//
    //  Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
    //  cout << center << endl; 
    //  int radius = cvRound(circles[i][2]);
    //  cout << radius << endl;
    //  //绘制圆心
    //  circle(srcImage, center, 3, Scalar(255, 0, 0), -1, 8, 0);
    //  //绘制轮廓
    //  circle(srcImage, center, radius, Scalar(0, 255, 0), 3, 8, 0);
    //}
    vector<Vec3f>::const_iterator itc = circles.begin();
    while (itc != circles.end())
    {
        circle(srcImage, Point((*itc)[0], (*itc)[1]),//圆心
            (*itc)[2], cv::Scalar(0,255,255),//半径和颜色
            5);//厚度
        circle(srcImage, Point((*itc)[0], (*itc)[1]), 3, Scalar(255), -1, 8, 0);//画圆心
        ++itc;
    }
    imshow("效果图", srcImage);

    waitKey(0);
    return 0;
}
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值