1、算法简要说明
文中介绍Canny、Sobel和Laplacian三个opencv接口的边缘检测,下面主要介绍三个接口的参数说明并附带测试用例,文中并未介绍算法原理,后期会做介绍。
1.1 Canny算子
优缺点:
低错误率:标识出尽可能多的实际边缘,同时尽可能地减少噪声产生的误报。高定位性:标识出的边缘要与图像中的实际边缘尽可能接近最小响应:图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘
函数原型:
void cvCanny( const CvArr* image, CvArr* edges, double threshold1, double threshold2, int aperture_size=3 );
参数说明:
第一个参数,表示输入图像,必须为单通道灰度图。
第二个参数,表示输出的边缘图像,为单通道黑白图。
第三个参数和第四个参数表示阈值,这二个阈值中当中的小阈值用来控制边缘连接,大的阈值用来控制强边缘的初始分割即如果一个像素的梯度大与上限值,则被认为是边缘像素,如果小于下限阈值,则被抛弃。如果该点的梯度在两者之间则当这个点与高于上限值的像素点连接时我们才保留,否则删除。
第五个参数:表示Sobel 算子大小,默认为3即表示一个3*3的矩阵。Sobel 算子与高斯拉普拉斯算子都是常用的边缘算子
1.2 Sobel算子
函数原型:
void Sobel ( InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT );
参数说明:
第一个参数,InputArray 类型的src,为输入图像,填Mat类型即可。
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
第三个参数,int类型的ddepth,输出图像的深度,支持如下src.depth()和ddepth的组合:
若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F
若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F
若src.depth() = CV_64F, 取ddepth = -1/CV_64F
第四个参数,int类型dx,x 方向上的差分阶数。
第五个参数,int类型dy,y方向上的差分阶数。
第六个参数,int类型ksize,有默认值3,表示Sobel核的大小;必须取1,3,5或7。
第七个参数,double类型的scale,计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。我们可以在文档中查阅getDerivKernels的相关介绍,来得到这个参数的更多信息。
第八个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0。
第九个参数, int类型的borderType,我们的老朋友了(万年是最后一个参数),边界模式,默认值为BORDER_DEFAULT。这个参数可以在官方文档中borderInterpolate处得到更详细的信息。
1.3 Laplacian算子
函数原型:
void Laplacian(InputArray src, OutputArray dst, int ddepth, int ksize=1, double scale=1, double delta=0, intborderType=BORDER_DEFAULT );
参数介绍:
第一个参数,InputArray类型的image,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位图像。
第二个参数,OutputArray类型的edges,输出的边缘图,需要和源图片有一样的尺寸和通道数。
第三个参数,int类型的ddept,目标图像的深度。
第四个参数,int类型的ksize,用于计算二阶导数的滤波器的孔径尺寸,大小必须为正奇数,且有默认值1。
第五个参数,double类型的scale,计算拉普拉斯值的时候可选的比例因子,有默认值1。
第六个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0。
第七个参数, int类型的borderType,边界模式,默认值为BORDER_DEFAULT。这个参数可以在官方文档中borderInterpolate()处得到更详细的信息。
2 代码示例:
/***************************************************************
*代码通过读取摄像头中的图片进行处理,
*如需要测试其他图片只需要更改Mat结构路径即可
*********************************************************/
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
void borderDetect(Mat img, string sType, Mat& dstImg)
{
if (img.empty())
{
cout << "borderDetect no image !" << endl;
return;
}
if (sType == "Canny")
{
Mat grayImg, edge;
if (img.channels() == 1)
{
grayImg = img;
}
else if (img.channels() == 3)
{
cvtColor(img, grayImg, CV_BGR2GRAY);
}
blur(grayImg, edge, Size(5, 5));
Canny(edge, dstImg, 3, 9, 3);
}
else if (sType == "Sobel")
{
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
// 求X方向梯度
Sobel(img, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
// 求Y方向梯度
Sobel(img, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT);
convertScaleAbs(grad_y, abs_grad_y);
// 合并梯度
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dstImg);
}
else if (sType == "Laplacian")
{
Mat gray, mask;
GaussianBlur(img, img, Size(3, 3), 0, 0, BORDER_DEFAULT);
cvtColor(img, gray, CV_BGR2GRAY);
Laplacian(gray, mask, CV_16S, 3, 1, 0, BORDER_DEFAULT);
convertScaleAbs(mask, dstImg);
}
}
int main()
{
Mat frame;
VideoCapture videoFiles;
videoFiles.open(0);
if (!videoFiles.isOpened())
{
cout << "open camera failed." << endl;
std::system("pause");
return 0;
}
while (true)
{
videoFiles >> frame;
if (frame.empty())
{
continue;
}
Mat dstImg;
Mat img;
borderDetect(frame, "Canny", dstImg);
if (dstImg.channels() == 1)
{
vector<Mat> vDstImg(3);
vDstImg[0] = dstImg;
vDstImg[1] = dstImg;
vDstImg[2] = dstImg;
merge(vDstImg, img);
}
else if (dstImg.channels() == 3)
{
img = dstImg;
}
Mat showImg(frame.rows, frame.cols * 2, CV_8UC3);
frame.copyTo(showImg(Rect(0, 0, frame.cols, frame.rows)));
img.copyTo(showImg(Rect(frame.cols, 0, frame.cols, frame.rows)));
imshow("border detect", showImg);
waitKey(20);
}
system("pause");
return 0;
}