admin管理员组文章数量:1417434
I am trying to identify the empty spaces in the image and if there is no image, then I would like to crop it by eliminating the spaces. Just like in the images below.
-->
I would be grateful for your help. Thanks in advance!
I was using the following code, but was not really working.
import cv2
import numpy as np
def crop_empty_spaces_refined(image_path, threshold_percentage=0.01): image = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
if image is None:
print(f"Error: Could not read image at {image_path}")
return None
if image.shape[2] == 4: # RGBA image
gray = image[:, :, 3] # Use alpha channel
else:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)
kernel = np.ones((3, 3), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
image_area = image.shape[0] * image.shape[1]
min_contour_area = image_area * threshold_percentage
valid_contours = [cnt for cnt in contours if cv2.contourArea(cnt) >= min_contour_area]
if valid_contours:
# ***Corrected Bounding Box Calculation***
x_coords = []
y_coords = []
for cnt in valid_contours:
x, y, w, h = cv2.boundingRect(cnt)
x_coords.extend([x, x + w]) # Add both start and end x
y_coords.extend([y, y + h]) # Add both start and end y
x_min = min(x_coords)
y_min = min(y_coords)
x_max = max(x_coords)
y_max = max(y_coords)
cropped_image = image[y_min:y_max, x_min:x_max]
return cropped_image
else:
print("No valid contours found after filtering. Returning original image.")
return image
else:
return image
image_path = '/mnt/data/Untitled.png' # file path cropped_image = crop_empty_spaces_refined(image_path, threshold_percentage=0.0001)
if cropped_image is not None: cv2.imwrite('/mnt/data/cropped_output.png', cropped_image) print("Image Cropped and saved") else: print("Could not crop image")
I am trying to identify the empty spaces in the image and if there is no image, then I would like to crop it by eliminating the spaces. Just like in the images below.
-->
I would be grateful for your help. Thanks in advance!
I was using the following code, but was not really working.
import cv2
import numpy as np
def crop_empty_spaces_refined(image_path, threshold_percentage=0.01): image = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
if image is None:
print(f"Error: Could not read image at {image_path}")
return None
if image.shape[2] == 4: # RGBA image
gray = image[:, :, 3] # Use alpha channel
else:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)
kernel = np.ones((3, 3), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
image_area = image.shape[0] * image.shape[1]
min_contour_area = image_area * threshold_percentage
valid_contours = [cnt for cnt in contours if cv2.contourArea(cnt) >= min_contour_area]
if valid_contours:
# ***Corrected Bounding Box Calculation***
x_coords = []
y_coords = []
for cnt in valid_contours:
x, y, w, h = cv2.boundingRect(cnt)
x_coords.extend([x, x + w]) # Add both start and end x
y_coords.extend([y, y + h]) # Add both start and end y
x_min = min(x_coords)
y_min = min(y_coords)
x_max = max(x_coords)
y_max = max(y_coords)
cropped_image = image[y_min:y_max, x_min:x_max]
return cropped_image
else:
print("No valid contours found after filtering. Returning original image.")
return image
else:
return image
image_path = '/mnt/data/Untitled.png' # file path cropped_image = crop_empty_spaces_refined(image_path, threshold_percentage=0.0001)
if cropped_image is not None: cv2.imwrite('/mnt/data/cropped_output.png', cropped_image) print("Image Cropped and saved") else: print("Could not crop image")
Share Improve this question edited Jan 31 at 12:54 Ushay asked Jan 31 at 11:01 UshayUshay 1259 bronze badges 03 Answers
Reset to default 3Approach:
- threshold -> obtain mask
- use
boundingRect()
on the mask - crop
im = cv.imread("Cb768fyr.jpg")
gray = cv.cvtColor(im, cv.COLOR_BGR2GRAY)
th = 240 # 255 won't do, the image's background isn't perfectly white
(th, mask) = cv.threshold(gray, th, 255, cv.THRESH_BINARY_INV)
(x, y, w, h) = cv.boundingRect(mask)
pad = 0 # increase to give it some border
cropped = im[y-pad:y+h+pad, x-pad:x+w+pad]
Why threshold at all? Because cv.boundingRect()
would otherwise treat all non-zero pixels as "true", i.e. the background would be considered foreground.
Why threshold with something other than 255? The background isn't perfectly white, due to the source image having been compressed lossily. If you did, that would be the result:
If you wanted to replace cv.boundingRect()
, you can do it like this:
- max-reduce mask along each axis in turn
- find first and last index of positive values
xproj = np.max(mask, axis=1) # collapse X, have Y
ymin = np.argmax(xproj)
ymax = len(xproj) - np.argmax(xproj[::-1])
print(f"{ymin=}, {ymax=}")
yproj = np.max(mask, axis=0)
xmin = np.argmax(yproj)
xmax = len(yproj) - np.argmax(yproj[::-1])
print(f"{xmin=}, {xmax=}")
cropped = im[ymin-pad:ymax+pad, xmin-pad:xmax+pad]
This could also use np.argwhere()
. I won't bother comparing these two approaches since cv.boundingRect()
does the job already.
The findContours
approach will pick any connected component, not all of them. This means it could sometimes pick the triad (bottom left) or text (top left), and entirely discard most of the image.
You could fix that by slapping a convex hull on all the contours, but you'd still have to call boundingRect()
anyway. So, all the contour stuff is wasted effort.
Assuming you want to also get rid of the small structures in the upper left and lower left corners, you can do the following:
- Use
cv2.connectedComponentsWithStats()
to find all connected components, together with their statistics (area, bounding box, center; see this answer for and explanation of the returned results). - Get mask and rectangle of component with largest area (ignoring the background component at index 0).
- Fill everything that does not belong to the component's mask with white.
- Crop with component's rectangle.
Code:
import cv2
in_path = "stackoverflow/car.jpg" # TODO: Adjust as necessary
out_path = "stackoverflow/cropped.png" # TODO: Adjust as necessary
image = cv2.imread(in_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, mask = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)
# Following https://stackoverflow/a/35854198/7395592
_, labels, stats, _ = cv2.connectedComponentsWithStats(mask)
# Ignore label 0, which is the background
stats = stats[1:]
# Get mask and rectangle (columns 0–3) for region with largest area (column 4)
row_idx_largest = stats[:, -1].argmax()
mask_largest = (labels == row_idx_largest + 1) # +1: we cropped background row
x, y, w, h, _ = stats[row_idx_largest]
# Fill everything outside the largest region with white, then crop
image[~mask_largest] = 255
cropped_image = image[y:y+h, x:x+w]
cv2.imwrite(out_path, cropped_image)
Resulting image:
You can solve the problem by doing sort of "raycasting". To find the top edge you would measure where the topmost non-white pixel is in the first column. Then repeat for the second column and third etc.
The smallest of these values (i.e. the topmost) defines the top edge. You then repeat the process for the other sides.
Below is sample code demonstrating the idea.
import cv2
import numpy as np
def crop_empty_spaces(image_path):
# Load the image
image = cv2.imread(image_path)
# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Apply a binary threshold to identify non-empty (non-white) areas
_, thresh = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)
# Find contours
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Get the bounding box of the largest contour
if contours:
x, y, w, h = cv2.boundingRect(contours[0])
cropped_image = image[y:y+h, x:x+w]
return cropped_image
else:
return image # Return original if no contours found
# Example usage
image_path = '/mnt/data/Untitled.png' # Update with your file path
cropped_image = crop_empty_spaces(image_path)
cv2.imwrite('/mnt/data/cropped_output.png', cropped_image)
本文标签: pythoncropping the image by removing the white spacesStack Overflow
版权声明:本文标题:python - cropping the image by removing the white spaces - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1745266231a2650618.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论