边缘检测
本文翻译自 Edge Masks,原作者:kageru。
译者:“mask”的中文译名应该是“蒙版”,不太习惯这个词,于是把标题写成了边缘检测,文内还是直接用英文“mask”。
理论、实例和解释
译者:本节篇幅较长,译者添加了小标题。
大多数流行的算法通过卷积来衡量像素邻域变化,以确定亮度变化。卷积计算的时间复杂度为 O(n^2),其中 n 为卷积核半径,因此卷积核在保存适当精度的前提下越小越好。卷积核半径越低,越容易受到噪声(noise)和瑕疵(artifacts)的影响。
多数算法采用 3x3 卷积核,在速度和准确性间提供了最佳平衡。例子便是 Prewitt、Sobel、Scharr 和 Kirsch 提出的算子。对于无噪声的干净信号源,也可使用 2x2 卷积(文献),但目前的硬件已经能够实时处理 3x3 卷积。
Sobel 算子
以 Sobel 算子为例,x 方向和 y 方向分别进行卷积。
1 |
|
用 VapourSynth 简单实现如下。
1 |
|
甚至,VapourSynth 内置了core.std.Sobel
函数,我们不必自己写代码。
对于边缘模糊的图像,Sobel 算子的效果并不是很好。提高检测精度的一种方法是使用 8 邻域而非 4 邻域,即在邻域的 8 个方向上,或者说在 3x3 卷积核的对角线上,都将被计算。
Kirsch 算子
Russel A. Kirsch 在 1970 年提出了 Kirsch 算子(文献)。
1 |
|
该卷积核会旋转 45° 以回到其原始位置。
在 VapourSynth 中并未内置 Kirsch 算子,尝试通过 VapourSynth 内置的卷积方法实现。
1 |
|
显然,简单的复制粘贴并不是一个好主意。当然,代码可以运行。但我不是数学家,而只有数学家才能去编写优雅的代码解决这一问题。换一种思路。
1 |
|
已经好多了,先不去管可读性更强的代码。
将 Sobel mask 与 Kirsch mask 比较,后者的准确性有了明显提升。
边缘检测的精度越高,就越容易将噪点识别为边缘,可以通过事先降噪来克服上述问题。
提升精度对速度的影响可以忽略不计,对于 8bit 1080p 输入源,单纯 Sobel 算子(非 VapourSynth 内置的 Sobel 函数,因为它还包括了高通/低通滤波与缩放功能,速度更慢)速度约为 215fps,Kirsch 算子速度为 175fps。诚然,Sobel 算子也检出了许多边缘,但有些边缘不明显,需要使用std.Binarize
增强才能达到 Kirsch 算子的效果。
Canny 算法
一种更复杂的边缘检测方法是 Canny 算法(译者注:tritical 在 AviSynth 框架下实现了 Canny 算法,被称为 TCanny 滤镜),这种算法使用类似的方法检测边缘,并将边缘的宽度缩小至 1 个像素。理想情况下,这些线条代表边缘的中部,且没有边缘被重复标记。此外,算法会进行高斯模糊,以降低噪声干扰(译者注:高斯模糊是前处理,在施加边缘检测算子之前)。一个例子如下。
1 |
|
其中,op=1
表示使用一种改进的算子,具有更好的信噪比。
下面是使用 5x5 卷积核的例子。
1 |
|
这是尝试通过边缘检测创建边缘 mask。进过一些后处理,可以用于去取 halo 或者清理线条(尽管可以使用其他方法配合常规 mask(或许更好),比如std.Maximum
或者std.Expr
)。
边缘 mask 的使用
我们已经了解了基础知识,来看一下实际应用。目前大多数视频仍为 8bit,几乎不可避免地会产生色带(banding)。正如我在之前提到的,恢复(restoration)滤镜会引入新的瑕疵。在去色带时,细节也随之损失。进一步地,加大去色带力度,则会导致图像模糊。边缘 mask 用于修补上述副作用,实际过程为先让去色带滤镜进行去色带操作,然后使用边缘 mask 识别边缘与细节,并通过std.MaskedMerge
恢复。
GradFun3 滤镜会在内部生成 mask,完成上述操作。另一个流行的去色带滤镜 f3kdb 则没有内置 mask 功能。
举个例子,单纯地进行去色带会破坏纹理(details,在这一语境下译为纹理比细节更合适),特别是暗场纹理。在这种情况下使用 Sobel 算子进行边缘检测,效果不好。
为了更好地识别暗场区域,使用 Retinex 算法进行局部对比度增强。
借助 Retinex 算法降低对比度,低对比度下我们能在暗场看到更丰富的内容。也许有人认为这些原本看不到的暗场细节没有意义,但随着 HDR 显示器的推广,普通观众也能看到这些细节。同时暗场细节不会占用过多码率,所以我认为保留它们没有什么坏处。
利用这些新知识,一些测试和一点点魔法,我们得到的 mask 准确性之高出乎意料。
1 |
|
进一步地,借助std.Binarize
(或类似的高通/低通函数),以及std.Maximum
、std.Inflate
的单独/组合调用。我们可以把这一 mask 变成适用性更强的 mask,以应用于去色带或者其他需要精确 mask 的场合。
性能
绝大部分边缘检测算法均为简单的卷积运算,在 HD 源上使用也能达到 100fps 以上的速度,像 Retinex 这样复杂的算法当然不能与之相比。虽然使用 Sobel 算子进行简单的边缘检测,速度能超过 200fps,但组合 Retinex 算法后仅为 25 fps。速度瓶颈在 Retinex 算法上,单独使用 Retinex 算法速度约为 36.6fps。一种类似但低精度的暗场增强方法为调整亮度曲线,以暴露低对比度的边缘。
1 |
|
理论上,可以通过调整亮度来改善暗场区域的边缘检测效果。
结论
数十年来,边缘检测一直是图像处理的强大工具,可以缩减图像处理的范围,助推图像分析。在视频处理中同样有重要作用,可以最大限度地降低的副作用与瑕疵。通过卷积可以快速而准确地建立边缘 mask,并且可以通过调整内核参数来自定义卷积,以用于不同目的。此外,还可以通过局部对比度增强来提高检测精度,虽然速度会慢得多。
文中提到的代码可以在这里找到。