两个相似形状之间的 OpenCV 形状匹配

Posted

技术标签:

【中文标题】两个相似形状之间的 OpenCV 形状匹配【英文标题】:OpenCV shape matching between two similar shapes 【发布时间】:2019-08-27 00:03:20 【问题描述】:

我正在尝试将稍微不规则的形状与形状数据库相匹配。例如,这里是我要匹配的轮廓:

有关详细信息,这是 HDMI 连接器的轮廓,以轮廓表示。有点粗糙,因为这是拿着 HDMI 用手机拍摄的。

这是我的连接器数据库:

HDMI:

DVI:

5PinDIN:

DB25:

这些更清晰,因为这些轮廓是从互联网上的连接器图像中收集的。

对于我所尝试的:

cv2.matchShapes()

由于这些都只是轮廓,我尝试使用 matchShapes() 方法直接比较它们,但未能产生好的结果。不规则轮廓与我的数据库的相似之处是:

HDMI:0.90

DB25:0.84

5 针 DIN:0.5

DVI:0.21

由于轮廓越接近匹配结果越接近0,算法完全失败。我通过改变第三个参数尝试了其他匹配方法,但仍然不成功。

ORB:

与SIFT类似,我尝试了关键点匹配。平均我的数据库中不同匹配之间的距离(在找到前 15% 的匹配之后):

mean([m.distance for m in matches])

距离如下:

五针 DIN:7.6

DB25:11.7

DVI:12.1

HDMI:19.6

由于这将圆形归类为最像我的轮廓的形状,所以这也失败了。

以下是实际 HDMI 插槽的 ORB 与我的示例 HDMI 插槽的匹配关键点,以了解更多信息:

有什么想法/其他算法我应该尝试吗?或者 CNN 是我唯一的选择(我宁愿避免,因为我没有适当数量的数据)。

【问题讨论】:

只要在网上搜索轮廓匹配就可以找到无数种方法。一种相当简单但有效的方法是使用比例和旋转不变的特征,如紧凑性、偏心度、纵横比。这应该足以区分最常见的连接器。 【参考方案1】:

可以执行多个步骤以获得更好的结果。并且不需要 CNN 或一些复杂的特征匹配,让我们尝试使用非常基本的方法来解决这个问题。

1。规范化查询图像和数据库图像。

这可以通过紧密裁剪输入轮廓然后将所有图像的大小调整为相同的高度或宽度来完成。我会在这里选择宽度,比如 300px。让我们为此定义一个实用方法:

def normalize_contour(img):
    im, cnt, _ = cv2.findContours(img.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    bounding_rect = cv2.boundingRect(cnt[0])
    img_cropped_bounding_rect = img[bounding_rect[1]:bounding_rect[1] + bounding_rect[3],
                                bounding_rect[0]:bounding_rect[0] + bounding_rect[2]]

    new_height = int((1.0 * img.shape[0])/img.shape[1] * 300.0)
    img_resized = cv2.resize(img_cropped_bounding_rect, (300, new_height))
    return img_resized

此代码 sn-p 将返回一个裁剪良好的轮廓,其固定宽度为 300。将此方法应用于所有数据库图像并输入查询图像。

2。仅使用输入标准化图像的高度进行过滤。

由于我们已将输入图像标准化为 300 像素,因此我们可以拒绝所有高度不接近标准化图像高度的候选者。这将排除 5PinDIN。

3。比较面积

现在您可以尝试使用最大重叠对结果进行排序,您可以cv2.contourArea() 获取轮廓区域并对所有剩余候选者进行排序以获得最接近的匹配。

【讨论】:

谢谢!最好的解决方案往往是最简单的。 很高兴为您提供帮助:)。请使用脚本的输出在此线程上发布答案。【参考方案2】:

这组图像的简短答案是使用 OpenCV matchShapes 方法 I2 并使用较小的“eps”重新编码方法 matchShapes。 double eps = 1.e-20; 足够小了。

我是一名高中机器人团队的导师,我认为 OpenCV matchShapes 正是我们改善机器人视觉所需的(缩放、平移和旋转不变,并且学生易于在现有的 OpenCV 代码中使用)。我在研究几个小时后发现了这篇文章,这太可怕了!鉴于这些结果,matchShapes 怎么可能对我们有用?我对这些糟糕的结果感到怀疑。

我编写了自己的 matchShapes(在 Java 中 - 这是学生们想要使用的),以查看更改 eps 的效果(显然保护 log10 函数不为零并通过调用它们来防止大差异的小值完美匹配 - 与实际情况相反;我找不到价值的基础)。我将 matchShapes eps 从 OpenCV 编号 1.e-5 更改为 1.e-20 并获得了不错的结果,但过程仍然令人不安。

如果给出了正确的答案,我们可以扭曲一个过程来得到它,这很棒但也很可​​怕。附图包含了胡矩比较的所有3种方法,方法2和3都做得很好。

我的过程是保存上面的图像,转换为二进制 1 通道,扩张 1,腐蚀 1,findCountours,matchShapes 与 eps = 1.e-20。

Method 2,Target HDMI with itself = 0., HDMI=1.15, DVI=11.48, DB25=27.37, DIN=74.82
Method 3,Target HDMI with itself = 0. ,HDMI=0.34, DVI= 0.48, DB25= 2.33, DIN= 3.29

