OpenCV 学习笔记(6) Filtering the Images

In this chapter, we will cover:

  • Filtering images using low-pass filters
  • Filtering images using a median filter
  • Applying directional filters to detect edges
  • Computing the Laplacian of an image

Introduction

  Observing the frequency of those variations in an image constitutes another way of characterizing an image. This point of view is referred to as the frequency domain(频域), while characterizing an image by observing its gray-level distribution is referred to as the spatial domain(空间域).
  由上面可知,频域是分析图像强度(intensity)的变化,空间域分析图像灰度的分布;那么在频域中去分析图像,高频信息是有图像强度的快速变化产生,一般对应图像的边缘信息,因为在边缘图像强度才会变化很快, 那么对应的低频信息多是图像中某一块区域,如上一章那个牛的图片中的背景区域,或者前景区域(牛),不会陡变,所以是低频信息,但是从牛到背景的 这个边缘,就会发生陡变,那么对应高频。

  图像是二维的,它包含垂直频率(垂直方向的变化)和水平频率(水平方向的变化)。

  而滤波(Filtering)的作用就是增强部分频段,同时限制(或衰减)其他频段。Low-pass(低通)滤波器就是消除图像中高频信息,High-pass(高通)滤波器就是去除低频成分,让高频成分通过。

  本章主要讲述用于图像处理的滤波器。


Filtering images using low-pass filters

箱式滤波器cv::blur

  如书上所说,low-pass滤波器去除高频信息,那么就是想降低图像变化的幅度。OpenCV中实现该功能的函数是cv::blur,作用是将每个像素替换为相邻矩形内像素的平均值,从而实现平滑。也称cv::blur为boxFilter(箱式滤波器)。

1
2
cv::blur(image, result, cv::Size(5, 5), anchor, borderType);
cv::boxFilter(image, result, src.type(), anchor, true, borderType);

  上面两个等价,当cv::boxFilter的第五个参数为true,第三个参数设定输出与输入类型一致,她就是均值滤波,第五个参数为false,则相当于把公式(1)前面的系数去掉,cv::blur第三个参数是定义kernel的大小,kernel是一个N*N的矩阵, 如公式(1)所示,anchor 默认是矩阵中心, See more in cv::blur and cv::boxFilter.

\begin{equation}
K=\frac{1}{ksize.width*ksize.height}
\begin{bmatrix}
1&1& \cdots &1&1& \\
1&1& \cdots &1&1& \\
&& \cdots \cdots \cdots & \\
1&1& \cdots &1&1&
\end{bmatrix} \tag{1}
\end{equation}

高斯滤波cv::GaussianBlur

  • OpenCV 函数调用

   有些情况,需要对靠近的像素分配更多的权重,常用的加权方案是基于高斯函数(Gaussian Function),OpenCV中实现该功能的函数cv::GaussianBlur:

1
cv::GaussianBlur(image,result,cv::Size(5,5),1.5);

  • 数学原理

  在高斯滤波器中,像素的权重和它离开中心像素点距离成正比。一维高斯函数的形式如公式(2)所示:

\begin{equation}
G(x) = Ae^{-x^2/2\sigma^2} \tag{2}
\end{equation}

  二维高斯函数的形式如公式(3)所示: \begin{equation} G(x,y) = Ae^{- \left( \frac{{\left(x-{\mu}_{x} \right)}^{2}}{2{{\sigma}_{x}}^{2}} + \frac{{\left(y-{\mu}_{y} \right)}^{2}}{2{{\sigma}_{y}}^{2}} \right)} \tag{3} \end{equation}

  归一化系数A使得不同权重之和为1,σ来控制高斯函数的高度(高斯函数就是以前学的正态分布的那个函数,所以正态分布也叫高斯分布),这个值越大,生成的函数越平坦,越小,越抖。(可以说σ是描述数据分布的离散程度,σ越大,数据越分散,σ越小,数据越集中,而学正态分布也知道σ代表方差,方差小数据自然就集中的平均数μ附近,也恰好说明函数图像为何很抖)。
  正太分布里有个3σ原则,也能说明为啥他能控制高斯函数的高度:P(μ-σ<X≤μ+σ)=68.3%;P(μ-2σ<X≤μ+2σ)=95.4%;P(μ-3σ<X≤μ+3σ)=99.7%;

  OpenCV提供cv::getGaussianKernel函数根据提供的σ来计算系数:

