0

I try to recognize 4 the same fiducial marks on a map. With help of the internet, I have created something, but I am looking for ways to improve the search, since the result is far from perfect.

What I tried so far:

  • Changing the threshold
  • Trying different cv2 methods
  • Making the image and the template smaller

This is my code:

import cv2
import numpy as np
from imutils.object_detection import non_max_suppression
  
# Reading and resizing the image

big_image = cv2.imread('20221028_093830.jpg')
 
scale_percent = 10 # percent of original size
width = int(big_image.shape[1] * scale_percent / 100)
height = int(big_image.shape[0] * scale_percent / 100)
dim = (width, height)

img = cv2.resize(big_image, dim, interpolation = cv2.INTER_AREA)


temp = cv2.imread('try_fiduc.png')
  
# save the image dimensions
W, H = temp.shape[:2]
  
# Converting them to grayscale
img_gray = cv2.cvtColor(img, 
                        cv2.COLOR_BGR2GRAY)
temp_gray = cv2.cvtColor(temp,
                         cv2.COLOR_BGR2GRAY)

# Blur the image
img_blurred = cv2.GaussianBlur(img_gray, (7, 7), 0)

# Increasing contrast
img_contrast = img_blurred*3

# Passing the image to matchTemplate method
match = cv2.matchTemplate(
    image=img_contrast, templ=temp_gray, 
  method=cv2.TM_CCOEFF)\

# Define a minimum threshold
thresh = 6000000

# Select rectangles with confidence greater than threshold
(y_points, x_points) = np.where(match >= thresh)
  
# initialize our list of rectangles
boxes = list()
  
# loop over the starting (x, y)-coordinates again
for (x, y) in zip(x_points, y_points):
    
    # update our list of rectangles
    boxes.append((x, y, x + W, y + H))
  
# apply non-maxima suppression to the rectangles
# this will create a single bounding box
boxes = non_max_suppression(np.array(boxes))
  
# loop over the final bounding boxes
for (x1, y1, x2, y2) in boxes:
    
    # draw the bounding box on the image
    cv2.rectangle(img, (x1, y1), (x2, y2),
                  (255, 0, 0), 3)
  

# Show the template and the final output
cv2.imshow("Template", temp_gray)
cv2.imshow("Image", img_contrast)
cv2.imshow("After NMS", img)
cv2.waitKey(0)
  
# destroy all the windows manually to be on the safe side
cv2.destroyAllWindows()

This is my template: enter image description here

This is my image: https://ibb.co/QHQh65s

This is the result:

What are more ways to improve the template matching? In the end I want to be able to recognize them from further distance, and not have the false match. Any help would be appreciated.

Annelotte
  • 75
  • 7
  • Can you employ more knowledge about the target? For example, if some rule for relative position from mark to mark exists, it might be able to be used to filter out false positives. – fana Oct 28 '22 at 09:10
  • @fana The marks will be in the corners of the page, the diamater of the black circle is 50 mm and the white circle within 30 mm. There is not known what the position to the corner will be, but the distances to the corners will be the same for all four circles – Annelotte Oct 28 '22 at 09:20
  • Your example show 5 candidates, but the 1 is false positive. For example, If you look for a combination of four points that forms a rectangle, you might avoid the false positive. – fana Oct 28 '22 at 09:31
  • Employing additional check that confirm whether a circle really exists in candidate region, may be considerable. – fana Oct 28 '22 at 09:35
  • @fana do you maybe have any idea how that would be executed? Is there documentation about that? – Annelotte Oct 28 '22 at 11:07
  • Your thresh is way to low. Also do not use TM_COEFF_NORMED. Better to use TM_SQDIFF_NORMED. Then your threshold might be appropriate, but you want values below the threshold, since a perfect match would be a score of 0. – fmw42 Oct 28 '22 at 15:50
  • @fmw42 my result was way worse with a higher threshold sadly. I will apply the different method and see what happens. Do you have any other advice? – Annelotte Oct 28 '22 at 16:30
  • Does the template match in scale the circles in the image? You can resize using fractions for the scale directly. You do not need to compute the new sizes. See fx and fy at https://docs.opencv.org/4.1.1/da/d54/group__imgproc__transform.html#ga47a974309e9102f5f08231edc7e7529d – fmw42 Oct 28 '22 at 16:48
  • Sorry, in the above, I meant TM_SQDIFF. But it will not be in the range 0 to 1, but will be 0 to some large number. You will have to find an appropriate threshold and test for values below the threshold. – fmw42 Oct 29 '22 at 04:30
  • @fmw42 my results seem to be less accurate using TM_SQDIFF, I found a threshold that kinda works but it only recognizes 2 out of 4 marks and gives multiple false matches – Annelotte Oct 29 '22 at 10:11
  • Post your original full resolution input and I presume the template above is correct. If not post the template also that you think is at the same scale as the image. I will take a look. If the image is too large for this forum, post it to some free hosting service and put the URL in your question or in a comment. – fmw42 Oct 29 '22 at 15:17
  • @fmw42 I updated my code in the original post, including a link to the full picture. I made some changes since someone said that this would help. I get no false results now, but I feel like it still could be better – Annelotte Oct 30 '22 at 09:36
  • Your template and image are not at the same scale. Also the polarity of the template is opposite of the rings in the image. The latter is a significant issue. Make a better template! – fmw42 Oct 30 '22 at 16:26
  • @fmw42 I changed the template to make it similar to the changes to the image (grayscaling and blurring). I checked the image after the changes, cutted one of the fiducial marks and used that as template. Would it be better to change the fiducial mark the same way the image is changed? And I am not sure why they should be the same scale? At the end, the image will vary with different sizes. – Annelotte Oct 30 '22 at 17:21
  • The template is white on black and in the image the rings are black on white. You should invert your template. If your background in the image and the template might be different, then you should use a mask in matchTemplate. But that only works for TM_SQDIFF and TM_CORR_NORMED. If you invert your template and use TM_SQDIFF searching for a minimum value or below threshold, that should work. Did you check your resized image to be sure that the template is at the proper scale for the image? – fmw42 Oct 30 '22 at 19:06

