1.拉普拉斯(Laplacian)算子
1.1基础介绍
最简单的各向同性导数算子是拉普赖斯算子,其具有旋转不变性,对于两个变量的函数
f
(
x
,
y
)
f(x,y)
f(x,y),其定义为
▽
2
f
=
∂
2
f
∂
x
2
+
∂
2
f
∂
y
2
\triangledown^2f=\frac{\partial ^2f}{\partial x ^2} + \frac{\partial ^2f}{\partial y ^2}
▽2f=∂x2∂2f+∂y2∂2f,以离散形式表示上述公式为:
<br/>
x方向有:$\frac{\partial ^2f}{\partial x ^2} = f(x+1, y) + f(x-1, y) - 2f(x,y) = (f(x+1, y) -f(x,y)) - (f(x,y)-f(x-1,y))
‘
<
b
r
/
>
‘
y
方
向
有
`<br/>`y方向有
‘<br/>‘y方向有\frac{\partial ^2f}{\partial y ^2} = f(x, y+1) + f(x, y-1) - 2f(x,y)$
由上面的公式,离散形式的拉普拉斯变换为:
▽ 2 f ( x , y ) = f ( x , y + 1 ) + f ( x , y − 1 ) + f ( x + 1 , y ) + f ( x − 1 , y ) − 4 f ( x , y ) \triangledown ^2f(x,y) = f(x, y+1) + f(x, y-1)+f(x+1, y) + f(x-1, y) - 4f(x,y) ▽2f(x,y)=f(x,y+1)+f(x,y−1)+f(x+1,y)+f(x−1,y)−4f(x,y)
使用卷积核的形式可表示为:
[
0
1
0
1
−
4
1
0
1
0
]
\begin{bmatrix} 0& 1 & 0\\ 1& -4 & 1 \\ 0 & 1 & 0 \end{bmatrix}
⎣⎡0101−41010⎦⎤
拉普拉斯算子是图像的二阶梯度算子,在灰度恒定区域其结果为0,在像素均匀变化区域其结果也为0,在像素变化速率变化较大区域其值也会变大,因此可将拉普拉斯算子应用到图像锐化和边缘检测。
应用到图像锐化时,应用拉普拉斯算子后的结果丢失了恒定像素区域的信息,因此将其与原图相加可求得锐化后的图像。(负中心就减正中心就加)。
1.2OpenCV API
Laplacian
见OpenCV文档
void cv::Laplacian(
InputArray src,
OutputArray dst,
int ddepth,
int ksize = 1,
double scale = 1,
double delta = 0,
int borderType = BORDER_DEFAULT
)
src
原图dst
结果,与原图同shape
ddepth
Desired depth目标图像的深度ksize
卷积核的大小,正奇数scale
可选的计算拉普拉斯值时的缩放因子delta
可选的计算拉普拉斯值时需加的值borderType
像素外推方法,不支持BORDER_WRAP
1.3 示例
void laplacianOperator(cv::Mat &img)
{
int kernel_size = 1;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
const char* window_name = "Laplace Demo";
cv::GaussianBlur(img, img, cv::Size(3, 3), 0, 0, cv::BORDER_DEFAULT);
cv::Mat img_gray;
cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
cv::Mat dst;
cv::Laplacian(img_gray, dst, ddepth, kernel_size, scale, delta, cv::BORDER_DEFAULT);
cv::Mat dst_abs;
// converting back to CV_8U
cv::convertScaleAbs(dst, dst_abs);
cv::Mat compound_img;
cv::Mat imageEnhance;
cv::Mat kernel = (cv::Mat_<float>(3, 3) << 0, 1, 0, 1, -4, 1, 0, 1, 0);
cv::filter2D(img_gray, imageEnhance, CV_8UC3, kernel);
cv::imshow("filter2D", imageEnhance);
cv::subtract(img_gray, imageEnhance, compound_img);
cv::imshow("Origin: ", img_gray);
cv::imshow(window_name, dst_abs);
cv::imshow("compound_img: ", compound_img);
cv::waitKey(0);
cv::imwrite("origin_image.png", img_gray);
cv::imwrite("laplacian.png", dst_abs);
cv::imwrite("enchanced_image.png", compound_img);
}
原图:
应用拉普拉斯算子后的二阶梯度图:
锐化后的图像:
2.Sobel算子
2.1基础
在进行边缘检测时需先求出图像的梯度,在像素的每个位置计算偏导数
∂
f
∂
x
\frac{\partial f}{\partial x}
∂x∂f和
∂
f
∂
y
\frac{\partial f}{\partial y}
∂y∂f,图像的梯度计算,通常使用前向或中心有限差分。使用前向差分计算:<br/>
g
x
(
x
,
y
)
=
∂
f
(
x
,
y
)
∂
x
=
f
(
x
+
1
,
y
)
−
f
(
x
,
y
)
g_x(x,y) = \frac{\partial f(x, y)}{\partial x} = f(x+1, y) - f(x, y)
gx(x,y)=∂x∂f(x,y)=f(x+1,y)−f(x,y)
<br/>
g
y
(
x
,
y
)
=
∂
f
(
x
,
y
)
∂
y
=
f
(
x
,
y
+
1
)
−
f
(
x
,
y
)
g_y(x,y) = \frac{\partial f(x, y)}{\partial y} = f(x, y+1) - f(x, y)
gy(x,y)=∂y∂f(x,y)=f(x,y+1)−f(x,y)
上述梯度的计算可以使用一维核对
f
(
x
,
y
)
f(x,y)
f(x,y)滤波实现。
[
−
1
1
]
\begin{bmatrix} -1\\ 1 \end{bmatrix}
[−11]和
[
1
−
1
]
\begin{bmatrix} 1\\ -1 \end{bmatrix}
[1−1]
感兴趣的区域是对角边缘时,需要使用二维核。Sobel核在中心系数上使用了权值2,
g
x
=
∂
f
∂
x
=
(
z
7
+
2
z
8
+
z
9
)
−
(
z
1
+
2
z
2
+
z
3
)
g_x = \frac{\partial f}{\partial x} = (z_7 + 2z_8 + z_9) - (z_1+2z_2+z_3)
gx=∂x∂f=(z7+2z8+z9)−(z1+2z2+z3)和
g
y
=
∂
f
∂
y
=
(
z
3
+
2
z
6
+
z
9
)
−
(
z
1
+
2
z
4
+
z
7
)
g_y = \frac{\partial f}{\partial y} = (z_3+2z_6+z_9)-(z_1+2z_4+z_7)
gy=∂y∂f=(z3+2z6+z9)−(z1+2z4+z7)
[
−
1
−
2
−
1
0
0
0
1
2
1
]
\begin{bmatrix} -1 & -2 & -1\\ 0& 0 & 0\\ 1& 2 & 1 \end{bmatrix}
⎣⎡−101−202−101⎦⎤
和
[
−
1
0
1
−
2
0
2
−
1
0
1
]
\begin{bmatrix} -1 & 0 & 1\\ -2& 0 & 2\\ -1& 0 & 1 \end{bmatrix}
⎣⎡−1−2−1000121⎦⎤
在中心位置使用2可以平滑图像,Sobel算子只为垂直边缘和水平边缘给出各向同性的结果。
Sobel算子在kernel大小为3时,使用上述算子计算梯度会产生不可忽略的误差,对图像中较弱的边缘提取效果较差。为了能够有效的提取出较弱的边缘,需要将像素值间的差距增大,因此引入Scharr算子。Scharr算子是对Sobel算子差异性的增强。
[
−
3
−
10
−
3
0
0
0
3
10
3
]
\begin{bmatrix} -3 & -10 & -3\\ 0& 0 & 0\\ 3& 10 & 3 \end{bmatrix}
⎣⎡−303−10010−303⎦⎤
和
[
−
3
0
3
−
10
0
10
−
3
0
3
]
\begin{bmatrix} -3 & 0 & 3\\ -10& 0 & 10\\ -3& 0 & 3 \end{bmatrix}
⎣⎡−3−10−30003103⎦⎤
2.2 OpenCV API
void cv::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
输出图像,与src
同shape
ddepth
输出图像深度dx
x偏导的阶dy
y偏导的阶ksize``Sobel
算子的大小scale
可选的梯度值的缩放因子delta
可选的梯度值的偏置borderType
边界值的处理方式,dst
与src
同尺寸,故需对src
边界扩充,可用方式见BorderTypes,不支持BORDER_WRAP
2.3实例
void sobelOperator(cv::Mat &img)
{
cv::GaussianBlur(img, img, cv::Size(5, 5), 0, 0, cv::BORDER_DEFAULT);
cv::Mat img_gray;
cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
cv::Mat grad, grad_x, grad_y, abs_grad_x, abs_grad_y;
const cv::String window_name = "Sobel Demo - Simple Edge Detector";
int ksize = 3;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
cv::Sobel(img_gray, grad_x, ddepth, 1, 0, ksize, scale, delta, cv::BORDER_DEFAULT);
cv::Sobel(img_gray, grad_y, ddepth, 0, 1, ksize), scale, delta, cv::BORDER_DEFAULT;
cv::convertScaleAbs(grad_x, abs_grad_x);
cv::convertScaleAbs(grad_y, abs_grad_x);
cv::addWeighted(abs_grad_x, 0.5, abs_grad_x, 0.5, 0, grad);
cv::imshow(window_name, grad);
// cv::Mat kernel_x = (cv::Mat_<float>(3, 3) << -1, 0, 1, -2, 0, 2, -2, 0, 2);
// cv::Mat kernel_y = (cv::Mat_<float>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);
cv::Mat kernel_x = (cv::Mat_<float>(3, 3) << -3, 0, 3, -10, 0, 10, -3, 0, 3);
cv::Mat kernel_y = (cv::Mat_<float>(3, 3) << -3, -10, -3, 0, 0, 0, 3, 10, 3);
cv::Mat grad_k, grad_x_k, grad_y_k, abs_grad_x_k, abs_grad_y_k;
cv::filter2D(img_gray, grad_x_k, ddepth, kernel_x, cv::Point(-1, -1), 0, cv::BORDER_DEFAULT);
cv::filter2D(img_gray, grad_y_k, ddepth, kernel_y, cv::Point(-1, -1), 0, cv::BORDER_DEFAULT);
cv::convertScaleAbs(grad_x_k, abs_grad_x_k);
cv::convertScaleAbs(grad_y_k, abs_grad_y_k);
cv::addWeighted(abs_grad_y_k, 0.5, abs_grad_x_k, 0.5, 0, grad_k);
cv::imshow("Soble by Filtered2D", grad_k);
cv::imshow("Origin", img_gray);
cv::imwrite("sobel.png", grad);
cv::waitKey(0);
}
梯度图像:
3.Roberts算子【1965】
3.1基本理论
罗伯特交叉梯度算子是最早使用具有对角性能的二维核的算子之一,罗伯特算子的实现方式为对角差分,x方向
g
x
=
∂
f
∂
x
=
(
z
9
−
z
5
)
g_x = \frac{\partial f}{\partial x}=(z_9 -z_5)
gx=∂x∂f=(z9−z5)算子为
[
−
1
0
0
1
]
\begin{bmatrix} -1 &0 \\ 0& 1 \end{bmatrix}
[−1001], y方向
g
y
=
∂
f
∂
y
=
(
z
8
−
z
6
)
g_y = \frac{\partial f}{\partial y}=(z_8 -z_6)
gy=∂y∂f=(z8−z6),算子为
[
0
−
1
−
1
0
]
\begin{bmatrix} 0&-1 \\ -1& 0 \end{bmatrix}
[0−1−10], 2x2的核概念上简单,但不如中心对称的核准确。
3.2OpenCV API
OpenCV中没有专门的函数实现Roberts算子。可以借助filter2D()
函数实现
void cv::filter2D (
InputArray src,
OutputArray dst,
int ddepth,
InputArray kernel,
Point anchor = Point(-1,-1),
double delta = 0,
int borderType = BORDER_DEFAULT
)
src
输入图像dst
与src
同shape
ddepth
目标图的深度,uint8
等kernel
单通道卷积核,浮点矩阵,若想在不同的通道上使用不同的卷积核则将原图像按channel
拆分,再使用不同的卷积核计算anchor
卷积的作用点,(-1, -1)表示卷积核的中心delta
卷积后追加的偏置borderType
边缘像素的处理方式
anchor
的使用方式
3.3示例
void robertsOperator(cv::Mat &img)
{
int ddepth = CV_16S;
cv::GaussianBlur(img, img, cv::Size(5, 5), 0, 0, cv::BORDER_DEFAULT);
cv::Mat img_gray;
cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
cv::Mat kernel_x = (cv::Mat_<float>(2, 2) << -1, 0, 0, 1);
cv::Mat kernel_y = (cv::Mat_<float>(2, 2) << 0, -1, 1, 0);
cv::Mat grad_k, grad_x_k, grad_y_k, abs_grad_x_k, abs_grad_y_k;
cv::filter2D(img_gray, grad_x_k, ddepth, kernel_x, cv::Point(-1, -1), 0, cv::BORDER_DEFAULT);
cv::filter2D(img_gray, grad_y_k, ddepth, kernel_y, cv::Point(-1, -1), 0, cv::BORDER_DEFAULT);
cv::convertScaleAbs(grad_x_k, abs_grad_x_k);
cv::convertScaleAbs(grad_y_k, abs_grad_y_k);
cv::addWeighted(abs_grad_y_k, 0.5, abs_grad_x_k, 0.5, 0, grad_k);
cv::imshow("robert by Filtered2D", grad_k);
cv::imshow("Origin", img_gray);
cv::imwrite("roberts.png", grad_k);
cv::waitKey(0);
}
4.Prewitt算子
4.1基本理论
Prewitt
算子与Sobel
算子十分类似,区别在于Sobel
算子中心值为2,能够平滑噪声,Prewitt
算子实现起来更简单,形如, x方向
和y方向分别为:
[
−
1
0
1
−
2
0
2
−
1
0
1
]
\begin{bmatrix} -1 & 0 & 1\\ -2& 0 & 2\\ -1& 0 & 1 \end{bmatrix}
⎣⎡−1−2−1000121⎦⎤
[ − 1 − 2 − 1 0 0 0 1 2 1 ] \begin{bmatrix} -1 & -2 & -1\\ 0& 0 & 0\\ 1& 2 & 1 \end{bmatrix} ⎣⎡−101−202−101⎦⎤
4.2OpenCV API
使用filter2D
实现
4.3示例
void prewittOperator(cv::Mat &img)
{
int ddepth = CV_16S;
cv::GaussianBlur(img, img, cv::Size(5, 5), 0, 0, cv::BORDER_DEFAULT);
cv::Mat img_gray;
cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
cv::Mat kernel_x = (cv::Mat_<float>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
cv::Mat kernel_y = (cv::Mat_<float>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);
cv::Mat grad_k, grad_x_k, grad_y_k, abs_grad_x_k, abs_grad_y_k;
cv::filter2D(img_gray, grad_x_k, ddepth, kernel_x, cv::Point(-1, -1), 0, cv::BORDER_DEFAULT);
cv::filter2D(img_gray, grad_y_k, ddepth, kernel_y, cv::Point(-1, -1), 0, cv::BORDER_DEFAULT);
cv::convertScaleAbs(grad_x_k, abs_grad_x_k);
cv::convertScaleAbs(grad_y_k, abs_grad_y_k);
cv::addWeighted(abs_grad_y_k, 0.5, abs_grad_x_k, 0.5, 0, grad_k);
cv::imshow("prewitt by Filtered2D", grad_k);
cv::imshow("Origin", img_gray);
cv::imwrite("prewitt.png", grad_k);
cv::waitKey(0);
}
5.其他
此外还有Kirsch
罗盘核,使用8个不同核,在8个方向上计算梯度,进行边缘检测
Tips
- convertScaleAbs