1
cv::Mat gauss= cv::getGaussianKernel(9,sigma,CV_32F);

不同的σ计算出来的系数自然也就不一样,这里列出书上的例子:
(a)σ = 0.5

1
[0.0 0.0 0.00026 0.10645 0.78657 0.10645 0.00026 0.0 0.0]

(b)σ = 1.5

1
[0.00761 0.036075 0.10959 0.21345 0.26666 0.21345 0.10959 0.03608 0.00761 ]

  这里可以参考这篇文章:深入剖析高斯滤波,文中指出:

  在图像处理中,高斯滤波一般有两种实现方式,一种是离散化的滑动窗口进行卷积运算,另一种是通过傅立叶变化来实现。最常见的就是第一种卷积核实现,只有当离散化的窗口非常大,计算非常耗时(这时可使用可分离滤波器)的情况下,可能会考虑基于傅里叶变换的实现方法。这里我们只讨论第一种方法。

  我们可以看到这篇文章的那个图,二维高斯函数可以用matlab仿真出来,网上有许多这样的代码。文中说:离散化的主旨就是保留高斯函数中心能量最集中的中间部分,忽略四周能量很小的平坦区域。这就可以为离中心像素近的权重大,离得远的权重小。

  • 这里重点把文中的第三个问题摘抄出来:为什么使用可分离滤波器?

  即书上为啥不直接用二维高斯函数来滤波,而是用一维高斯函数,先对图像的行用一维高斯滤波器(影响垂直方向的频率),在对图像的列应用相同的一维高斯滤波器(影响的是水平方向的频率),处理结果一样,利用的是高斯函数可以写成可分离的形式(Gaussian filter is a separable filter),因此可以采用可分离滤波器实现来加速。所谓的可分离滤波器,就是可以把多维的卷积化成多个一维卷积。具体到二维的高斯滤波,就是指先对行做一维卷积,再对列做一维卷积。这样就可以将计算复杂度从O(M*M*N*N)降到O(2*M*M*N),M,N分别是图像和滤波器的窗口大小,具体为啥如果以后回头看这篇文章的我想不出来先打自己一下,然后去看文章的链接。(对当初学信号与系统时的一些东西稍微理解的更深一点,以前只会计算题目。。。)

  在OpenCV中可以调用cv::sepFilter2D来使用分离的滤波器,怎么使用,看一眼文档立马就会了,见cv::sepFilter2D.这里书上还说了一个函数cv::filter2D,可以使用它来直接进行二维滤波,具体使用见OpenCV 学习笔记(2) Manipulating the Pixels (下).
  在使用cv::GaussianBlur函数时,第三个参数Size,长宽必须是奇数,此外,如果size为0,函数将根据第四个参数σ自动确定较为合适的系数的数量,至于怎么确定的,参考前面链接里的那篇文章,里面有把OPenCV源码拿出来讲解。此外,反过来提供尺寸的值,设置σ为0,函数也能自动确定较好的σ值。

金字塔

  再将图像按照2的倍数缩放时,由于原来图像中的高频分量(如尖锐的边缘)在缩小图中可能呈现出楼梯状,造成失真,为了使缩小后没有这样的情况,那么就先对原图像进行低通滤波,然后在缩小,OpenCV就提供这样的函数,cv::pyrDown:

