2012-11-24 15 views
23

Penso che dovrebbe essere un problema molto semplice, ma non riesco a trovare una soluzione o una parola chiave efficace per la ricerca.Ritaglia bordi neri con OpenCV

Ho solo questa immagine.

the original image

I bordi neri sono inutili in modo che voglio tagliare loro, lasciando soltanto l'icona di Windows (e lo sfondo blu).

Non voglio calcolare le coordinate e la dimensione dell'icona di Windows. GIMP e Photoshop hanno una sorta di funzione autocrop. OpenCV non ne ha uno?

risposta

31

Non sono sicuro che tutte le immagini siano così. Ma per questa immagine, sotto c'è un semplice codice python-opencv per ritagliarlo.

prime librerie di importazione:

import cv2 
import numpy as np 

leggere l'immagine, lo trasformano in scala di grigi, e rendere l'immagine binaria in per il valore soglia del 1.

img = cv2.imread('sofwin.png') 
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) 
_,thresh = cv2.threshold(gray,1,255,cv2.THRESH_BINARY) 

Ora trova contorni in esso. Ci sarà un solo oggetto, quindi trova il rettangolo di limitazione per esso.

contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) 
cnt = contours[0] 
x,y,w,h = cv2.boundingRect(cnt) 

Ora ritaglia l'immagine e salvala in un altro file.

crop = img[y:y+h,x:x+w] 
cv2.imwrite('sofwinres.png',crop) 

seguenti è il risultato:

enter image description here

+0

Grazie. Vuoi dire che OpenCV non fornisce una funzione consolidata per tagliare i bordi. – Gqqnbig

+2

+1 Bella risposta. E sì, @LoveRight, questo è esattamente ciò che intende. Un altro approccio per affrontare questo problema è stato [discusso qui] (http://stackoverflow.com/a/10317919/176769). – karlphillip

+0

Voglio solo sottolineare che puoi giocare un po 'con la soglia se non fa proprio quello che vuoi, ho dovuto alzare il 1 a circa 10. '' '_, thresh = cv2.threshold (grigio, 10,255, cv2.THRESH_BINARY) '' ' – deweydb

7
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 
+1

perché rimuovi il canale dei colori? '' 'flatImage = np.max (image, 2)' '' –

+0

A causa dell'uso di un valore di soglia grigio. Ci sono più implementazioni adeguate come al solito, questo è solo uno di questi. – fviktor

0

Che ne dite di un po 'di funzione ricorsiva chiazza di petrolio?

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 

di carico e la soglia l'immagine al fine di garantire le aree scure sono neri:

img = cv2.imread("path_to_image.png") 
thold = (img>120)*img 

quindi chiamare la funzione ricorsiva

trimmedImage = trim(thold) 
2

OK, quindi per completezza, ho implementato ciascuno dei raccomandazioni sopra, ha aggiunto una versione iterativa dell'algoritmo ricorsivo (una volta corretto) e ha fatto una serie di test delle prestazioni.

TLDR: Recursive è probabilmente il migliore per il caso medio (ma usa quello sotto - l'OP ha un paio di bug), e l'autocrop è il migliore per le immagini che ti aspetti di essere quasi vuoto.

Risultati generali: 1. L'algoritmo ricorsivo di cui sopra ha un paio di bug fuori-da-1 in esso. La versione corretta è sotto. 2. La funzione cv2.findContours ha problemi con le immagini non rettangolari e in realtà riduce anche parte dell'immagine in diversi scenari. Ho aggiunto una versione che utilizza cv2.CHAIN_APPROX_NONE per vedere se aiuta (non aiuta). 3. L'implementazione di AutoProp è ottima per immagini sparse, ma povere per quelle dense, l'inverso dell'algoritmo ricorsivo/iterativo.

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 

Poi di provarlo, ho fatto questa semplice funzione:

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

... EI RISULTATI ...

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 
0

In caso aiuta nessuno, sono andato con questo tweak di @ wordsforthewise replacement per una soluzione basata su PIL:

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 , :] 

(È un ritocco in quanto il codice originale prevede di ritagliare uno sfondo bianco anziché uno nero.)

Problemi correlati