Search code examples
pythonopencvimage-processing

how to detect square in image with noisy background


I have several image of seedling plots. I want to run a code to slice each square pot and store it.

Original Image To do this, first convert it to gray image then to reduce noise bluer it and finally convert it to binary using threshold. thresh

Now I am trying to find geometry location of each square using cv.findContours but seems just can return points in corner of image instead finding corner of each square.

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
edges = cv2.Canny(blurred, 10, 50)

thresh = cv2.adaptiveThreshold(gray, 400, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 7, 4)
lines = cv2.HoughLines(thresh, rho=1, theta=np.pi/180, threshold=100)
contours, hierarchy = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) 
squares = []
for contour in contours:
    peri = cv2.arcLength(contour, True)
    approx = cv2.approxPolyDP(contour, 0.02 * peri, True)
    if len(approx) == 4:
        x, y, w, h = cv2.boundingRect(approx)
        aspect_ratio = float(w) / h
        if 0.98 <= aspect_ratio <= 1:  # Adjust this range as per your requirement
            squares.append(approx)
# Draw squares on the original image
for square in squares:
    cv2.drawContours(image, [square], -1, (0, 255, 0), 2)

The problem with result is that I get many point that is not exactly the border of each pot. I would appreciate for your tips or help, how can I optimize it

result

I have already searched previous posts but most of them did it in situation with enough light and contrast that is not applicable in this case


Solution

  • There are two global approaches we can take here: use computer vision all throughout (could be inaccurate), or use a workaround (simpler approach). The latter seems better. Thus, first, we have to crop the original image to be left with one that only has the grid of seedling plots. This can be done with any simple image processing tool (i.e., Preview on Mac or Paint on Windows):

    Cropped seedling plots

    Great, now, we have to identify the different squares. If we notice, we have a 10x6 grid of even squares, so we can use matplotlib as follows:

    import matplotlib.pyplot as plt
    import matplotlib.patches as patches
    
    def draw_seedling_squares(image_path, grid_size=(10, 6)):
        # Load the image
        img = plt.imread(image_path)
        
        # Create a new figure
        fig, ax = plt.subplots()
        ax.imshow(img)
        
        # Calculate the width and height of each square
        img_height, img_width, _ = img.shape
        square_width = img_width / grid_size[0]
        square_height = img_height / grid_size[1]
        
        # Draw squares above each seedling plot
        for i in range(grid_size[0]):
            for j in range(grid_size[1]):
                x = i * square_width
                y = j * square_height
                rect = patches.Rectangle((x, y), square_width, square_height, linewidth=1, edgecolor='r', facecolor='none')
                ax.add_patch(rect)
        
        # Show the image with squares
        plt.show()
    
    draw_seedling_squares('cropped_image.jpeg')
    

    Which yields:

    grid of seedling plots

    Very accurate! Now we have one task left: saving each seedling plot in a folder called screenshots, for example. To do this, we may modify our above code a little bit as follows:

    import os
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.patches as patches
    
    def extract_and_save_seedling_plots(image_path, output_folder='screenshots', grid_size=(10, 6)):
        # Create the output folder if it doesn't exist
        if not os.path.exists(output_folder):
            os.makedirs(output_folder)
        
        # Load the image
        img = plt.imread(image_path)
        
        # Calculate the width and height of each seedling plot
        img_height, img_width, _ = img.shape
        plot_width = img_width / grid_size[0]
        plot_height = img_height / grid_size[1]
        
        # Extract and save each seedling plot as individual files
        for i in range(grid_size[0]):
            for j in range(grid_size[1]):
                x = i * plot_width
                y = j * plot_height
                
                # Define the bounding box for cropping
                bbox = (int(x), int(y), int(x + plot_width), int(y + plot_height))
                
                # Crop the seedling plot
                seedling_plot = img[bbox[1]:bbox[3], bbox[0]:bbox[2]]
                
                # Save the cropped seedling plot as an individual file
                filename = os.path.join(output_folder, f'seedling_plot_{i}_{j}.png')
                plt.imsave(filename, seedling_plot)
    
    extract_and_save_seedling_plots('cropped_image.jpeg')
    

    Great! Now we have a folder with 60 cropped images like the ones below, corresponding to the first row:

    p0 p1 p2 p3 p4 p5 p6 p7 p8 p9

    Hope this helped! And may the code be with you...