1
2
cv::Mat reducedImage;
cv::pyrDown(image, reducedImage);

  cv::pyrDown做了两件事,先对原图像进行低通滤波,然后隔行隔列的去除偶数行像素(官网说的,见cv::pyrDown),缩小为原来的一半。与之对应的函数叫cv::pyrUp,他们经常用来创建图像金字塔(image pyramids)。常常用于高效的图像分析,例如想要检测图像中的物体,首先在金字塔顶层的小尺寸图像进行检测,然后定位感兴趣的物体,并在包含高分辨图像的低层金字塔中进行更精确的搜索。此外改变图像尺寸也可以使用cv::resize.


Filtering images using a median filter

  上一节说的都隶属于线性滤波器的范畴,这一节的中值滤波器就是非线性滤波器。中值滤波器对去除椒盐噪点(Salt-and-Pepper,前面的章节讲过)很有帮助,OpenCV的函数是cv::medianBlur:

1
cv::medianBlur(image, result, 5);

  由于他是非线性的,无法表示成之前的矩阵形式(因为取中位数,不确定性),她就是用相邻区域内的中值来替代当前的像素值。这也是为啥能对付椒盐噪点,突然冒出来的黑色或白色必然不会是中位数,早晚被替换掉,因为噪点只能改变平均数,但是改变不了中位数,除非他们聚集在一起很多,不过这也可能就不是噪点了。函数的第三个参数是确定相邻区域的大小(kSize*ksize的区域),必须是大于1且为奇数。中值滤波同时还具有保留边缘锐利度的优点,还能去除相同区域中的纹理。(这里还需注意使用medianBlur的时候,需要注意输入的要求,具体见cv::medianBlur)


Applying directional filters to detect edges

  之前主要讲的是低通滤波,接下来说高通滤波,经常用来做边缘检测(edge detection);

Sobel算子

示例

  具有方向性,根据使用的kernel的不同,只改变图像的水平或垂直频率。

1
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 );

  dx=1,dy=0是针对水平方向的滤波,dx=0,dy=1是针对垂直方向的滤波;ksize必须是1,3,5 or 7,当是1时,代表一维的意思,即3x1或1x3的核,如[-1, 0, 1] 和 [-1, 0, 1]的转置,其他数字代表的就是ksize*ksize的核;其他比如输出的类型等等具体参考 cv::Sobel.

  书上给的例子还是蛮好玩的,

1
2
cv::Sobel(image,sobelX,CV_8U,1,0,3,0.4,128);	//horizontal directions.
cv::Sobel(image,sobelY,CV_8U,0,1,3,0.4,128); //vertical directions.

  有意思的是后面的偏移量,将本来的得到的负值变成了低于128的正值,而原本的正值变成大于128的正值,这样的结果看起来有种书上所说的浮雕特效。

  书上又给了一个将水平和垂直方向结合的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Compute norm of Sobel
cv::Sobel(image,sobelX,CV_16S,1,0);
cv::Sobel(image,sobelY,CV_16S,0,1);
cv::Mat sobel;
//compute the L1 norm
sobel= abs(sobelX)+abs(sobelY);

double sobmin, sobmax;
cv::minMaxLoc(sobel,&sobmin,&sobmax);
// Conversion to 8-bit image
// sobelImage = -alpha*sobel + 255
cv::Mat sobelImage;
sobel.convertTo(sobelImage,CV_8U,-255./sobmax,255);

  得到的结果还是挺美的,通过convertTo将所有的值映射到0~255范围内。从这幅图像就能看出为何将其边缘检测器。


作用原理

  sobel 算子3x3的核如下公式(3):水平方向,(4):垂直方向 所示:

\begin{equation}
horizontal(kernel) =
\begin{bmatrix}
-1&0&1 \\
-2&0&2 \\
-1&0&1
\end{bmatrix} \tag{3}
\end{equation}

\begin{equation}
vertical(kernel) =
\begin{bmatrix}
-1&-2&-1 \\
0&0&0 \\
1&2&1
\end{bmatrix} \tag{4}
\end{equation}

  sobel算子可以认为是用来测量图像垂直和水平方向变化,这种变化叫梯度(gradient).

