Come rilevare un albero di Natale? [chiuso]


382

Quali tecniche di elaborazione delle immagini potrebbero essere utilizzate per implementare un'applicazione che rileva gli alberi di Natale visualizzati nelle seguenti immagini?

Sto cercando soluzioni che funzioneranno su tutte queste immagini. Pertanto, gli approcci che richiedono l'addestramento di classificatori a cascata haar o la corrispondenza dei modelli non sono molto interessanti.

Sto cercando qualcosa che possa essere scritto in qualsiasi linguaggio di programmazione, purché utilizzi solo tecnologie Open Source . La soluzione deve essere testata con le immagini condivise su questa domanda. Ci sono 6 immagini di input e la risposta dovrebbe visualizzare i risultati dell'elaborazione di ciascuna di esse. Infine, per ogni immagine in uscita devono essere tracciate delle linee rosse per circondare l'albero rilevato.

Come faresti a rilevare programmaticamente gli alberi in queste immagini?


3
È consentito utilizzare alcune delle immagini per la formazione o tutte le immagini fornite devono essere utilizzate per la convalida? Ad ogni modo, bella competizione: D
Hannes Ovrén il

7
@karlphillip, vuoi che usiamo queste immagini per i test e altre immagini per l'allenamento? È solo che non è chiaro quale sia il set di allenamento.
GilLevi,

16
@karlphillip: Il mio consiglio: eliminare il requisito "open source". Non importa davvero quale lingua / framework usi. Gli algoritmi di elaborazione delle immagini / visione artificiale sono indipendenti dal linguaggio, quindi se riesci a scriverlo in MATLAB, puoi certamente farlo OpenCV o qualsiasi altro framework che preferisci ... Inoltre non sono ancora chiaro cosa consideri formazione / test delle immagini !
Amro,

