Is there any way to crop an image inside a box?

Viewed 158

Image

I want to crop the image only inside the box or rectangle. I tried so many approaches but nothing worked.

import cv2
import numpy as np

img  = cv2.imread("C:/Users/hp/Desktop/segmentation/add.jpeg", 0);
h, w = img.shape[:2]
# print(img.shape)
kernel = np.ones((3,3),np.uint8)

img2 = img.copy()

img2 = cv2.medianBlur(img2,5)
img2 = cv2.adaptiveThreshold(img2,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
            cv2.THRESH_BINARY,11,2)

img2 = 255 - img2
img2 = cv2.dilate(img2, kernel)
img2 = cv2.medianBlur(img2, 9)
img2 = cv2.medianBlur(img2, 9)

cv2.imshow('anything', img2)
cv2.waitKey(0)
cv2.destroyAllWindows()


position = np.where(img2 !=0)
x0 = position[0].min()
x1 = position[0].max()
y0 = position[1].min()
y1 = position[1].max()

print(x0,x1,y0,y1)

result = img[x0:x1,y0:y1]

cv2.imshow('anything', result)
cv2.waitKey(0)
cv2.destroyAllWindows()

Output should be the image inside the sqaure.

2 Answers

You can use contour detection for this. If your image has basically only a hand drawn rectangle in it, I think it's good enough to assume it's the largest closed contour in the image. From that contour, we can figure out a polygon/quadrilateral approximation and then finally get an approximate rectangle. I'll define some utilities at the beginning which I generally use to make my time easier when messing around with images:

def load_image(filename):
    return cv2.imread(filename)

def bnw(image):
    return cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)

def col(image):
    return cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)

def fixrgb(image):
    return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

def show_image(image, figsize=(7,7), cmap=None):
    cmap = cmap if len(image.shape)==3 else 'gray'
    plt.figure(figsize=figsize)
    plt.imshow(image, cmap=cmap)
    plt.show()

def AdaptiveThresh(gray):
    blur = cv2.medianBlur(gray, 5)
    adapt_type = cv2.ADAPTIVE_THRESH_GAUSSIAN_C
    thresh_type = cv2.THRESH_BINARY_INV
    return cv2.adaptiveThreshold(blur, 255, adapt_type, thresh_type, 11, 2)

def get_rect(pts):
    xmin = pts[:,0,1].min()
    ymin = pts[:,0,0].min()
    xmax = pts[:,0,1].max()
    ymax = pts[:,0,0].max()
    return (ymin,xmin), (ymax,xmax)

Let's load the image and convert it to grayscale:

image_name = 'test.jpg'
image_original = fixrgb(load_image(image_name))
image_gray = 255-bnw(image_original)
show_image(image_gray)

grayscale

Use some morph ops to enhance the image:

kernel = np.ones((3,3),np.uint8)
d = 255-cv2.dilate(image_gray,kernel,iterations = 1)
show_image(d)    

d

Find the edges and enhance/denoise:

e = AdaptiveThresh(d)
show_image(e)

e

m = cv2.dilate(e,kernel,iterations = 1)
m = cv2.medianBlur(m,11)
m = cv2.dilate(m,kernel,iterations = 1)
show_image(m)

enter image description here

Contour detection:

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

total_area = np.prod(image_gray.shape)
max_area = 0
for cnt in contours:
    # Simplify contour
    perimeter = cv2.arcLength(cnt, True)
    approx = cv2.approxPolyDP(cnt, 0.03 * perimeter, True)
    area = cv2.contourArea(approx)

    # Shape is recrangular, so 4 points approximately and it's convex
    if (len(approx) == 4 and cv2.isContourConvex(approx) and max_area<area<total_area):
        max_area = cv2.contourArea(approx)
        quad_polygon = approx

img1 = image_original.copy()
img2 = image_original.copy()

cv2.polylines(img1,[quad_polygon],True,(0,255,0),10)
show_image(img1)
tl, br = get_rect(quad_polygon)
cv2.rectangle(img2, tl, br, (0,255,0), 10)
show_image(img2)

img1 img2

So you can see the approximate polygon and the corresponding rectangle, using which you can get your crop. I suggest you play around with median blur and morphological ops like erosion, dilation, opening, closing etc and see which set of operations suits your images the best; I can't really say what's good from just one image. You can crop using the top left and bottom right coordinates:

show_image(image_original[tl[1]:br[1],tl[0]:br[0],:])

enter image description here

Draw the square with a different color (e.g red) so it can be distinguishable from other writing and background. Then threshold it so you get a black and white image: the red line will be white in this image. Get the coordinates of white pixels: from this set, select only the two pairs (minX, minY)(maxX,maxY). They are the top-left and bottom-right points of the box (remember that in an image the 0,0 point is on the top left of the image) and you can use them to crop the image.

Related