\begin{equation}
grad(I) =
{\begin{bmatrix}
\frac{\partial I}{\partial x}, \frac{\partial I}{\partial y}
\end{bmatrix}}^\mathrm{T} \tag{5}
\end{equation}

  公式虽说是求偏导,实际就是像素值求差分,这里是一阶差分,举例说明如下,为了更好的说明问题,用矩阵来替代图像,这样更加直观,毕竟图像就是矩阵:

\begin{equation}
src =
\begin{bmatrix}
0&0&0&0&0&0&0&0&0 \\
0&0&0&0&0&0&0&0&0 \\
0&0&1&1&1&1&1&0&0 \\
0&0&1&1&1&1&1&0&0 \\
0&0&1&1&1&1&1&0&0 \\
0&0&1&1&1&1&1&0&0 \\
0&0&1&1&1&1&1&0&0 \\
0&0&0&0&0&0&0&0&0 \\
0&0&0&0&0&0&0&0&0
\end{bmatrix} \tag{6}
\end{equation}

  这里使用的sobel算子如公式(3)(4)所示,得到的结果分别如下(7)(8)所示:

\begin{equation}
dst_x =
\begin{bmatrix}
0&0&0&0&0&0&0&0&0 \\
0&1&1&0&0&0&-1&-1&0 \\
0&3&3&0&0&0&-3&-3&0 \\
0&4&4&0&0&0&-4&-4&0 \\
0&4&4&0&0&0&-4&-4&0 \\
0&4&4&0&0&0&-4&-4&0 \\
0&3&3&0&0&0&-3&-3&0 \\
0&1&1&0&0&0&-1&-1&0 \\
0&0&0&0&0&0&0&0&0
\end{bmatrix} \tag{7}
\end{equation}

\begin{equation}
dst_y =
\begin{bmatrix}
0&0&0&0&0&0&0&0&0 \\
0&1&3&4&4&4&3&1&0 \\
0&1&3&4&4&4&3&1&0 \\
0&0&0&0&0&0&0&0&0 \\
0&0&0&0&0&0&0&0&0 \\
0&0&0&0&0&0&0&0&0 \\
0&-1&-3&-4&-4&-4&-3&-1&0 \\
0&-1&-3&-4&-4&-4&-3&-1&0 \\
0&0&0&0&0&0&0&0&0
\end{bmatrix} \tag{8}
\end{equation}

  就是将核与原图像做卷积,然后得到的值替换原像素值即可。从矩阵的结果中也能看出为何之前说有负值。

  图像的梯度是一个二维向量,如公式(5)所示,它拥有距离和方向,梯度向量的距离通常给出变化的幅度,通常使用欧拉距离(也称L2 norm):

\begin{equation}
\left|grad(I)\right| =
\sqrt{
{\left(\frac{\partial I}{\partial x}\right)}^2 + {\left(\frac{\partial I}{\partial y}\right)}^2
} \tag{9}
\end{equation}

  由于L2 norm的计算费时,所以经常有L1 norm替代,即取绝对值:

1
sobel= abs(sobelX)+abs(sobelY);

  梯度向量总是指向变化最剧烈的方向,在图像中, 这意味着梯度向量总是与边缘正交,从暗处指向亮出。梯度的方向是:

\begin{equation}
\angle grad(I) = arctan \left(-\frac{\partial I}{\partial y} \bigg/ \frac{\partial I}{\partial x} \right) \tag{10}
\end{equation}

  如果不仅需要计算距离,还需要方向,则用cv::cartToPolar

1
2
3
4
5
6
// Sobel must be computed in floating points
cv::Sobel(image,sobelX,CV_32F,1,0);
cv::Sobel(image,sobelY,CV_32F,0,1);
// Compute the L2 norm and direction of the gradient
cv::Mat norm, dir;
cv::cartToPolar(sobelX,sobelY,norm,dir);

  这里默认方向的单位是弧度(from 0 to 2*Pi),将额外的参数angleInDegrees设为true,即可得到角度(0 to 360 degrees), see more in cv::cartToPolar).

  对于梯度图阈值化即可得到二值的边缘图,合适的阈值很难,一种可选的途径是磁滞阈值化(hysteresis thresholding)的概念,将canny算子会用到。

