[Sobel]图像求导¶
参考:
通过Sobel
算子计算图像导数
导数和边缘¶
图像边缘处的像素值变化强烈,通过导数计算更能够发现变化强烈的地方,所以在进行边缘检测之前先进行求导操作,能够有利于图像轮廓的检测
Sobel算子¶
Sobel
算子是对一阶导数的近似计算,通过定义内核对图像进行水平和垂直方向的卷积运算,最后求取L2
范数得到图像的近似梯度
水平方向计算如下:
垂直方向计算如下:
从内核可看出,Sobel
算子结合了高斯平滑和差分功能,更加具有抗噪声的能力
计算梯度:
也可以使用近似计算公式:
函数解析¶
源文件:
/path/to/modules/imgproc/src/filter.dispatch.cpp
/path/to/modules/imgproc/src/deriv.cpp
OpenCV
提供了Sobel
算子的实现:
CV_EXPORTS_W 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 );
src
:原图dst
:结果图像ddepth
:输出图像深度,使用CV_16S
以避免溢出dx
:导数在x
轴方向的阶数dy
:导数在y
轴方向的阶数ksize
:Sobel
内核大小,比如3/5/7/9/11
等等scale
:计算导数值的比例因子,默认为1
delta
:添加到每个梯度的值,默认为0
borderType
:边界填充类型,默认为BORDER_DEFAULT
还有一个相关的函数是getDerivKernels
,其返回计算空间图像导数的滤波系数
void cv::getDerivKernels( OutputArray kx, OutputArray ky, int dx, int dy,
int ksize, bool normalize, int ktype )
{
if( ksize <= 0 )
getScharrKernels( kx, ky, dx, dy, normalize, ktype );
else
getSobelKernels( kx, ky, dx, dy, ksize, normalize, ktype );
}
如果指定内核大小ksize
大于0
,则调用函数getSobelKernels
制作Sobel
算子内核
示例¶
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
// 滑动条名
const string ksize_trackbarname = "ksize";
const string scale_trackbarname = "scale";
const string delta_trackbarname = "delta";
// 窗口名
const string winname = "Sobel Demo - Simple Edge Detector";
// 最大值
const int maxNum = 4;
int ksize_value, scale_value, delta_value;
Mat image, src, src_gray, grad;
int ddepth = CV_16S;
void onSobel(int, void *) {
int ksize = 1 + 2 * (ksize_value % 5); // ksize取值为 1/3/5/7/9
double scale = 1 + scale_value; // scale取值为 1/2/3/4/5
double delta = 10 * delta_value; // delta取值为 0/10/20/30/40
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
Sobel(src_gray, grad_x, ddepth, 1, 0, ksize, scale, delta, BORDER_DEFAULT); // x方向求导
Sobel(src_gray, grad_y, ddepth, 0, 1, ksize, scale, delta, BORDER_DEFAULT); // y方向求导
// converting back to CV_8U
convertScaleAbs(grad_x, abs_grad_x);
convertScaleAbs(grad_y, abs_grad_y);
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad); // 近似计算图像梯度
imshow(winname, grad);
}
int main(int argc, char **argv) {
cv::CommandLineParser parser(argc, argv,
"{@input |../lena.jpg|input image}"
"{help h|false|show help message}");
cout << "The sample uses Sobel or Scharr OpenCV functions for edge detection\n\n";
parser.printMessage();
cout << "\nPress 'ESC' to exit program.\nPress 'R' to reset values ( ksize will be -1 equal to Scharr function )";
String imageName = parser.get<String>("@input");
// As usual we load our source image (src)
image = imread(imageName, IMREAD_COLOR); // Load an image
// Check if image is loaded fine
if (image.empty()) {
printf("Error opening image: %s\n", imageName.c_str());
return 1;
}
// Remove noise by blurring with a Gaussian filter ( kernel size = 3 )
GaussianBlur(image, src, Size(3, 3), 0, 0, BORDER_DEFAULT);
// Convert the image to grayscale
cvtColor(src, src_gray, COLOR_BGR2GRAY);
namedWindow(winname);
createTrackbar(ksize_trackbarname, winname, &ksize_value, maxNum, onSobel, NULL);
createTrackbar(scale_trackbarname, winname, &scale_value, maxNum, onSobel, NULL);
createTrackbar(delta_trackbarname, winname, &delta_value, maxNum, onSobel, NULL);
onSobel(0, NULL);
waitKey(0);
return 0;
}
Sobel
算子有3
个关键参数:ksize/scale/delta
。实现步骤如下:
- 读取彩色图像
- 高斯平滑操作,去除噪声
- 转换成灰度图像
- 创建
3
个滑动条,分别控制ksize/scale/delta
Sobel
滤波
从实验结果发现
ksize
越大,图像轮廓越不明显,取ksize=3
即可scale
有助于显示更多图像轮廓信息,不过scale
过大会导致过多的轮廓信息出现,取scale=1
或2
即可delta
有助于提高图像整体亮度
ksize=3, scale=1, delta=0
ksize=5, scale=1, delta=0
ksize=3, scale=2, delta=0