Автоматическое обрезание изображения с помощью python/PIL
Может ли кто-нибудь помочь мне разобраться, что происходит в моем изображении с автоматическим обрезкой script? У меня есть png-изображение с большой прозрачной областью/пространством. Я хотел бы иметь возможность автоматически обрезать это пространство и оставить необходимое. Исходное изображение имеет квадрат холста, оптимально он будет прямоугольным, инкапсулируя только молекулу.
здесь исходное изображение:
![Original Image]()
Выполняя некоторые поисковые запросы, я столкнулся с кодом PIL/python, который, как сообщается, работал, однако в моих руках, при запуске кода ниже перегружает изображение.
import Image
import sys
image=Image.open('L_2d.png')
image.load()
imageSize = image.size
imageBox = image.getbbox()
imageComponents = image.split()
rgbImage = Image.new("RGB", imageSize, (0,0,0))
rgbImage.paste(image, mask=imageComponents[3])
croppedBox = rgbImage.getbbox()
print imageBox
print croppedBox
if imageBox != croppedBox:
cropped=image.crop(croppedBox)
print 'L_2d.png:', "Size:", imageSize, "New Size:",croppedBox
cropped.save('L_2d_cropped.png')
вывод следующий: ![script's output]()
Может ли кто-нибудь более знакомый с обработкой изображений /PLI помочь мне разобраться с проблемой?
Ответы
Ответ 1
Вы можете использовать numpy, преобразовать изображение в массив, найти все непустые столбцы и строки, а затем создать изображение из них:
import Image
import numpy as np
image=Image.open('L_2d.png')
image.load()
image_data = np.asarray(image)
image_data_bw = image_data.max(axis=2)
non_empty_columns = np.where(image_data_bw.max(axis=0)>0)[0]
non_empty_rows = np.where(image_data_bw.max(axis=1)>0)[0]
cropBox = (min(non_empty_rows), max(non_empty_rows), min(non_empty_columns), max(non_empty_columns))
image_data_new = image_data[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
new_image = Image.fromarray(image_data_new)
new_image.save('L_2d_cropped.png')
Результат выглядит как
![cropped image]()
Если что-то неясно, просто спросите.
Ответ 2
Для меня это работает как:
import Image
image=Image.open('L_2d.png')
imageBox = image.getbbox()
cropped=image.crop(imageBox)
cropped.save('L_2d_cropped.png')
При поиске границ с помощью mask=imageComponents[3]
вы выполняете поиск только по синему каналу.
Ответ 3
Я проверил большинство ответов, ответивших в этом посте, однако я получил свой собственный ответ. Я использовал анаконду python3.
from PIL import Image, ImageChops
def trim(im):
bg = Image.new(im.mode, im.size, im.getpixel((0,0)))
diff = ImageChops.difference(im, bg)
diff = ImageChops.add(diff, diff, 2.0, -100)
#Bounding box given as a 4-tuple defining the left, upper, right, and lower pixel coordinates.
#If the image is completely empty, this method returns None.
bbox = diff.getbbox()
if bbox:
return im.crop(bbox)
if __name__ == "__main__":
bg = Image.open("test.jpg") # The image to be cropped
new_im = trim(bg)
new_im.show()
Ответ 4
Здесь другая версия, используя pyvips.
Это немного странно: он смотрит на пиксель в (0, 0), предполагает, что это цвет фона, затем делает медианный фильтр и находит первую и последнюю строку и столбец, содержащие пиксель, который отличается от что более чем порог. Эта дополнительная обработка означает, что она также работает на фотографических или сжатых изображениях, где простая обрезка может быть сброшена шумовыми или компрессионными артефактами.
import sys
import pyvips
# An equivalent of ImageMagick -trim in libvips ... automatically remove
# "boring" image edges.
# We use .project to sum the rows and columns of a 0/255 mask image, the first
# non-zero row or column is the object edge. We make the mask image with an
# amount-differnt-from-background image plus a threshold.
im = pyvips.Image.new_from_file(sys.argv[1])
# find the value of the pixel at (0, 0) ... we will search for all pixels
# significantly different from this
background = im(0, 0)
# we need to smooth the image, subtract the background from every pixel, take
# the absolute value of the difference, then threshold
mask = (im.median(3) - background).abs() > 10
# sum mask rows and columns, then search for the first non-zero sum in each
# direction
columns, rows = mask.project()
# .profile() returns a pair (v-profile, h-profile)
left = columns.profile()[1].min()
right = columns.width - columns.fliphor().profile()[1].min()
top = rows.profile()[0].min()
bottom = rows.height - rows.flipver().profile()[0].min()
# and now crop the original image
im = im.crop(left, top, right - left, bottom - top)
im.write_to_file(sys.argv[2])
Здесь он работает на 8k x 8k пиксельном изображении NASA earth:
$ time ./trim.py /data/john/pics/city_lights_asia_night_8k.jpg x.jpg
real 0m1.868s
user 0m13.204s
sys 0m0.280s
peak memory: 100mb
До:
![Земля ночью перед урожаем]()
После:
![Земля после урожая]()
Там есть сообщение в блоге с более подробным обсуждением здесь.
Ответ 5
Совсем недавно появился этот пост и заметил, что библиотека PIL изменилась. Я повторно выполнил это с помощью openCV:
import cv2
def crop_im(im, padding=0.1):
"""
Takes cv2 image, im, and padding % as a float, padding,
and returns cropped image.
"""
bw = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
rows, cols = bw.shape
non_empty_columns = np.where(bw.min(axis=0)<255)[0]
non_empty_rows = np.where(bw.min(axis=1)<255)[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))
cropped = im[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
return cropped
im = cv2.imread('testimage.png')
cropped = crop_im(im)
cv2.imshow('', cropped)
cv2.waitKey(0)
Ответ 6
Это лучше, чем простой ответ, который работает на прозрачном фоне. С помощью mathematical morphology
мы можем заставить его работать на белом фоне (вместо прозрачного), используя следующий код:
from PIL import Image
from skimage.io import imread
from skimage.morphology import convex_hull_image
im = imread('L_2d.jpg')
plt.imshow(im)
plt.title('input image')
plt.show()
# create a binary image
im1 = 1 - rgb2gray(im)
threshold = 0.5
im1[im1 <= threshold] = 0
im1[im1 > threshold] = 1
chull = convex_hull_image(im1)
plt.imshow(chull)
plt.title('convex hull in the binary image')
plt.show()
imageBox = Image.fromarray((chull*255).astype(np.uint8)).getbbox()
cropped = Image.fromarray(im).crop(imageBox)
cropped.save('L_2d_cropped.jpg')
plt.imshow(cropped)
plt.show()
![enter image description here]()
Ответ 7
Я знаю, что этот пост старый, но по какой-то причине ни один из предложенных ответов не работал у меня. Поэтому я взломал свою версию из существующих ответов:
import Image
import numpy as np
import glob
import shutil
import os
grey_tolerance = 0.7 # (0,1) = crop (more,less)
f = 'test_image.png'
file,ext = os.path.splitext(f)
def get_cropped_line(non_empty_elms,tolerance,S):
if (sum(non_empty_elms) == 0):
cropBox = ()
else:
non_empty_min = non_empty_elms.argmax()
non_empty_max = S - non_empty_elms[::-1].argmax()+1
cropBox = (non_empty_min,non_empty_max)
return cropBox
def get_cropped_area(image_bw,tol):
max_val = image_bw.max()
tolerance = max_val*tol
non_empty_elms = (image_bw<=tolerance).astype(int)
S = non_empty_elms.shape
# Traverse rows
cropBox = [get_cropped_line(non_empty_elms[k,:],tolerance,S[1]) for k in range(0,S[0])]
cropBox = filter(None, cropBox)
xmin = [k[0] for k in cropBox]
xmax = [k[1] for k in cropBox]
# Traverse cols
cropBox = [get_cropped_line(non_empty_elms[:,k],tolerance,S[0]) for k in range(0,S[1])]
cropBox = filter(None, cropBox)
ymin = [k[0] for k in cropBox]
ymax = [k[1] for k in cropBox]
xmin = min(xmin)
xmax = max(xmax)
ymin = min(ymin)
ymax = max(ymax)
ymax = ymax-1 # Not sure why this is necessary, but it seems to be.
cropBox = (ymin, ymax-ymin, xmin, xmax-xmin)
return cropBox
def auto_crop(f,ext):
image=Image.open(f)
image.load()
image_data = np.asarray(image)
image_data_bw = image_data[:,:,0]+image_data[:,:,1]+image_data[:,:,2]
cropBox = get_cropped_area(image_data_bw,grey_tolerance)
image_data_new = image_data[cropBox[0]:cropBox[1]+1, cropBox[2]:cropBox[3]+1 , :]
new_image = Image.fromarray(image_data_new)
f_new = f.replace(ext,'')+'_cropped'+ext
new_image.save(f_new)