其他的梯度算子

Prewitt operator

\begin{equation}
horizontal(kernel) =
\begin{bmatrix}
-1&0&1 \\
-1&0&1 \\
-1&0&1
\end{bmatrix} \tag{11}
\end{equation}

\begin{equation}
vertical(kernel) =
\begin{bmatrix}
-1&-1&-1 \\
0&0&0 \\
1&1&1
\end{bmatrix} \tag{12}
\end{equation}

Roberts operator

\begin{equation}
\begin{bmatrix}
1&0 \\
0&-1
\end{bmatrix} \tag{13}
\end{equation}

\begin{equation}
\begin{bmatrix}
0&1 \\
-1&0
\end{bmatrix} \tag{14}
\end{equation}

Scharr operator

\begin{equation}
horizontal(kernel) =
\begin{bmatrix}
-3&0&3 \\
-10&0&10 \\
-3&0&3
\end{bmatrix} \tag{15}
\end{equation}

\begin{equation}
vertical(kernel) =
\begin{bmatrix}
-3&-10&-3 \\
0&0&0 \\
3&10&3
\end{bmatrix} \tag{16}
\end{equation}

  当cv::Sobel的ksize参数为CV_SCHARR时,他们二者等价,如下所示:

1
2
cv::Sobel (image, sobelX,  CV_16S, 1, 0, CV_SCHARR);
cv::Scharr(image, scharrX, CV_16S, 1, 0, 3);


Computing the Laplacian of an image

  Laplacian 是另一种基于图像导数的高通线性滤波器,他计算二阶导数以衡量图像的弯曲度,从而得到边缘。

数学原理

  Laplacian算子是一种各向同性算子,二阶微分算子,在只关心边缘而不考虑其周围像素灰度差值时比较适合。Laplacian算子对孤立像素的响应比对边缘或线的响应更加强烈,这一点可参见公式(17)看出,所以适合无噪声图像,如果有噪声,应先进行低通滤波去噪,然后在应用Laplacian算子。

\begin{equation}
\begin{bmatrix}
0&1&0 \\
1&-4&1 \\
0&1&0
\end{bmatrix} \tag{17}
\end{equation}

  Laplacian算子具有旋转不变形,根据公式(17)的3x3的核,(一般如果我们不在意速度时,多用7x7核)我们可以定义:
\begin{equation}
\triangledown^{2} f(x,y)= {[f(x+1,y)-f(x,y)]-[f(x,y)-f(x-1,y)]}+{[f(x,y+1)-f(x,y)]-[f(x,y)-f(x,y-1)]} \\ = f(x+1,y)+f(x-1,y)+f(x,y+1)+f(x,y-1)-4f(x,y) \tag{18}
\end{equation}

  公式(18)在图像处理中更好理解,在数学上,如下所示:

\begin{equation}
\triangledown^{2} f = \frac{\partial^{2} f}{\partial x^{2}}+\frac{\partial^{2} f}{\partial y^{2}} \tag{19}
\end{equation}

  由于Laplacian算子度量的是图像的弯曲度(curvature),所以在平坦区域等于0。

  边缘的存在是不同灰度区域间快速过度的结果,沿着图像在一条边上的变化(例如, 从暗处到亮处), 可以观察到灰度的提升意味着正曲率(强度值开始上升)到负曲率(当强度值到达最高值)的渐变。所以正负Laplacian算子值(或导数)是用来指示边缘存在的标志。书上那个图可以诠释这一点。

  这里再来举个例子,帮助自己以后复习更快理解:
