OpenCV Canny Detector

理论 Canny边缘检测是由John F. Canny在1986年开发的。许多人也将其称为最佳检测器,Canny算法旨在满足三个主要标准。 好的检测:算法能够尽可能多地标识出图像中的实际边缘。 好的定位:标识出的边缘要与实际图像中的实际边缘尽可能接近。 最小响应:图像中的边缘只能标识一次,并且可能存在的图像雜訊不应标识为边缘。 步骤 降噪:使用高斯滤波来达到,下面是一个大小为 $5$ 的高斯核的例子: $$ K = \frac{1}{159}\begin{bmatrix} 2 & 4 & 5 & 4 & 2\\ 4 & 9 & 12 & 9 & 4\\ 5 & 12 & 15 & 12 & 5\\ 4 & 9 & 12 & 9 & 4\\ 2 & 4 & 5 & 4 & 2 \end{bmatrix} $$ 找到图像的亮度梯度:为此,我们遵循一个类似于Sobel的程序: 应用一对卷积masks(在 $x$ 和 $y$ 方向上): $$ G_x = \begin{bmatrix} -1 & 0 & +1\\ -2 & 0 & +2\\ -1 & 0 & +1 \end{bmatrix}, G_y=\begin{bmatrix} -1 & -2 & -1\\ 0 & 0 & 0\\ +1 & +2 & +1 \end{bmatrix} $$ 寻找梯度强度和方向: $$ G=\sqrt{G_x^2+G_y^2}\\ \theta = \arctan(\frac{G_y}{G_x}) $$ 方向被四舍五入为四个可能的角度之一(即 $0\degree$ 、 $45\degree$ 、 $90\degree$ 或 $135\degree$ )。 过滤非最大值:在高斯滤波过程中,边缘有可能被放大了。这个步骤使用一个规则来过滤不是边缘的点,使边缘的宽度尽可能为1个像素点:如果一个像素点属于边缘,那么这个像素点在梯度方向上的梯度值是最大的。否则不是边缘,将灰度值设为 $0$ 。 $$ M_T(m, n) = \begin{cases} M(m, n)& \text{if } M(m, n) \lt T\\ 0 & \text{otherwise} \end{cases} $$...

January 17, 2022 · 2 min · fffzlfk

OpenCV Laplace Operator

理论 在之前的教程中,我们学习了如何使用Sobel算子。它是基于这样一个事实,即在边缘区域,像素强度显示了一个 “跳跃"或强度的高变化。得到强度的一阶导数,我们观察到边缘的特征是一个最大值,如图所示: 那么,如果我们取二阶导数会怎样? 你可以观察到,边缘二阶导数是零! 因此,我们也可以用这个标准来尝试检测图像的边缘。然而,请注意,零值不仅会出现在边缘(它们实际上可以出现在其他无意义的位置);这可以通过在需要时使用过滤来解决。 拉普拉斯算子 从上面的解释中,我们可以推断出,二阶导数可以用来检测边缘。由于图像是二维的,我们需要在两个维度上取导数。这里,拉普拉斯算子就派上用场了。 拉普拉斯算子的定义是: $$ Laplace(f) = \frac{\partial^2f}{\partial x^2}+\frac{\partial^2f}{\partial y^2} $$ 拉普拉斯算子在OpenCV中是由函数Laplacian()实现的。事实上,由于拉普拉斯算子使用图像的梯度,它在内部调用索贝尔算子来进行计算的。 Code 代码链接 Explanation 变量声明 // Declare the variables we are going to use Mat src, src_gray, dst; int kernel_size = 3; int scale = 1; int delta = 0; int ddepth = CV_16S; const char* window_name = "Laplace Demo"; 加载图像 const char* imageName = argc >=2 ? argv[1] : "./images/lena.jpg"; src = imread( samples::findFile( imageName ), IMREAD_COLOR ); // Load an image // Check if image is loaded fine if(src....

January 17, 2022 · 1 min · fffzlfk

OpenCV Sobel Derivatives

