I'm trying to match a slightly irregular shape to a database of shapes. For example, here the contour I'm trying to match:
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:
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:
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).
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.
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.
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.
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.