it-swarm.com.ru

Автоматическая обрезка изображения с помощью Python / PIL

Может ли кто-нибудь помочь мне понять, что происходит в сценарии автоматической обрезки изображений? У меня есть изображение 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, может помочь мне разобраться в проблеме?

14
dimka

Вы можете использовать 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

Если что-то неясно, просто спросите.

17
Thorsten Kranz

Для меня это работает как:

import Image

image=Image.open('L_2d.png')

imageBox = image.getbbox()
cropped=image.crop(imageBox)
cropped.save('L_2d_cropped.png')

Когда вы ищете границы по mask=imageComponents[3], вы ищете только по синему каналу.

28
sneawo

Я проверил большинство ответов, ответивших в этом посте, однако я получил свой собственный ответ. Я использовал анаконду 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)
    bbox = diff.getbbox()
    if bbox:
        return im.crop(bbox)

if __== "__main__":
    bg = Image.open("test.jpg") # The image to be cropped
    new_im = trim(bg)
    new_im.show()
4
neouyghur

Наткнулся на этот пост недавно и заметил, что библиотека 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)
2
wordsforthewise

Вот еще одна версия, использующая pyvips

Это немного интереснее: он смотрит на пиксель в точке (0, 0), предполагает, что это цвет фона, затем выполняет медианный фильтр и находит первую и последнюю строку и столбец, содержащие пиксель, который отличается от пикселя более чем порог. Эта дополнительная обработка означает, что она также работает на фотографических или сжатых изображениях, где простая обрезка может быть отброшена шумом или артефактами сжатия. 

import sys
import pyvips

# An equivalent of ImageMagick's -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 пикселей :

$ 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

До:

 Earth at night before crop

После:

 Earth after crop

Здесь есть сообщение в блоге с дальнейшим обсуждением здесь

2
jcupitt

Я знаю, что этот пост старый, но почему-то ни один из предложенных ответов не сработал для меня. Поэтому я взломал свою версию из существующих ответов:

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)
0
Charles

Это улучшение по сравнению с ответом snew, которое работает на прозрачном фоне. С 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 enter image description here enter image description here

0
Sandipan Dey