我正在尝试将稍微不规则的形状与形状数据库匹配。例如,在这里我要匹配的轮廓:
有关更多信息,这是HDMI连接器的轮廓,以轮廓表示。有点粗糙,因为这是在握住HDMI的同时用手机拍摄的。
这是我的连接器数据库:
这些轮廓更加清晰,因为这些轮廓是从互联网的连接器图像中收集的。
对于我尝试过的:
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插槽与我的示例HDMI插槽的ORB中的匹配关键点,以获取更多信息:
我应该尝试任何想法/其他算法吗?还是CNN是我唯一的选择(由于我没有足够的数据量,所以我宁愿避免使用它。
答案 0 :(得分:2)
这组图像的简短答案是使用OpenCV matchShapes方法I2,并使用较小的“ eps”重新编码matchShapes方法。 double eps = 1.e-20;
足够小。
我是一名高中机器人团队的导师,我认为OpenCV matchShapes正是我们改善机器人视力所需要的(缩放,平移和旋转不变,并且使学生易于在现有的OpenCV代码中使用)。我在研究中花了两个小时才看到这篇文章,这真是令人震惊!鉴于这些结果,matchShapes如何为我们工作?我对这些糟糕的结果感到怀疑。
我编写了自己的matchShapes(用Java-这是学生想要使用的代码),以查看更改eps的效果(该值很小,显然可以保护log10函数免受零影响,并通过将其称为a来防止BIG差异完美匹配-与实际情况相反;我找不到价值的基础)。我将matchShapes eps从OpenCV的1.e-5更改为1.e-20,并获得了不错的结果,但是过程仍然令人不安。
这是一个奇妙但令人恐惧的事情,只要给出正确的答案,我们就可以扭曲获取它的过程。随附的图片包含了Hu Moment比较的所有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
我继续我的幼稚研究(统计学的小背景),并找到了进行标准化和比较的各种其他方法。我无法弄清楚皮尔森相关系数和其他协方差方法的细节,也许它们不合适。我测试了另外两种归一化方法和另一种匹配方法。
OpenCV使用Log10函数对其所有三个匹配计算进行标准化。
我尝试通过与每对Hu矩之比与每对最大值的比率max(Ai,Bi)归一化,并尝试将每对Hu矩归一化为矢量长度1(除以平方和的平方和)。 / p>
在使用余弦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中的这一弱点。]
答案 1 :(得分:1)
可以执行多个步骤以获得更好的结果。而且不需要CNN或某些复杂的功能匹配,让我们尝试使用非常基本的方法来解决此问题。
这可以通过仔细裁剪输入轮廓,然后将所有图像调整为相同的高度或宽度来完成。我将在这里选择宽度,例如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
此代码段将返回固定宽度为300的裁剪良好的轮廓。将此方法应用于所有数据库图像以及输入的查询图像。
由于我们已将输入图像标准化为300像素,因此我们可以拒绝所有高度不接近标准化图像高度的候选对象。这将排除5PinDIN。
现在,您可以尝试以最大重叠度对结果进行排序,可以cv2.contourArea()
来获取轮廓区域,并对所有剩余的候选项进行排序以得到最接近的匹配项。
答案 2 :(得分:1)
此答案基于ZdaR此处https://stackoverflow.com/a/55530040/1787145的答案。我尝试了一些变体,希望通过在预处理过程中加入更多内容来使用单一的区分标准(cv2.matchShapes()
)。
我喜欢规范化的概念(裁剪和调整大小)。但是在缩小图像后,由于像素的分辨率低,其最初关闭的轮廓可能会分成多个断开的部分。 cv2.matchShapes()
的结果不可靠。通过比较整个调整大小的图像,我得到以下结果。它说圈子是最相似的。不好!</ p>
通过填充形状,我们考虑了面积。结果看起来更好,但是DVI仍然比HDMI具有更高的高度或高度/宽度比。我们想忽略那个。
通过将所有尺寸调整为相同大小,我们消除了尺寸上的某些比例。 (300,300)在这里效果很好。
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))