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)
I didn't get any contours to get the interesting part as below image
I would suggest to try the following approach to isolate your piece of paper:
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)