上一篇我们学习了边缘检测相关的Sobel算了,经过学习之后,我们应该可以对Sobel算子进行熟悉的运用,那么今天,我们一起来学习一下边缘检测相关的Laplacian(拉普拉斯)算子以及scharr滤波器。
一、理论
Laplacian 算子是n维欧几里德空间中的一个二阶微分算子,定义为梯度grad()的散度div(),因此如果f是二阶可微的实函数,则f的拉普拉斯算子定义为:
(1) f的拉普拉斯算子也是笛卡儿坐标系xi中的所有非混合二阶偏导数求和;
(2) 作为一个二阶微分算子,拉普拉斯算子把C函数映射到C函数,对于k ≥ 2。表达式(1)(或(2))定义了一个算子Δ :C(R) → C(R),或更一般地,定义了一个算子Δ : C(Ω) → C(Ω),对于任何开集Ω。
根据图像处理的原理我们知道,二阶导数可以用来进行检测边缘 。 因为图像是 “二维”,我们需要在两个方向进行求导,使用Laplacian算子将会使求导过程变得简单。
由于 Laplacian使用了图像梯度,它内部的代码其实是调用了 Sobel 算子的。
另附一个小tips:让一幅图像减去它的Laplacian可以增强对比度。
离散函数导数
离散函数的导数退化成了差分,一维一阶差分公式和二阶差分公式分别为
Laplace算子的差分形式
分别对Laplace算子x,y两个方向的二阶导数进行差分就得到了离散函数的Laplace算子
在一个二维函数f(x,y)中,x,y两个方向的二阶差分分别为,
所以Laplace算子的差分形式为,
Laplacian算子的差分形式函数的拉普拉斯算子也是该函数的Hessian 矩阵的迹,可以证明,它具有各向同性,即与坐标轴方向无关,坐标轴旋转后梯度结果不变。如果邻域系统是4 邻域,Laplacian 算子的模板为:
4邻域Laplacian 算子的模板如果邻域系统是8 邻域,Laplacian 算子的模板为:
8邻域Laplacian 算子的模板Laplacian 算子对噪声比较敏感,所以图像一般先经过平滑处理,因为平滑处理也是用模板进行的,所以,通常的分割算法都是把Laplacian 算子和平滑算子结合起来生成一个新的模板。
二、OpenCV中Laplacian()函数详解
1、函数原型
void Laplacian(InputArray src,
OutputArray dst,
int ddepth,
int ksize = 1,
double scale = 1,
double delta = 0,
intborderType =
BORDER_DEFAULT);
2、函数功能
Laplacian函数可以计算出图像经过拉普拉斯变换后的结果。
3、参数详解
-
第一个参数,InputArray类型的image,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位图像;
-
第二个参数,OutputArray类型的edges,输出的边缘图,需要和源图片有一样的尺寸和通道数;
-
第三个参数,int类型的ddept,目标图像的深度;
-
第四个参数,int类型的ksize,用于计算二阶导数的滤波器的孔径尺寸,大小必须为正奇数,且有默认值1;
当ksize>1时,
当ksize = 1,拉普拉斯算子默认采用下面的卷积核
kszie =1 卷积核-
第五个参数,double类型的scale,计算拉普拉斯值的时候可选的比例因子,有默认值1;
-
第六个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0;
-
第七个参数, int类型的borderType,边界模式,默认值为BORDER_DEFAULT。
4、实例:实现对摄像头的图像进行Laplacian边缘检测
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <ctype.h>
#include <stdio.h>
#include <iostream>
using namespace cv;
using namespace std;
static void help()
{
cout <<
"\nThis program demonstrates \
Laplace point/edge detection \
using OpenCV function Laplacian()\n"
"It captures from the camera of \
your choice: 0, 1, ... default 0\n"
"Call:\n"
"./laplace -c=<camera #, default 0> \
-p=<index of the frame to be \
decoded/captured next>\n" << endl;
}
// 三种滤波
enum { GAUSSIAN, BLUR, MEDIAN };
int sigma = 3;
// 默认滤波为高斯滤波
int smoothType = GAUSSIAN;
const char* keys =
{
"{ c | 0 | }{ p | | }"
};
int main(int argc, char** argv)
{
cv::CommandLineParser parser(argc,
argv, keys );
help();
VideoCapture cap; // 摄像机接口
// 获取摄像机参数
string camera = parser.get<string>("c");
if (camera.size() == 1 && isdigit(camera[0]))
cap.open(parser.get<int>("c"));
else
cap.open(samples::findFileOrKeep(camera));
// 判断相机是否打开
if (!cap.isOpened())
{
cerr << "Can't open camera/video stream: "
<< camera << endl;
return 1;
}
// 打开相机后,输出相机的相关信息
cout << "Video " << parser.get<string>("c") <<
": width=" << cap.get(CAP_PROP_FRAME_WIDTH) <<
", height=" << cap.get(CAP_PROP_FRAME_HEIGHT) <<
", nframes=" << cap.get(CAP_PROP_FRAME_COUNT) << endl;
int pos = 0;
if (parser.has("p"))
{
pos = parser.get<int>("p");
}
if (!parser.check())
{
parser.printErrors();
return -1;
}
if (pos != 0)
{
cout << "seeking to frame #" << pos << endl;
// 设置视频的播放位置到pos
if (!cap.set(CAP_PROP_POS_FRAMES, pos))
{
cerr << "ERROR: seekeing is not supported" << endl;
}
}
namedWindow("Laplacian",
WINDOW_AUTOSIZE);
// 创建滑动条
createTrackbar("Sigma",
"Laplacian",
&sigma,
15,
0);
Mat smoothed, laplace, result;
for (;;)
{
Mat frame;
// 将相机中的每一帧图像存储到frame中
cap >> frame;
if (frame.empty())
break;
// 卷积核大小
int ksize = (sigma * 5) | 1;
// 高斯滤波
if (smoothType == GAUSSIAN)
GaussianBlur(frame, smoothed,
Size(ksize, ksize),
sigma, sigma);
// 均值滤波
else if (smoothType == BLUR)
blur(frame, smoothed,
Size(ksize, ksize));
// 中值滤波
else
medianBlur(frame, smoothed, ksize);
// 进行拉普拉斯边缘检测处理
Laplacian(smoothed, laplace, CV_16S, 5);
convertScaleAbs(laplace, result,
(sigma + 1)*0.25);
imshow("Laplacian", result);
// 等待30毫秒在读取下一帧图像
// 即帧率为30
char c = (char)waitKey(30);
// 单击空格切换各种滤波模式
if (c == ' ')
smoothType = smoothType == GAUSSIAN ? BLUR :
smoothType == BLUR ? MEDIAN : GAUSSIAN;
// 退出循环
if (c == 'q' || c == 'Q' || c == 27)
break;
}
return 0;
}
三、实例中一些函数的解释
1、CommandLineParser类
功能:对命令行进行解析。
构造函数:
CommandLineParser(int argc,
const char* const argv[],
const String& keys);
参数详解:
第一个参数:argc 命令行参数的数量;
第二个参数:命令行参数的命令;
第三个参数:第3个就是刚刚定义的keys了,keys的结构有一定规律,比如说"{c |camera |false | use camera or not}" 都是用大括号和双引号引起来,然后中间的内容分成4断,用”|”分隔开,分别表示简称,文件来源,文件值和帮助语句。第二行和第三行表示打开摄像头和打开文件,文件的文件名等都在keys指针中了。
2、convertScaleAbs()函数
函数原型:
void convertScaleAbs(InputArray src,
OutputArray dst,
double alpha = 1,
double beta = 0);
函数功能:
计算图像每个像素的绝对值,并将结果转换为8位无符号整数。
参数详解:
- 第一个参数:src ,原图像
- 第二个参数:dst ,目标图像 (深度为 8u).
- 第三个参数:scale ,成数因子.
- 第四个参数:shift,偏移量
转换公式
convertScaleAbs公式四、scharr滤波器
scharr滤波器在OpenCV中主要是配合Sobel算子的运算而存在的。
函数原型
void Scharr(InputArray src,
OutputArray dst,
int ddepth,
int dx,
int dy,
double scale = 1,
double delta = 0,
int borderType =
BORDER_DEFAULT);
函数功能
该函数的功能与sobel算子基本上上一样,但是Scharr滤波器仅作用于大小为3的内核,具有和sobel算子一样的速度,但结果更为精确。
参数详解
-
第一个参数,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方向上的差分阶数;
-
第六个参数,double类型的scale,计算导数值时可选的缩放因子,默认值是1,表示默认情况下是没有应用缩放的。我们可以在文档中查阅getDerivKernels的相关介绍,来得到这个参数的更多信息
-
第七个参数,double类型的delta,表示在结果存入目标图(第二个参数dst)之前可选的delta值,有默认值0;
-
第八个参数, int类型的borderType,边界模式,默认值为BORDER_DEFAULT。
实例
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
//创建 grad_x 和 grad_y 矩阵
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y, dst;
//载入原始图
Mat src = imread("lena.png");
//显示原始图
imshow("【原始图】Scharr滤波器", src);
//求 X方向梯度
Scharr(src, grad_x, CV_16S,
1, 0, 1, 0, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
imshow("【效果图】 X方向Scharr", abs_grad_x);
//求Y方向梯度
Scharr(src, grad_y, CV_16S,
0, 1, 1, 0, BORDER_DEFAULT);
convertScaleAbs(grad_y, abs_grad_y);
imshow("【效果图】Y方向Scharr", abs_grad_y);
//合并梯度(近似)
addWeighted(abs_grad_x, 0.5,
abs_grad_y, 0.5,
0, dst);
//显示效果图
imshow("【效果图】合并梯度后Scharr", dst);
waitKey(0);
return 0;
}
实验结果:
Scharr原图 Scharr效果图好了,今天的OpenCV学到这里就结束了,喜欢的朋友可以给我点个赞哦!!!
网友评论