Search code examples
pythonopencvcomputer-visionedge-detectioncanny-operator

OpenCV: Detection of clearly delineated object boundary failing


I have the following image:

enter image description here

I want to detect the edge of the document placed on the table in the image. I tried the following code (the earlier approaches are commented out).

def detectDocEdge(imagePath):
    image = loadImage(imagePath)
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    
    # Approach 1
    # equalized_image = cv2.equalizeHist(gray_image)
    # blurred = cv2.GaussianBlur(equalized_image, (15, 15), 0)  # TODO: May not be required
    # edges = cv2.Canny(blurred, 5, 150, apertureSize=3)
    # kernel = np.ones((15, 15), np.uint8)
    # dilated_edges = cv2.dilate(edges, kernel, iterations=1)

    # Approach 2
    # blurred = cv2.GaussianBlur(gray_image, (5, 5), 0)
    # thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
    # kernel = np.ones((5, 5), np.uint8)
    # dilated_edges = cv2.dilate(thresh, kernel, iterations=1)

    # Approach 3
    # Apply morphological operations
    # kernel = np.ones((3, 3), np.uint8)
    # closed_image = cv2.morphologyEx(thresholded_image, cv2.MORPH_CLOSE, kernel)

    # Approach 4
    blurred = cv2.GaussianBlur(gray_image, (5, 5), 0)
    edges = cv2.Canny(blurred, 50, 150)
    dilated = cv2.dilate(edges, None, iterations=5)
    eroded = cv2.erode(dilated, None, iterations=5)

    contours, hierarchy = cv2.findContours(eroded, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    contour_img = image.copy()
    cv2.drawContours(contour_img, contours, -1, (0, 255, 0), 2)
    showImage(contour_img)

    max_contour = None
    max_measure = 0  # This could be area or combination of area and perimeter
    max_area = 0

    for contour in contours:
        area = cv2.contourArea(contour)
        perimeter = cv2.arcLength(contour, True)

        # Here we use a combination of area and perimeter to select the contour
        measure = area + perimeter  # You can also try different combinations

        # if measure > max_measure:
        #     max_measure = measure
        #     max_contour = contour

        if area > max_area:
            max_area = area
            max_contour = contour

    contour_img = image.copy()
    cv2.drawContours(contour_img, [max_contour], -1, (0, 255, 0), 2)
    showImage(contour_img)

None of the approaches seem to work and I always get the following edge: enter image description here

Could you suggest a fix? I need the code to be as generic as possible so that it can detect the document edge under as many condition as possible.


Solution

  • I could get the contour around the document by scaling down the image and scaling up the contours back.

    import cv2
    import numpy as np
    
    def detectDocEdge(imagePath):
        image = cv2.imread(imagePath, cv2.IMREAD_COLOR)
    
        scale_factor = .25
        small_image = cv2.resize(image, None, fx=scale_factor, fy=scale_factor)
        gray = cv2.cvtColor(small_image, cv2.COLOR_BGR2GRAY)
        edges = cv2.Canny(gray, 50, 150)
        kernel = np.ones((5, 5), np.uint8)
        edges = cv2.dilate(edges, kernel, iterations=1)
        contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        contour = sorted(contours, key=cv2.contourArea, reverse=True)[0]
        contour[:, :, 0] = contour[:, :, 0] / scale_factor
        contour[:, :, 1] = contour[:, :,  1] / scale_factor
    
        contour_img = image.copy()
        cv2.drawContours(contour_img, [contour], -1, (0, 255, 0), 10)
        cv2.namedWindow('Image', cv2.WINDOW_NORMAL)
        cv2.imshow('Image', contour_img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()
    

    enter image description here