contours and Hu Moment comparisons - matchShapes 3 methods

我继续我的幼稚研究(很少有统计学背景),并找到了各种其他方法来进行标准化和比较。我无法弄清楚 Pearson 相关系数和其他协方差方法的细节,也许它们不合适。我又测试了两种归一化方法和另一种匹配方法。

OpenCV 使用 Log10 函数对其所有三个匹配计算进行归一化。

我尝试使用与每对最大值 max(Ai,Bi) 的比率对每对 Hu 矩进行归一化,并尝试将每对归一化为向量长度 1(除以平方和的 sqrt)。

在使用余弦 theta 方法计算 7 维 Hu 矩向量之间的角度之前,以及在计算类似于 OpenCV 方法 I2 的元素对差异之和之前,我使用了这两个新的归一化。

我的四个新混合物效果很好,但除了值的范围更小并且仍然排序相同之外,除了具有“校正”eps 的 openCV I2 之外没有任何贡献。

请注意,I3 方法不是对称的 - 交换 matchShapes 参数顺序会更改结果。对于这组图像,将“未知”的时刻作为第一个参数,并与已知形状列表作为第二个参数进行比较以获得最佳结果。反过来将结果更改为“错误”的答案!

我尝试的匹配方法中的数字 7 仅与 Hu Moments 的数量 - 7 巧合。

7 种不同计算的匹配索引说明

|Id|normalization            |matching index computation       |best value|
|--|-------------------------|---------------------------------|----------|
|I1|OpenCV log               |sum element pair reciprocals diff|0|
|I2|OpenCV log               |sum element pair diff            |0|
|I3|OpenCV log               |maximum fraction to A diff       |0|
|T4|ratio to element pair max|vectors cosine angle             |1|
|T5|unit vector              |vectors cosine angle             |1|
|T6|ratio to element pair max|sum element pair diff            |0|
|T7|unit vector              |sum element pair diff            |0|

对 5 幅图像中的每幅图像进行 7 次不同计算的匹配索引结果

|               |  I1 |  I2 |  I3 |  T4 |  T5 |  T6 |  T7 |
|---------------|-----|-----|-----|-----|-----|-----|-----|     
|HDMI 0         | 1.13| 1.15| 0.34| 0.93| 0.92| 2.02| 1.72|
|DB25 1         | 1.37|27.37| 2.33| 0.36| 0.32| 5.79| 5.69|
|DVI 2          | 0.36|11.48| 0.48| 0.53| 0.43| 5.06| 5.02|
|DIN5 3         | 1.94|74.82| 3.29| 0.38| 0.34| 6.39| 6.34|
|unknown(HDMI) 4| 0.00| 0.00| 0.00| 1.00| 1.00| 0.00| 0.00|(this image matches itself)

[创建 OpenCV 问题 16997 来解决 matchShapes 中的这个弱点。]

【讨论】:

【参考方案3】:

此答案基于 ZdaR 在此处https://***.com/a/55530040/1787145 的答案。我尝试了一些变化,希望通过在预处理中加入更多内容来使用单一的识别标准 (cv2.matchShapes())。

1。比较图像而不是轮廓

我喜欢标准化的想法(裁剪和调整大小)。但是在缩小图像后,由于像素分辨率低,其原本闭合的轮廓可能会被分解成多个不连贯的部分。 cv2.matchShapes() 的结果不可靠。通过比较整个调整大小的图像,我得到以下结果。它说圆圈是最相似的。不好!

2。填充形状

通过填充形状,我们考虑了面积。结果看起来更好,但 DVI 仍然优于 HDMI,因为它具有更相似的高度或高度/宽度比。我们想忽略它。

3。将每张图片调整为相同大小

通过将所有尺寸调整为相同尺寸,我们消除了尺寸中的某些比例。 (300, 300) 在这里效果很好。

4。代码

def normalize_filled(img):
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    im, cnt, _ = cv2.findContours(img.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    # fill shape
    cv2.fillPoly(img, pts=cnt, color=(255,255,255))
    bounding_rect = cv2.boundingRect(cnt[0])
    img_cropped_bounding_rect = img[bounding_rect[1]:bounding_rect[1] + bounding_rect[3], bounding_rect[0]:bounding_rect[0] + bounding_rect[2]]
    # resize all to same size
    img_resized = cv2.resize(img_cropped_bounding_rect, (300, 300))
    return img_resized

imgs = [imgQuery, imgHDMI, imgDVI, img5PinDin, imgDB25]
imgs = [normalize_filled(i) for i in imgs]

for i in range(1, 6):
    plt.subplot(2, 3, i), plt.imshow(imgs[i - 1], cmap='gray')
    print(cv2.matchShapes(imgs[0], imgs[i - 1], 1, 0.0))

【讨论】:

以上是关于两个相似形状之间的 OpenCV 形状匹配的主要内容,如果未能解决你的问题,请参考以下文章

[OpenCV实战]10 使用Hu矩进行形状匹配

使用 OpenCV 识别模式

视觉相似度算法(用于 CBIR)

OpenCV计算机图像处理 —— 凸性缺陷 + 点多边形测试 + 形状匹配 + 轮廓分层与cv.findContours()

opencv 如何检测特定形状的物体

Halcon视觉形状模板匹配