2

enter image description here

Is there a way to rotate these kind of images and remove the background whitespace or any background and get and image like this

enter image description here

I tried to remove the background if the image doesn't have any rotation i am able to remove the background whitespace by using this script but if the image got any rotation it doesn't remove any space i followed this How to crop or remove white background from an image

import cv2
import numpy as np

img = cv2.imread('cheque_img\rotate.PNG')
## (1) Convert to gray, and threshold
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
th, threshed = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)

## (2) Morph-op to remove noise
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11,11))
morphed = cv2.morphologyEx(threshed, cv2.MORPH_CLOSE, kernel)

## (3) Find the max-area contour
cnts = cv2.findContours(morphed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
cnt = sorted(cnts, key=cv2.contourArea)[-1]

## (4) Crop and save it
x,y,w,h = cv2.boundingRect(cnt)
dst = img[y:y+h, x:x+w]
cv2.imwrite("001.png", dst) 

Please try it with any scanned image and rotate it and try to get rid of the background white space and rotate it to its original dimension for doing computer vision operation

DataDoctor
  • 113
  • 1
  • 3
  • 11
  • Try using Perspective Transform. Link to documentation: https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_geometric_transformations/py_geometric_transformations.html – Vardan Agarwal Jan 04 '20 at 14:11
  • @VardanAgarwal The problem is , the co-ordinates of the image might be different for different picture, – DataDoctor Jan 04 '20 at 14:13
  • I guess that the question you should ask is "how can I find the coordinates of the corners of the documents". The rest is easy work. –  Jan 04 '20 at 15:21
  • @YvesDaoust How can i find the coordinates of the dynamic images , yes true! – DataDoctor Jan 04 '20 at 16:37

4 Answers4

4

Using cv2.boundingRect will give you the minimum non-rotating rectangle that fit the contour. cv2.boundingRect result :

enter image description here

Instead of cv2.boundingRect, you will need to use cv2.minAreaRect to obtain a rectangle that fit the contour. cv2.minAreaRect result :

enter image description here

After the obtaining the rotated rect information, you will need to find the affine transform matrix between the model points and the current points. Current points are the points found in rotated rect and the model point is the point of the original object. In this case an object with the initial location (0,0) and the width and height of the rotated rect.

Affine might be an overkill here but for generality affine transform is used.

enter image description here

Detailed explanation is located in the code.

import cv2
import numpy as np

img = cv2.imread('Bcm3h.png')

## (1) Convert to gray, and threshold
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
th, threshed = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)


## (2) Morph-op to remove noise
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11,11))
morphed = cv2.morphologyEx(threshed, cv2.MORPH_CLOSE, kernel)

## (3) Find the max-area contour
cnts = cv2.findContours(morphed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
cnt = sorted(cnts, key=cv2.contourArea)[-1]


## This will extract the rotated rect from the contour
rot_rect = cv2.minAreaRect(cnt)

# Extract useful data
cx,cy = (rot_rect[0][0], rot_rect[0][1]) # rect center
sx,sy = (rot_rect[1][0], rot_rect[1][1]) # rect size
angle = rot_rect[2] # rect angle


# Set model points : The original shape
model_pts = np.array([[0,sy],[0,0],[sx,0],[sx,sy]]).astype('int')
# Set detected points : Points on the image
current_pts = cv2.boxPoints(rot_rect).astype('int')

# sort the points to ensure match between model points and current points
ind_model = np.lexsort((model_pts[:,1],model_pts[:,0]))
ind_current = np.lexsort((current_pts[:,1],current_pts[:,0]))

model_pts = np.array([model_pts[i] for i in ind_model])
current_pts = np.array([current_pts[i] for i in ind_current])


# Estimate the transform betwee points
M = cv2.estimateRigidTransform(current_pts,model_pts,True)

# Wrap the image
wrap_gray = cv2.warpAffine(gray, M, (int(sx),int(sy)))


# for display
cv2.imshow("dst",wrap_gray)
cv2.waitKey(0)

#cv2.imwrite("001.png", dst) 

Result :

enter image description here

yapws87
  • 1,809
  • 7
  • 16
  • I am getting this error and i am using open-cv > 4.0 ` wrap_gray = cv2.warpAffine(gray, M, (int(sx),int(sy))) TypeError: Expected Ptr for argument '%s' ` – DataDoctor Jan 06 '20 at 09:34
  • can you try typecasting with cv2.umat() on the gray variable? i.e. wrap_gray = cv2.warpAffine(cv2.umat(gray), M, (int(sx),int(sy))) – yapws87 Jan 06 '20 at 15:32
  • I tried that as well as tried this img = cv2.UMat(cv2.imread("image.jpg", cv2.IMREAD_COLOR)) imgUMat = cv2.UMat(img) gray = cv2.cvtColor(imgUMat, cv2.COLOR_BGR2GRAY) also this gray = cv2.cvtColor(cv2.umat(img), cv2.COLOR_BGR2GRAY) – DataDoctor Jan 06 '20 at 17:32
  • you get the same error even with the modifications? – yapws87 Jan 07 '20 at 02:04
  • Yes i do, i guess it has to do something with the warpAffine function, because with the same code excluding the warpAffine i can get a image response. – DataDoctor Jan 07 '20 at 03:41
  • Could you please check your opencv version and if it is less than 4.0 could you please create another environment and update the opencv version and see what changes needs in the code. I am absolutely new in opencv image processing. – DataDoctor Jan 07 '20 at 03:47
  • my version is lower than 4 and i have no intention of upgrading it for this problem. Maybe you can print the inputs you insert to warpaffine function and see if they are proper – yapws87 Jan 07 '20 at 04:13
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/205487/discussion-between-datadoctor-and-yapws87). – DataDoctor Jan 07 '20 at 06:24
  • Check your version of cv2.findContours to see how many items are returned. Different versions of OpenCV return different number of items and the actual contours may be at a different position in the returned items. – fmw42 Jun 25 '22 at 17:04
