使用 Python 进行边缘检测

为何检测边缘?
我们首先应该了解的问题是:“为什么要费尽心思去做边缘检测?”除了它的效果很酷外 , 为什么边缘检测还是一种实用的技术?为了更好地解答这个问题 , 请仔细思考并对比下面的风车图片和它的“仅含边缘的图”:
使用 Python 进行边缘检测文章插图
可以看到 , 左边的原始图像有着各种各样的色彩、阴影 , 而右边的“仅含边缘的图”是黑白的 。 如果有人问 , 哪一张图片需要更多的存储空间 , 你肯定会告诉他原始图像会占用更多空间 。 这就是边缘检测的意义:通过对图片进行边缘检测 , 丢弃大多数的细节 , 从而得到“更轻量化”的图片 。
因此 , 在无须保存图像的所有复杂细节 , 而 “只关心图像的整体形状” 的情况下 , 边缘检测会非常有用 。
如何进行边缘检测 —— 数学
在讨论代码实现前 , 让我们先快速浏览一下边缘检测背后的数学原理 。 作为人类 , 我们非常擅长识别图像中的“边” , 那如何让计算机做到同样的事呢?
首先 , 假设有一张很简单的图片 , 在白色背景上有一个黑色的正方形:
使用 Python 进行边缘检测文章插图
在这个例子中 , 由于处理的是黑白图片 , 因此我们可以考虑将图中的每个像素的值都用 0(黑色) 或 1(白色) 来表示 。 除了黑白图片 , 同样的理论也完全适用于彩色图像 。
现在 , 我们需要判断上图中绿色高亮的像素是不是这个图像边缘的一部分 。 作为人类 , 我们当然可以认出它是图像的边缘;但如何让计算机利用相邻的像素来得到同样的结果呢?
我们以绿色高亮的像素为中心 , 设定一个 3 x 3 像素大小的小框 , 在图中以红色示意 。 接着 , 对这个小方框“应用”一个过滤器(filter):
使用 Python 进行边缘检测文章插图
上图展示了我们将要“应用”的过滤器 。 乍一看上去很神秘 , 让我们仔细研究它做的事情:当我们说 “将过滤器应用于一小块局部像素块” 时 , 具体是指红色框中的每个像素与过滤器中与之位置对应的像素进行相乘 。 因此 , 红色框中左上角像素值为 1 , 而过滤器中左上角像素值为 -1 , 它们相乘得到 -1 , 这也就是结果图中左上角像素显示的值 。 结果图中的每个像素都是用这种方式得到的 。
下一步是对过滤结果中的所有像素值求和 , 得到 -4 。 请注意 , -4 其实是我们应用这个过滤器可获得的“最小”值(因为原始图片中的像素值只能在 0 到 1 之间) 。 因此 , 当获得 -4 这个最小值的时候 , 我们就能知道 , 对应的像素点是图像中正方形顶部竖直方向边缘的一部分 。
为了更好地掌握这种变换 , 我们可以看看将此过滤器应用于图中正方形底边上的一个像素会发生什么:
使用 Python 进行边缘检测文章插图
可以看到 , 我们得到了与前文相似的结果 , 相加之后得到的结果是 4 , 这是应用此过滤器能得到的最大值 。 因此 , 由于我们得到了 4 这一最大值 , 可以知道这个像素是图像中正方形底部竖直方向边缘的一部分 。
为了把这些值映射到 0-1 的范围内 , 我们可以简单地给其加上 4 再除以 8 , 这样就能把 -4 映射成 0(黑色) , 把 4 映射成 1(白色) 。 因此 , 我们将这种过滤器称为纵向 Sobel 过滤器 , 可以用它轻松检测图像中垂直方向的边缘 。
那如何检测水平方向的边缘呢?只需简单地将纵向过滤器进行转置(按照其数值矩阵的对角线进行翻转)就能得到一个新的过滤器 , 可以用于检测水平方向的边缘 。
如果需要同时检测水平方向、垂直方向以及介于两者之间的边缘 , 我们可以把纵向过滤器得分和横向过滤器得分进行结合 , 这个步骤在后面的代码中将有所体现 。


推荐阅读