如何分离重叠的轮廓但不删除小的轮廓。侵蚀-扩张循环的替代方案

信息处理 图像处理 计算机视觉 opencv 边缘检测
2022-02-08 03:02:15

背景:
我试图通过计算随后成对的图像的立体视差来检测一系列航拍图像上的树木。这就是差距的样子。白点是树木: 接下来,我在视差图上定位轮廓并获得以下图像: 从这里我想检测树木。
在此处输入图像描述

在此处输入图像描述

我现在正在做的事情:
首先,我正在做侵蚀:

kernel = np.ones(shape=(5, 5),
                 dtype=np.uint8)

image= cv2.erode(image,
                 kernel=kernel,
                 iterations=14)  

并得到一些重叠,例如: 在此处输入图像描述

为了分离那些重叠的轮廓,我这次做膨胀:

image= cv2.dilate(image,
                  kernel=kernel,
                  iterations=12)

在此处输入图像描述
Ups,一些轮廓变得如此之小,几乎消失了。
反转伤害:

image= cv2.erode(image,
                 kernel=kernel,
                 iterations=7)

在此处输入图像描述
行!更好的!但是..所有这些过程似乎都是一个肮脏的解决方法..此外,这不适用于其他一些图像(忽略左侧的噪音,我应该添加裁剪):
在此处输入图像描述

问题:
- 我如何确保不重叠树木的轮廓并且在扩张/侵蚀时不移除它们?
- 有没有比我现在正在做的更好的选择?

编辑:
可能对于全图我应该补充一点,在获得具有单独轮廓的良好图像后,我使用:

disparity, contours, hierarchy = cv2.findContours(image,
                                                  mode=cv2.RETR_LIST,
                                                  method=cv2.CHAIN_APPROX_SIMPLE)

contours在原始图像上绘制:
在此处输入图像描述

编辑 Nº2:
添加视差图的横截面。原始数据可从此处获得

阴谋:

在此处输入图像描述


结果更新:

我尝试了@A_A 建议的中值过滤器,并且很好地将树木与地面分开:

ground = medfilt(disparity, kernel_size=125)
trees = disparity - ground
boolean_mask = (trees > 2.) & (trees < 15.)
plt.imshow(boolean_mask, cmap='gray')

这个过滤器的问题是速度。
scipy.signal.medfilt我花了 20 多分钟来计算一切。也有一个scipy.ndimage.filters.median_filter我不知道它们之间有什么区别,但我从中得到的结果几乎是相同的,除了靠近图像边界的一些区域,它需要 2 到 5 分钟。

在此处输入图像描述

1个回答

我不知道这是极端情况还是数据集中的常态,但这是一个相对容易处理的情况。例如,在城市环境中检测树木会更加困难,您可能会寻找更多模式识别类型的解决方案。

该解决方案背后的基本思想是表面拟合,无论是直接检测树木还是检测地面的“浮动”水平,并通过该信息检测树木。

以下基于Octave并使用 1D 数据,但对其他平台(和 2 维)的概括应该很简单。

在这里可以做的第一件事是核密度估计(KDE)在这种情况下,您将通过一组高斯曲线来近似总可用表面(地面和树木)。二维情况下,这将为您提供每棵树一个高斯或树木位置周围更高密度的高斯。一口气。无需进一步处理。实际上,您可能会发现该技术通过核密度近似您的视差图像,并返回一组高斯数据,您可以进一步处理这些数据以确定树的位置。

但是,快速查看数据后,它包含明显的不连续性,这可能不利于 KDE 收敛。虽然您可以使用高斯低通“模糊”差异,然后尝试使用 KDE 对其进行近似,但这意味着您的原始分辨率会降低,并且可能会降低您区分大小树的能力。无论如何,这也是一个选项(首先使用合适的FWHM高斯进行模糊,然后使用 KDE 进行近似)。

同样,这似乎是一个相对简单的情况,您甚至可以使用简单的阈值来检测树木。当然,问题在于地面高度现在会调节您的阈值。平地上的树可以是 25 米以上的任何东西,但肯定不是 22 米山顶上 3 米高的棚屋。

所以,我们想要阈值,但我们想要考虑地面轮廓。理想情况下,您可以拟合一条更接近实际地面(椭圆体)的曲线,但也许您可以采用更简单的拟合方案。

这里的主要思想是我们想要“撤消”地面的调制,然后应用一个简单的阈值。如果我们把地面弄平,22米的小山就会消失,那么22米以上的任何东西都更有可能真的是一棵树。

如果您忽略零(我假设在您的情况下是“缺失值”(高不确定性或实际缺失值)),则信号如下所示:

在此处输入图像描述

您可以将此阈值设置为62但是您可以看到第二棵树比第一棵树低,但在看起来像小山的斜坡上更远。

让我们把小山夷为平地。这里的山峰变平基本上相当于从信号本身中减去缓慢变化的分量(山峰),甚至可能用一个系数来控制去除量。在这里,我将使用简单的多项式拟合,但任何可以近似高度场的缓慢变化分量的方法都可以。

xrange=0:(length(b)-1);
sv = polyval(polyfit(xrange,b,2),xrange); # Fit and evaluate a second order polynomial. You might want to go higher if you have ridges or depressions but not too high because we just want to approximate the slow varying ground changes.
plot(b);hold on;plot(sv,'r');grid on; # Plot everything

现在看起来像这样:

在此处输入图像描述

显然,我们可以简单地从蓝色中减去红色曲线,然后得到信号的“扁平化”版本,但是正如你所看到的,树木稍微“拉高了地面”,这就是为什么如果你盲目地这样做,你会得到这个:

在此处输入图像描述

就树木之间的比较而言,我们现在肯定处于更好的状态,因为我们已经移除了地面,而且我们还可以看出第二棵树比第一棵树低,但是,我们现在尝试移除山丘时有所下降. 要针对这种情况进行调整,您可以引入一个系数来控制要执行多少减法,例如:

m=0.43;plot(m.*b-(1-m).*sv);

这里,m[0..1.0]控制有多少真实信号以及我们考虑多少它的近似值。0.43在反复试验后确定,结果是:

在此处输入图像描述

基本上,树木从几乎相同的高度开始。

因此,现在您可以对上述任何内容设置阈值,-8.2并更加确定您正在选择树木。

有一种方法可以使地面几乎完全变平,但它需要一些关于树冠大小的知识。

这在相同的前提下工作,但使用不同的方法近似地面。

基本上,您可以使用中值滤波器来“砍掉”树木对高度场造成的峰值。之后剩下的任何东西基本上都是地面变化。中值滤波器以“滚动窗口”方式应用,并用中值代替样本N围绕它的价值观。

现在,要应用它,您需要确定N它应该几乎和你最宽的树冠一样宽。在这个例子中,我已经确定这大约是 125(像素),所以通过这样做:

plot(b-medfilt1(b,125)); grid on;

你会得到这样的东西:

在此处输入图像描述

这几乎太好了。至少这些树从完全相同的点开始。同样,现在需要应用一个简单的阈值(任何高于 1 (?))来检测树木的位置。

此过程将为您提供一个二进制掩码,然后您可以将其缩小(甚至使用简单的方法进行聚类)以确定一个质心,然后将其与树的位置相关联。显然,处理每个“集群”的大小可能还可以告诉您树冠的宽度。

就概括而言,您可以拟合 2D 多项式或 2D NURB,甚至可以简单地应用 2D 中位数medfilt

希望这可以帮助。