pythonopencvcomputer-visiongeometryhough-transform

Improvement of CV2 shape detection for spot detection on TLC


In my work, I use TLC which give me object like the one on this picture. It is a piece of paper with spot on it at a precise height.

TLC picture

I need to detect the plate itself and it measurments. Then I need to transform it as this picture :

enter image description here

I think I can generate the second picture, but I'm having troubles with the spots detection. I used the code below with lots of different parameters and I'm not able to detect them. Do you have any idea ?

import numpy
import cv2
image_path="C:/Users/jules/Downloads/Start.jpg"

image = cv2.imread(image_path)
img = cv2.resize(img, (950, 1480)) 
output = image.copy()

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

hist = cv2.equalizeHist(gray)

blur = cv2.GaussianBlur(hist, (31,31), cv2.BORDER_DEFAULT)

_, thresh_image = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)

height, width = thresh_image.shape[:2]

minR = round(width/65)
maxR = round(width/11)
minDis = round(width/7)

circles = cv2.HoughCircles(thresh_image, cv2.HOUGH_GRADIENT, 1, minDis, param1=14, param2=25, minRadius=minR, maxRadius=maxR)

if circles is not None:
    circles = numpy.round(circles[0, :]).astype("int")
    for (x, y, r) in circles:
        cv2.circle(output, (x, y), r, (0, 255, 0), 2)
        cv2.rectangle(output, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)
cv2.imshow("result", numpy.hstack([image, output]))

Thanks in advance for your help !


Solution

  • Your general approach seems correct and just requires more finetuning.

    First step is to read the doc to understand these parameters.

    Then you have to manipulate them, visualize their impact and build intuition. In OpenCV a nice way to do this is to build a demo with trackbars.

    In this case I manage to detect all the circles with the code below. But beware, with only one image and so much parameters its certain that its overfitted to some extent and that it will need adjustments.

    enter image description here

    import numpy as np
    import cv2
    
    def resize_image(img, height = None, width = None):
        original_height, original_width = img.shape[:2]
        aspect_ratio = original_width / original_height
        if width is None:
            width = int(height * aspect_ratio)
        if height is None:
            height = int(width * 1/aspect_ratio)
        return cv2.resize(img, (width, height))
    
    def crop_to_biggest_rectangle(image):
        # Find biggest rectangle
        contours, _ = cv2.findContours(image, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
        max_rect = (0,0,0,0)      
        max_area = 0
        for cnt in contours:
            x, y, w, h = cv2.boundingRect(cnt)
            area = w*h
            if area > max_area:
                max_rect = x,y,w,h
                max_area = area
    
        # Crop image
        x, y, w, h = max_rect
        cropped_image = image[y:y+h, x:x+w]
        return max_rect, cropped_image
    
    
    def find_circles(image, 
                    k_open = 8, 
                    k_dilate = 7, 
                    dilate_iter = 3, 
                    hcircles_dp=9, 
                    hcircles_min_dist = 4, 
                    hcircles_param1=103, 
                    hcircles_param2=0.59 ) -> list:
        # Preprocess the image to get clear circle shapes
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        hist = cv2.equalizeHist(gray)
        blur = cv2.GaussianBlur(hist, (3,3), cv2.BORDER_DEFAULT)
        _, thresh_image = cv2.threshold(blur, 120, 255, cv2.THRESH_BINARY)
    
        # Crop to roi
        crop_rect, cropped_tresh_image = crop_to_biggest_rectangle(thresh_image)
    
        height, width = thresh_image.shape[:2]
        minR = round(width/65)
        maxR = round(width/5)
        minDis = round(width/7)
    
        # Remove noise and improve circles "roundess".
        inversed_cropped_tresh_image = cv2.bitwise_not(cropped_tresh_image)
        kernel_open =cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(k_open, k_open))
        kernel_dilate =cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(k_dilate, k_dilate))
        opening = cv2.morphologyEx(inversed_cropped_tresh_image, cv2.MORPH_OPEN, kernel_open)
        dilate = cv2.dilate(opening, kernel_dilate, iterations = dilate_iter)
    
        # Find circles
        circles = cv2.HoughCircles(dilate, 
                                    cv2.HOUGH_GRADIENT_ALT, 
                                    hcircles_dp, 
                                    hcircles_min_dist, 
                                    param1=hcircles_param1, 
                                    param2=hcircles_param2, 
                                    minRadius=0, 
                                    maxRadius=maxR)
    
        # Move back the circle to original image coordinates
        shifted_circles = []
        for (x, y, r) in circles[0]:
            shifted_circles.append([x +crop_rect[0],y+crop_rect[1] , r])
        return np.array([shifted_circles])
    
    if __name__=="__main__":
        image_path="<path-to-your-image>"
        image = cv2.imread(image_path)
        image = resize_image(image, height=700)
        output = image.copy()
    
        circles = find_circles(image)
        if circles is not None:
            circles = np.round(circles[0, :]).astype("int")
            for (x, y, r) in circles:
                cv2.circle(image, (x, y), r, (0, 255, 0), 2)
                cv2.rectangle(image, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)
    
        cv2.imshow("result", image)
        cv2.waitKey(0) 
        cv2.destroyAllWindows()