原矩阵如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26, 27, 28, 29, 30, 31, 32, 33, 102, 105, 108, 111, 114, 117, 120, 123, 42, 43, 44, 45, 46, 47, 48, 49, 50,
27, 28, 29, 30, 31, 32, 33, 34, 105, 108, 111, 114, 117, 120, 123, 126, 43, 44, 45, 46, 47, 48, 49, 50, 51,
28, 29, 30, 31, 32, 33, 34, 35, 108, 111, 114, 117, 120, 123, 126, 129, 44, 45, 46, 47, 48, 49, 50, 51, 52,
29, 30, 31, 32, 33, 34, 35, 36, 111, 114, 117, 120, 123, 126, 129, 132, 45, 46, 47, 48, 49, 50, 51, 52, 53,
30, 31, 32, 33, 34, 35, 36, 37, 114, 117, 120, 123, 126, 129, 132, 135, 46, 47, 48, 49, 50, 51, 52, 53, 54,
31, 32, 33, 34, 35, 36, 37, 38, 117, 120, 123, 126, 129, 132, 135, 138, 47, 48, 49, 50, 51, 52, 53, 54, 55,
32, 33, 34, 35, 36, 37, 38, 39, 120, 123, 126, 129, 132, 135, 138, 141, 48, 49, 50, 51, 52, 53, 54, 55, 56,
33, 34, 35, 36, 37, 38, 39, 40, 123, 126, 129, 132, 135, 138, 141, 144, 49, 50, 51, 52, 53, 54, 55, 56, 57,
34, 35, 36, 37, 38, 39, 40, 41, 126, 129, 132, 135, 138, 141, 144, 147, 50, 51, 52, 53, 54, 55, 56, 57, 58,
35, 36, 37, 38, 39, 40, 41, 42, 129, 132, 135, 138, 141, 144, 147, 150, 51, 52, 53, 54, 55, 56, 57, 58, 59,
36, 37, 38, 39, 40, 41, 42, 43, 132, 135, 138, 141, 144, 147, 150, 153, 52, 53, 54, 55, 56, 57, 58, 59, 60,
37, 38, 39, 40, 41, 42, 43, 44, 135, 138, 141, 144, 147, 150, 153, 156, 53, 54, 55, 56, 57, 58, 59, 60, 61,
38, 39, 40, 41, 42, 43, 44, 45, 138, 141, 144, 147, 150, 153, 156, 159, 54, 55, 56, 57, 58, 59, 60, 61, 62,
39, 40, 41, 42, 43, 44, 45, 46, 141, 144, 147, 150, 153, 156, 159, 162, 55, 56, 57, 58, 59, 60, 61, 62, 63,
40, 41, 42, 43, 44, 45, 46, 47, 144, 147, 150, 153, 156, 159, 162, 165, 56, 57, 58, 59, 60, 61, 62, 63, 64,
41, 42, 43, 44, 45, 46, 47, 48, 147, 150, 153, 156, 159, 162, 165, 168, 57, 58, 59, 60, 61, 62, 63, 64, 65,
42, 43, 44, 45, 46, 47, 48, 49, 150, 153, 156, 159, 162, 165, 168, 171, 58, 59, 60, 61, 62, 63, 64, 65, 66,
43, 44, 45, 46, 47, 48, 49, 50, 153, 156, 159, 162, 165, 168, 171, 174, 59, 60, 61, 62, 63, 64, 65, 66, 67,
44, 45, 46, 47, 48, 49, 50, 51, 156, 159, 162, 165, 168, 171, 174, 177, 60, 61, 62, 63, 64, 65, 66, 67, 68,
45, 46, 47, 48, 49, 50, 51, 52, 159, 162, 165, 168, 171, 174, 177, 180, 61, 62, 63, 64, 65, 66, 67, 68, 69,
46, 47, 48, 49, 50, 51, 52, 53, 162, 165, 168, 171, 174, 177, 180, 183, 62, 63, 64, 65, 66, 67, 68, 69, 70,
47, 48, 49, 50, 51, 52, 53, 54, 165, 168, 171, 174, 177, 180, 183, 186, 63, 64, 65, 66, 67, 68, 69, 70, 71,
48, 49, 50, 51, 52, 53, 54, 55, 168, 171, 174, 177, 180, 183, 186, 189, 64, 65, 66, 67, 68, 69, 70, 71, 72,
49, 50, 51, 52, 53, 54, 55, 56, 171, 174, 177, 180, 183, 186, 189, 192, 65, 66, 67, 68, 69, 70, 71, 72, 73,
50, 51, 52, 53, 54, 55, 56, 57, 174, 177, 180, 183, 186, 189, 192, 195, 66, 67, 68, 69, 70, 71, 72, 73, 74

