提到的图像平滑,从信号处理的角度来看,实际上是一种“低通滤波器”,数字图像的边缘,通常都是像素值变化剧烈的区域 (“高频”),故可将边缘检测视为一种 “高通滤波器”。
mat
第一个参数是rows,该矩阵的行数;第二个参数是cols,该矩阵的列数;第三个参数是该矩阵元素的类型。这句话表示创建一个大小为240×320的矩阵,里面的元素为8位unsigned型,通道数(channel)有3个。
Sobel
Sobel用于检测水平,垂直方向的边缘,Sobel算子并没有将图像的主体与背景严格地区分开来,即Sobel算子没有严格地模拟人的视觉生理特征,所以提取的图像轮廓有时并不能令人满意。
void Sobel(InputArray src, OutputArray dst,int ddepth, int dx, int dy, int ksize=3, double scale=1, double delta=0,intborderType=BORDER_DEFAULT )
参数
src –输入图像。
dst –输出图像,与输入图像同样大小,拥有同样个数的通道。
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
当 ddepth为-1时,输出图像将和输入图像有相同的深度。输入8位图像则会截取顶端的导数。
xorder – x方向导数运算参数。
yorder – y方向导数运算参数。
ksize – Sobel内核的大小,可以是:1,3,5,7。
scale –可选的缩放导数的比例常数。
delta –可选的增量常数被叠加到导数中。
borderType –用于判断图像边界的模式。
convertScaleAbs
void convertScaleAbs(InputArray src,OutputArray dst, double alpha=1, double beta=0)
参数
src –输入数组。
dst –输出数组。
alpha –可选缩放比例常数。
beta –可选叠加到结果的常数。
addWeighted
opencv 通过 addWeighted 函数实现图片的线性融合。
cvAddWeighted( const CvArr* src1, doublealpha, const CvArr* src2, doublebeta, double gamma, CvArr* dst );
src1 //第一个原数组.
alpha //第一个数组元素的权值
src2 //第二个原数组
beta //第二个数组元素的权值
dst //输出数组
gamma //添加的常数项。
函数 cvAddWeighted 计算两数组的加权值的和:
dst(I)=src1(I)*alpha+src2(I)*beta+gamma
例子:
#include <iostream>
#include <stdio.h>
#include "opencv/cv.h"
#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/avutil.h"
};
using namespace std;
using namespace cv;
int main(int argc, char **argv)
{
Mat grad;
char* window_name = "Sobel Demo - Simple Edge Detector";
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
if(0)
{
Mat yuvImg;
int width = 288;
int height = 352;
string pic = "/home/otvcloud/zf/code-blocks/getpic/1.yuv";
FILE* fp = fopen(pic.c_str(),"rb");
const int len = width*height;
char * sbuf = new char[len];
int nread = fread(sbuf,1,len ,fp);
yuvImg.create(width,height, CV_8UC1);
const int framesize = width * height;
memcpy(yuvImg.data, sbuf, framesize*sizeof(unsigned char));
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
Sobel( yuvImg, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_x, abs_grad_x );
Sobel( yuvImg, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_y, abs_grad_y );
// 准确的梯度:Mat norm,dir; 2 计算L2范数和方向, cartToPolar(sobelX,sobelY,norm,dir);
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
imshow( window_name, grad );
waitKey(0);
}else{
string pic = "/home/otvcloud/zf/code-blocks/getpic/1.jpg";
Mat src,src_gray;
src = imread( pic.c_str());
if( !src.data )
{
return -1;
}
//高斯模糊
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
//转成灰度图
cvtColor( src, src_gray, CV_RGB2GRAY );
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
//在 x 和 y 方向分别”求导“
//Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT);
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT);
convertScaleAbs( grad_x, abs_grad_x );
//Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT);
Sobel( src_gray, grad_y, ddepth, 0, 1, 3,scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_y, abs_grad_y );
//将两个方向的梯度相加来求取近似梯度。
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );
cout << "M = " << endl << " "<< grad << endl << endl;
imshow( window_name, grad );
waitKey(0);
}
return 0;
}
注:可以通过参数 -1 来设定输出图像的深度(数据类型)与原图像保持一致,但是在代码中使用的却是 cv2.CV_64F,这是为什么呢?想象一下一个从黑到白的边界的导数是整数,而一个从白到黑的边界点导数却是负数。如果原图像的深度是np.int8 时,所有的负值都会被截断变成 0,换句话说就是把边界丢失掉。所以如果这两种边界你都想检测到,最好的的办法就是将输出的数据类型设置的更高,比如 cv2.CV_16S, cv2.CV_64F 等。取绝对值然后再把它转回到 cv2.CV_8U。下面的示例演示了输出图片的深度不同造成的不同效果。
Scharr 卷积核
当卷积核大小为 3x3 时,使用 sobel 卷积核来计算并不是很精确,此时常用 Scharr 卷积核来代替。
对于 Scharr 函数,要求 dx 和 dy 都 >= 0 且 dx + dy ==1,假如 dx 和 dy 都设为 1,则会抛出异常。
因此,对于 Sobel 和 Scharr 函数,通常各自求其 x 和 y 方向的导数,然后通过加权来进行边缘检测。
拉普拉斯算子 (Laplace)
索贝尔算子 (Sobel) 和拉普拉斯算子(Laplace) 都是用来对图像进行边缘检测的,不同之处在于,前者是求一阶导,后者是求二阶导。
例子:
在进行 Sobel,Laplacian 和 Canny 边缘检测之前,统一调用 GaussianBlur 来降低图像噪声。
#include"opencv2/imgproc/imgproc.hpp"
#include"opencv2/highgui/highgui.hpp"
using namespace std;
using namespace cv;
int main()
{
Mat src, src_gray, dst;
src = imread("E:/Edge/bird.jpg");
if(src.empty())
return -1;
namedWindow("Original", CV_WINDOW_AUTOSIZE);
namedWindow("Sobel", CV_WINDOW_AUTOSIZE);
namedWindow("Laplace", CV_WINDOW_AUTOSIZE);
namedWindow("Canny", CV_WINDOW_AUTOSIZE);
imshow("Original", src);
Mat grad_x, grad_y, abs_grad_x, abs_grad_y;
GaussianBlur(src, src, Size(3,3),0);
cvtColor(src,src_gray,COLOR_BGR2GRAY);
Sobel(src_gray, grad_x,CV_16S,0,1); // use CV_16S to avoid overflow
convertScaleAbs( grad_x, abs_grad_x );
Sobel(src_gray, grad_y,CV_16S,1,0); // use CV_16S to avoid overflow
convertScaleAbs( grad_y, abs_grad_y );
addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst );
imshow("Sobel", dst);
imwrite("Sobel.jpg",dst);
Laplacian(src_gray,dst,-1,3);
imshow("Laplace", dst);
imwrite("Laplace.jpg",dst);
Canny(src_gray,dst,100,300);
imshow("Canny",dst);
imwrite("Canny.jpg",dst);
waitKey(0);
}
Canny边缘检
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
using namespace cv;
using namespace std;
Mat src,src_gray;
int thresh = 100;
int max_thresh = 255;
RNG rng(12345);
void thresh_callback(int, void* );
int main( int, char** argv )
{
// 读图
src = imread("Pillnitz.jpg", IMREAD_COLOR);
if (src.empty())
return -1;
// 转化为灰度图
cvtColor(src, src_gray, COLOR_BGR2GRAY );
blur(src_gray, src_gray, Size(3,3) );
// 显示
namedWindow("Source", WINDOW_AUTOSIZE );
imshow( "Source", src );
// 滑动条
createTrackbar("Canny thresh:", "Source", &thresh, max_thresh, thresh_callback );
// 回调函数
thresh_callback( 0, 0 );
waitKey(0);
}
// 回调函数
void thresh_callback(int, void* )
{
Mat canny_output;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
// canny 边缘检测
Canny(src_gray, canny_output, thresh, thresh*2, 3);
// 寻找轮廓
findContours( canny_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0) );
Mat drawing = Mat::zeros( canny_output.size(), CV_8UC3);
// 画出轮廓
for( size_t i = 0; i< contours.size(); i++ ) {
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
drawContours( drawing, contours, (int)i, color, 2, 8, hierarchy, 0, Point() );
}
namedWindow( "Contours", WINDOW_AUTOSIZE );
imshow( "Contours", drawing );
}