2
@karlphillip grazie per aver mobilitato tutti noi per contribuire a questa tua "ricerca"! È stata una grande opportunità per passare alcune ore in modo produttivo, ma soprattutto, per vedere quanti diversi approcci possono essere trovati per un singolo problema ... Spero che lo faccia di nuovo per il 1 ° gennaio (forse una 'slitta di La sfida di Babbo Natale? ;-))
sepdek il

2
OK, ho riformulato la domanda per rimuovere gli elementi della competizione. Penso che dovrebbe consentirgli di resistere da solo.
Brad Larson

Risposte:


184

Ho un approccio che penso sia interessante e un po 'diverso dal resto. La differenza principale nel mio approccio, rispetto ad alcuni degli altri, sta nel modo in cui viene eseguita la fase di segmentazione delle immagini: ho usato l' algoritmo di clustering DBSCAN dal scikit-learn di Python; è ottimizzato per trovare forme alquanto amorfe che potrebbero non avere necessariamente un singolo centroide chiaro.

Al livello più alto, il mio approccio è abbastanza semplice e può essere suddiviso in circa 3 passaggi. Per prima cosa applico una soglia (o effettivamente, la "o" logica di due soglie separate e distinte). Come con molte altre risposte, ho ipotizzato che l'albero di Natale sarebbe stato uno degli oggetti più luminosi nella scena, quindi la prima soglia è solo un semplice test di luminosità monocromatico; tutti i pixel con valori superiori a 220 su una scala 0-255 (dove il nero è 0 e il bianco è 255) vengono salvati in un'immagine binaria in bianco e nero. La seconda soglia cerca di cercare le luci rosse e gialle, che sono particolarmente prominenti negli alberi in alto a sinistra e in basso a destra delle sei immagini, e si distinguono bene sullo sfondo blu-verde che è prevalente nella maggior parte delle foto. Converto l'immagine rgb in spazio hsv, e richiede che la tonalità sia inferiore a 0,2 su una scala 0,0-1,0 (corrispondente approssimativamente al bordo tra giallo e verde) o maggiore di 0,95 (corrispondente al bordo tra viola e rosso) e inoltre ho bisogno di colori brillanti e saturi: la saturazione e il valore devono essere entrambi superiori a 0,7. I risultati delle due procedure di soglia sono logicamente "o" uniti e la matrice risultante di immagini binarie in bianco e nero è mostrata di seguito:

Alberi di Natale, dopo soglia su HSV e luminosità monocromatica

Puoi vedere chiaramente che ogni immagine ha un grande gruppo di pixel che corrisponde all'incirca alla posizione di ciascun albero, inoltre alcune immagini hanno anche alcuni altri piccoli gruppi corrispondenti alle luci nelle finestre di alcuni edifici o a un scena di sfondo all'orizzonte. Il passaggio successivo consiste nel far riconoscere al computer che si tratta di cluster separati ed etichettare correttamente ciascun pixel con un numero ID appartenenza al cluster.

Per questo compito ho scelto DBSCAN . Esiste un discreto confronto visivo del comportamento tipico di DBSCAN, rispetto ad altri algoritmi di clustering, disponibile qui . Come ho detto prima, funziona bene con forme amorfe. L'output di DBSCAN, con ogni cluster tracciato in un colore diverso, è mostrato qui:

Output del cluster DBSCAN

Ci sono alcune cose da tenere presente quando si guarda questo risultato. Il primo è che DBSCAN richiede all'utente di impostare un parametro di "prossimità" al fine di regolare il suo comportamento, che controlla efficacemente quanto deve essere separata una coppia di punti affinché l'algoritmo possa dichiarare un nuovo cluster separato anziché agglomerare un punto di test su un cluster già preesistente. Ho impostato questo valore su 0,04 volte la dimensione lungo la diagonale di ogni immagine. Poiché le dimensioni delle immagini variano da circa VGA a circa HD 1080, questo tipo di definizione relativa alla scala è fondamentale.

Un altro punto degno di nota è che l'algoritmo DBSCAN, come implementato in scikit-learn, ha limiti di memoria che sono abbastanza impegnativi per alcune delle immagini più grandi in questo esempio. Pertanto, per alcune delle immagini più grandi, in realtà ho dovuto "decimare" (ovvero conservare solo ogni 3 ° o 4 ° pixel e rilasciare gli altri) ogni cluster per rimanere entro questo limite. Come risultato di questo processo di abbattimento, i singoli pixel radi rimanenti sono difficili da vedere su alcune delle immagini più grandi. Pertanto, solo a scopo di visualizzazione, i pixel con codice colore nelle immagini sopra sono stati effettivamente "dilatati" solo leggermente in modo da risaltare meglio. È un'operazione puramente cosmetica per il bene della narrativa; anche se ci sono commenti che menzionano questa dilatazione nel mio codice,

Una volta identificati ed etichettati i cluster, il terzo e ultimo passaggio è semplice: prendo semplicemente il cluster più grande in ogni immagine (in questo caso, ho scelto di misurare "dimensioni" in termini di numero totale di pixel membri, anche se si potrebbe hanno usato altrettanto facilmente un qualche tipo di metrica che misura l'estensione fisica) e calcolano lo scafo convesso per quel cluster. Lo scafo convesso diventa quindi il bordo dell'albero. I sei scafi convessi calcolati con questo metodo sono indicati di seguito in rosso:

Alberi di Natale con i loro bordi calcolati

Il codice sorgente è scritto per Python 2.7.6 e dipende da numpy , scipy , matplotlib e scikit-learn . L'ho diviso in due parti. La prima parte è responsabile dell'elaborazione effettiva dell'immagine:

from PIL import Image
import numpy as np
import scipy as sp
import matplotlib.colors as colors
from sklearn.cluster import DBSCAN
from math import ceil, sqrt

"""
Inputs:

    rgbimg:         [M,N,3] numpy array containing (uint, 0-255) color image

    hueleftthr:     Scalar constant to select maximum allowed hue in the
                    yellow-green region

    huerightthr:    Scalar constant to select minimum allowed hue in the
                    blue-purple region

    satthr:         Scalar constant to select minimum allowed saturation

    valthr:         Scalar constant to select minimum allowed value

    monothr:        Scalar constant to select minimum allowed monochrome
                    brightness

    maxpoints:      Scalar constant maximum number of pixels to forward to
                    the DBSCAN clustering algorithm

    proxthresh:     Proximity threshold to use for DBSCAN, as a fraction of
                    the diagonal size of the image

Outputs:

    borderseg:      [K,2,2] Nested list containing K pairs of x- and y- pixel
                    values for drawing the tree border

    X:              [P,2] List of pixels that passed the threshold step

    labels:         [Q,2] List of cluster labels for points in Xslice (see
                    below)

    Xslice:         [Q,2] Reduced list of pixels to be passed to DBSCAN

"""

def findtree(rgbimg, hueleftthr=0.2, huerightthr=0.95, satthr=0.7, 
             valthr=0.7, monothr=220, maxpoints=5000, proxthresh=0.04):

    # Convert rgb image to monochrome for
    gryimg = np.asarray(Image.fromarray(rgbimg).convert('L'))
    # Convert rgb image (uint, 0-255) to hsv (float, 0.0-1.0)
    hsvimg = colors.rgb_to_hsv(rgbimg.astype(float)/255)

    # Initialize binary thresholded image
    binimg = np.zeros((rgbimg.shape[0], rgbimg.shape[1]))
    # Find pixels with hue<0.2 or hue>0.95 (red or yellow) and saturation/value
    # both greater than 0.7 (saturated and bright)--tends to coincide with
    # ornamental lights on trees in some of the images
    boolidx = np.logical_and(
                np.logical_and(
                  np.logical_or((hsvimg[:,:,0] < hueleftthr),
                                (hsvimg[:,:,0] > huerightthr)),
                                (hsvimg[:,:,1] > satthr)),
                                (hsvimg[:,:,2] > valthr))
    # Find pixels that meet hsv criterion
    binimg[np.where(boolidx)] = 255
    # Add pixels that meet grayscale brightness criterion
    binimg[np.where(gryimg > monothr)] = 255

    # Prepare thresholded points for DBSCAN clustering algorithm
    X = np.transpose(np.where(binimg == 255))
    Xslice = X
    nsample = len(Xslice)
    if nsample > maxpoints:
        # Make sure number of points does not exceed DBSCAN maximum capacity
        Xslice = X[range(0,nsample,int(ceil(float(nsample)/maxpoints)))]

    # Translate DBSCAN proximity threshold to units of pixels and run DBSCAN
    pixproxthr = proxthresh * sqrt(binimg.shape[0]**2 + binimg.shape[1]**2)
    db = DBSCAN(eps=pixproxthr, min_samples=10).fit(Xslice)
    labels = db.labels_.astype(int)

    # Find the largest cluster (i.e., with most points) and obtain convex hull   
    unique_labels = set(labels)
    maxclustpt = 0
    for k in unique_labels:
        class_members = [index[0] for index in np.argwhere(labels == k)]
        if len(class_members) > maxclustpt:
            points = Xslice[class_members]
            hull = sp.spatial.ConvexHull(points)
            maxclustpt = len(class_members)
            borderseg = [[points[simplex,0], points[simplex,1]] for simplex
                          in hull.simplices]

    return borderseg, X, labels, Xslice

e la seconda parte è uno script a livello di utente che chiama il primo file e genera tutti i grafici sopra:

#!/usr/bin/env python

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from findtree import findtree

# Image files to process
fname = ['nmzwj.png', 'aVZhC.png', '2K9EF.png',
         'YowlH.png', '2y4o5.png', 'FWhSP.png']

# Initialize figures
fgsz = (16,7)        
figthresh = plt.figure(figsize=fgsz, facecolor='w')
figclust  = plt.figure(figsize=fgsz, facecolor='w')
figcltwo  = plt.figure(figsize=fgsz, facecolor='w')
figborder = plt.figure(figsize=fgsz, facecolor='w')
figthresh.canvas.set_window_title('Thresholded HSV and Monochrome Brightness')
figclust.canvas.set_window_title('DBSCAN Clusters (Raw Pixel Output)')
figcltwo.canvas.set_window_title('DBSCAN Clusters (Slightly Dilated for Display)')
figborder.canvas.set_window_title('Trees with Borders')

for ii, name in zip(range(len(fname)), fname):
    # Open the file and convert to rgb image
    rgbimg = np.asarray(Image.open(name))

    # Get the tree borders as well as a bunch of other intermediate values
    # that will be used to illustrate how the algorithm works
    borderseg, X, labels, Xslice = findtree(rgbimg)

    # Display thresholded images
    axthresh = figthresh.add_subplot(2,3,ii+1)
    axthresh.set_xticks([])
    axthresh.set_yticks([])
    binimg = np.zeros((rgbimg.shape[0], rgbimg.shape[1]))
    for v, h in X:
        binimg[v,h] = 255
    axthresh.imshow(binimg, interpolation='nearest', cmap='Greys')

    # Display color-coded clusters
    axclust = figclust.add_subplot(2,3,ii+1) # Raw version
    axclust.set_xticks([])
    axclust.set_yticks([])
    axcltwo = figcltwo.add_subplot(2,3,ii+1) # Dilated slightly for display only
    axcltwo.set_xticks([])
    axcltwo.set_yticks([])
    axcltwo.imshow(binimg, interpolation='nearest', cmap='Greys')
    clustimg = np.ones(rgbimg.shape)    
    unique_labels = set(labels)
    # Generate a unique color for each cluster 
    plcol = cm.rainbow_r(np.linspace(0, 1, len(unique_labels)))
    for lbl, pix in zip(labels, Xslice):
        for col, unqlbl in zip(plcol, unique_labels):
            if lbl == unqlbl:
                # Cluster label of -1 indicates no cluster membership;
                # override default color with black
                if lbl == -1:
                    col = [0.0, 0.0, 0.0, 1.0]
                # Raw version
                for ij in range(3):
                    clustimg[pix[0],pix[1],ij] = col[ij]
                # Dilated just for display
                axcltwo.plot(pix[1], pix[0], 'o', markerfacecolor=col, 
                    markersize=1, markeredgecolor=col)
    axclust.imshow(clustimg)
    axcltwo.set_xlim(0, binimg.shape[1]-1)
    axcltwo.set_ylim(binimg.shape[0], -1)

    # Plot original images with read borders around the trees
    axborder = figborder.add_subplot(2,3,ii+1)
    axborder.set_axis_off()
    axborder.imshow(rgbimg, interpolation='nearest')
    for vseg, hseg in borderseg:
        axborder.plot(hseg, vseg, 'r-', lw=3)
    axborder.set_xlim(0, binimg.shape[1]-1)
    axborder.set_ylim(binimg.shape[0], -1)

plt.show()

La soluzione di @lennon310 è il clustering. (k-
medie

1
@stachyra Ho anche pensato a questo approccio prima di proporne di più semplici. Penso che questo abbia un grande potenziale per essere esteso e generalizzato per produrre buoni risultati anche in altri casi. Potresti sperimentare reti neurali per il clustering. Qualcosa come una SOM o un gas neurale farebbe un lavoro eccellente. Tuttavia, ottima proposta e complimenti da parte mia!
Sepdek,

4
@Faust e Ryan Carlson: grazie ragazzi! Sì, sono d'accordo sul fatto che il sistema di votazione, benché funzioni bene per giudicare tra 2 o 3 risposte brevi, tutte presentate entro poche ore l'una dall'altra, ha seri pregiudizi quando si tratta di concorsi con risposte lunghe che si svolgono per lunghi periodi di tempo . Per prima cosa, i primi invii iniziano ad accumulare voti prima che quelli successivi siano persino disponibili per la revisione pubblica. E se le risposte sono tutte lunghe, non appena si stabilisce un vantaggio modesto, c'è spesso un "effetto carrozzone" poiché le persone votano solo il primo senza preoccuparsi di leggere il resto.
Stachyra,

2
@stachyra grande amico delle notizie! I più calorosi complimenti e che questo possa segnare un inizio per il tuo nuovo anno!
Sepdek,

1
@ lennon310: Non ho ancora provato un filtro massimo di rilevamento locale su questo problema, ma se si desidera esplorare da soli, SciPy include questo . Il mio codice sorgente Python per questo progetto era così breve che sono stato effettivamente in grado di pubblicarlo al 100%; letteralmente tutto ciò che dovresti fare è copiare e incollare i miei due frammenti di codice in file .py separati e quindi sostituire una chiamata scipy.ndimage.filters.maximum_filter()nello stesso posto in cui avevo usato una soglia.
Stachyra,

145

NOTA EDIT: Ho modificato questo post per (i) elaborare ogni immagine dell'albero singolarmente, come richiesto nei requisiti, (ii) per considerare sia la luminosità che la forma dell'oggetto al fine di migliorare la qualità del risultato.


Di seguito viene presentato un approccio che prende in considerazione la luminosità e la forma dell'oggetto. In altre parole, cerca oggetti con una forma triangolare e con una luminosità significativa. È stato implementato in Java, utilizzando il framework di elaborazione delle immagini Marvin .

Il primo passo è il limite di colore. L'obiettivo qui è focalizzare l'analisi su oggetti con luminosità significativa.

immagini di output:

codice sorgente:

public class ChristmasTree {

private MarvinImagePlugin fill = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.fill.boundaryFill");
private MarvinImagePlugin threshold = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.thresholding");
private MarvinImagePlugin invert = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.invert");
private MarvinImagePlugin dilation = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.morphological.dilation");

public ChristmasTree(){
    MarvinImage tree;

    // Iterate each image
    for(int i=1; i<=6; i++){
        tree = MarvinImageIO.loadImage("./res/trees/tree"+i+".png");

        // 1. Threshold
        threshold.setAttribute("threshold", 200);
        threshold.process(tree.clone(), tree);
    }
}
public static void main(String[] args) {
    new ChristmasTree();
}
}

Nel secondo passaggio, i punti più luminosi nell'immagine sono dilatati per formare le forme. Il risultato di questo processo è la probabile forma degli oggetti con una luminosità significativa. Applicando la segmentazione del riempimento, vengono rilevate forme disconnesse.

immagini di output:

codice sorgente:

public class ChristmasTree {

private MarvinImagePlugin fill = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.fill.boundaryFill");
private MarvinImagePlugin threshold = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.thresholding");
private MarvinImagePlugin invert = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.invert");
private MarvinImagePlugin dilation = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.morphological.dilation");

public ChristmasTree(){
    MarvinImage tree;

    // Iterate each image
    for(int i=1; i<=6; i++){
        tree = MarvinImageIO.loadImage("./res/trees/tree"+i+".png");

        // 1. Threshold
        threshold.setAttribute("threshold", 200);
        threshold.process(tree.clone(), tree);

        // 2. Dilate
        invert.process(tree.clone(), tree);
        tree = MarvinColorModelConverter.rgbToBinary(tree, 127);
        MarvinImageIO.saveImage(tree, "./res/trees/new/tree_"+i+"threshold.png");
        dilation.setAttribute("matrix", MarvinMath.getTrueMatrix(50, 50));
        dilation.process(tree.clone(), tree);
        MarvinImageIO.saveImage(tree, "./res/trees/new/tree_"+1+"_dilation.png");
        tree = MarvinColorModelConverter.binaryToRgb(tree);

        // 3. Segment shapes
        MarvinImage trees2 = tree.clone();
        fill(tree, trees2);
        MarvinImageIO.saveImage(trees2, "./res/trees/new/tree_"+i+"_fill.png");
}

private void fill(MarvinImage imageIn, MarvinImage imageOut){
    boolean found;
    int color= 0xFFFF0000;

    while(true){
        found=false;

        Outerloop:
        for(int y=0; y<imageIn.getHeight(); y++){
            for(int x=0; x<imageIn.getWidth(); x++){
                if(imageOut.getIntComponent0(x, y) == 0){
                    fill.setAttribute("x", x);
                    fill.setAttribute("y", y);
                    fill.setAttribute("color", color);
                    fill.setAttribute("threshold", 120);
                    fill.process(imageIn, imageOut);
                    color = newColor(color);

                    found = true;
                    break Outerloop;
                }
            }
        }

        if(!found){
            break;
        }
    }

}

private int newColor(int color){
    int red = (color & 0x00FF0000) >> 16;
    int green = (color & 0x0000FF00) >> 8;
    int blue = (color & 0x000000FF);

    if(red <= green && red <= blue){
        red+=5;
    }
    else if(green <= red && green <= blue){
        green+=5;
    }
    else{
        blue+=5;
    }

    return 0xFF000000 + (red << 16) + (green << 8) + blue;
}

public static void main(String[] args) {
    new ChristmasTree();
}
}

Come mostrato nell'immagine di output, sono state rilevate più forme. In questo problema, ci sono solo alcuni punti luminosi nelle immagini. Tuttavia, questo approccio è stato implementato per affrontare scenari più complessi.

Nel passaggio successivo viene analizzata ogni forma. Un semplice algoritmo rileva forme con un motivo simile a un triangolo. L'algoritmo analizza la forma dell'oggetto riga per riga. Se il centro della massa di ciascuna linea di forma è quasi lo stesso (data una soglia) e la massa aumenta all'aumentare di y, l'oggetto ha una forma triangolare. La massa della linea della forma è il numero di pixel in quella linea che appartiene alla forma. Immagina di tagliare l'oggetto in orizzontale e di analizzare ogni segmento orizzontale. Se sono centralizzati tra loro e la lunghezza aumenta dal primo segmento all'ultimo in uno schema lineare, probabilmente hai un oggetto che ricorda un triangolo.

codice sorgente:

private int[] detectTrees(MarvinImage image){
    HashSet<Integer> analysed = new HashSet<Integer>();
    boolean found;
    while(true){
        found = false;
        for(int y=0; y<image.getHeight(); y++){
            for(int x=0; x<image.getWidth(); x++){
                int color = image.getIntColor(x, y);

                if(!analysed.contains(color)){
                    if(isTree(image, color)){
                        return getObjectRect(image, color);
                    }

                    analysed.add(color);
                    found=true;
                }
            }
        }

        if(!found){
            break;
        }
    }
    return null;
}

private boolean isTree(MarvinImage image, int color){

    int mass[][] = new int[image.getHeight()][2];
    int yStart=-1;
    int xStart=-1;
    for(int y=0; y<image.getHeight(); y++){
        int mc = 0;
        int xs=-1;
        int xe=-1;
        for(int x=0; x<image.getWidth(); x++){
            if(image.getIntColor(x, y) == color){
                mc++;

                if(yStart == -1){
                    yStart=y;
                    xStart=x;
                }

                if(xs == -1){
                    xs = x;
                }
                if(x > xe){
                    xe = x;
                }
            }
        }
        mass[y][0] = xs;
        mass[y][3] = xe;
        mass[y][4] = mc;    
    }

    int validLines=0;
    for(int y=0; y<image.getHeight(); y++){
        if
        ( 
            mass[y][5] > 0 &&
            Math.abs(((mass[y][0]+mass[y][6])/2)-xStart) <= 50 &&
            mass[y][7] >= (mass[yStart][8] + (y-yStart)*0.3) &&
            mass[y][9] <= (mass[yStart][10] + (y-yStart)*1.5)
        )
        {
            validLines++;
        }
    }

    if(validLines > 100){
        return true;
    }
    return false;
}

Infine, la posizione di ogni forma simile a un triangolo e con una luminosità significativa, in questo caso un albero di Natale, viene evidenziata nell'immagine originale, come mostrato di seguito.

immagini di output finale:

codice sorgente finale:

public class ChristmasTree {

private MarvinImagePlugin fill = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.fill.boundaryFill");
private MarvinImagePlugin threshold = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.thresholding");
private MarvinImagePlugin invert = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.color.invert");
private MarvinImagePlugin dilation = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.morphological.dilation");

public ChristmasTree(){
    MarvinImage tree;

    // Iterate each image
    for(int i=1; i<=6; i++){
        tree = MarvinImageIO.loadImage("./res/trees/tree"+i+".png");

        // 1. Threshold
        threshold.setAttribute("threshold", 200);
        threshold.process(tree.clone(), tree);

        // 2. Dilate
        invert.process(tree.clone(), tree);
        tree = MarvinColorModelConverter.rgbToBinary(tree, 127);
        MarvinImageIO.saveImage(tree, "./res/trees/new/tree_"+i+"threshold.png");
        dilation.setAttribute("matrix", MarvinMath.getTrueMatrix(50, 50));
        dilation.process(tree.clone(), tree);
        MarvinImageIO.saveImage(tree, "./res/trees/new/tree_"+1+"_dilation.png");
        tree = MarvinColorModelConverter.binaryToRgb(tree);

        // 3. Segment shapes
        MarvinImage trees2 = tree.clone();
        fill(tree, trees2);
        MarvinImageIO.saveImage(trees2, "./res/trees/new/tree_"+i+"_fill.png");

        // 4. Detect tree-like shapes
        int[] rect = detectTrees(trees2);

        // 5. Draw the result
        MarvinImage original = MarvinImageIO.loadImage("./res/trees/tree"+i+".png");
        drawBoundary(trees2, original, rect);
        MarvinImageIO.saveImage(original, "./res/trees/new/tree_"+i+"_out_2.jpg");
    }
}

private void drawBoundary(MarvinImage shape, MarvinImage original, int[] rect){
    int yLines[] = new int[6];
    yLines[0] = rect[1];
    yLines[1] = rect[1]+(int)((rect[3]/5));
    yLines[2] = rect[1]+((rect[3]/5)*2);
    yLines[3] = rect[1]+((rect[3]/5)*3);
    yLines[4] = rect[1]+(int)((rect[3]/5)*4);
    yLines[5] = rect[1]+rect[3];

    List<Point> points = new ArrayList<Point>();
    for(int i=0; i<yLines.length; i++){
        boolean in=false;
        Point startPoint=null;
        Point endPoint=null;
        for(int x=rect[0]; x<rect[0]+rect[2]; x++){

            if(shape.getIntColor(x, yLines[i]) != 0xFFFFFFFF){
                if(!in){
                    if(startPoint == null){
                        startPoint = new Point(x, yLines[i]);
                    }
                }
                in = true;
            }
            else{
                if(in){
                    endPoint = new Point(x, yLines[i]);
                }
                in = false;
            }
        }

        if(endPoint == null){
            endPoint = new Point((rect[0]+rect[2])-1, yLines[i]);
        }

        points.add(startPoint);
        points.add(endPoint);
    }

    drawLine(points.get(0).x, points.get(0).y, points.get(1).x, points.get(1).y, 15, original);
    drawLine(points.get(1).x, points.get(1).y, points.get(3).x, points.get(3).y, 15, original);
    drawLine(points.get(3).x, points.get(3).y, points.get(5).x, points.get(5).y, 15, original);
    drawLine(points.get(5).x, points.get(5).y, points.get(7).x, points.get(7).y, 15, original);
    drawLine(points.get(7).x, points.get(7).y, points.get(9).x, points.get(9).y, 15, original);
    drawLine(points.get(9).x, points.get(9).y, points.get(11).x, points.get(11).y, 15, original);
    drawLine(points.get(11).x, points.get(11).y, points.get(10).x, points.get(10).y, 15, original);
    drawLine(points.get(10).x, points.get(10).y, points.get(8).x, points.get(8).y, 15, original);
    drawLine(points.get(8).x, points.get(8).y, points.get(6).x, points.get(6).y, 15, original);
    drawLine(points.get(6).x, points.get(6).y, points.get(4).x, points.get(4).y, 15, original);
    drawLine(points.get(4).x, points.get(4).y, points.get(2).x, points.get(2).y, 15, original);
    drawLine(points.get(2).x, points.get(2).y, points.get(0).x, points.get(0).y, 15, original);
}

private void drawLine(int x1, int y1, int x2, int y2, int length, MarvinImage image){
    int lx1, lx2, ly1, ly2;
    for(int i=0; i<length; i++){
        lx1 = (x1+i >= image.getWidth() ? (image.getWidth()-1)-i: x1);
        lx2 = (x2+i >= image.getWidth() ? (image.getWidth()-1)-i: x2);
        ly1 = (y1+i >= image.getHeight() ? (image.getHeight()-1)-i: y1);
        ly2 = (y2+i >= image.getHeight() ? (image.getHeight()-1)-i: y2);

        image.drawLine(lx1+i, ly1, lx2+i, ly2, Color.red);
        image.drawLine(lx1, ly1+i, lx2, ly2+i, Color.red);
    }
}

private void fillRect(MarvinImage image, int[] rect, int length){
    for(int i=0; i<length; i++){
        image.drawRect(rect[0]+i, rect[1]+i, rect[2]-(i*2), rect[3]-(i*2), Color.red);
    }
}

private void fill(MarvinImage imageIn, MarvinImage imageOut){
    boolean found;
    int color= 0xFFFF0000;

    while(true){
        found=false;

        Outerloop:
        for(int y=0; y<imageIn.getHeight(); y++){
            for(int x=0; x<imageIn.getWidth(); x++){
                if(imageOut.getIntComponent0(x, y) == 0){
                    fill.setAttribute("x", x);
                    fill.setAttribute("y", y);
                    fill.setAttribute("color", color);
                    fill.setAttribute("threshold", 120);
                    fill.process(imageIn, imageOut);
                    color = newColor(color);

                    found = true;
                    break Outerloop;
                }
            }
        }

        if(!found){
            break;
        }
    }

}

private int[] detectTrees(MarvinImage image){
    HashSet<Integer> analysed = new HashSet<Integer>();
    boolean found;
    while(true){
        found = false;
        for(int y=0; y<image.getHeight(); y++){
            for(int x=0; x<image.getWidth(); x++){
                int color = image.getIntColor(x, y);

                if(!analysed.contains(color)){
                    if(isTree(image, color)){
                        return getObjectRect(image, color);
                    }

                    analysed.add(color);
                    found=true;
                }
            }
        }

        if(!found){
            break;
        }
    }
    return null;
}

private boolean isTree(MarvinImage image, int color){

    int mass[][] = new int[image.getHeight()][11];
    int yStart=-1;
    int xStart=-1;
    for(int y=0; y<image.getHeight(); y++){
        int mc = 0;
        int xs=-1;
        int xe=-1;
        for(int x=0; x<image.getWidth(); x++){
            if(image.getIntColor(x, y) == color){
                mc++;

                if(yStart == -1){
                    yStart=y;
                    xStart=x;
                }

                if(xs == -1){
                    xs = x;
                }
                if(x > xe){
                    xe = x;
                }
            }
        }
        mass[y][0] = xs;
        mass[y][12] = xe;
        mass[y][13] = mc;   
    }

    int validLines=0;
    for(int y=0; y<image.getHeight(); y++){
        if
        ( 
            mass[y][14] > 0 &&
            Math.abs(((mass[y][0]+mass[y][15])/2)-xStart) <= 50 &&
            mass[y][16] >= (mass[yStart][17] + (y-yStart)*0.3) &&
            mass[y][18] <= (mass[yStart][19] + (y-yStart)*1.5)
        )
        {
            validLines++;
        }
    }

    if(validLines > 100){
        return true;
    }
    return false;
}

private int[] getObjectRect(MarvinImage image, int color){
    int x1=-1;
    int x2=-1;
    int y1=-1;
    int y2=-1;

    for(int y=0; y<image.getHeight(); y++){
        for(int x=0; x<image.getWidth(); x++){
            if(image.getIntColor(x, y) == color){

                if(x1 == -1 || x < x1){
                    x1 = x;
                }
                if(x2 == -1 || x > x2){
                    x2 = x;
                }
                if(y1 == -1 || y < y1){
                    y1 = y;
                }
                if(y2 == -1 || y > y2){
                    y2 = y;
                }
            }
        }
    }

    return new int[]{x1, y1, (x2-x1), (y2-y1)};
}

private int newColor(int color){
    int red = (color & 0x00FF0000) >> 16;
    int green = (color & 0x0000FF00) >> 8;
    int blue = (color & 0x000000FF);

    if(red <= green && red <= blue){
        red+=5;
    }
    else if(green <= red && green <= blue){
        green+=30;
    }
    else{
        blue+=30;
    }

    return 0xFF000000 + (red << 16) + (green << 8) + blue;
}

public static void main(String[] args) {
    new ChristmasTree();
}
}

Il vantaggio di questo approccio è il fatto che probabilmente funzionerà con immagini contenenti altri oggetti luminosi poiché analizza la forma dell'oggetto.

Buon Natale!


MODIFICA NOTA 2

C'è una discussione sulla somiglianza delle immagini di output di questa soluzione e alcune altre. In effetti, sono molto simili. Ma questo approccio non segmenta solo gli oggetti. Analizza anche le forme degli oggetti in un certo senso. Può gestire più oggetti luminosi nella stessa scena. In realtà, l'albero di Natale non deve essere il più brillante. Lo sto solo accettando per arricchire la discussione. C'è un pregiudizio nei campioni che solo cercando l'oggetto più luminoso, troverai gli alberi. Ma vogliamo davvero fermare la discussione a questo punto? A questo punto, fino a che punto il computer riconosce davvero un oggetto che ricorda un albero di Natale? Proviamo a colmare questa lacuna.

Di seguito viene presentato un risultato solo per chiarire questo punto:

immagine di input

inserisci qui la descrizione dell'immagine

produzione

inserisci qui la descrizione dell'immagine


2
Interessante. Spero che tu possa ottenere gli stessi risultati quando ogni immagine viene elaborata individualmente. Ho modificato la domanda 4 ore prima pubblicando la risposta per specificarlo. Sarebbe fantastico se potessi aggiornare la tua risposta con questi risultati.
karlphillip,

@Marvin nella rilevazione del tuo triangolo, come hai gestito la fluttuazione della massa? Non è un triangolo rigoroso, la massa non è mono quando cambia y
user3054997

2
@ user3054997: questo è un altro punto. Come ho pubblicato, l'algoritmo non cerca le forme rigorose del triangolo. Analizza ogni oggetto e considera un albero che "assomiglia" a un triangolo con un semplice criterio: la massa dell'oggetto viene utilizzata per aumentare all'aumentare di y e il centro della massa di ciascun segmento di oggetto orizzontale è quasi centralizzato l'uno con l'altro .
Gabriel Ambrósio Archanjo,

@Marvin La mia soluzione è davvero semplice, l'ho dichiarato anche nella mia risposta. A proposito, ha funzionato meglio della tua prima soluzione. Se ricordo bene, nella tua prima risposta, hai parlato di descrittori di caratteristiche per rilevare una piccola trama chiara, che non è ciò che stai facendo qui. Ho semplicemente detto che il tuo attuale approccio e risultati sono molto più simili ai miei che alla tua prima soluzione. Ovviamente non mi aspetto che tu lo ammetta, l'ho dichiarato solo per la cronaca.
smeso,

1
@sepdek Qui ci sono un paio di soluzioni davvero molto migliori delle mie e stanno ancora ottenendo la metà dei miei voti. Non c'è niente di sbagliato nel "farsi ispirare" da altre soluzioni. Ho visto anche le tue soluzioni, non ho nulla da dire contro di te, le hai pubblicate dopo di me e la mia "idea" non era così originale da dire che mi hai appena copiato. Ma Marvin è stato l'unico che ha pubblicato prima di me e modificato la soluzione dopo aver visto il mio usando lo stesso algoritmo ... almeno avrebbe potuto dire "Sì, mi è piaciuta la tua soluzione e l'ho riutilizzata" non c'è niente di sbagliato, è solo un gioco.
smeso,

75

Ecco la mia soluzione semplice e stupida. Si basa sul presupposto che l'albero sarà la cosa più luminosa e grande nella foto.

//g++ -Wall -pedantic -ansi -O2 -pipe -s -o christmas_tree christmas_tree.cpp `pkg-config --cflags --libs opencv`
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int argc,char *argv[])
{
    Mat original,tmp,tmp1;
    vector <vector<Point> > contours;
    Moments m;
    Rect boundrect;
    Point2f center;
    double radius, max_area=0,tmp_area=0;
    unsigned int j, k;
    int i;

    for(i = 1; i < argc; ++i)
    {
        original = imread(argv[i]);
        if(original.empty())
        {
            cerr << "Error"<<endl;
            return -1;
        }

        GaussianBlur(original, tmp, Size(3, 3), 0, 0, BORDER_DEFAULT);
        erode(tmp, tmp, Mat(), Point(-1, -1), 10);
        cvtColor(tmp, tmp, CV_BGR2HSV);
        inRange(tmp, Scalar(0, 0, 0), Scalar(180, 255, 200), tmp);

        dilate(original, tmp1, Mat(), Point(-1, -1), 15);
        cvtColor(tmp1, tmp1, CV_BGR2HLS);
        inRange(tmp1, Scalar(0, 185, 0), Scalar(180, 255, 255), tmp1);
        dilate(tmp1, tmp1, Mat(), Point(-1, -1), 10);

        bitwise_and(tmp, tmp1, tmp1);

        findContours(tmp1, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
        max_area = 0;
        j = 0;
        for(k = 0; k < contours.size(); k++)
        {
            tmp_area = contourArea(contours[k]);
            if(tmp_area > max_area)
            {
                max_area = tmp_area;
                j = k;
            }
        }
        tmp1 = Mat::zeros(original.size(),CV_8U);
        approxPolyDP(contours[j], contours[j], 30, true);
        drawContours(tmp1, contours, j, Scalar(255,255,255), CV_FILLED);

        m = moments(contours[j]);
        boundrect = boundingRect(contours[j]);
        center = Point2f(m.m10/m.m00, m.m01/m.m00);
        radius = (center.y - (boundrect.tl().y))/4.0*3.0;
        Rect heightrect(center.x-original.cols/5, boundrect.tl().y, original.cols/5*2, boundrect.size().height);

        tmp = Mat::zeros(original.size(), CV_8U);
        rectangle(tmp, heightrect, Scalar(255, 255, 255), -1);
        circle(tmp, center, radius, Scalar(255, 255, 255), -1);

        bitwise_and(tmp, tmp1, tmp1);

        findContours(tmp1, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
        max_area = 0;
        j = 0;
        for(k = 0; k < contours.size(); k++)
        {
            tmp_area = contourArea(contours[k]);
            if(tmp_area > max_area)
            {
                max_area = tmp_area;
                j = k;
            }
        }

        approxPolyDP(contours[j], contours[j], 30, true);
        convexHull(contours[j], contours[j]);

        drawContours(original, contours, j, Scalar(0, 0, 255), 3);

        namedWindow(argv[i], CV_WINDOW_NORMAL|CV_WINDOW_KEEPRATIO|CV_GUI_EXPANDED);
        imshow(argv[i], original);

        waitKey(0);
        destroyWindow(argv[i]);
    }

    return 0;
}

Il primo passo è rilevare i pixel più luminosi nell'immagine, ma dobbiamo fare una distinzione tra l'albero stesso e la neve che riflette la sua luce. Qui proviamo ad escludere la neve applicando un filtro davvero semplice sui codici colore:

GaussianBlur(original, tmp, Size(3, 3), 0, 0, BORDER_DEFAULT);
erode(tmp, tmp, Mat(), Point(-1, -1), 10);
cvtColor(tmp, tmp, CV_BGR2HSV);
inRange(tmp, Scalar(0, 0, 0), Scalar(180, 255, 200), tmp);

Quindi troviamo ogni pixel "luminoso":

dilate(original, tmp1, Mat(), Point(-1, -1), 15);
cvtColor(tmp1, tmp1, CV_BGR2HLS);
inRange(tmp1, Scalar(0, 185, 0), Scalar(180, 255, 255), tmp1);
dilate(tmp1, tmp1, Mat(), Point(-1, -1), 10);

Infine uniamo i due risultati:

bitwise_and(tmp, tmp1, tmp1);

Ora cerchiamo il più grande oggetto luminoso:

findContours(tmp1, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
max_area = 0;
j = 0;
for(k = 0; k < contours.size(); k++)
{
    tmp_area = contourArea(contours[k]);
    if(tmp_area > max_area)
    {
        max_area = tmp_area;
        j = k;
    }
}
tmp1 = Mat::zeros(original.size(),CV_8U);
approxPolyDP(contours[j], contours[j], 30, true);
drawContours(tmp1, contours, j, Scalar(255,255,255), CV_FILLED);

Ora abbiamo quasi fatto, ma ci sono ancora alcune imperfezioni dovute alla neve. Per tagliarli costruiremo una maschera usando un cerchio e un rettangolo per approssimare la forma di un albero per eliminare pezzi indesiderati:

m = moments(contours[j]);
boundrect = boundingRect(contours[j]);
center = Point2f(m.m10/m.m00, m.m01/m.m00);
radius = (center.y - (boundrect.tl().y))/4.0*3.0;
Rect heightrect(center.x-original.cols/5, boundrect.tl().y, original.cols/5*2, boundrect.size().height);

tmp = Mat::zeros(original.size(), CV_8U);
rectangle(tmp, heightrect, Scalar(255, 255, 255), -1);
circle(tmp, center, radius, Scalar(255, 255, 255), -1);

bitwise_and(tmp, tmp1, tmp1);

L'ultimo passo è trovare il contorno del nostro albero e disegnarlo sull'immagine originale.

findContours(tmp1, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
max_area = 0;
j = 0;
for(k = 0; k < contours.size(); k++)
{
    tmp_area = contourArea(contours[k]);
    if(tmp_area > max_area)
    {
        max_area = tmp_area;
        j = k;
    }
}

approxPolyDP(contours[j], contours[j], 30, true);
convexHull(contours[j], contours[j]);

drawContours(original, contours, j, Scalar(0, 0, 255), 3);

Mi dispiace, ma al momento ho una cattiva connessione, quindi non è possibile caricare foto. Proverò a farlo più tardi.

Buon Natale.

MODIFICARE:

Ecco alcune immagini dell'output finale:


1
Ciao! Assicurati che la tua risposta soddisfi tutti i requisiti: ci sono 6 immagini in ingresso e la risposta dovrebbe mostrare i risultati dell'elaborazione di ciascuna di esse; .
karlphillip,

Ciao! È possibile passare i nomi di file come argomenti CLI per il mio programma: ./christmas_tree ./*.png. Possono essere quanti ne vuoi, i risultati verranno mostrati uno dopo l'altro premendo un tasto qualsiasi. È sbagliato?
smeso,

Va bene, ma devi comunque caricare le immagini e condividerle nella tua domanda in modo che gli utenti del thread possano effettivamente vedere il tuo risultato. Consentire alle persone di vedere cosa hai fatto aumenterà le tue possibilità di alzare i voti;)
karlphillip,

Sto cercando di trovare una soluzione per questo, ho alcuni problemi di connettività.
smeso,

2
Grande! Ora puoi ridimensionarli all'interno della risposta con il seguente codice: <img src="http://i.stack.imgur.com/nmzwj.png" width="210" height="150">Basta cambiare il link all'immagine;)
karlphillip,

60

Ho scritto il codice in Matlab R2007a. Ho usato k-mean per estrarre approssimativamente l'albero di Natale. Mostrerò il mio risultato intermedio solo con un'immagine e i risultati finali con tutti e sei.

Innanzitutto, ho mappato lo spazio RGB sullo spazio Lab, che potrebbe migliorare il contrasto del rosso nel suo canale b:

colorTransform = makecform('srgb2lab');
I = applycform(I, colorTransform);
L = double(I(:,:,1));
a = double(I(:,:,2));
b = double(I(:,:,3));

inserisci qui la descrizione dell'immagine

Oltre alla funzione nello spazio colore, ho anche usato la funzione texture rilevante per il vicinato piuttosto che per ogni pixel stesso. Qui ho combinato linearmente l'intensità dei 3 canali originali (R, G, B). Il motivo per cui ho formattato in questo modo è perché gli alberi di Natale nella foto hanno tutti luci rosse su di loro e talvolta anche l'illuminazione verde / a volte blu.

R=double(Irgb(:,:,1));
G=double(Irgb(:,:,2));
B=double(Irgb(:,:,3));
I0 = (3*R + max(G,B)-min(G,B))/2;

inserisci qui la descrizione dell'immagine

Ho applicato un modello binario locale 3X3, ho I0usato il pixel centrale come soglia e ho ottenuto il contrasto calcolando la differenza tra il valore di intensità del pixel medio sopra la soglia e il valore medio sotto di esso.

I0_copy = zeros(size(I0));
for i = 2 : size(I0,1) - 1
    for j = 2 : size(I0,2) - 1
        tmp = I0(i-1:i+1,j-1:j+1) >= I0(i,j);
        I0_copy(i,j) = mean(mean(tmp.*I0(i-1:i+1,j-1:j+1))) - ...
            mean(mean(~tmp.*I0(i-1:i+1,j-1:j+1))); % Contrast
    end
end

inserisci qui la descrizione dell'immagine

Dato che ho 4 funzionalità in totale, sceglierei K = 5 nel mio metodo di clustering. Di seguito è riportato il codice per k-medie (è del corso di apprendimento automatico del Dr. Andrew Ng. Ho già seguito il corso e ho scritto il codice da solo nel suo incarico di programmazione).

[centroids, idx] = runkMeans(X, initial_centroids, max_iters);
mask=reshape(idx,img_size(1),img_size(2));

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [centroids, idx] = runkMeans(X, initial_centroids, ...
                                  max_iters, plot_progress)
   [m n] = size(X);
   K = size(initial_centroids, 1);
   centroids = initial_centroids;
   previous_centroids = centroids;
   idx = zeros(m, 1);

   for i=1:max_iters    
      % For each example in X, assign it to the closest centroid
      idx = findClosestCentroids(X, centroids);

      % Given the memberships, compute new centroids
      centroids = computeCentroids(X, idx, K);

   end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function idx = findClosestCentroids(X, centroids)
   K = size(centroids, 1);
   idx = zeros(size(X,1), 1);
   for xi = 1:size(X,1)
      x = X(xi, :);
      % Find closest centroid for x.
      best = Inf;
      for mui = 1:K
        mu = centroids(mui, :);
        d = dot(x - mu, x - mu);
        if d < best
           best = d;
           idx(xi) = mui;
        end
      end
   end 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function centroids = computeCentroids(X, idx, K)
   [m n] = size(X);
   centroids = zeros(K, n);
   for mui = 1:K
      centroids(mui, :) = sum(X(idx == mui, :)) / sum(idx == mui);
   end

Dal momento che il programma funziona molto lentamente sul mio computer, ho appena eseguito 3 iterazioni. Normalmente il criterio di arresto è (i) tempo di iterazione di almeno 10 o (ii) nessuna modifica sui centroidi. Secondo il mio test, aumentare l'iterazione può differenziare lo sfondo (cielo e albero, cielo e edificio, ...) in modo più accurato, ma non ha mostrato cambiamenti drastici nell'estrazione dell'albero di Natale. Si noti inoltre che k-medie non è immune all'inizializzazione casuale del centroide, quindi si consiglia di eseguire il programma più volte per effettuare un confronto.

Dopo i k-medie, è I0stata scelta la regione etichettata con la massima intensità di . E il tracciamento dei confini è stato utilizzato per estrarre i confini. Per me, l'ultimo albero di Natale è il più difficile da estrarre poiché il contrasto in quella foto non è abbastanza alto come nei primi cinque. Un altro problema nel mio metodo è che ho usato la bwboundariesfunzione in Matlab per tracciare il confine, ma a volte sono inclusi anche i confini interni, come puoi osservare nei risultati 3 °, 5 °, 6 °. Il lato oscuro all'interno degli alberi di Natale non solo non riesce a raggrupparsi con il lato illuminato, ma porta anche a così tanti piccoli confini interni che tracciano ( imfillnon migliorano molto). In tutto il mio algoritmo ha ancora molto spazio di miglioramento.

Alcune pubblicazioni indicano che lo spostamento della media può essere più robusto rispetto alla media k e che molti algoritmi basati sul taglio di grafici sono anche molto competitivi sulla segmentazione complicata dei confini. Ho scritto io stesso un algoritmo di spostamento dei media, sembra estrarre meglio le regioni senza abbastanza luce. Ma lo spostamento medio è un po 'troppo segmentato ed è necessaria una certa strategia di fusione. Ha funzionato anche molto più lentamente di k-mean nel mio computer, temo di dover rinunciare. Attendo con impazienza di vedere altri presentare qui risultati eccellenti con quei moderni algoritmi sopra menzionati.

Tuttavia, credo sempre che la selezione delle funzionalità sia il componente chiave nella segmentazione delle immagini. Con una corretta selezione delle funzionalità in grado di massimizzare il margine tra oggetto e sfondo, molti algoritmi di segmentazione funzioneranno sicuramente. Diversi algoritmi possono migliorare il risultato da 1 a 10, ma la selezione della funzione può migliorarlo da 0 a 1.

Buon Natale !


2
Grazie per la risposta! Volevo solo sottolineare che Matlab non è open source , ma Scilab lo è. Anche a me piacerebbe vedere questa risposta in competizione con gli altri. ;)
karlphillip il

6
Grazie Karl. Octave è un altro software open source che condivide quasi la stessa grammatica di codifica con Matlab: mathworks.fr/matlabcentral/answers/14399-gnu-octave-vs-matlab .
lennon310,

Interessante, non lo sapevo, grazie! Il tuo codice funziona su Octave?
karlphillip,

Non ho ancora testato, ma penso che non sia un problema :)
lennon310,

@ lennon310 Penso che se lasci cadere i confini e ottieni lo scafo convesso, eliminerai il problema dei buchi. Ricorda che lo scafo convesso è l'area più piccola che include tutti i punti in un set.
Sepdek,

57

Questo è il mio post finale usando i tradizionali approcci di elaborazione delle immagini ...

Qui in qualche modo unisco le mie altre due proposte, ottenendo risultati ancora migliori . È un dato di fatto che non riesco a vedere come questi risultati potrebbero essere migliori (specialmente quando si guardano le immagini mascherate che il metodo produce).

Al centro dell'approccio c'è la combinazione di tre ipotesi chiave :

  1. Le immagini dovrebbero avere alte fluttuazioni nelle regioni degli alberi
  2. Le immagini dovrebbero avere una maggiore intensità nelle regioni degli alberi
  3. Le regioni di sfondo dovrebbero avere una bassa intensità ed essere prevalentemente blu-ish

Tenendo presenti questi presupposti, il metodo funziona come segue:

  1. Converti le immagini in HSV
  2. Filtra il canale V con un filtro LoG
  3. Applicare il limite massimo sull'immagine filtrata LoG per ottenere la maschera 'attività' A
  4. Applicare la soglia rigida al canale V per ottenere la maschera di intensità B
  5. Applicare il limite del canale H per catturare regioni a bassa intensità blu nella maschera di sfondo C
  6. Combina le maschere usando AND per ottenere la maschera finale
  7. Dilatare la maschera per ingrandire le regioni e collegare i pixel dispersi
  8. Elimina le piccole regioni e ottieni la maschera finale che alla fine rappresenterà solo l'albero

Ecco il codice in MATLAB (di nuovo, lo script carica tutte le immagini jpg nella cartella corrente e, di nuovo, questo è ben lungi dall'essere un pezzo di codice ottimizzato):

% clear everything
clear;
pack;
close all;
close all hidden;
drawnow;
clc;

% initialization
ims=dir('./*.jpg');
imgs={};
images={}; 
blur_images={}; 
log_image={}; 
dilated_image={};
int_image={};
back_image={};
bin_image={};
measurements={};
box={};
num=length(ims);
thres_div = 3;

for i=1:num, 
    % load original image
    imgs{end+1}=imread(ims(i).name);

    % convert to HSV colorspace
    images{end+1}=rgb2hsv(imgs{i});

    % apply laplacian filtering and heuristic hard thresholding
    val_thres = (max(max(images{i}(:,:,3)))/thres_div);
    log_image{end+1} = imfilter( images{i}(:,:,3),fspecial('log')) > val_thres;

    % get the most bright regions of the image
    int_thres = 0.26*max(max( images{i}(:,:,3)));
    int_image{end+1} = images{i}(:,:,3) > int_thres;

    % get the most probable background regions of the image
    back_image{end+1} = images{i}(:,:,1)>(150/360) & images{i}(:,:,1)<(320/360) & images{i}(:,:,3)<0.5;

    % compute the final binary image by combining 
    % high 'activity' with high intensity
    bin_image{end+1} = logical( log_image{i}) & logical( int_image{i}) & ~logical( back_image{i});

    % apply morphological dilation to connect distonnected components
    strel_size = round(0.01*max(size(imgs{i})));        % structuring element for morphological dilation
    dilated_image{end+1} = imdilate( bin_image{i}, strel('disk',strel_size));

    % do some measurements to eliminate small objects
    measurements{i} = regionprops( logical( dilated_image{i}),'Area','BoundingBox');

    % iterative enlargement of the structuring element for better connectivity
    while length(measurements{i})>14 && strel_size<(min(size(imgs{i}(:,:,1)))/2),
        strel_size = round( 1.5 * strel_size);
        dilated_image{i} = imdilate( bin_image{i}, strel('disk',strel_size));
        measurements{i} = regionprops( logical( dilated_image{i}),'Area','BoundingBox');
    end

    for m=1:length(measurements{i})
        if measurements{i}(m).Area < 0.05*numel( dilated_image{i})
            dilated_image{i}( round(measurements{i}(m).BoundingBox(2):measurements{i}(m).BoundingBox(4)+measurements{i}(m).BoundingBox(2)),...
                round(measurements{i}(m).BoundingBox(1):measurements{i}(m).BoundingBox(3)+measurements{i}(m).BoundingBox(1))) = 0;
        end
    end
    % make sure the dilated image is the same size with the original
    dilated_image{i} = dilated_image{i}(1:size(imgs{i},1),1:size(imgs{i},2));
    % compute the bounding box
    [y,x] = find( dilated_image{i});
    if isempty( y)
        box{end+1}=[];
    else
        box{end+1} = [ min(x) min(y) max(x)-min(x)+1 max(y)-min(y)+1];
    end
end 

%%% additional code to display things
for i=1:num,
    figure;
    subplot(121);
    colormap gray;
    imshow( imgs{i});
    if ~isempty(box{i})
        hold on;
        rr = rectangle( 'position', box{i});
        set( rr, 'EdgeColor', 'r');
        hold off;
    end
    subplot(122);
    imshow( imgs{i}.*uint8(repmat(dilated_image{i},[1 1 3])));
end

risultati

risultati

I risultati ad alta risoluzione sono ancora disponibili qui!
Ancora più esperimenti con immagini aggiuntive possono essere trovati qui.


1
Roba fantastica! Assicurati che anche le altre risposte seguano questo formato. Per competere per la generosità devi usare una tecnologia open source , e purtroppo Matlab non è una di queste. Tuttavia, SciLab e Octave lo sono e forniscono sintassi e funzioni simili. ;)
karlphillip il

Il codice di ottava è lo stesso ...
sepdek,

@karlphillip In qualche modo questa domanda ha finito per avere un tag Matlab. Se l'open source è davvero un must, consiglierei di rimuoverlo.
Dennis Jaheruddin,

@sepdek Molto bello, forse si potrebbe ancora fare qualcosa per includere i 'buchi' nella foto finale. (Aggiungi tutti i pixel completamente circondati da pixel approvati ?!)
Dennis Jaheruddin il

1
@karlphillip grazie uomo! Sono contento che tu abbia trovato interessante il mio approccio. Inoltre vorrei congratularmi con te per aver scelto la soluzione più elegante e non quella con il maggior numero di voti !!!
Sepdek,

36

I miei passaggi per la soluzione:

  1. Ottieni canale R (da RGB) - tutte le operazioni che eseguiamo su questo canale:

  2. Crea regione di interesse (ROI)

    • Soglia canale R con valore minimo 149 (immagine in alto a destra)

    • Dilatare la regione del risultato (immagine al centro a sinistra)

  3. Rileva spigoli nel roi calcolato. L'albero ha molti bordi (immagine in mezzo a destra)

    • Dilatare il risultato

    • Erode con raggio maggiore (immagine in basso a sinistra)

  4. Seleziona l'oggetto più grande (per area): è la regione del risultato

  5. Scafo convesso (l'albero è poligono convesso) (immagine in basso a destra)

  6. Bounding box (immagine in basso a destra - grren box)

Passo dopo passo: inserisci qui la descrizione dell'immagine

Il primo risultato - il più semplice ma non nel software open source - "Adaptive Vision Studio + Adaptive Vision Library": questo non è open source ma molto veloce da prototipare:

Intero algoritmo per rilevare l'albero di Natale (11 blocchi): Soluzione AVL

Passo successivo. Vogliamo una soluzione open source. Cambia i filtri AVL in filtri OpenCV: qui ho fatto piccole modifiche, ad esempio Edge Detection, usando il filtro cvCanny, per rispettare il roi ho moltiplicato l'immagine della regione con l'immagine dei bordi, per selezionare l'elemento più grande che ho usato findContours + contourArea ma l'idea è la stessa.

https://www.youtube.com/watch?v=sfjB3MigLH0&index=1&list=UUpSRrkMHNHiLDXgylwhWNQQ

Soluzione OpenCV

Non riesco a mostrare le immagini con passaggi intermedi ora perché posso inserire solo 2 collegamenti.

Ok ora usiamo i filtri openSource ma non è ancora tutto open source. Ultimo passaggio: porta al codice c ++. Ho usato OpenCV nella versione 2.4.4

Il risultato del codice c ++ finale è: inserisci qui la descrizione dell'immagine

Anche il codice c ++ è piuttosto breve:

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/opencv.hpp"
#include <algorithm>
using namespace cv;

int main()
{

    string images[6] = {"..\\1.png","..\\2.png","..\\3.png","..\\4.png","..\\5.png","..\\6.png"};

    for(int i = 0; i < 6; ++i)
    {
        Mat img, thresholded, tdilated, tmp, tmp1;
        vector<Mat> channels(3);

        img = imread(images[i]);
        split(img, channels);
        threshold( channels[2], thresholded, 149, 255, THRESH_BINARY);                      //prepare ROI - threshold
        dilate( thresholded, tdilated,  getStructuringElement( MORPH_RECT, Size(22,22) ) ); //prepare ROI - dilate
        Canny( channels[2], tmp, 75, 125, 3, true );    //Canny edge detection
        multiply( tmp, tdilated, tmp1 );    // set ROI

        dilate( tmp1, tmp, getStructuringElement( MORPH_RECT, Size(20,16) ) ); // dilate
        erode( tmp, tmp1, getStructuringElement( MORPH_RECT, Size(36,36) ) ); // erode

        vector<vector<Point> > contours, contours1(1);
        vector<Point> convex;
        vector<Vec4i> hierarchy;
        findContours( tmp1, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

        //get element of maximum area
        //int bestID = std::max_element( contours.begin(), contours.end(), 
        //  []( const vector<Point>& A, const vector<Point>& B ) { return contourArea(A) < contourArea(B); } ) - contours.begin();

            int bestID = 0;
        int bestArea = contourArea( contours[0] );
        for( int i = 1; i < contours.size(); ++i )
        {
            int area = contourArea( contours[i] );
            if( area > bestArea )
            {
                bestArea  = area;
                bestID = i;
            }
        }

        convexHull( contours[bestID], contours1[0] ); 
        drawContours( img, contours1, 0, Scalar( 100, 100, 255 ), img.rows / 100, 8, hierarchy, 0, Point() );

        imshow("image", img );
        waitKey(0);
    }


    return 0;
}

Quale compilatore può creare questo programma senza errori?
karlphillip,

Ho usato Visual Studio 2012 per costruirlo. Dovresti usare il compilatore c ++ con supporto c ++ 11.
AdamF,

Non ho un sistema a mia disposizione. Potresti riscrivere la std::max_element()chiamata? Vorrei anche premiare la tua risposta. Penso di avere gcc 4.2.
karlphillip,

Ok, questa è la funzione c ++ 11;) Ho cambiato il codice sorgente sopra. Per favore prova adesso.
AdamF,

Va bene grazie. L'ho provato ed è bellissimo. Non appena questa domanda viene riaperta (altri utenti devono aiutarmi in tal senso) posso impostare un'altra ricompensa per premiarti. Congratulazioni!
karlphillip,

31

... un'altra soluzione vecchio stile - basata esclusivamente sull'elaborazione HSV :

  1. Converti immagini nello spazio colore HSV
  2. Crea maschere in base all'euristica nell'HSV (vedi sotto)
  3. Applicare la dilatazione morfologica sulla maschera per collegare le aree disconnesse
  4. Scarta piccole aree e blocchi orizzontali (ricorda che gli alberi sono blocchi verticali)
  5. Calcola il rettangolo di selezione

Una parola sull'euristica nell'elaborazione HSV:

  1. ogni cosa con Tonalità (H) tra 210 e 320 gradi viene scartata come blu-magenta che dovrebbe essere in background o in aree non rilevanti
  2. tutto ciò che ha valori (V) inferiori al 40% viene anche scartato perché troppo scuro per essere rilevante

Naturalmente si possono sperimentare numerose altre possibilità per mettere a punto questo approccio ...

Ecco il codice MATLAB per fare il trucco (attenzione: il codice è lungi dall'essere ottimizzato !!! Ho usato tecniche non raccomandate per la programmazione MATLAB solo per essere in grado di tracciare qualsiasi cosa nel processo, questo può essere notevolmente ottimizzato):

% clear everything
clear;
pack;
close all;
close all hidden;
drawnow;
clc;

% initialization
ims=dir('./*.jpg');
num=length(ims);

imgs={};
hsvs={}; 
masks={};
dilated_images={};
measurements={};
boxs={};

for i=1:num, 
    % load original image
    imgs{end+1} = imread(ims(i).name);
    flt_x_size = round(size(imgs{i},2)*0.005);
    flt_y_size = round(size(imgs{i},1)*0.005);
    flt = fspecial( 'average', max( flt_y_size, flt_x_size));
    imgs{i} = imfilter( imgs{i}, flt, 'same');
    % convert to HSV colorspace
    hsvs{end+1} = rgb2hsv(imgs{i});
    % apply a hard thresholding and binary operation to construct the mask
    masks{end+1} = medfilt2( ~(hsvs{i}(:,:,1)>(210/360) & hsvs{i}(:,:,1)<(320/360))&hsvs{i}(:,:,3)>0.4);
    % apply morphological dilation to connect distonnected components
    strel_size = round(0.03*max(size(imgs{i})));        % structuring element for morphological dilation
    dilated_images{end+1} = imdilate( masks{i}, strel('disk',strel_size));
    % do some measurements to eliminate small objects
    measurements{i} = regionprops( dilated_images{i},'Perimeter','Area','BoundingBox'); 
    for m=1:length(measurements{i})
        if (measurements{i}(m).Area < 0.02*numel( dilated_images{i})) || (measurements{i}(m).BoundingBox(3)>1.2*measurements{i}(m).BoundingBox(4))
            dilated_images{i}( round(measurements{i}(m).BoundingBox(2):measurements{i}(m).BoundingBox(4)+measurements{i}(m).BoundingBox(2)),...
                round(measurements{i}(m).BoundingBox(1):measurements{i}(m).BoundingBox(3)+measurements{i}(m).BoundingBox(1))) = 0;
        end
    end
    dilated_images{i} = dilated_images{i}(1:size(imgs{i},1),1:size(imgs{i},2));
    % compute the bounding box
    [y,x] = find( dilated_images{i});
    if isempty( y)
        boxs{end+1}=[];
    else
        boxs{end+1} = [ min(x) min(y) max(x)-min(x)+1 max(y)-min(y)+1];
    end

end 

%%% additional code to display things
for i=1:num,
    figure;
    subplot(121);
    colormap gray;
    imshow( imgs{i});
    if ~isempty(boxs{i})
        hold on;
        rr = rectangle( 'position', boxs{i});
        set( rr, 'EdgeColor', 'r');
        hold off;
    end
    subplot(122);
    imshow( imgs{i}.*uint8(repmat(dilated_images{i},[1 1 3])));
end

risultati:

Nei risultati mostro l'immagine mascherata e il riquadro di selezione. inserisci qui la descrizione dell'immagine


Ciao, grazie per la risposta. Per favore, prenditi un momento per leggere la sezione Requisiti per assicurarti che la tua risposta segua tutte le istruzioni. Hai dimenticato di condividere le immagini risultanti. ;)
karlphillip il

2
@karlphillip sepdek non ha abbastanza reputazione per condividere immagini, ho spostato le immagini nel corpo della risposta secondo il suo link e le istruzioni. Non sono sicuro, però, che quelli siano corretti, sentiti libero di commentare questa parte.
alko,

@alko lo so, grazie. Ma alcune delle immagini che hai condiviso non erano nel set di input . La risposta deve mostrare il risultato dell'elaborazione di tutte e 6 le immagini condivise sulla domanda.
karlphillip,

@karlphillip sono le sue immagini, non le mie. questo è esattamente ciò che intendevo per "commentare questa parte";)
alko,

2
Ci scusiamo per aver causato problemi ... non è mia intenzione. Ho incluso tutte le immagini nel set di dati iniziale e l'ho migliorato con ancora di più solo per dimostrare che il mio concetto è solido ...
Sepdek,

23

Qualche approccio di elaborazione delle immagini vecchio stile ...
L'idea si basa sul presupposto che le immagini raffigurino alberi illuminati su sfondi tipicamente più scuri e più uniformi (o in primo piano in alcuni casi). L' area dell'albero illuminata è più "energetica" e ha un'intensità più elevata .
Il processo è il seguente:

  1. Converti in graylevel
  2. Applicare il filtro LoG per ottenere le aree più "attive"
  3. Applicare una soglia intenzionale per ottenere le aree più luminose
  4. Combina i 2 precedenti per ottenere una maschera preliminare
  5. Applicare una dilatazione morfologica per ingrandire le aree e collegare i componenti vicini
  6. Elimina le aree candidate piccole in base alla dimensione dell'area

Quello che ottieni è una maschera binaria e un rettangolo di selezione per ogni immagine.

Ecco i risultati usando questa tecnica ingenua: inserisci qui la descrizione dell'immagine

Segue il codice su MATLAB: il codice viene eseguito su una cartella con immagini JPG. Carica tutte le immagini e restituisce i risultati rilevati.

% clear everything
clear;
pack;
close all;
close all hidden;
drawnow;
clc;

% initialization
ims=dir('./*.jpg');
imgs={};
images={}; 
blur_images={}; 
log_image={}; 
dilated_image={};
int_image={};
bin_image={};
measurements={};
box={};
num=length(ims);
thres_div = 3;

for i=1:num, 
    % load original image
    imgs{end+1}=imread(ims(i).name);

    % convert to grayscale
    images{end+1}=rgb2gray(imgs{i});

    % apply laplacian filtering and heuristic hard thresholding
    val_thres = (max(max(images{i}))/thres_div);
    log_image{end+1} = imfilter( images{i},fspecial('log')) > val_thres;

    % get the most bright regions of the image
    int_thres = 0.26*max(max( images{i}));
    int_image{end+1} = images{i} > int_thres;

    % compute the final binary image by combining 
    % high 'activity' with high intensity
    bin_image{end+1} = log_image{i} .* int_image{i};

    % apply morphological dilation to connect distonnected components
    strel_size = round(0.01*max(size(imgs{i})));        % structuring element for morphological dilation
    dilated_image{end+1} = imdilate( bin_image{i}, strel('disk',strel_size));

    % do some measurements to eliminate small objects
    measurements{i} = regionprops( logical( dilated_image{i}),'Area','BoundingBox');
    for m=1:length(measurements{i})
        if measurements{i}(m).Area < 0.05*numel( dilated_image{i})
            dilated_image{i}( round(measurements{i}(m).BoundingBox(2):measurements{i}(m).BoundingBox(4)+measurements{i}(m).BoundingBox(2)),...
                round(measurements{i}(m).BoundingBox(1):measurements{i}(m).BoundingBox(3)+measurements{i}(m).BoundingBox(1))) = 0;
        end
    end
    % make sure the dilated image is the same size with the original
    dilated_image{i} = dilated_image{i}(1:size(imgs{i},1),1:size(imgs{i},2));
    % compute the bounding box
    [y,x] = find( dilated_image{i});
    if isempty( y)
        box{end+1}=[];
    else
        box{end+1} = [ min(x) min(y) max(x)-min(x)+1 max(y)-min(y)+1];
    end
end 

%%% additional code to display things
for i=1:num,
    figure;
    subplot(121);
    colormap gray;
    imshow( imgs{i});
    if ~isempty(box{i})
        hold on;
        rr = rectangle( 'position', box{i});
        set( rr, 'EdgeColor', 'r');
        hold off;
    end
    subplot(122);
    imshow( imgs{i}.*uint8(repmat(dilated_image{i},[1 1 3])));
end

Non dimenticare di caricare le immagini risultanti, come ha fatto Faust.
karlphillip,

Sono un noob qui, quindi non posso caricare immagini. Si prega di vedere i risultati sui collegamenti forniti nella mia descrizione.
Sepdek,

Ok, ma devi comunque usare le immagini condivise sulla domanda come fanno tutti gli altri. Una volta elaborati, caricalo da qualche parte e modifica la tua risposta per aggiungere i collegamenti. Più tardi modificherò la tua risposta e inserirò le immagini al suo interno.
karlphillip,

Il collegamento sembra contenere ora le immagini corrette.
Dennis Jaheruddin,

22

Usando un approccio abbastanza diverso da quello che ho visto, ho creato un sceneggiatura che rileva gli alberi di Natale dalle loro luci. Il risultato è sempre un triangolo simmetrico e, se necessario, valori numerici come l'angolo ("grasso") dell'albero.

La più grande minaccia a questo algoritmo sono ovviamente le luci accanto (in gran numero) o davanti all'albero (il problema maggiore fino a un'ulteriore ottimizzazione). Modifica (aggiunto): Cosa non può fare: scopri se c'è un albero di Natale o no, trova più alberi di Natale in un'immagine, rileva correttamente un albero di Natale nel mezzo di Las Vegas, rileva alberi di Natale che sono fortemente piegati, sottosopra o tritato ...;)

Le diverse fasi sono:

  • Calcola la luminosità aggiunta (R + G + B) per ciascun pixel
  • Aggiungi questo valore di tutti gli 8 pixel vicini sopra ogni pixel
  • Posiziona tutti i pixel in base a questo valore (prima il più luminoso) - Lo so, non proprio sottile ...
  • Scegli N di questi, partendo dall'alto, saltando quelli troppo vicini
  • Calcola il di questi N superiore (ci fornisce il centro approssimativo dell'albero)
  • Inizia dalla posizione mediana verso l'alto in un raggio di ricerca allargato per la luce più in alto tra quelle più luminose selezionate (le persone tendono a mettere almeno una luce in cima)
  • Da lì, immagina linee che vanno 60 gradi a sinistra e a destra verso il basso (gli alberi di Natale non dovrebbero essere così grassi)
  • Diminuisci quei 60 gradi fino a quando il 20% delle luci più luminose si trova all'esterno di questo triangolo
  • Trova la luce nella parte inferiore del triangolo, dandoti il ​​bordo orizzontale inferiore dell'albero
  • Fatto

Spiegazione dei segni:

  • Grande croce rossa al centro dell'albero: mediana delle prime N luci più luminose
  • Linea tratteggiata da lì verso l'alto: "raggio di ricerca" per la parte superiore dell'albero
  • Croce rossa più piccola: cima dell'albero
  • Croci rosse davvero piccole: tutte le prime N luci più luminose
  • Triangolo rosso: D'uh!

Codice sorgente:

<?php

ini_set('memory_limit', '1024M');

header("Content-type: image/png");

$chosenImage = 6;

switch($chosenImage){
    case 1:
        $inputImage     = imagecreatefromjpeg("nmzwj.jpg");
        break;
    case 2:
        $inputImage     = imagecreatefromjpeg("2y4o5.jpg");
        break;
    case 3:
        $inputImage     = imagecreatefromjpeg("YowlH.jpg");
        break;
    case 4:
        $inputImage     = imagecreatefromjpeg("2K9Ef.jpg");
        break;
    case 5:
        $inputImage     = imagecreatefromjpeg("aVZhC.jpg");
        break;
    case 6:
        $inputImage     = imagecreatefromjpeg("FWhSP.jpg");
        break;
    case 7:
        $inputImage     = imagecreatefromjpeg("roemerberg.jpg");
        break;
    default:
        exit();
}

// Process the loaded image

$topNspots = processImage($inputImage);

imagejpeg($inputImage);
imagedestroy($inputImage);

// Here be functions

function processImage($image) {
    $orange = imagecolorallocate($image, 220, 210, 60);
    $black = imagecolorallocate($image, 0, 0, 0);
    $red = imagecolorallocate($image, 255, 0, 0);

    $maxX = imagesx($image)-1;
    $maxY = imagesy($image)-1;

    // Parameters
    $spread = 1; // Number of pixels to each direction that will be added up
    $topPositions = 80; // Number of (brightest) lights taken into account
    $minLightDistance = round(min(array($maxX, $maxY)) / 30); // Minimum number of pixels between the brigtests lights
    $searchYperX = 5; // spread of the "search beam" from the median point to the top

    $renderStage = 3; // 1 to 3; exits the process early


    // STAGE 1
    // Calculate the brightness of each pixel (R+G+B)

    $maxBrightness = 0;
    $stage1array = array();

    for($row = 0; $row <= $maxY; $row++) {

        $stage1array[$row] = array();

        for($col = 0; $col <= $maxX; $col++) {

            $rgb = imagecolorat($image, $col, $row);
            $brightness = getBrightnessFromRgb($rgb);
            $stage1array[$row][$col] = $brightness;

            if($renderStage == 1){
                $brightnessToGrey = round($brightness / 765 * 256);
                $greyRgb = imagecolorallocate($image, $brightnessToGrey, $brightnessToGrey, $brightnessToGrey);
                imagesetpixel($image, $col, $row, $greyRgb);
            }

            if($brightness > $maxBrightness) {
                $maxBrightness = $brightness;
                if($renderStage == 1){
                    imagesetpixel($image, $col, $row, $red);
                }
            }
        }
    }
    if($renderStage == 1) {
        return;
    }


    // STAGE 2
    // Add up brightness of neighbouring pixels

    $stage2array = array();
    $maxStage2 = 0;

    for($row = 0; $row <= $maxY; $row++) {
        $stage2array[$row] = array();

        for($col = 0; $col <= $maxX; $col++) {
            if(!isset($stage2array[$row][$col])) $stage2array[$row][$col] = 0;

            // Look around the current pixel, add brightness
            for($y = $row-$spread; $y <= $row+$spread; $y++) {
                for($x = $col-$spread; $x <= $col+$spread; $x++) {

                    // Don't read values from outside the image
                    if($x >= 0 && $x <= $maxX && $y >= 0 && $y <= $maxY){
                        $stage2array[$row][$col] += $stage1array[$y][$x]+10;
                    }
                }
            }

            $stage2value = $stage2array[$row][$col];
            if($stage2value > $maxStage2) {
                $maxStage2 = $stage2value;
            }
        }
    }

    if($renderStage >= 2){
        // Paint the accumulated light, dimmed by the maximum value from stage 2
        for($row = 0; $row <= $maxY; $row++) {
            for($col = 0; $col <= $maxX; $col++) {
                $brightness = round($stage2array[$row][$col] / $maxStage2 * 255);
                $greyRgb = imagecolorallocate($image, $brightness, $brightness, $brightness);
                imagesetpixel($image, $col, $row, $greyRgb);
            }
        }
    }

    if($renderStage == 2) {
        return;
    }


    // STAGE 3

    // Create a ranking of bright spots (like "Top 20")
    $topN = array();

    for($row = 0; $row <= $maxY; $row++) {
        for($col = 0; $col <= $maxX; $col++) {

            $stage2Brightness = $stage2array[$row][$col];
            $topN[$col.":".$row] = $stage2Brightness;
        }
    }
    arsort($topN);

    $topNused = array();
    $topPositionCountdown = $topPositions;

    if($renderStage == 3){
        foreach ($topN as $key => $val) {
            if($topPositionCountdown <= 0){
                break;
            }

            $position = explode(":", $key);

            foreach($topNused as $usedPosition => $usedValue) {
                $usedPosition = explode(":", $usedPosition);
                $distance = abs($usedPosition[0] - $position[0]) + abs($usedPosition[1] - $position[1]);
                if($distance < $minLightDistance) {
                    continue 2;
                }
            }

            $topNused[$key] = $val;

            paintCrosshair($image, $position[0], $position[1], $red, 2);

            $topPositionCountdown--;

        }
    }


    // STAGE 4
    // Median of all Top N lights
    $topNxValues = array();
    $topNyValues = array();

    foreach ($topNused as $key => $val) {
        $position = explode(":", $key);
        array_push($topNxValues, $position[0]);
        array_push($topNyValues, $position[1]);
    }

    $medianXvalue = round(calculate_median($topNxValues));
    $medianYvalue = round(calculate_median($topNyValues));
    paintCrosshair($image, $medianXvalue, $medianYvalue, $red, 15);


    // STAGE 5
    // Find treetop

    $filename = 'debug.log';
    $handle = fopen($filename, "w");
    fwrite($handle, "\n\n STAGE 5");

    $treetopX = $medianXvalue;
    $treetopY = $medianYvalue;

    $searchXmin = $medianXvalue;
    $searchXmax = $medianXvalue;

    $width = 0;
    for($y = $medianYvalue; $y >= 0; $y--) {
        fwrite($handle, "\nAt y = ".$y);

        if(($y % $searchYperX) == 0) { // Modulo
            $width++;
            $searchXmin = $medianXvalue - $width;
            $searchXmax = $medianXvalue + $width;
            imagesetpixel($image, $searchXmin, $y, $red);
            imagesetpixel($image, $searchXmax, $y, $red);
        }

        foreach ($topNused as $key => $val) {
            $position = explode(":", $key); // "x:y"

            if($position[1] != $y){
                continue;
            }

            if($position[0] >= $searchXmin && $position[0] <= $searchXmax){
                $treetopX = $position[0];
                $treetopY = $y;
            }
        }

    }

    paintCrosshair($image, $treetopX, $treetopY, $red, 5);


    // STAGE 6
    // Find tree sides
    fwrite($handle, "\n\n STAGE 6");

    $treesideAngle = 60; // The extremely "fat" end of a christmas tree
    $treeBottomY = $treetopY;

    $topPositionsExcluded = 0;
    $xymultiplier = 0;
    while(($topPositionsExcluded < ($topPositions / 5)) && $treesideAngle >= 1){
        fwrite($handle, "\n\nWe're at angle ".$treesideAngle);
        $xymultiplier = sin(deg2rad($treesideAngle));
        fwrite($handle, "\nMultiplier: ".$xymultiplier);

        $topPositionsExcluded = 0;
        foreach ($topNused as $key => $val) {
            $position = explode(":", $key);
            fwrite($handle, "\nAt position ".$key);

            if($position[1] > $treeBottomY) {
                $treeBottomY = $position[1];
            }

            // Lights above the tree are outside of it, but don't matter
            if($position[1] < $treetopY){
                $topPositionsExcluded++;
                fwrite($handle, "\nTOO HIGH");
                continue;
            }

            // Top light will generate division by zero
            if($treetopY-$position[1] == 0) {
                fwrite($handle, "\nDIVISION BY ZERO");
                continue;
            }

            // Lights left end right of it are also not inside
            fwrite($handle, "\nLight position factor: ".(abs($treetopX-$position[0]) / abs($treetopY-$position[1])));
            if((abs($treetopX-$position[0]) / abs($treetopY-$position[1])) > $xymultiplier){
                $topPositionsExcluded++;
                fwrite($handle, "\n --- Outside tree ---");
            }
        }

        $treesideAngle--;
    }
    fclose($handle);

    // Paint tree's outline
    $treeHeight = abs($treetopY-$treeBottomY);
    $treeBottomLeft = 0;
    $treeBottomRight = 0;
    $previousState = false; // line has not started; assumes the tree does not "leave"^^

    for($x = 0; $x <= $maxX; $x++){
        if(abs($treetopX-$x) != 0 && abs($treetopX-$x) / $treeHeight > $xymultiplier){
            if($previousState == true){
                $treeBottomRight = $x;
                $previousState = false;
            }
            continue;
        }
        imagesetpixel($image, $x, $treeBottomY, $red);
        if($previousState == false){
            $treeBottomLeft = $x;
            $previousState = true;
        }
    }
    imageline($image, $treeBottomLeft, $treeBottomY, $treetopX, $treetopY, $red);
    imageline($image, $treeBottomRight, $treeBottomY, $treetopX, $treetopY, $red);


    // Print out some parameters

    $string = "Min dist: ".$minLightDistance." | Tree angle: ".$treesideAngle." deg | Tree bottom: ".$treeBottomY;

    $px     = (imagesx($image) - 6.5 * strlen($string)) / 2;
    imagestring($image, 2, $px, 5, $string, $orange);

    return $topN;
}

/**
 * Returns values from 0 to 765
 */
function getBrightnessFromRgb($rgb) {
    $r = ($rgb >> 16) & 0xFF;
    $g = ($rgb >> 8) & 0xFF;
    $b = $rgb & 0xFF;

    return $r+$r+$b;
}

function paintCrosshair($image, $posX, $posY, $color, $size=5) {
    for($x = $posX-$size; $x <= $posX+$size; $x++) {
        if($x>=0 && $x < imagesx($image)){
            imagesetpixel($image, $x, $posY, $color);
        }
    }
    for($y = $posY-$size; $y <= $posY+$size; $y++) {
        if($y>=0 && $y < imagesy($image)){
            imagesetpixel($image, $posX, $y, $color);
        }
    }
}

// From http://www.mdj.us/web-development/php-programming/calculating-the-median-average-values-of-an-array-with-php/
function calculate_median($arr) {
    sort($arr);
    $count = count($arr); //total numbers in array
    $middleval = floor(($count-1)/2); // find the middle value, or the lowest middle value
    if($count % 2) { // odd number, middle is the median
        $median = $arr[$middleval];
    } else { // even number, calculate avg of 2 medians
        $low = $arr[$middleval];
        $high = $arr[$middleval+1];
        $median = (($low+$high)/2);
    }
    return $median;
}


?>

Immagini: Superiore sinistro Centro inferiore In basso a sinistra In alto a destra Centro superiore In basso a destra

Bonus: un tedesco Weihnachtsbaum, da Wikipedia Römerberg http://commons.wikimedia.org/wiki/File:Weihnachtsbaum_R%C3%B6merberg.jpg


17

Ho usato Python con Opencv.

Il mio algoritmo va così:

  1. Innanzitutto prende il canale rosso dall'immagine
  2. Applicare una soglia (valore minimo 200) al canale rosso
  3. Quindi applicare la sfumatura morfologica e quindi eseguire una "chiusura" (dilatazione seguita da erosione)
  4. Quindi trova i contorni nel piano e seleziona il contorno più lungo.

Il risultato:

Il codice:

import numpy as np
import cv2
import copy


def findTree(image,num):
    im = cv2.imread(image)
    im = cv2.resize(im, (400,250))
    gray = cv2.cvtColor(im, cv2.COLOR_RGB2GRAY)
    imf = copy.deepcopy(im)

    b,g,r = cv2.split(im)
    minR = 200
    _,thresh = cv2.threshold(r,minR,255,0)
    kernel = np.ones((25,5))
    dst = cv2.morphologyEx(thresh, cv2.MORPH_GRADIENT, kernel)
    dst = cv2.morphologyEx(dst, cv2.MORPH_CLOSE, kernel)

    contours = cv2.findContours(dst,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)[0]
    cv2.drawContours(im, contours,-1, (0,255,0), 1)

    maxI = 0
    for i in range(len(contours)):
        if len(contours[maxI]) < len(contours[i]):
            maxI = i

    img = copy.deepcopy(r)
    cv2.polylines(img,[contours[maxI]],True,(255,255,255),3)
    imf[:,:,2] = img

    cv2.imshow(str(num), imf)

def main():
    findTree('tree.jpg',1)
    findTree('tree2.jpg',2)
    findTree('tree3.jpg',3)
    findTree('tree4.jpg',4)
    findTree('tree5.jpg',5)
    findTree('tree6.jpg',6)

    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()

Se cambio il kernel da (25,5) a (10,5) ottengo risultati migliori su tutti gli alberi tranne in basso a sinistra, inserisci qui la descrizione dell'immagine

il mio algoritmo presuppone che l'albero abbia luci su di esso, e nell'albero in basso a sinistra, la parte superiore ha meno luce degli altri.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.