pythonopencvimage-processingcontour

How to measure the length of image from a masked image using OpenCV?


I am new in image processing and I am trying to improve myself by doing some projects and I have a problem about my project. I have an image dataset containing lakes with their corresponding binary mask images. I want to calculate the perimeter (boundary length) and area of the lake in each image using OpenCV.

So far, I have tried Canny Edge Detection and cv2.findContours() to detect the lake boundaries to find lake's real area and real perimeter, but I am struggling to get only the lake's contour without including unwanted edges (such as image borders or noise).

Here is my current code:

import cv2
import matplotlib.pyplot as plt
import numpy as np



# Load the image
image = cv2.imread("water_body_3.jpg")

# Apply Canny edge detection
edged = cv2.Canny(image, 50, 100)

# Find contours
contours, _ = cv2.findContours(edged, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

largest_contour = max(contours, key=cv2.contourArea)

# Draw the detected contours
cv2.drawContours(image, [largest_contour], -1, (0, 255, 0), 2)

plt.imshow(image, cmap="gray")
plt.title("Canny Edge Detection")
plt.axis("off")
plt.show()

Problems I'm Facing:

Expected Output:

Question:

Images:

code output image: https://i.sstatic.net/mLCvT5sD.png

mask image that I use in code (water_body_3.jpg): https://i.sstatic.net/psXSsJfg.jpg

original image:https://i.sstatic.net/4v6f4wLj.jpg


Solution

  • Take a look at my code : Its not only a problem of contour detection cause the contour was around your image. that's why before the detection i just draw a rectangle of 1 pixel to the image borders. And then the idea is to detect the biggest white area and making all white in this area to avoid some small noise and make a full black mask of the image then all is clean and it come easy to get what you want!

    import cv2
    import numpy as np
    
    # Draw a black border of 1 pixel around the image
    cv2.rectangle(image, (0, 0), (image.shape[1] - 1, image.shape[0] - 1), (0, 0, 0), thickness=1)
    
    # Convert the image to a binary (black & white) format
    # Any pixel value greater than 127 becomes 255 (white), others become 0 (black)
    _, binary = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
    
    # Find the contours (external shapes) in the binary image
    # RETR_EXTERNAL: Retrieves only the outermost contour
    # CHAIN_APPROX_SIMPLE: Compresses horizontal, vertical, and diagonal segments to save memory
    contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    # Identify the largest contour based on its area
    largest_contour = max(contours, key=cv2.contourArea)
    
    # Calculate the area (in pixels) of the largest contour
    area = cv2.contourArea(largest_contour)
    
    # Calculate the perimeter (length of the boundary) of the largest contour
    peri = cv2.arcLength(largest_contour, True)  # 'True' means it's a closed shape
    
    # Create text labels for area and perimeter, adding "px²" for area and "px" for perimeter
    text1 = f"Area: {int(area)} px²"
    text2 = f"Perimeter: {int(peri)} px"
    
    # Create a blank mask (same size as the original image) filled with zeros (black)
    mask = np.zeros_like(image)
    
    # Fill the largest contour area with white (255) on the mask
    cv2.drawContours(mask, [largest_contour], 0, 255, cv2.FILLED)
    
    # Convert the grayscale mask to a BGR (color) image so we can draw in color
    image_color = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
    
    # Draw the largest contour filled with green (0, 255, 0) on the color image
    cv2.drawContours(image_color, [largest_contour], -1, (0, 255, 0), cv2.FILLED)
    
    # Draw the largest contour with red (0, 0, 255) on the color image
    cv2.drawContours(image_color, [largest_contour], -1,(0, 0, 255), 1)
    
    #From here its just the codes to display the info box
    (x, y) = (10, 10)
    (w, h) = (140, 55)
    
    cv2.rectangle(image_color, (x, y), (x + w, y + h), (255, 255, 255), -1)  # -1 fills the rectangle
    
    cv2.putText(image_color, text1, (x + 10, y + 20), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 0), 1)
    cv2.putText(image_color, text2, (x + 10, y + 45), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 0), 1)
    
    cv2.imwrite("output.jpg", image_color)
    cv2.imshow("Result Image", image_color)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    Result:

    Contour detection