pythonopencvimage-processingcomputer-vision

How to detect scanned document edges and cropping the image in Python?


I have hard, messy scanned images with a noisy background as below

This is what I have done

image = cv2.imread(r'Images\2.png')
orig = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(gray, 75, 200)
cnts, hierarchy = cv2.findContours(
    edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contour_img = image.copy()
cv2.drawContours(contour_img, cnts, -1, (0, 255, 0), 2)

enter image description here

I didn't get any contours to get the interesting part as below image

]


Solution

  • I would suggest to try the following approach to isolate your piece of paper:

    1. Threshold the image into a binary version
    2. Apply morphological opening to remove connections to clutter/noisy lines
    3. Perform a connected-component analysis
    4. Get the bounding box of the largest component

    I am sure you can adapt the code to your needs from there. You might have to tweak the parameters of the thresholding and the structuring element to work on your set of images.

    import cv2
    import numpy as np
    
    image  = cv2.imread('input2.png')
    gray   = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    gray   = cv2.normalize(gray, None, 0, 255, cv2.NORM_MINMAX)
    # binary = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)[1]  # Alternative to adaptive thresholding
    binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
    
    # Morphological openening
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
    binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
    
    # Find the biggest component in the binary image via connected components
    _, labels_im  = cv2.connectedComponents(binary)
    largest_label = 1 + np.argmax(np.bincount(labels_im.flat)[1:])  # Ignore background label 0
    
    mask = np.zeros_like(labels_im, dtype=np.uint8)
    mask[labels_im == largest_label] = 255  # Your mask - you might want to fill the holes with morphological operations
    
    # Debug output
    x, y, w, h = cv2.boundingRect(mask)
    mask[(binary > 0) & (mask == 0)] = 128  # Show the other components
    cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
    cv2.imshow('Binary Components', mask)
    cv2.imshow('Bounding Box', image)
    cv2.waitKey(0)
    

    Binary components

    Resulting bounding box