在经过公式(17)所示的Laplacian算子滤波后,结果变成下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
16 8 8 8 8 8 8 288 -248 24 24 24 24 24 24 -320 344 8 8 8 8 8 8 8 0
8 0 0 0 0 0 0 280 -272 0 0 0 0 0 0 -344 336 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 288 -280 0 0 0 0 0 0 -352 344 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 296 -288 0 0 0 0 0 0 -360 352 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 304 -296 0 0 0 0 0 0 -368 360 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 312 -304 0 0 0 0 0 0 -376 368 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 320 -312 0 0 0 0 0 0 -384 376 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 328 -320 0 0 0 0 0 0 -392 384 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 336 -328 0 0 0 0 0 0 -400 392 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 344 -336 0 0 0 0 0 0 -408 400 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 352 -344 0 0 0 0 0 0 -416 408 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 360 -352 0 0 0 0 0 0 -424 416 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 368 -360 0 0 0 0 0 0 -432 424 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 376 -368 0 0 0 0 0 0 -440 432 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 384 -376 0 0 0 0 0 0 -448 440 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 392 -384 0 0 0 0 0 0 -456 448 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 400 -392 0 0 0 0 0 0 -464 456 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 408 -400 0 0 0 0 0 0 -472 464 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 416 -408 0 0 0 0 0 0 -480 472 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 424 -416 0 0 0 0 0 0 -488 480 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 432 -424 0 0 0 0 0 0 -496 488 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 440 -432 0 0 0 0 0 0 -504 496 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 448 -440 0 0 0 0 0 0 -512 504 0 0 0 0 0 0 0 -8
8 0 0 0 0 0 0 456 -448 0 0 0 0 0 0 -520 512 0 0 0 0 0 0 0 -8
0 -8 -8 -8 -8 -8 -8 448 -472 -24 -24 -24 -24 -24 -24 -544 504 -8 -8 -8 -8 -8 -8 -8 -16

  上面的结果是用opencv 的函数cv::Laplacian函数,使用的核是3x3的,如公式(17)所示,如果你自己动手算一下,你会发现OpenCV出来的答案是自己手算的四倍,至于为啥没搞明白,不过官网上定义的ksize=1时,核就是如公式(17)所示,可能是因为重复的原因,ksize=3就把相同的核得到的结果乘以4?决定不管这个,记住就ok了。当然我定义的数据类型是CV_32F不知道跟这有没有关系。

  此外,OpenCV中对于二维核应该都给拆分成一维核来计算,从而节省计算量,如何获取。用cv::getDerivKernels:

1
2
3
4
cv::Mat kx, ky;
cv::getDerivKernels(kx, ky, 2, 2, 3);
// kx: 1 -2 1
// ky: 1 -2 1

  如果把上述参数中的2换成1,就变成了sobel算子的核,2是2阶导数的意思(刚好是Laplacian算子),1就代表1阶导数(刚好是sobel算子)。

  此外,如果想增加图像对比度(图像增强),可以使用这样的卷积核:

\begin{equation}
\begin{bmatrix}
0&-1&0 \\
-1&5&-1 \\
0&-1&0
\end{bmatrix} \tag{20}
\end{equation}

  即1减去Laplacian核,也就是原图像减去他的经过Laplacian算子操作后的结果。

  书上还讲了一个如果去得到零交叉线的代码,即如果得到边缘,这里就不写了,想知道看书。


END