1

Considering you don't know the angle of the rotation and can be different for each scanned image, you need to find it first.

Combine what you already did with accepted answer for this question.

For the image you provided:

Angle is -25.953375702364195

enter image description here

Dositej
  • 114
  • 5
  • It does rotate the image but doesn't remove the white space using the above code.Thank you for part 1 solution – DataDoctor Jan 04 '20 at 16:48
0

If the background is guaranteed to be saturated white (value 255) and the document mostly unsaturated values, binarize below the threshold 255 and fit a bounding rectangle.

0

I had some problems running the code presented above, so here is my slightly modified version:

import cv2
import numpy as np

def crop_minAreaRect(img, rect):
    # rotate img
    angle = rect[2]
    print("angle: " + str(angle))
    rows,cols = img.shape[0], img.shape[1]
      
    M = cv2.getRotationMatrix2D((cols/2,rows/2),angle,1)
    img_rot = cv2.warpAffine(img,M,(cols,rows))

    # rotate bounding box
    rect0 = (rect[0], rect[1], angle) 
    box = cv2.boxPoints(rect0)
    pts = np.int0(cv2.transform(np.array([box]), M))[0]    
    pts[pts < 0] = 0

    # crop
    img_crop = img_rot[pts[1][1]:pts[0][1], 
                       pts[1][0]:pts[2][0]]

    return img_crop
    
def ResizeWithAspectRatio(image, width=None, height=None, inter=cv2.INTER_AREA):
    dim = None
    (h, w) = image.shape[:2]

    if width is None and height is None:
        return image
    if width is None:
        r = height / float(h)
        dim = (int(w * r), height)
    else:
        r = width / float(w)
        dim = (width, int(h * r))

    return cv2.resize(image, dim, interpolation=inter)

img = cv2.imread('rotatedCheque.png')
cv2.imshow("orig", img)
img_copy = img.copy()

# (1) Convert to gray, and threshold
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
th, threshed = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)

# (2) Morph-op to remove noise
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11, 11))
morphed = cv2.morphologyEx(threshed, cv2.MORPH_CLOSE, kernel)

# (3) Find the max-area contour
cnts = cv2.findContours(morphed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
cnt = sorted(cnts, key=cv2.contourArea)[-1]

# This will extract the rotated rect from the contour
rot_rect = cv2.minAreaRect(cnt)

cropped_img = crop_minAreaRect(img, rot_rect)

width, height = img.shape[0], img.shape[1]
if height > width:
  cropped_img = cv2.rotate(cropped_img, cv2.ROTATE_90_CLOCKWISE)
  
resized_img = ResizeWithAspectRatio(cropped_img, width=800)

cv2.imshow("cropped", resized_img)
cv2.waitKey(0)
Nick_F
  • 1,103
  • 2
  • 13
  • 30