Reputation:
I think it should be a very simple problem, but I cannot find a solution or an effective keyword for search.
I just have this image.
The black edges are useless so that I want to cut them, only leaving the Windows icon (and the blue background).
I do not want to calculate the coordinate and the size of the Windows icon. GIMP and Photoshop have sort of autocrop function. OpenCV does not have one?
Upvotes: 58
Views: 67618
Reputation: 101
Adaptation of PIL code used Here in openCV, that is more general. it's way faster than PIL
def trim_opencv(im):
# sensitivity of the crop
threshold = 128
# Converts image to gray and does stuff described above
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
bg = np.full_like(gray, gray[0,0])
diff = abs(gray - bg) - threshold
_,thresh = cv2.threshold(diff,diff[0,0],255,cv2.THRESH_BINARY)
# finds bounding box and crops
x,y,w,h = cv2.boundingRect(thresh)
crop = im[y:y+h,x:x+w]
return crop
Upvotes: 0
Reputation: 3550
additional information on Abid Rahman K's answer:
cv2.boundingRect
can do the job without finding outer contour like below
_,thresh = cv2.threshold(gray,1,255,cv2.THRESH_BINARY)
x,y,w,h = cv2.boundingRect(thresh)
(this feature probably added after Abid's answer and runs FASTER)
Upvotes: 2
Reputation: 1873
I thought this answer is much more succinct:
def crop(image):
y_nonzero, x_nonzero, _ = np.nonzero(image)
return image[np.min(y_nonzero):np.max(y_nonzero), np.min(x_nonzero):np.max(x_nonzero)]
Upvotes: 25
Reputation: 423
Python Version 3.6
Crop images and insert into a 'CropedImages' folder
import cv2
import os
arr = os.listdir('./OriginalImages')
for itr in arr:
img = cv2.imread(itr)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
_,thresh = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)
_, contours, _ = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[0]
x,y,w,h = cv2.boundingRect(cnt)
crop = img[y:y+h,x:x+w]
cv2.imwrite('CropedImages/'+itr,crop)
Change the number 120 to other in 9th line and try for your images, It will work
Upvotes: 0
Reputation: 15355
In case it helps anyone, I went with this tweak of @wordsforthewise's replacement for a PIL-based solution:
bw = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
rows, cols = bw.shape
non_empty_columns = np.where(bw.max(axis=0) > 0)[0]
non_empty_rows = np.where(bw.max(axis=1) > 0)[0]
cropBox = (min(non_empty_rows) * (1 - padding),
min(max(non_empty_rows) * (1 + padding), rows),
min(non_empty_columns) * (1 - padding),
min(max(non_empty_columns) * (1 + padding), cols))
return img[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
(It's a tweak in that the original code expects to crop away a white background rather than a black one.)
Upvotes: 0
Reputation: 331
OK, so for completeness, I implemented each of the recommendations above, added an iterative version of the recursive algorithm (once corrected) and did a set of performance tests.
TLDR: Recursive is probably the best for the average case (but use the one below--the OP has a couple bugs), and the autocrop is the best for images you expect to be almost empty.
General findings: 1. The recursive algorithm above has a couple of off-by-1 bugs in it. Corrected version is below. 2. The cv2.findContours function has problems with non-rectangular images, and actually even trims some of the image off in various scenarios. I added a version which uses cv2.CHAIN_APPROX_NONE to see if it helps (it doesn't help). 3. The autocrop implementation is great for sparse images, but poor for dense ones, the inverse of the recursive/iterative algorithm.
import numpy as np
import cv2
def trim_recursive(frame):
if frame.shape[0] == 0:
return np.zeros((0,0,3))
# crop top
if not np.sum(frame[0]):
return trim_recursive(frame[1:])
# crop bottom
elif not np.sum(frame[-1]):
return trim_recursive(frame[:-1])
# crop left
elif not np.sum(frame[:, 0]):
return trim_recursive(frame[:, 1:])
# crop right
elif not np.sum(frame[:, -1]):
return trim_recursive(frame[:, :-1])
return frame
def trim_contours(frame):
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
_,thresh = cv2.threshold(gray,1,255,cv2.THRESH_BINARY)
_, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if len(contours) == 0:
return np.zeros((0,0,3))
cnt = contours[0]
x, y, w, h = cv2.boundingRect(cnt)
crop = frame[y:y + h, x:x + w]
return crop
def trim_contours_exact(frame):
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
_,thresh = cv2.threshold(gray,1,255,cv2.THRESH_BINARY)
_, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
if len(contours) == 0:
return np.zeros((0,0,3))
cnt = contours[0]
x, y, w, h = cv2.boundingRect(cnt)
crop = frame[y:y + h, x:x + w]
return crop
def trim_iterative(frame):
for start_y in range(1, frame.shape[0]):
if np.sum(frame[:start_y]) > 0:
start_y -= 1
break
if start_y == frame.shape[0]:
if len(frame.shape) == 2:
return np.zeros((0,0))
else:
return np.zeros((0,0,0))
for trim_bottom in range(1, frame.shape[0]):
if np.sum(frame[-trim_bottom:]) > 0:
break
for start_x in range(1, frame.shape[1]):
if np.sum(frame[:, :start_x]) > 0:
start_x -= 1
break
for trim_right in range(1, frame.shape[1]):
if np.sum(frame[:, -trim_right:]) > 0:
break
end_y = frame.shape[0] - trim_bottom + 1
end_x = frame.shape[1] - trim_right + 1
# print('iterative cropping x:{}, w:{}, y:{}, h:{}'.format(start_x, end_x - start_x, start_y, end_y - start_y))
return frame[start_y:end_y, start_x:end_x]
def autocrop(image, threshold=0):
"""Crops any edges below or equal to threshold
Crops blank image to 1x1.
Returns cropped image.
"""
if len(image.shape) == 3:
flatImage = np.max(image, 2)
else:
flatImage = image
assert len(flatImage.shape) == 2
rows = np.where(np.max(flatImage, 0) > threshold)[0]
if rows.size:
cols = np.where(np.max(flatImage, 1) > threshold)[0]
image = image[cols[0]: cols[-1] + 1, rows[0]: rows[-1] + 1]
else:
image = image[:1, :1]
return image
Then to test it, I made this simple function:
import datetime
import numpy as np
import random
ITERATIONS = 10000
def test_image(img):
orig_shape = img.shape
print ('original shape: {}'.format(orig_shape))
start_time = datetime.datetime.now()
for i in range(ITERATIONS):
recursive_img = trim_recursive(img)
print ('recursive shape: {}, took {} seconds'.format(recursive_img.shape, (datetime.datetime.now()-start_time).total_seconds()))
start_time = datetime.datetime.now()
for i in range(ITERATIONS):
contour_img = trim_contours(img)
print ('contour shape: {}, took {} seconds'.format(contour_img.shape, (datetime.datetime.now()-start_time).total_seconds()))
start_time = datetime.datetime.now()
for i in range(ITERATIONS):
exact_contour_img = trim_contours(img)
print ('exact contour shape: {}, took {} seconds'.format(exact_contour_img.shape, (datetime.datetime.now()-start_time).total_seconds()))
start_time = datetime.datetime.now()
for i in range(ITERATIONS):
iterative_img = trim_iterative(img)
print ('iterative shape: {}, took {} seconds'.format(iterative_img.shape, (datetime.datetime.now()-start_time).total_seconds()))
start_time = datetime.datetime.now()
for i in range(ITERATIONS):
auto_img = autocrop(img)
print ('autocrop shape: {}, took {} seconds'.format(auto_img.shape, (datetime.datetime.now()-start_time).total_seconds()))
def main():
orig_shape = (10,10,3)
print('Empty image--should be 0x0x3')
zero_img = np.zeros(orig_shape, dtype='uint8')
test_image(zero_img)
print('Small image--should be 1x1x3')
small_img = np.zeros(orig_shape, dtype='uint8')
small_img[3,3] = 1
test_image(small_img)
print('Medium image--should be 3x7x3')
med_img = np.zeros(orig_shape, dtype='uint8')
med_img[5:8, 2:9] = 1
test_image(med_img)
print('Random image--should be full image: 100x100')
lg_img = np.zeros((100,100,3), dtype='uint8')
for y in range (100):
for x in range(100):
lg_img[y,x, 0] = random.randint(0,255)
lg_img[y, x, 1] = random.randint(0, 255)
lg_img[y, x, 2] = random.randint(0, 255)
test_image(lg_img)
main()
...AND THE RESULTS...
Empty image--should be 0x0x3
original shape: (10, 10, 3)
recursive shape: (0, 0, 3), took 0.295851 seconds
contour shape: (0, 0, 3), took 0.048656 seconds
exact contour shape: (0, 0, 3), took 0.046273 seconds
iterative shape: (0, 0, 3), took 1.742498 seconds
autocrop shape: (1, 1, 3), took 0.093347 seconds
Small image--should be 1x1x3
original shape: (10, 10, 3)
recursive shape: (1, 1, 3), took 1.342977 seconds
contour shape: (0, 0, 3), took 0.048919 seconds
exact contour shape: (0, 0, 3), took 0.04683 seconds
iterative shape: (1, 1, 3), took 1.084258 seconds
autocrop shape: (1, 1, 3), took 0.140886 seconds
Medium image--should be 3x7x3
original shape: (10, 10, 3)
recursive shape: (3, 7, 3), took 0.610821 seconds
contour shape: (0, 0, 3), took 0.047263 seconds
exact contour shape: (0, 0, 3), took 0.046342 seconds
iterative shape: (3, 7, 3), took 0.696778 seconds
autocrop shape: (3, 7, 3), took 0.14493 seconds
Random image--should be full image: 100x100
original shape: (100, 100, 3)
recursive shape: (100, 100, 3), took 0.131619 seconds
contour shape: (98, 98, 3), took 0.285515 seconds
exact contour shape: (98, 98, 3), took 0.288365 seconds
iterative shape: (100, 100, 3), took 0.251708 seconds
autocrop shape: (100, 100, 3), took 1.280476 seconds
Upvotes: 8
Reputation: 3208
How about a slick little recursive function?
import cv2
import numpy as np
def trim(frame):
#crop top
if not np.sum(frame[0]):
return trim(frame[1:])
#crop bottom
elif not np.sum(frame[-1]):
return trim(frame[:-2])
#crop left
elif not np.sum(frame[:,0]):
return trim(frame[:,1:])
#crop right
elif not np.sum(frame[:,-1]):
return trim(frame[:,:-2])
return frame
Load and threshold the image to ensure the dark areas are black:
img = cv2.imread("path_to_image.png")
thold = (img>120)*img
Then call the recursive function
trimmedImage = trim(thold)
Upvotes: 2
Reputation: 2958
import numpy as np
def autocrop(image, threshold=0):
"""Crops any edges below or equal to threshold
Crops blank image to 1x1.
Returns cropped image.
"""
if len(image.shape) == 3:
flatImage = np.max(image, 2)
else:
flatImage = image
assert len(flatImage.shape) == 2
rows = np.where(np.max(flatImage, 0) > threshold)[0]
if rows.size:
cols = np.where(np.max(flatImage, 1) > threshold)[0]
image = image[cols[0]: cols[-1] + 1, rows[0]: rows[-1] + 1]
else:
image = image[:1, :1]
return image
Upvotes: 13
Reputation: 52646
I am not sure whether all your images are like this. But for this image, below is a simple python-opencv code to crop it.
first import libraries :
import cv2
import numpy as np
Read the image, convert it into grayscale, and make in binary image for threshold value of 1.
img = cv2.imread('sofwin.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
_,thresh = cv2.threshold(gray,1,255,cv2.THRESH_BINARY)
Now find contours in it. There will be only one object, so find bounding rectangle for it.
contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[0]
x,y,w,h = cv2.boundingRect(cnt)
Now crop the image, and save it into another file.
crop = img[y:y+h,x:x+w]
cv2.imwrite('sofwinres.png',crop)
Below is the result :
Upvotes: 81