理解多点触控/卷积和反卷积
数学上,卷积被描述为一个运算符,它接收两个函数 f 和 g,并生成第三个函数,表示 f 和 g 之间的重叠量。卷积在信号分析中被广泛使用,因为卷积运算通常非常便宜,可以并行执行,并且易于调整以适应情况。
在我们的案例中,我们将讨论二维意义上的卷积,其中图像和已知卷积函数(称为 **滤波器**)将被组合以生成一个更好地表示我们正在寻找的数据的图像。由于您应该已经熟悉这些概念,因此这将是对卷积工作原理的简要概述,以及我们感兴趣的特定卷积滤波器。
如果您回忆一下,我们的理论传感器模型将数据作为 8x8 的 8 位数据矩阵输入系统,类似于以下内容
Horizontal Address 0 1 2 3 4 5 6 7 V A 0 00 00 00 00 00 00 00 00 e d 1 00 00 00 00 00 00 00 00 r d 2 00 00 00 00 00 00 00 00 t r 3 00 00 00 00 00 00 00 00 i e 4 00 00 00 00 00 00 00 00 c s 5 00 00 00 00 00 00 00 00 a s 6 00 00 00 00 00 00 00 00 l 7 00 00 00 00 00 00 00 00
然而,我们的卷积核尺寸要小得多。对于我们将用于理论模型的算法,3x3 卷积核就足够了。
在实现卷积核时,重要的是要记住,我们正在接受两个函数并生成第三个函数。原始函数可以被丢弃,或者可以返回到系统中用于其他目的,例如降噪。因此,当我们在软件中实现我们的滤波器时,我们将希望生成一个函数,该函数将返回一个与输入缓冲区大小相同的填充缓冲区给用户。
卷积操作应该看起来非常类似于此伪代码
function Convolution ( in_buffer[8][8], out_buffer[8][8], divisor )
{
define kernel[3][3];
for (x from 0 to 7)
for (y from 0 to 7)
out_buffer[x][y] = 0; //make sure the buffer is zeroed
if (x-1 >= 0 && y-1 >= 0)
out_buffer[x][y] += in_buffer[x-1][y-1] * kernel[0][0];
if (x-1 >= 0)
out_buffer[x][y] += in_buffer[x-1][y] * kernel[0][1];
if (x-1 >= 0 && y+1 <= 7)
out_buffer[x][y] += in_buffer[x-1][y+1] * kernel[0][2];
if (y-1 >= 0)
out_buffer[x][y] += in_buffer[x][y-1] * kernel[1][0];
//this is our "free" operation, the only one that will always work.
out_buffer[x][y] += in_buffer[x][y] * kernel[1][1];
if (y+1 <= 7)
out_buffer[x][y] += in_buffer[x][y+1] * kernel[1][2];
if (x+1 <= 7 && y-1 >= 0)
out_buffer[x][y] += in_buffer[x+1][y-1] * kernel[2][0];
if (x+1 <= 7)
out_buffer[x][y] += in_buffer[x+1][y] * kernel[2][1];
if (x+1 <= 7 && y+1 <= 7)
out_buffer[x][y] += in_buffer[x+1][y+1] * kernel[2][2];
if (divisor && divisor > 0)
out_buffer[x][y] /= divisor;
else
out_buffer[x][y] /= 9;
end for
end for
}
上述内容可能在您的硬件上进行了优化,因为您通常可以通过确保您的缓冲区足够大来避免分支语句,以捕获可能发生的溢出,而无需进行边界测试,以内存换分支操作。您的算法通常不需要除了常数内核之外的任何临时数据,如果您过去处理过信号处理,您可能已经在工具包中拥有类似的功能。
人们经常问在滤波器边缘(称为 **边缘条件**)应该怎么做。有许多选择,例如选择已知的“背景强度”并将其应用于该元素(在本例中,数据为零,因此不会影响内核操作;这与在本例中截断滤波器完全相同),但是还有许多其他方法来处理这种情况,包括扩展图像边缘的数据,截断内核并且不运行操作,或者根本不将内核应用于超出边缘的点(通常称为“就地复制”)。
您还会注意到,我们在像素操作期间没有指定任何 else 情况;在所有不进行操作的情况下,我们假设该操作的输出将为零。例如,如果卷积核的中心项为零,就像通常情况下那样,没有必要进行乘法并得到零。只需跳过该计算并继续下一个。
在某些情况下,例如如果我们的数据是整数格式,我们需要应用一个除数来满足数字格式的约束(在本例中,我们的缓冲区由 8 位数据组成)。除数通常是内核宽度乘以内核高度(3x3 为 9,5x5 为 25 等等),但也有一些情况下可能需要其他除数。
我们将要看的第一个内核是 **索贝尔算子**,也称为 **边缘检测** 内核。索贝尔算子通过降低低频像素的强度,同时提高高频像素的强度,来强调图像函数中的高频变化。本质上,索贝尔算子正在对图像的每个坐标处的强度求二阶导数,然而,对于那些对数学不太了解的人,以及我们中的一些人来说,将其理解为卷积滤波器更容易。
-1 0 1 -2 0 2 -1 0 1
我们可以通过查看常数函数 f(x) 的一维二阶导数来思考这一点:. 通过取常数并将它们存储在一个矩阵中,我们构建了一维卷积核:. 我们通过将该矩阵与垂直矩阵 相乘,将其转换为二维卷积核,该矩阵只是我们正在进行卷积的点的绝对差值。
这个相对简单的滤波器有几个变体。例如,一个常见的调整是强调的方向
1 0 -1 2 0 -2 1 0 -1
这种调整将滤波器的前沿改为右侧,而不是像前一个例子中那样强调左侧。它与上面相同的滤波器“翻转”了,方法是将函数乘以 -1。
1 2 1 -1 -2 -1 0 0 0 or 0 0 0 -1 -2 -1 1 2 1
这些是原始滤波器的旋转,分别强调向上和向下。