- 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调用函数的讲解。
- 霍夫变换的实现
在之前一篇博客中,介绍霍夫变换的具体原理,这里就不做详细介绍。
在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.
- 霍夫圆的检测
由于圆的描述有半径和坐标(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;
}