如何从底部填充图像,直到使用 OpenCV 检测到边缘?

Posted

技术标签:

【中文标题】如何从底部填充图像,直到使用 OpenCV 检测到边缘?【英文标题】:How to fill an image from bottom side until an edge is detected using OpenCV? 【发布时间】:2017-12-21 12:02:33 【问题描述】:

我的目标是能够使用 OpenCV 3 复制此 link 中显示的避障方法。他们提供的软件似乎仅适用于 Windows。我认为这可以使用 OpenCV 进行复制。我目前正在使用 Canny 边缘检测的第 2 步。我不确定我可以使用哪些函数来创建第 3 步,从底部填充图像直到检测到边缘。任何参考材料将不胜感激。谢谢。

【问题讨论】:

等我坐下来写一个解决方案时,这可能会有答案,但最简单的方法是使用np.where() 查找所有非- 二进制图像中的零值,然后在 where 的结果中找到每一列的最大行索引,然后设置 image[max_row_index:, column] = 255(或任何适合您图像类型的白色值)。 【参考方案1】:

方法0

这是完成此任务的标准、基于循环的方法。这个想法是从每列的底部开始,将每个像素着色为白色,直到碰到一个白色像素。这就是 Piglet 下面的建议。

h, w = edges.shape[:2]
filled_from_bottom = np.zeros((h, w))
for col in range(w):
    for row in reversed(range(h)):
        if edges[row][col] < 255: filled_from_bottom[row][col] = 255
        else: break

方法一

现在这个方法使用了一些numpy的技巧来加速操作。

首先,对于每一列,找到边缘图像中存在非零值的最大行索引。

h, w = img.shape[:2]
row_inds = np.indices((h, w))[0] # gives row indices in shape of img
row_inds_at_edges = row_inds.copy()
row_inds_at_edges[edges==0] = 0 # only get indices at edges, 0 elsewhere
max_row_inds = np.amax(row_inds_at_edges, axis=0) # find the max row ind over each col

然后你可以创建一个布尔数组,其中每个大于或等于最大索引的索引都是True

inds_after_edges = row_inds >= max_row_inds

然后你可以简单地在布尔数组给出的新索引处用白色填充一个新的空白图像

filled_from_bottom = np.zeros((h, w))
filled_from_bottom[inds_after_edges] = 255

方法二

此方法的内存效率略高,速度效率略高。与方法一的基本前提相同。

首先,对于每一列,找到对应于每一列中最大值的行索引(这将是边缘图像中的白色)。请注意,函数np.argmax 将返回数组中最大值的第一个实例,而我们想要最后一个:

如果最大值多次出现,则返回与第一次出现对应的索引。

因此,一种简单的解决方法是垂直翻转数组,但这会给我们提供反向数组的索引。看到单线我觉得单单解释更直观:

h, w = img.shape[:2]
max_row_inds = h - np.argmax(edges[::-1], axis=0)

切片[::-1] 从上到下反转边缘(或者可以使用np.flipud)。然后由于数组被翻转,np.argmax 给出了从末尾开始的索引,所以h - np.argmax 给出了正确定向数组的索引。而np.argmax(..., axis=0) 表示我们在每一列上取最大值。

现在我们可以像以前一样创建布尔数组了:

row_inds = np.indices((h, w))[0]
inds_after_edges = row_inds >= max_row_inds

这种方法稍微好一点的原因是因为我们没有创建数组的副本,而是删除了许多值的数组分配。


速度测试

第一种方法是最简单的,但在 Python 中是最慢的。 Python 循环非常慢,而numpy 操作通常在基于 C 或 Fortran 的方法中实现,因此它们非常敏捷。我用以下代码测试了差异:

import timeit
times = range(1000)

start_time = timeit.default_timer()
A = [method0(edges) for t in times]
print("method0: ", timeit.default_timer() - start_time)

start_time = timeit.default_timer()
B = [method1(edges) for t in times]
print("method1: ", timeit.default_timer() - start_time)

start_time = timeit.default_timer()
C = [method2(edges) for t in times]
print("method2: ", timeit.default_timer() - start_time)

所以每个方法运行 1000 次。结果:

method0: 62.79985192901222
method1: 0.9703722179983743
method2: 0.7760374149947893

正如预期的那样,我们看到最终的方法是最快的;只是比method1快一点,但并不疯狂。但是,基于循环的方法之间的差异是巨大的。


输出

【讨论】:

你的意思可能是效率较低? @YvesDaoust 我没有,但也许我错了。我之前的解决方案需要遍历列索引,而这不需要——所以我的意思是这可能比我的答案预编辑更快。我将不胜感激任何加快速度的建议!可以在反转的列上使用argmax,因为边缘是白色的,而不是将非边缘值设置为零然后amax,但这仍然需要创建np.indices 数组(尽管不需要复制它)。 ..还有其他建议吗? 查看@Piglet 的答案。 @YvesDaoust 我刚刚测试过,我没有循环的方法比 Piglet 建议的循环快 100 倍左右。将发布用于测试的代码。 可能在用 Python 编码时,但 x100 看起来很大。【参考方案2】:

这可以从图像底部开始并继续 垂直逐像素填充每个空的黑色像素,直到 看到非黑色像素。然后填充停止该垂直列 并继续下一步。

您不需要任何花哨的 OpenCV 函数。这可以通过简单地使用几个循环来完成。

您可以就地执行此操作,也可以使用使用零初始化的单独输出图像。

您所要做的就是从底部开始遍历图像的列。如果像素的值为零,则将输出像素设置为 255,一旦遇到非零像素,则将剩余像素设置为 0(或将它们保留为 0)

【讨论】:

以上是关于如何从底部填充图像,直到使用 OpenCV 检测到边缘?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 OpenCV 从图像中检测旋转对象?

如何使用opencv从图像中检测文本

使用 OpenCV 进行三角形检测

使用 findContours 时如何避免检测到图像帧

如何使用 OpenCV 从图像中提取文本行

如何在opencv java中裁剪检测到的面部图像