理论 在之前的两个教程中,我们已经看到了卷积的应用实例。最重要的卷积之一是计算图像中的导数(或对它们的近似值) 为什么图像中的导数计算可能是重要的?让我们设想一下,我们要检测图像中存在的边缘。比如说: 你可以很容易地注意到,在一个边缘,像素强度的变化是很明显的。一个表达变化的好方法是使用导数。梯度的高变化表示图像中的一个重大变化。 为了更加形象,让我们假设我们有一个一维图像。在下面的图中,一个边缘由强度的 “跳跃 “来表示: 如果我们取第一个导数,可以更容易地看到边缘的 “跳跃”(实际上,这里出现的是一个最大值): 因此,从上面的解释中,我们可以推断出,检测图像中的边缘的方法可以通过定位梯度高于其邻居的像素位置(或者概括地说,高于一个阈值)来进行。 更详细的解释,请参考Bradski和Kaehler的《Learning OpenCV》。 Sobel 算子 Sobel算子是一个离散的微分算子,它计算图像强度函数的梯度的近似值。 Sobel算子结合了Gaussian smoothing和微分。 Formulation 假设要操作的图像为 $I$: 我们计算两个导数: 水平变化:这是通过用奇数大小的内核 $G_x$ 对 $I$ 进行卷积计算的。例如,对于内核大小为 $3$ , $G_x$ 将被计算为: $$ G_x=\begin{bmatrix} -1 & 0 & +1\\ -2 & 0 & +2\\ -1 & 0 & +1 \end{bmatrix} * I $$ 垂直变化:这是通过用奇数大小的内核 $G_y$ 对 $I$ 进行卷积计算的。例如,对于内核大小为 $3$ , $G_y$ 将被计算为: $$ G_y = \begin{bmatrix} -1 & -2 & -1\\ 0 & 0 & 0\\ +1 & +2 & + 1 \end{bmatrix} * I $$ 在图像的每一点上,我们通过结合上述两个结果计算出该点的梯度近似值: $$ G = \sqrt{G_{x}^{2}+G_{y}^{2}} $$ 有时会使用以下更简单的方程式: $$ G = |G_x|+|G_y| $$ 当核的大小为 $3$ 时,上面显示的 Sobel核可能会产生明显的不准确(毕竟, Sobel 只是一个导数的近似值)。OpenCV通过使用 Scharr() 函数来解决这种大小为 $3$ 的核的不精确性。这和标准的 Sobel 函数一样快,但比它更准确。它可以实现以下内核 $$ G_x=\begin{bmatrix} -3 & 0 & +3\\ -10 & 0 & +10\\ -3 & 0 & +3 \end{bmatrix}, G_y=\begin{bmatrix} -3 & -10 & -3\\ 0 & 0 & 0\\ +3 & +10 & +3 \end{bmatrix} $$...

January 16, 2022 · 2 min · fffzlfk

OpenCV Erosion Dilatation

形态学操作 简而言之:一套基于形状处理图像的操作。形态学操作将一个结构化元素应用于输入图像,并生成一个输出图像。 最基本的形态学操作是。腐蚀和膨胀。它们有广泛的用途,即 去除噪音 隔离单个元素和连接图像中不同的元素 寻找图像中的强度凹凸点或洞 我们将以下面的图像为例,简要地解释膨胀和侵蚀。 Dilation(膨胀) 这种操作包括将图像A与一些kernel B进行卷积,内核可以有任何形状或大小,通常是一个正方形或圆形。 kernel B有一个定义的锚点(anchor point),通常是核的中心。 当kernel B在图像上扫描时,我们计算出被B重叠的最大像素值,并用该最大值替换锚点位置的图像像素。正如你可以推断的那样,这种最大化的操作会使图像中的明亮区域 “增长”(因此被称为膨胀)。 膨胀操作: $dst(x, y)=max(x^{’}, y^{’})_{:element(x^{’}, y^{’}) \ne 0} src(x+x^{’}, y+y^{’})$ 以上面的图像为例。应用膨胀的方法,我们可以得到 腐蚀 它在给定内核的区域内计算局部最小值。当内核B在图像上被扫描时,我们计算出被B重叠的最小像素值,并用该最小值替换锚点下的图像像素。 腐蚀操作为:$dst(x, y)=min(x^{’}, y^{’})_{:element(x^{’}, y^{’}) \ne 0} src(x+x^{’}, y+y^{’})$ 与膨胀的例子类似,我们可以对原始图像应用腐蚀算子(如上图)。你可以在下面的结果中看到,图像的亮区变薄了,而暗区变大了。 Code #include "basic/erosion_dilatation.hpp" namespace basic { namespace erosion_dilatation { namespace impl { Mat src, erosion_dst, dilation_dst; int erosion_elem = 0; int erosion_size = 0; int dilation_elem = 0; int dilation_size = 0; int const max_elem = 2; int const max_kernel_size = 21; int work(int argc, char **argv) { CommandLineParser parser(argc, argv, "{@input | ....

January 9, 2022 · 2 min · fffzlfk

OpenCV Blurring

理论 Smoothing也叫blurring(模糊化),是一个简单而常用的图像处理操作。 Smoothing有很多原因。在本教程中,我们将重点讨论平滑操作,以减少噪音。 为了进行平滑操作,我们将对我们的图像应用一个filter。最常见的filter是线性的,其中输出像素的值(即 $g(i,j)$ )为输入像素值的加权和(即 $f(i+k,j+l)$ )。 $$ g(i, j) = \sum_{k, l}{f(i+k, j+l)h(k, l)} $$ $h(k,l)$ 被称为kernel,它只不过是filter的系数。这有助于把滤波器想象成一个在图像上滑动的系数窗口。 滤波器有很多种类,这里我们将提到最常用的几种。 Normalized Box Filter(归一化块滤波器) 这个滤波器是最简单的,每个输出像素都是其内核邻居的平均值(所有的像素都有相同的权重)。 $$ K = \frac{1}{K_{width} \cdot k_{height}} \begin{bmatrix} 1 & 1 & 1 & …& 1\\ 1 & 1 & 1 & …& 1\\ . & . & . & …& 1\\ 1 & 1 & 1 & …& 1 \end{bmatrix} $$ Gaussian Filter(高斯滤波器) 可能是最有用的滤波器(尽管不是最快的)。高斯滤波是通过用高斯内核对输入阵列中的每个点进行卷积,然后将它们全部相加来产生输出阵列的。 让我们回顾一下1D Gaussian kernel是什么样子: 假设图像是一维的,你可以注意到,位于中间的像素会有最大的权重。它的邻居的权重随着它们与中心像素之间的空间距离增加而减少。...

January 9, 2022 · 1 min · fffzlfk