OpenCV shape matching between two similar shapes

Cyon picture Cyon · Apr 5, 2019 · Viewed 9.3k times · Source

I'm trying to match a slightly irregular shape to a database of shapes. For example, here the contour I'm trying to match:

enter image description here

For more information, this is an outline of an HDMI connector, represented as a contour. It is slightly rough as this was taken with a phone while holding the HDMI.

This is my database of connectors:

HDMI: enter image description here

DVI: enter image description here

5PinDIN: enter image description here

DB25: enter image description here

These are a lot clearer as these are contours gathered from connector images from the internet.

For what I have tried:

cv2.matchShapes()

Since these are all just contours, I tried directly comparing them using the matchShapes() method, and it failed to produce good results. The similarities between the irregular contour, and my database was:

HDMI: 0.90

DB25: 0.84

5 Pin DIN: 0.5

DVI: 0.21

Since contours are more similar the closer to 0 the match result is, the algorithm completely failed. I tried the other methods of matching by changing the third parameter and was still unsuccessful.

ORB:

Being similar to SIFT, I tried keypoint matching. Averaging the distance between the different matches in my database (after finding the top 15% of matches):

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

The distances came up as:

Five Pin DIN: 7.6

DB25: 11.7

DVI: 12.1

HDMI: 19.6

As this classified a circle as the shape most like my contour, this has failed as well.

Here are the matching key points from ORB of the actual HDMI slot vs my example HDMI slot for more information: enter image description here

Are there any ideas/other algorithms I should try? Or is a CNN my only choice (which I would rather avoid as I don't have the appropriate amount of data).

Answer

ZdaR picture ZdaR · Apr 5, 2019

There are multiple steps which can be performed to get better results. And there is no need of a CNN or some complex feature matching, lets try to solve this using very basic approached.

1. Normalize query image and database images as well.

This can be done by closely cropping the input contour and then resize all the images either to same height or width. I will chose width here, let's say 300px. Let's define a utility method for this:

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

This code snippet would return a nicely cropped contour with a fixed width of 300. Apply this method to all the database images and input query image as well.

2. Filter simply using the height of input normalized image.

Since we have normalized the input image to 300 px we can reject all the candidates whose height is not close to the normalized image height. This will rule out 5PinDIN.

3. Compare area

Now you can try sorting the results with max overlap, you can cv2.contourArea() to get the contour area and sort all the remaining candidates to get the closest possible match.