Search code examples
pythonimagescikit-learnobject-detectiontemplate-matching

Multiple template matching with Scikit-image


I want to find the middle of a coin and find the radii. The radii can tell me if the coin is 5 cents of 50 cents. After finding the image, I must be able to detect the middle of the circles so I can give them a color

I already made a beginning with writing a code for Hough-transform

Hopefully someone can help me solve this exercise.

Code for hough transform:

image = img_as_ubyte(image)
edges = feature.canny(image, sigma=1.5, low_threshold=10, high_threshold=25)

# Detect two radii
hough_radii = np.arange(17, 35, 2)
hough_res = hough_circle(edges, hough_radii)

# Select the 11 coins
accums, cx, cy, radii = hough_circle_peaks(hough_res, hough_radii,total_num_peaks=11)

# Draw them
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(10, 4))
image = color.gray2rgb(image)
count = 0
for center_y, center_x, radius in zip(cy, cx, radii):
    circy, circx = circle_perimeter(center_y, center_x, radius,
                                    shape=image.shape)
    image[circy, circx] = (220, 20, 20)
    count += 1


ax.imshow(image)
plt.figure(figsize=(25,25))
print("In this image we can detect", count, "coins")
plt.show()


Solution

  • You have 2 choices to find matching circles.

    Sample Input and It's Edges

    first one: sliding window

    smaller windows is a template that you seek for it in input image, here two images with different radii 19 and 21 respectively. First at all, find edge of template and input image, after that, multiplying cropped part of image with template. each region that is near to template should have higher value.

    def correlation_coefficient(patch1, patch2):
        product = np.mean((patch1 - patch1.mean()) * (patch2 - patch2.mean()))
        stds = patch1.std() * patch2.std()
        if stds == 0:
            return 0
        else:
            product /= stds
            return product
    
    
    im1 =  cv2.imread("Original.jpg")
    im1 = cv2.cvtColor(np.float32(im1), cv2.COLOR_BGR2GRAY)
    
    
    im2 = cv2.imread("bigger.png")
    im2 = cv2.cvtColor(np.float32(im2), cv2.COLOR_BGR2GRAY)
    
    
    sh_row, sh_col = im1.shape
    correlation = np.zeros_like(im1)
    
    for i in range(sh_row - im2.shape[1]):
        for j in range(sh_col - im2.shape[0]):
            temp1 = im1[i : i + im2.shape[1], j : j + im2.shape[0]]
            if(temp1.shape != im2.shape):
              correlation[i, j] = 0
              continue
            correlation[i, j] = correlation_coefficient(temp1, im2)
    
    
    fig = plt.figure(figsize=(10, 7))
    plt.imshow(correlation, cmap=plt.cm.gray)
    plt.show()
    

    Here template is smaller or bigger one. output for bigger one is

    Template Matching for Bigger Template

    Template Matching for Smaller Template

    As you can see some points highlighted as center of circles. Or by using second matching calculations:

    im1 =  cv2.imread("Original.jpg")
    im1 = cv2.cvtColor(np.float32(im1), cv2.COLOR_BGR2GRAY)
    
    im1 = canny(im1, sigma=3, low_threshold=5, high_threshold=40)
    im1 = im1.astype(np.uint8)
    
    im2 = cv2.imread("bigger.png")
    im2 = cv2.cvtColor(np.float32(im2), cv2.COLOR_BGR2GRAY)
    
    im2 = canny(im2, sigma=3, low_threshold=5, high_threshold=40)
    im2 = im2.astype(np.uint8)
    sh_row, sh_col = im1.shape
    
    d = 1
    
    correlation = np.zeros_like(im1)
    
    for i in range(sh_row - im2.shape[1]):
        for j in range(sh_col - im2.shape[0]):
            temp1 = im1[i : i + im2.shape[1], j : j + im2.shape[0]]
            if(temp1.shape != im2.shape):
              correlation[i, j] = 0
              continue
            correlation[i, j] = np.sum(np.multiply(temp1, im2))
    
    io.imshow(correlation, cmap='gray')
    io.show()
    

    Also, we have same results:

    Template Matching for Bigger Template in second Calculation Template Matching for Smaller Template in second Calculation

    First method could not help us to find specified circles. Because you need to set a threshold for both templates and resolve more and more challenges. Let's investigate another method.

    first one: Hough Transform

    At first, run canny edge detector, then find all circles with radii range from 1 to 100. Then near circles(circles that centers are near to each others) are deleted:

    import numpy as np
    import matplotlib.pyplot as plt
    import cv2
    from skimage import data, color
    from skimage.transform import hough_circle, hough_circle_peaks
    from skimage.feature import canny
    from skimage.draw import circle_perimeter
    from skimage.util import img_as_ubyte
    from skimage import io, feature
    from scipy import ndimage
    import imutils
    from scipy import signal
    from skimage import io, feature
    
    
    
    image = cv2.imread("Original.jpg")
    fig = plt.figure(figsize=(10, 7))
    
    fig.add_subplot(1, 2, 1)
    plt.imshow(image)
    plt.axis('off')
    plt.title("Original Image")
    
    image = cv2.cvtColor(np.float32(image), cv2.COLOR_BGR2GRAY)
    edges = canny(image, sigma=3, low_threshold=5, high_threshold=40)
    
    fig.add_subplot(1, 2, 2)
    plt.imshow(edges, cmap=plt.cm.gray)
    plt.axis('off')
    plt.title("After Run Canny Edge Detector")
    
    # which raddii?
    hough_radii = np.arange(1, 100)
    hough_res = hough_circle(edges, hough_radii)
    
    accums, cx, cy, radii = hough_circle_peaks(hough_res, hough_radii, total_num_peaks=100)
    output = np.column_stack((cx, cy, radii))
    output = output[(output[:,2] > 10)]
    
    output = output[np.argsort(output[:, 1])]
    output = output[np.argsort(output[:, 0])]
    
    print(f"Circles Before Edit")
    print(f"cx={output[:,0]}")
    print(f"cy={output[:,1]}")
    print(f"radii={output[:,2]}")
    
    index = 0
    flag = False
    while (index < output.shape[0] - 1):
    
      if(abs (output[index][0] - output[index+1][0]) < 5 ):
        if(abs (output[index][1] - output[index+1][1]) < 5 ):
          # print(f"del index {index+1}")
          output = np.delete(output, (index+1), axis=0)
          flag = True
        else:
          flag = False
      else:
        flag = False
      if(flag is not True):
        index += 1
    print(f"Circles After Edit")
    print(f"cx={output[:,0]}")
    print(f"cy={output[:,1]}")
    print(f"radii={output[:,2]}")
    
    
    plt.show()
    
    
    red = output[(output[:,2] <= 20)]
    print(f"Red Circles")
    print(f"cx={red[:,0]}")
    print(f"cy={red[:,1]}")
    print(f"radii={red[:,2]}")
    
    green = output[(output[:,2] >= 20)]
    print(f"Green Circles")
    print(f"cx={green[:,0]}")
    print(f"cy={green[:,1]}")
    print(f"radii={green[:,2]}")
    

    Check results:

    Circles Before Edit
    cx=[ 96  96  97  98 105 106 146 165 188 189 196 196 202 203 204 216 264 265]
    cy=[137 138 136 138 232 232 356 229 102 102 166 166 222 221 286 322 116 116]
    radii=[22 23 23 21 22 21 19 21 21 22 19 18 19 18 22 19 18 19]
    Circles After Edit
    cx=[ 96 105 146 165 188 196 202 204 216 264]
    cy=[137 232 356 229 102 166 222 286 322 116]
    radii=[22 22 19 21 21 19 19 22 19 18]
    

    After do all necessary calculations:

    Red Circles
    cx=[146 196 202 216 264]
    cy=[356 166 222 322 116]
    radii=[19 19 19 19 18]
    Green Circles
    cx=[ 96 105 165 188 204]
    cy=[137 232 229 102 286]
    radii=[22 22 21 21 22]
    

    As mentioned in results, 5 circles are proposed to red and 5 circles proposed to green.

    Update#1

    Be cautious before doing preprocessing in such problems. These processing likes erosion, dilation and median filters change radius of circles.

    Drawing Circles:

    Input radius are 19 and 21, so radius larger than 20 are belong to green color and radius smaller than 20 are belong to red color. For simplicity I set yellow color for radius = 20. In the last step you should delete abundance circles. Bellow I bring all of them together:

    def remove_redundance(output):
      
      print(f"output.shape={output.shape}")
      index = 0
      del_first = False
      index_first = 0
      while (index_first < output.shape[0]):
        index_second = index_first + 1
        del_second = False
        while (index_second < output.shape[0]):
    
          if( (abs(output[index_first][0] - output[index_second][0]) < 10) and 
             (abs(output[index_first][1] - output[index_second][1]) < 10) ):
            
            if(output[index_first][3] > output[index_second][3]):
              output = np.delete(output, (index_second), axis=0)
              del_second = True
            else:
              output = np.delete(output, (index_first), axis=0)
              del_first = True
              break
    
          else:
            del_second = False
            del_first = False
    
          if (del_second == False):
            index_second += 1
        
        
        if (del_first == False):
          index_first += 1
        else:
          del_first = False
    
      print(f"output.shape={output.shape}")
      return output
      
    
    def draw_circles(circle, coordinate, green = 0):
      for cx, cy, radii in zip(coordinate[:,0], coordinate[:,1], coordinate[:,2]):
        # Center coordinates
        center_coordinates = (int(cx), int(cy))
        
        # Radius of circle
        radius = int(radii)
        
        if(green == 1):
          color = (0, 255, 0)
        elif(green == 0):
          color = (0, 0, 255)
        elif(green == -1):
          color = (0, 255, 255)
          
        thickness = 1#-1
          
        circle = cv2.circle(circle, center_coordinates, radius, color, thickness)
    
      return circle
    
    
    
    image = cv2.imread("Original.jpg")
    # image = cv2.medianBlur(image, 3)
    fig = plt.figure(figsize=(10, 7))
    
    fig.add_subplot(1, 2, 1)
    plt.imshow(image)
    plt.axis('off')
    plt.title("Original Image")
    
    image = cv2.cvtColor(np.float32(image), cv2.COLOR_BGR2GRAY)
    edges = canny(image, sigma=3, low_threshold=5, high_threshold=40)
    
    fig.add_subplot(1, 2, 2)
    plt.imshow(edges, cmap=plt.cm.gray)
    plt.axis('off')
    plt.title("After Run Canny Edge Detector")
    
    # which raddii?
    hough_radii = np.arange(1, 500)
    hough_res = hough_circle(edges, hough_radii)
    
    accums, cx, cy, radii = hough_circle_peaks(hough_res, hough_radii, total_num_peaks=500)
    output = np.column_stack((cx, cy, radii, accums))#
    output = output[(output[:,2] > 10)]
    
    output = output[np.argsort(output[:, 1])]
    output = output[np.argsort(output[:, 0])]
    
    print(f"Circles Before Edit")
    print(f"cx={output[:,0]}")
    print(f"cy={output[:,1]}")
    print(f"radii={output[:,2]}")
    
    
    output = remove_redundance(output)
    
    
    print(f"Circles After Edit")
    print(f"cx={output[:,0]}")
    print(f"cy={output[:,1]}")
    print(f"radii={output[:,2]}")
    
    
    plt.show()
    
    
    red = output[(output[:,2] < 20)]
    if(red.shape[0]>0):
      print(f"Red Circles")
      print(f"cx={red[:,0]}")
      print(f"cy={red[:,1]}")
      print(f"radii={red[:,2]}")
    
    green = output[(output[:,2] > 20)]
    if(green.shape[0]>0):
      print(f"Green Circles")
      print(f"cx={green[:,0]}")
      print(f"cy={green[:,1]}")
      print(f"radii={green[:,2]}")
    
    
    yellow = output[(output[:,2] == 20)]
    if(yellow.shape[0]>0):
      print(f"yellow Circles")
      print(f"cx={yellow[:,0]}")
      print(f"cy={yellow[:,1]}")
      print(f"radii={yellow[:,2]}")
    
    
    circle = cv2.imread("Original.jpg")
    
    if(red.shape[0]>0):
      circle  = draw_circles(circle, red, green = 0)
    if(green.shape[0]>0):
      circle  = draw_circles(circle, green, green = 1)
    if(yellow.shape[0]>0):
      circle  = draw_circles(circle, yellow, green = -1)
    
    circle = cv2.cvtColor(circle, cv2.COLOR_BGR2RGB)
    
    fig = plt.figure(figsize=(10, 7))
    plt.imshow(circle)
    plt.axis('off')
    plt.title("Circles") 
    

    Results:

    Results