1 Answers1

1

Here is how I would do that in Python/OpenCV. Mostly the same as yours with several changes.

First, I would not bother computing the dim for resize. I would just use your scale_percent/100 so a fraction. Resize permits that in place of the size.

Second, I would threshold your images and invert the template so that you are matching black rings in both the image and template.

Third, I would use TM_SQDIFF and find values below a threshold.

import cv2
import numpy as np
from imutils.object_detection import non_max_suppression
  
# Reading and resizing the image

big_image = cv2.imread('diagram.jpg')
 
scale_percent = 10 # percent of original size
scale = scale_percent/100

img = cv2.resize(big_image, (0,0), fx=scale, fy=scale, interpolation = cv2.INTER_AREA)

temp = cv2.imread('ring.png')

# save the image dimensions
W, H = temp.shape[:2]
  
# Converting them to grayscale
img_gray = cv2.cvtColor(img, 
                        cv2.COLOR_BGR2GRAY)
temp_gray = cv2.cvtColor(temp,
                         cv2.COLOR_BGR2GRAY)

# threshold (and invert template)
img_thresh = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
temp_thresh = cv2.threshold(temp_gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]

# Passing the image to matchTemplate method
match = cv2.matchTemplate(
    image=img_thresh, templ=temp_thresh, 
  method=cv2.TM_SQDIFF)\

min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(match)
print(min_val, max_val)

# Define a threshold
# thresh between 40000000 and 60000000 works
thresh = 50000000

# Select rectangles with confidence less than threshold for TM_SQDIFF
(y_points, x_points) = np.where(match <= thresh)
  
# initialize our list of rectangles
boxes = list()
  
# loop over the starting (x, y)-coordinates again
for (x, y) in zip(x_points, y_points):    
    # update our list of rectangles
    boxes.append((x, y, x + W, y + H))
  
# apply non-maxima suppression to the rectangles
# this will create a single bounding box
boxes = non_max_suppression(np.array(boxes))
  
# loop over the final bounding boxes
result = img.copy()
for (x1, y1, x2, y2) in boxes:    
    # draw the bounding box on the image
    cv2.rectangle(result, (x1, y1), (x2, y2),
                  (255, 0, 0), 3)

# save result
cv2.imwrite('diagram_match_locations.jpg', result) 

# Show the template and the final output
cv2.imshow("Template_thresh", temp_thresh)
cv2.imshow("Image_thresh", img_thresh)
cv2.imshow("After NMS", result)
cv2.waitKey(0)
  
# destroy all the windows manually to be on the safe side
cv2.destroyAllWindows()

Result:

fmw42
  • 46,825
  • 10
  • 62
  • 80