7

I have the following image which is a receipt image and a lot of white space around the receipt in focus. I would like to crop the white space. I can't manually crop it so I'm looking for a way that I could do it.

enter image description here

Cropped one:

enter image description here

Tried this code from the following post: How to remove whitespace from an image in OpenCV?

gray = load_image(IMG_FILE) # image file
gray = 255*(gray < 128).astype(np.uint8)
coords = cv2.findNonZero(gray) # Find all non-zero points (text)
x, y, w, h = cv2.boundingRect(coords) # Find minimum spanning bounding box
rect = load_image(IMG_FILE)[y:y+h, x:x+w] # Crop the image - note we do this on the original image

it's cropping a tiny part of the white space.

rayryeng
  • 102,964
  • 22
  • 184
  • 193
user_12
  • 1,778
  • 7
  • 31
  • 72
  • Similar to https://stackoverflow.com/questions/49907382/how-to-remove-whitespace-from-an-image-in-opencv – Tanya Gupta Jan 07 '20 at 21:50
  • 1
    @TanyaGupta But that one was a dense text spaced equally and line by line. In my case it's a receipt image and it's not exactly equally spaced (or) dense. It didn't worked for me correctly. The above code that I've tried is taken from that answer. – user_12 Jan 07 '20 at 21:53
  • 1
    I am the original author of the post you mentioned. Your code does work with a very slight fix. I have written an answer for self-containment. BTW, for disclosure you **did not** use the entire solution properly. You removed the morphology part of the answer which does the noise filtering - check the second version of the code in my answer that is linked. This is the main reason it did not work as expected. – rayryeng Jan 08 '20 at 09:23

2 Answers2

8

Here's a simple approach:

  1. Obtain binary image. Load the image, convert to grayscale, apply a large Gaussian blur, and then Otsu's threshold

  2. Perform morphological operations. We first morph open with a small kernel to remove noise then morph close with a large kernel to combine the contours

  3. Find enclosing bounding box and crop ROI. We find the coordinates of all non-zero points, find the bounding rectangle, and crop the ROI.


Here's the detected ROI to crop highlighted in green

enter image description here

Cropped ROI

import cv2

# Load image, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread('1.jpg')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (25,25), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Perform morph operations, first open to remove noise, then close to combine
noise_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, noise_kernel, iterations=2)
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7,7))
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, close_kernel, iterations=3)

# Find enclosing boundingbox and crop ROI
coords = cv2.findNonZero(close)
x,y,w,h = cv2.boundingRect(coords)
cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
crop = original[y:y+h, x:x+w]

cv2.imshow('thresh', thresh)
cv2.imshow('close', close)
cv2.imshow('image', image)
cv2.imshow('crop', crop)
cv2.waitKey()
nathancy
  • 42,661
  • 14
  • 115
  • 137
  • thank you :). Just a quick question, will this work if the background was not always white... It's just that I have some receipt where the background is little bit gray, light gray with some dots (i.e disturbances)? – user_12 Jan 07 '20 at 22:35
  • 1
    Yes it should work, Otsu's threshold automatically calculates a threshold value so if the lighting conditions or background changes it should still be ok. The reason for the large gaussian blur and morph open operations are to smooth and remove noise. Depending on the image, you may have to modify these values. Could you add examples where the background is a bit gray with dots (noise)? – nathancy Jan 07 '20 at 22:39
  • How does one reach a point in there career they know what operations to apply in image processing to reach the desired effect. This seems so magical to me. – MikanPotatos Sep 15 '21 at 11:20
  • 1
    @MikanPotatos I started learning image processing with no prior experience the same day I made this stackoverflow account. You just do a ton of problems and you eventually instantly see the pattern to apply when you see a situation – nathancy Sep 16 '21 at 01:20
  • File "/path/lib/python3.7/site-packages/fitz/__init__.py", line 1, in from frontend import * ModuleNotFoundError: No module named 'frontend' – Aayush Neupane Dec 04 '22 at 03:20
5

I am the original author of the code that you tried. The reason why it didn't work was because you have some noisy pixels that are surrounding the text that is throwing off the algorithm. If you remove the noise with a simple opening morphological operation, you get the result you need. This was in fact done in the second version of my answer which you unfortunately didn't try:

import cv2
import numpy as np

gray = load_image(IMG_FILE) # image file

# Threshold the image so that black text is white
gray = 255*(gray < 128).astype(np.uint8)

# Additionally do an opening operation with a 2 x 2 kernel
O = np.ones(2, dtype=np.uint8)
gray_morph = cv2.morphologyEx(gray, cv2.MORPH_OPEN, O)

# Continue where we left off
coords = cv2.findNonZero(gray_morph) # Find all non-zero points (text)
x, y, w, h = cv2.boundingRect(coords) # Find minimum spanning bounding box
rect = load_image(IMG_FILE)[y:y+h, x:x+w] # Crop the image

We thus get:

enter image description here

rayryeng
  • 102,964
  • 22
  • 184
  • 193
  • Can you tell me what this line is doing `255*(gray < 128).astype(np.uint8)`? – user_12 Jan 08 '20 at 09:36
  • 1
    It thresholds the image, converts it to uint8 and sets the non zero pixels to 255. You can achieve the same operation with `cv2.threshold` but I like doing this explicitly because it shows me what I'm actually doing. – rayryeng Jan 08 '20 at 09:39
  • Just one more small doubt regarding my problem, For the first un-cropped image I was given bounding box (annotations) but when I perform these steps I'm getting a cropped image and the old bounding box annotations doesn't work. **Is there any way that I could use the cropped ratio to adjust the bounding box to suit this cropped image?** – user_12 Jan 08 '20 at 09:50
  • @user_12 Some code would help. I don't quite understand what you're trying to ask. – rayryeng Jan 08 '20 at 17:54
  • Here's the question, that I've asked earlier please take a look once I think you can understand what I'm trying to ask in the above comment : https://stackoverflow.com/questions/59652250/after-cropping-a-image-how-to-find-new-bounding-box-coordinates – user_12 Jan 08 '20 at 19:01
  • @user_12 Someone already answered it. That's great. Let us know if you need any more help. – rayryeng Jan 08 '20 at 23:13