2013-01-08 28 views
10

Qualcuno può aiutarmi a capire cosa sta succedendo nello script di ritaglio automatico della mia immagine? Ho un'immagine png con una grande area/spazio trasparente. Mi piacerebbe essere in grado di ritagliare automaticamente quello spazio e lasciare l'essenziale. L'immagine originale ha una tela quadrata, in modo ottimale sarebbe rettangolare, incapsulando solo la molecola.Ritagliare automaticamente un'immagine con python/PIL

ecco l'immagine originale: Original Image

Facendo qualche googling mi sono imbattuto in codice PIL/python che è stato segnalato a lavorare, però nelle mie mani, in esecuzione il codice qui sotto over-colture l'immagine.

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') 

l'output è questo: script's output

Qualcuno può più familiarità con l'elaborazione delle immagini/PLI può aiutarmi a capire il problema?

risposta

14

È possibile utilizzare NumPy, convertire l'immagine in array, trovare tutte le colonne e le righe non vuote e quindi creare un'immagine da questi:

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') 

Il risultato assomiglia cropped image

Semmai non è chiaro, basta chiedere.

+4

'(...) CropBox [2]: CropBox [3] 1,:]' <- +1 per questo sorriso :) Sono nuovo di Python ...: P – cubuspl42

+0

Questo il metodo funziona con Python3 se si importa 'Image' come' from PIL import Image' (avendo installato ['PILLOW'] (https://python-pillow.org/) per Python3). – ryanjdillon

+0

Funziona come un fascino per le immagini RGB e RGBA ma non funziona con le immagini in modalità P .. puoi consigliarlo? – user12345

25

Per me funziona come:

import Image 

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

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

Quando si cercano i confini da mask=imageComponents[3], di cercare solo dal canale blu.

+0

upvote, anche se il modo in cui numpy-find-all-empty-cols-rows è molto più interessante. –

2

È arrivato in questo post di recente e ho notato che la libreria PIL è cambiata. I re-implementato questo con 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) 
0

So che questo post è vecchio ma, per qualche ragione, nessuna delle risposte suggerite ha lavorato per me. Così ho inciso la mia versione da risposte esistenti:

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) 
2

Ecco un'altra versione utilizzando pyvips.

Questo è un po 'più elaborato: guarda il pixel a (0, 0), presuppone che sia il colore di sfondo, quindi fa un filtro mediano e trova la prima e l'ultima riga e colonna contenente un pixel che differisce da quello di oltre una soglia. Questa elaborazione aggiuntiva significa che funziona anche su immagini fotografiche o compresse, in cui un semplice taglio può essere eliminato da rumori o artefatti da compressione.

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]) 

Qui è in esecuzione su un 8k x 8k pixel NASA earth image:

$ 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 

Prima:

Earth at night before crop

Dopo:

Earth after crop

C'è un blog post with some more discussion here.

1

Ho testato la maggior parte delle risposte in questo post, tuttavia, mi è stata data la mia risposta. Ho usato anaconda 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 __name__ == "__main__": 
    bg = Image.open("test.jpg") # The image to be cropped 
    new_im = trim(bg) 
    new_im.show() 
Problemi correlati