Wie erkennt man einen Weihnachtsbaum? [geschlossen]


382

Mit welchen Bildverarbeitungstechniken kann eine Anwendung implementiert werden, die die in den folgenden Bildern gezeigten Weihnachtsbäume erkennt?

Ich suche nach Lösungen, die an all diesen Bildern funktionieren. Daher sind Ansätze, die das Training von Haarkaskadenklassifikatoren oder den Vorlagenabgleich erfordern, nicht sehr interessant.

Ich suche etwas, das in jeder Programmiersprache geschrieben werden kann, solange es nur Open Source- Technologien verwendet. Die Lösung muss mit den Bildern getestet werden, die für diese Frage freigegeben sind. Es gibt 6 Eingabebilder und die Antwort sollte die Ergebnisse der Verarbeitung jedes einzelnen anzeigen. Schließlich wird für jedes Ausgangsbild muss es rote Linien ziehen , um den erfassten Baum zu umgeben.

Wie würden Sie die Bäume in diesen Bildern programmgesteuert erkennen?


3
Dürfen wir einige der Bilder für das Training verwenden oder sollten alle bereitgestellten Bilder zur Validierung verwendet werden? So oder so, cooler Wettbewerb: D
Hannes Ovrén

7
@karlphillip, möchten Sie, dass wir diese Bilder zum Testen und andere Bilder für das Training verwenden? Es ist nur nicht klar, was das Trainingsset ist.
GilLevi

16
@karlphillip: Mein Rat: Löschen Sie die "Open Source" -Anforderung. Es ist wirklich egal, welche Sprache / welches Framework Sie verwenden. Bildverarbeitungs- / Computer-Vision-Algorithmen sind sprachunabhängig. Wenn Sie sie also in MATLAB schreiben können, können Sie sie mit Sicherheit in OpenCV oder einem anderen von Ihnen bevorzugten Framework ausführen. Außerdem ist mir immer noch nicht klar, was Sie für das Trainieren / Testen von Bildern halten !
Amro

2
@karlphillip Danke, dass Sie uns alle mobilisiert haben, um zu Ihrer 'Suche' beizutragen! Es war eine großartige Gelegenheit, einige Stunden produktiv zu verbringen, aber vor allem, um zu sehen, wie viele verschiedene Ansätze für ein einzelnes Problem gefunden werden können ... Ich hoffe, Sie tun es für den 1. Januar erneut (vielleicht einen Schlitten von Santa Claus 'Herausforderung? ;-))
September

2
OK, ich habe die Frage umformuliert, um die Wettbewerbselemente zu entfernen. Ich denke, das sollte es ihm ermöglichen, für sich allein zu stehen.
Brad Larson

Antworten:


184

Ich habe einen Ansatz, den ich interessant finde und der sich ein bisschen von den anderen unterscheidet. Der Hauptunterschied in meinem Ansatz im Vergleich zu einigen anderen besteht darin, wie der Bildsegmentierungsschritt ausgeführt wird. Ich habe den DBSCAN- Clustering-Algorithmus aus Pythons Scikit-Learn verwendet. Es ist optimiert, um etwas amorphe Formen zu finden, die möglicherweise nicht unbedingt einen einzigen klaren Schwerpunkt haben.

Auf der obersten Ebene ist mein Ansatz ziemlich einfach und kann in ungefähr 3 Schritte unterteilt werden. Zuerst wende ich einen Schwellenwert an (oder tatsächlich das logische "oder" von zwei getrennten und unterschiedlichen Schwellenwerten). Wie bei vielen anderen Antworten nahm ich an, dass der Weihnachtsbaum eines der helleren Objekte in der Szene sein würde, daher ist die erste Schwelle nur ein einfacher monochromer Helligkeitstest. Alle Pixel mit Werten über 220 auf einer Skala von 0 bis 255 (wobei Schwarz 0 und Weiß 255 ist) werden in einem binären Schwarzweißbild gespeichert. Die zweite Schwelle versucht, nach roten und gelben Lichtern zu suchen, die in den Bäumen oben links und unten rechts in den sechs Bildern besonders hervorstechen und sich gut von dem blaugrünen Hintergrund abheben, der auf den meisten Fotos vorherrscht. Ich konvertiere das RGB-Bild in HSV-Raum, und verlangen, dass der Farbton entweder weniger als 0,2 auf einer Skala von 0,0 bis 1,0 (entspricht ungefähr der Grenze zwischen Gelb und Grün) oder mehr als 0,95 (entspricht der Grenze zwischen Lila und Rot) beträgt, und zusätzlich benötige ich helle, gesättigte Farben: Sättigung und Wert müssen beide über 0,7 liegen. Die Ergebnisse der beiden Schwellenwertverfahren sind logisch "oder" miteinander verknüpft, und die resultierende Matrix von Schwarzweiß-Binärbildern ist unten dargestellt:

Weihnachtsbäume, nach dem Schwellenwert auf HSV sowie monochrome Helligkeit

Sie können deutlich sehen, dass jedes Bild eine große Pixelgruppe hat, die ungefähr der Position jedes Baums entspricht, und einige der Bilder haben auch einige andere kleine Gruppen, die entweder Lichtern in den Fenstern einiger Gebäude oder a entsprechen Hintergrundszene am Horizont. Der nächste Schritt besteht darin, den Computer zu erkennen, dass es sich um separate Cluster handelt, und jedes Pixel korrekt mit einer Clustermitgliedschafts-ID zu kennzeichnen.

Für diese Aufgabe habe ich DBSCAN gewählt . Es gibt einen ziemlich guten visuellen Vergleich des Verhaltens von DBSCAN im Vergleich zu anderen hier verfügbaren Clustering-Algorithmen . Wie ich bereits sagte, eignet es sich gut für amorphe Formen. Die Ausgabe von DBSCAN, wobei jeder Cluster in einer anderen Farbe dargestellt ist, wird hier gezeigt:

DBSCAN-Clustering-Ausgabe

Bei der Betrachtung dieses Ergebnisses sind einige Dinge zu beachten. Erstens muss der Benutzer bei DBSCAN einen "Proximity" -Parameter festlegen, um sein Verhalten zu regulieren. Dieser steuert effektiv, wie getrennt ein Punktpaar sein muss, damit der Algorithmus einen neuen separaten Cluster deklarieren kann, anstatt einen Testpunkt zu agglomerieren ein bereits vorhandener Cluster. Ich habe diesen Wert auf das 0,04-fache der Größe entlang der Diagonale jedes Bildes eingestellt. Da die Größe der Bilder von ungefähr VGA bis ungefähr HD 1080 variiert, ist diese Art der skalierungsbezogenen Definition von entscheidender Bedeutung.

Ein weiterer erwähnenswerter Punkt ist, dass der DBSCAN-Algorithmus, wie er in Scikit-Learn implementiert ist, Speicherbeschränkungen aufweist, die für einige der größeren Bilder in diesem Beispiel ziemlich schwierig sind. Daher musste ich für einige der größeren Bilder tatsächlich jeden Cluster "dezimieren" (dh nur jedes 3. oder 4. Pixel beibehalten und die anderen löschen), um innerhalb dieser Grenze zu bleiben. Infolge dieses Keulungsprozesses sind die verbleibenden einzelnen Pixel mit geringer Dichte auf einigen der größeren Bilder schwer zu sehen. Daher wurden die farbcodierten Pixel in den obigen Bildern nur zu Anzeigezwecken effektiv nur geringfügig "erweitert", damit sie besser hervorstechen. Es ist eine rein kosmetische Operation für die Erzählung; obwohl es Kommentare gibt, die diese Erweiterung in meinem Code erwähnen,

Sobald die Cluster identifiziert und beschriftet sind, ist der dritte und letzte Schritt einfach: Ich nehme einfach den größten Cluster in jedem Bild (in diesem Fall habe ich mich entschieden, die "Größe" in Bezug auf die Gesamtzahl der Elementpixel zu messen, obwohl dies möglich ist haben genauso einfach stattdessen eine Art Metrik verwendet, die die physikalische Ausdehnung misst) und die konvexe Hülle für diesen Cluster berechnet. Die konvexe Hülle wird dann zur Baumgrenze. Die sechs konvexen Hüllen, die mit dieser Methode berechnet wurden, sind unten rot dargestellt:

Weihnachtsbäume mit ihren kalkulierten Grenzen

Der Quellcode ist für Python 2.7.6 geschrieben und hängt von numpy , scipy , matplotlib und scikit-learn ab . Ich habe es in zwei Teile geteilt. Der erste Teil ist für die eigentliche Bildverarbeitung verantwortlich:

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

und der zweite Teil ist ein Skript auf Benutzerebene, das die erste Datei aufruft und alle obigen Diagramme generiert:

#!/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()

Die Lösung von @ lennon310 ist Clustering. (k-means)
user3054997

1
@stachyra Ich habe auch über diesen Ansatz nachgedacht, bevor ich meine einfacheren vorgeschlagen habe. Ich denke, dies hat ein großes Potenzial, erweitert und verallgemeinert zu werden, um auch in anderen Fällen gute Ergebnisse zu erzielen. Sie könnten mit neuronalen Netzen zum Clustering experimentieren. So etwas wie ein SOM oder ein neuronales Gas würde hervorragende Arbeit leisten. Trotzdem toller Vorschlag und Daumen hoch von mir!
September

4
@Faust & Ryan Carlson: Danke, Leute! Ja, ich bin damit einverstanden, dass das Upvote-System, obwohl es gut für die Beurteilung von 2 oder 3 kurzen Antworten geeignet ist, die alle innerhalb weniger Stunden eingereicht wurden, ernsthafte Vorurteile aufweist, wenn es um Wettbewerbe mit langen Antworten geht, die sich über längere Zeiträume erstrecken . Zum einen sammeln frühe Einreichungen Upvotes, bevor spätere überhaupt zur öffentlichen Überprüfung verfügbar sind. Und wenn die Antworten alle langwierig sind, gibt es oft einen "Bandwagon-Effekt", sobald man einen bescheidenen Vorsprung aufbaut, da die Leute nur den ersten positiv bewerten, ohne sich die Mühe zu machen, den Rest zu lesen.
Stachyra

2
@ Stachyra gute Nachricht Freund! Herzlichen Glückwunsch und möge dies ein Beginn für Ihr neues Jahr sein!
September

1
@ lennon310: Ich habe noch keinen lokalen Filter für die maximale Erkennung dieses Problems ausprobiert, aber wenn Sie ihn selbst untersuchen möchten, enthält scipy diesen . Mein Python-Quellcode für dieses Projekt war so kurz, dass ich tatsächlich 100% davon veröffentlichen konnte. Im wahrsten Sinne des Wortes müssen Sie nur meine beiden Codefragmente kopieren und in separate .py-Dateien einfügen und dann einen Aufruf an scipy.ndimage.filters.maximum_filter()derselben Stelle ersetzen, an der ich einen Schwellenwert verwendet habe.
Stachyra

145

HINWEIS BEARBEITEN: Ich habe diesen Beitrag bearbeitet, um (i) jedes Baumbild einzeln zu verarbeiten, wie in den Anforderungen gefordert, (ii) um sowohl die Objekthelligkeit als auch die Form zu berücksichtigen, um die Qualität des Ergebnisses zu verbessern.


Im Folgenden wird ein Ansatz vorgestellt, der die Helligkeit und Form des Objekts berücksichtigt. Mit anderen Worten, es wird nach Objekten mit dreieckiger Form und signifikanter Helligkeit gesucht. Es wurde in Java unter Verwendung des Marvin- Bildverarbeitungsframeworks implementiert .

Der erste Schritt ist die Farbschwelle. Ziel ist es, die Analyse auf Objekte mit erheblicher Helligkeit zu konzentrieren.

Ausgabebilder:

Quellcode:

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

Im zweiten Schritt werden die hellsten Punkte im Bild erweitert, um Formen zu bilden. Das Ergebnis dieses Prozesses ist die wahrscheinliche Form der Objekte mit signifikanter Helligkeit. Bei Anwendung der Flutungssegmentierung werden nicht verbundene Formen erkannt.

Ausgabebilder:

Quellcode:

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

Wie im Ausgabebild gezeigt, wurden mehrere Formen erkannt. Bei diesem Problem gibt es nur wenige helle Punkte in den Bildern. Dieser Ansatz wurde jedoch implementiert, um komplexere Szenarien zu behandeln.

Im nächsten Schritt wird jede Form analysiert. Ein einfacher Algorithmus erkennt Formen mit einem Muster ähnlich einem Dreieck. Der Algorithmus analysiert die Objektform zeilenweise. Wenn der Massenmittelpunkt jeder Formlinie nahezu gleich ist (bei einem Schwellenwert) und die Masse mit zunehmendem y zunimmt, hat das Objekt eine dreieckige Form. Die Masse der Formlinie ist die Anzahl der Pixel in dieser Linie, die zur Form gehört. Stellen Sie sich vor, Sie schneiden das Objekt horizontal und analysieren jedes horizontale Segment. Wenn sie zueinander zentralisiert sind und die Länge in einem linearen Muster vom ersten zum letzten Segment zunimmt, haben Sie wahrscheinlich ein Objekt, das einem Dreieck ähnelt.

Quellcode:

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;
}

Schließlich wird die Position jeder Form, die einem Dreieck ähnelt und eine signifikante Helligkeit aufweist, in diesem Fall ein Weihnachtsbaum, im Originalbild hervorgehoben, wie unten gezeigt.

endgültige Ausgabebilder:

endgültiger Quellcode:

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

Der Vorteil dieses Ansatzes ist die Tatsache, dass er wahrscheinlich mit Bildern funktioniert, die andere leuchtende Objekte enthalten, da er die Objektform analysiert.

Fröhliche Weihnachten!


BEARBEITEN SIE HINWEIS 2

Es gibt eine Diskussion über die Ähnlichkeit der Ausgabebilder dieser Lösung und einiger anderer. In der Tat sind sie sehr ähnlich. Dieser Ansatz segmentiert jedoch nicht nur Objekte. In gewissem Sinne werden auch die Objektformen analysiert. Es kann mehrere leuchtende Objekte in derselben Szene verarbeiten. Tatsächlich muss der Weihnachtsbaum nicht der hellste sein. Ich schreibe es nur auf, um die Diskussion zu bereichern. In den Beispielen gibt es eine Tendenz, dass Sie nur nach dem hellsten Objekt suchen und die Bäume finden. Aber wollen wir die Diskussion an dieser Stelle wirklich beenden? Inwieweit erkennt der Computer zu diesem Zeitpunkt tatsächlich ein Objekt, das einem Weihnachtsbaum ähnelt? Versuchen wir, diese Lücke zu schließen.

Nachfolgend finden Sie ein Ergebnis, um diesen Punkt zu erläutern:

Eingabebild

Geben Sie hier die Bildbeschreibung ein

Ausgabe

Geben Sie hier die Bildbeschreibung ein


2
Das ist interessant. Ich hoffe, Sie können die gleichen Ergebnisse erzielen, wenn jedes Bild einzeln verarbeitet wird. Ich habe die Frage 4 Stunden zuvor bearbeitet, bevor Sie die Antwort veröffentlicht haben, um dies ausdrücklich anzugeben. Es wäre fantastisch, wenn Sie Ihre Antwort mit diesen Ergebnissen aktualisieren könnten.
Karlphillip

@ Marvin in Ihrer Dreieckserkennung, wie sind Sie mit der Schwankung der Masse umgegangen? Es ist kein striktes Dreieck, die Masse ist nicht mono, wenn sich y ändert
user3054997

2
@ user3054997: Das ist ein weiterer Punkt. Wie ich bereits geschrieben habe, sucht der Algorithmus nicht nach den strengen Dreiecksformen. Es analysiert jedes Objekt und betrachtet einen Baum, der einem Dreieck mit einfachen Kriterien "ähnelt": Die Masse des Objekts wird verwendet, um mit zunehmendem y zuzunehmen, und der Massenmittelpunkt jedes horizontalen Objektsegments ist nahezu zentral zueinander .
Gabriel Ambrósio Archanjo

@ Marvin Meine Lösung ist wirklich einfach, ich habe es auch in meiner Antwort angegeben. Übrigens hat es besser funktioniert als Ihre erste Lösung. Wenn ich mich richtig erinnere, haben Sie in Ihrer ersten Antwort über Feature-Deskriptoren gesprochen, um kleine Lichttexturen zu erkennen, was Sie hier nicht tun. Ich habe einfach gesagt, dass Ihr aktueller Ansatz und Ihre Ergebnisse meinem viel ähnlicher sind als Ihrer ersten Lösung. Natürlich erwarte ich nicht, dass Sie es zugeben, ich habe es nur für die Aufzeichnung angegeben.
Smeso

1
@sepdek Es gibt hier ein paar Lösungen, die wirklich viel besser sind als meine und die immer noch die Hälfte meiner Upvotes bekommen. Es ist nichts Falsches daran, sich von anderen Lösungen "inspirieren zu lassen". Ich habe auch Ihre Lösungen gesehen, ich habe nichts gegen Sie zu sagen, Sie haben sie nach mir gepostet und meine "Idee" war nicht so originell zu sagen, dass Sie mich nur kopiert haben. Aber Marvin war der einzige, der vor mir gepostet und seine Lösung bearbeitet hat, nachdem er meine mit demselben Algorithmus gesehen hatte ... zumindest hätte er sagen können: "Ja, Ihre Lösung hat mir gefallen und ich habe sie wiederverwendet." Es ist nichts falsch, es ist einfach ein Spiel.
Smeso

75

Hier ist meine einfache und dumme Lösung. Es basiert auf der Annahme, dass der Baum das hellste und größte Ding auf dem Bild sein wird.

//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;
}

Der erste Schritt besteht darin, die hellsten Pixel im Bild zu erkennen, aber wir müssen zwischen dem Baum selbst und dem Schnee unterscheiden, der sein Licht reflektiert. Hier versuchen wir, den Schnee auszuschließen, indem wir einen wirklich einfachen Filter auf die Farbcodes anwenden:

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

Dann finden wir jedes "helle" Pixel:

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

Schließlich verbinden wir die beiden Ergebnisse:

bitwise_and(tmp, tmp1, tmp1);

Jetzt suchen wir nach dem größten hellen Objekt:

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

Jetzt haben wir es fast geschafft, aber es gibt immer noch einige Unvollkommenheiten aufgrund des Schnees. Um sie abzuschneiden, erstellen wir eine Maske mit einem Kreis und einem Rechteck, um die Form eines Baumes zu approximieren und unerwünschte Teile zu löschen:

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

Der letzte Schritt besteht darin, die Kontur unseres Baumes zu finden und auf das Originalbild zu zeichnen.

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

Es tut mir leid, aber im Moment habe ich eine schlechte Verbindung, so dass ich keine Bilder hochladen kann. Ich werde es später versuchen.

Fröhliche Weihnachten.

BEARBEITEN:

Hier einige Bilder der endgültigen Ausgabe:


1
Hallo! Stellen Sie sicher, dass Ihre Antwort allen Anforderungen entspricht: Es gibt 6 Eingabebilder, und in der Antwort sollten die Ergebnisse der Verarbeitung jedes einzelnen angezeigt werden. .
Karlphillip

Hallo! Sie können Dateinamen als CLI-Argumente an mein Programm übergeben : ./christmas_tree ./*.png. Sie können beliebig viele sein. Die Ergebnisse werden nacheinander angezeigt, wenn Sie eine beliebige Taste drücken. Ist das falsch?
smeso

Es ist in Ordnung, aber Sie müssen die Bilder trotzdem hochladen und in Ihrer Frage freigeben, damit die Betrachter des Threads Ihr Ergebnis tatsächlich sehen können. Wenn Sie die Leute sehen lassen, was Sie getan haben, verbessern Sie Ihre Chancen, Stimmen zu erhalten;)
Karlphillip

Ich versuche eine Lösung dafür zu finden. Ich habe einige Verbindungsprobleme.
Smeso

2
Großartig! Jetzt können Sie sie in der Antwort mit dem folgenden Code neu <img src="http://i.stack.imgur.com/nmzwj.png" width="210" height="150">
skalieren

60

Ich habe den Code in Matlab R2007a geschrieben. Ich habe k-means verwendet, um den Weihnachtsbaum grob zu extrahieren. Ich werde mein Zwischenergebnis nur mit einem Bild und die Endergebnisse mit allen sechs zeigen.

Zuerst habe ich den RGB-Raum auf den Lab-Raum abgebildet, wodurch der Kontrast von Rot in seinem b-Kanal verbessert werden könnte:

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

Geben Sie hier die Bildbeschreibung ein

Neben dem Merkmal im Farbraum habe ich auch ein Texturmerkmal verwendet, das für die Nachbarschaft relevant ist und nicht für jedes Pixel selbst. Hier habe ich die Intensität der 3 Originalkanäle (R, G, B) linear kombiniert. Der Grund, warum ich auf diese Weise formatiert habe, ist, dass die Weihnachtsbäume auf dem Bild alle rote Lichter und manchmal auch grüne / manchmal blaue Beleuchtung haben.

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

Geben Sie hier die Bildbeschreibung ein

Ich habe ein lokales 3X3-Binärmuster I0angewendet, das mittlere Pixel als Schwellenwert verwendet und den Kontrast erhalten, indem ich die Differenz zwischen dem mittleren Pixelintensitätswert über dem Schwellenwert und dem Mittelwert darunter berechnet habe.

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

Geben Sie hier die Bildbeschreibung ein

Da ich insgesamt 4 Features habe, würde ich in meiner Clustering-Methode K = 5 wählen. Der Code für k-means ist unten dargestellt (er stammt aus dem maschinellen Lernkurs von Dr. Andrew Ng. Ich habe den Kurs zuvor besucht und den Code selbst in seine Programmieraufgabe geschrieben).

[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

Da das Programm auf meinem Computer sehr langsam läuft, habe ich nur 3 Iterationen ausgeführt. Normalerweise ist das Stoppkriterium (i) eine Iterationszeit von mindestens 10 oder (ii) keine Änderung mehr an den Zentroiden. Nach meinem Test kann eine Erhöhung der Iteration den Hintergrund (Himmel und Baum, Himmel und Gebäude, ...) genauer unterscheiden, zeigte jedoch keine drastischen Änderungen bei der Weihnachtsbaumextraktion. Beachten Sie auch, dass k-means nicht gegen die zufällige Schwerpunktinitialisierung immun ist. Daher wird empfohlen, das Programm mehrmals auszuführen, um einen Vergleich durchzuführen.

Nach dem k-Mittel wurde der markierte Bereich mit der maximalen Intensität von I0gewählt. Die Grenzverfolgung wurde verwendet, um die Grenzen zu extrahieren. Für mich ist der letzte Weihnachtsbaum am schwierigsten zu extrahieren, da der Kontrast in diesem Bild nicht hoch genug ist, wie in den ersten fünf. Ein weiteres Problem bei meiner Methode ist, dass ich die bwboundariesFunktion in Matlab verwendet habe, um die Grenze zu verfolgen, aber manchmal sind auch die inneren Grenzen enthalten, wie Sie in den Ergebnissen 3, 5 und 6 sehen können. Die dunkle Seite innerhalb der Weihnachtsbäume lässt sich nicht nur nicht mit der beleuchteten Seite gruppieren, sondern führt auch dazu, dass so viele winzige innere Grenzen nachgezeichnet werden ( imfillverbessert sich nicht sehr). Insgesamt hat mein Algorithmus noch viel Verbesserungsraum.

Einige Veröffentlichungen weisen darauf hin, dass die Mittelwertverschiebung robuster sein kann als die k-Mittelwerte, und viele auf Graph-Cut basierende Algorithmen sind auch bei der Segmentierung komplizierter Grenzen sehr wettbewerbsfähig. Ich habe selbst einen Mean-Shift-Algorithmus geschrieben, der die Regionen ohne genügend Licht besser zu extrahieren scheint. Die Mittelwertverschiebung ist jedoch etwas übersegmentiert, und es ist eine Strategie der Zusammenführung erforderlich. Es lief sogar viel langsamer als k-means in meinem Computer, ich fürchte, ich muss es aufgeben. Ich freue mich sehr darauf, dass andere hier mit den oben genannten modernen Algorithmen hervorragende Ergebnisse erzielen.

Ich glaube jedoch immer, dass die Merkmalsauswahl die Schlüsselkomponente bei der Bildsegmentierung ist. Mit einer geeigneten Merkmalsauswahl, die den Abstand zwischen Objekt und Hintergrund maximieren kann, funktionieren viele Segmentierungsalgorithmen definitiv. Verschiedene Algorithmen können das Ergebnis von 1 auf 10 verbessern, aber die Merkmalsauswahl kann es von 0 auf 1 verbessern.

Fröhliche Weihnachten !


2
Danke für die Antwort! Ich wollte nur darauf hinweisen, dass Matlab nicht Open Source ist , sondern Scilab . Ich würde auch gerne sehen, wie diese Antwort mit den anderen konkurriert. ;)
Karlphillip

6
Danke Karl. Octave ist eine weitere Open-Source-Software, die fast dieselbe Codierungsgrammatik wie Matlab verwendet: mathworks.fr/matlabcentral/answers/14399-gnu-octave-vs-matlab .
Lennon310

Interessant, das wusste ich nicht, danke! Funktioniert Ihr Code in Octave?
Karlphillip

Ich habe noch nicht getestet, aber ich denke, es ist kein Problem :)
Lennon310

@ lennon310 Ich denke, wenn Sie die Grenzen fallen lassen und die konvexe Hülle bekommen, werden Sie das Lochproblem los. Denken Sie daran, dass die konvexe Hülle der kleinste Bereich ist, der alle Punkte in einem Satz enthält.
September

57

Dies ist mein letzter Beitrag unter Verwendung der traditionellen Bildverarbeitungsansätze ...

Hier kombiniere ich irgendwie meine beiden anderen Vorschläge, um noch bessere Ergebnisse zu erzielen . Tatsächlich kann ich nicht sehen, wie diese Ergebnisse besser sein könnten (insbesondere wenn Sie sich die maskierten Bilder ansehen, die die Methode erzeugt).

Im Zentrum des Ansatzes steht die Kombination von drei Hauptannahmen :

  1. Bilder sollten starke Schwankungen in den Baumregionen aufweisen
  2. Bilder sollten in den Baumregionen eine höhere Intensität haben
  3. Hintergrundregionen sollten eine geringe Intensität haben und größtenteils blau sein

Unter Berücksichtigung dieser Annahmen funktioniert die Methode wie folgt:

  1. Konvertieren Sie die Bilder in HSV
  2. Filtern Sie den V-Kanal mit einem LoG-Filter
  3. Wenden Sie einen harten Schwellenwert auf das LoG-gefilterte Bild an, um die Aktivitätsmaske A zu erhalten
  4. Wenden Sie eine harte Schwellwertbildung auf den V-Kanal an, um die Intensitätsmaske B zu erhalten
  5. Wenden Sie die H-Kanal-Schwellwertbildung an, um blau gefärbte Bereiche mit geringer Intensität in der Hintergrundmaske C zu erfassen
  6. Kombinieren Sie Masken mit UND, um die endgültige Maske zu erhalten
  7. Erweitern Sie die Maske, um Bereiche zu vergrößern und verteilte Pixel zu verbinden
  8. Beseitigen Sie kleine Regionen und erhalten Sie die endgültige Maske, die schließlich nur den Baum darstellt

Hier ist der Code in MATLAB (wieder lädt das Skript alle JPG-Bilder in den aktuellen Ordner und dies ist wiederum weit davon entfernt, ein optimierter Code zu sein):

% 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

Ergebnisse

Ergebnisse

Hier sind noch hochauflösende Ergebnisse verfügbar!
Weitere Experimente mit zusätzlichen Bildern finden Sie hier.


1
Tolles Zeug! Bitte stellen Sie sicher, dass Ihre anderen Antworten ebenfalls diesem Format entsprechen. Um um das Kopfgeld zu kämpfen, müssen Sie eine Open-Source- Technologie verwenden, und leider gehört Matlab nicht dazu. SciLab und Octave bieten jedoch eine ähnliche Syntax und Funktionen. ;)
Karlphillip

Der
Oktavcode

@karlphillip Irgendwie hatte diese Frage ein Matlab-Tag. Wenn Open Source wirklich ein Muss ist, würde ich empfehlen, es zu entfernen.
Dennis Jaheruddin

@sepdek Sehr schön, vielleicht könnte noch etwas getan werden, um die 'Löcher' in das endgültige Bild aufzunehmen. (Fügen Sie alle Pixel hinzu, die vollständig von genehmigten Pixeln umgeben sind
?!

1
@ Karlphillip Danke Mann! Ich bin froh, dass Sie meinen Ansatz interessant fanden. Außerdem möchte ich Ihnen zur Auswahl der elegantesten Lösung gratulieren und nicht der mit den meisten Stimmen !!!
September

36

Meine Lösungsschritte:

  1. R-Kanal abrufen (von RGB) - alle Operationen, die wir auf diesem Kanal ausführen:

  2. Region of Interest (ROI) erstellen

    • Schwellenwert R-Kanal mit Mindestwert 149 (Bild oben rechts)

    • Ergebnisbereich erweitern (Bild Mitte links)

  3. Erkennen Sie Eges in berechneten Roi. Baum hat viele Kanten (Bild Mitte rechts)

    • Ergebnis erweitern

    • Erodieren mit größerem Radius (Bild unten links)

  4. Wählen Sie das größte (nach Fläche) Objekt aus - es ist die Ergebnisregion

  5. ConvexHull (Baum ist konvexes Polygon) (Bild unten rechts)

  6. Begrenzungsrahmen (Bild unten rechts - Grren-Feld)

Schritt für Schritt: Geben Sie hier die Bildbeschreibung ein

Das erste Ergebnis - am einfachsten, aber nicht in Open Source-Software - "Adaptive Vision Studio + Adaptive Vision Library": Dies ist kein Open Source, aber sehr schnell zum Prototyp:

Ganzer Algorithmus zur Erkennung des Weihnachtsbaums (11 Blöcke): AVL-Lösung

Nächster Schritt. Wir wollen eine Open Source Lösung. Ändern Sie AVL-Filter in OpenCV-Filter: Hier habe ich kleine Änderungen vorgenommen, z. B. Kantenerkennung. Verwenden Sie den cvCanny-Filter. Um dies zu respektieren, habe ich das Regionsbild mit dem Kantenbild multipliziert, um das größte Element auszuwählen, das ich gefunden habe.

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

OpenCV-Lösung

Ich kann jetzt keine Bilder mit Zwischenschritten anzeigen, da ich nur 2 Links einfügen kann.

Ok, jetzt verwenden wir OpenSource-Filter, aber es ist noch nicht ganz Open Source. Letzter Schritt - Port auf C ++ - Code. Ich habe OpenCV in Version 2.4.4 verwendet

Das Ergebnis des endgültigen C ++ - Codes ist: Geben Sie hier die Bildbeschreibung ein

C ++ - Code ist auch ziemlich kurz:

#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;
}

Welcher Compiler kann dieses Programm fehlerfrei erstellen?
Karlphillip

Ich habe Visual Studio 2012 verwendet, um es zu erstellen. Sie sollten den C ++ - Compiler mit Unterstützung für C ++ 11 verwenden.
AdamF

Damit habe ich kein System zur Verfügung. Könnten Sie den std::max_element()Anruf umschreiben ? Ich möchte auch Ihre Antwort belohnen. Ich glaube ich habe gcc 4.2.
Karlphillip

Ok, das ist die C ++ 11-Funktion;) Ich habe den obigen Quellcode geändert. Bitte versuchen Sie es jetzt.
AdamF

Gut, danke. Ich habe es getestet und es ist wunderschön. Sobald diese Frage erneut geöffnet wird (andere Benutzer müssen mir dabei helfen), kann ich ein weiteres Kopfgeld festlegen, um Sie zu belohnen. Herzliche Glückwünsche!
Karlphillip

31

... eine weitere altmodische Lösung - rein basierend auf der HSV-Verarbeitung :

  1. Konvertieren Sie Bilder in den HSV-Farbraum
  2. Erstellen Sie Masken gemäß Heuristik im HSV (siehe unten)
  3. Wenden Sie eine morphologische Erweiterung auf die Maske an, um nicht verbundene Bereiche zu verbinden
  4. Verwerfen Sie kleine Bereiche und horizontale Blöcke (denken Sie daran, dass Bäume vertikale Blöcke sind).
  5. Berechnen Sie den Begrenzungsrahmen

Ein Wort zur Heuristik in der HSV-Verarbeitung:

  1. Alles mit Farbtönen (H) zwischen 210 und 320 Grad wird als blau-magenta verworfen, das sich im Hintergrund oder in nicht relevanten Bereichen befinden soll
  2. Alles mit Werten (V) unter 40% wird ebenfalls als zu dunkel verworfen, um relevant zu sein

Natürlich kann man mit zahlreichen anderen Möglichkeiten experimentieren, um diesen Ansatz zu verfeinern ...

Hier ist der MATLAB-Code, um den Trick auszuführen (Warnung: Der Code ist weit davon entfernt, optimiert zu werden !!! Ich habe Techniken verwendet, die für die MATLAB-Programmierung nicht empfohlen wurden, nur um irgendetwas im Prozess verfolgen zu können - dies kann stark optimiert werden):

% 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

Ergebnisse:

In den Ergebnissen zeige ich das maskierte Bild und den Begrenzungsrahmen. Geben Sie hier die Bildbeschreibung ein


Hallo, danke für die Antwort. Bitte nehmen Sie sich einen Moment Zeit, um den Abschnitt Anforderungen zu lesen und sicherzustellen, dass Ihre Antwort allen Anweisungen entspricht. Sie haben vergessen, die resultierenden Bilder zu teilen. ;)
Karlphillip

2
@karlphillip sepdek hat nicht genug Ruf, um Bilder zu teilen. Ich habe Bilder gemäß seinem Link und seinen Anweisungen in den Antworttext verschoben. Sie sind sich jedoch nicht sicher, ob dies die richtigen sind. Sie können diesen Teil gerne kommentieren.
alko

@alko Ich weiß, danke. Einige der von Ihnen freigegebenen Bilder befanden sich jedoch nicht im Eingabesatz . Die Antwort muss das Ergebnis der Verarbeitung aller 6 auf der Frage freigegebenen Bilder enthalten.
Karlphillip

@ Karlphillip, das sind seine Bilder, nicht meine. das ist genau das, was ich mit "diesen Teil kommentieren" gemeint habe;)
alko

2
Entschuldigung für die Probleme ... nicht meine Absicht. Ich habe alle Bilder in den ursprünglichen Datensatz aufgenommen und ihn mit noch mehr erweitert, um zu beweisen, dass mein Konzept robust ist ...
September

23

Einige altmodische Bildverarbeitungsansätze ...
Die Idee basiert auf der Annahme, dass Bilder beleuchtete Bäume auf normalerweise dunkleren und glatteren Hintergründen (oder in einigen Fällen im Vordergrund) darstellen. Der beleuchtete Baumbereich ist "energetischer" und hat eine höhere Intensität .
Der Prozess ist wie folgt:

  1. In Graylevel konvertieren
  2. Wenden Sie die LoG-Filterung an, um die "aktivsten" Bereiche zu erhalten
  3. Wenden Sie einen absichtlichen Schwellenwert an, um die hellsten Bereiche zu erhalten
  4. Kombinieren Sie die vorherigen 2, um eine vorläufige Maske zu erhalten
  5. Wenden Sie eine morphologische Erweiterung an, um Bereiche zu vergrößern und benachbarte Komponenten zu verbinden
  6. Beseitigen Sie kleine Kandidatenbereiche entsprechend ihrer Flächengröße

Was Sie erhalten, ist eine binäre Maske und ein Begrenzungsrahmen für jedes Bild.

Hier sind die Ergebnisse mit dieser naiven Technik: Geben Sie hier die Bildbeschreibung ein

Code auf MATLAB folgt: Der Code wird in einem Ordner mit JPG-Bildern ausgeführt. Lädt alle Bilder und gibt erkannte Ergebnisse zurück.

% 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

Vergessen Sie nicht, die resultierenden Bilder hochzuladen, wie es Faust getan hat.
Karlphillip

Ich bin ein Noob hier, daher kann ich keine Bilder hochladen. Bitte sehen Sie die Ergebnisse auf den bereitgestellten Links in meiner Beschreibung.
27.

Ok, aber Sie müssen immer noch die Bilder verwenden, die für die Frage freigegeben wurden, wie es alle anderen tun. Sobald Sie sie verarbeitet haben, laden Sie sie irgendwo hoch und bearbeiten Sie Ihre Antwort, um die Links hinzuzufügen. Später werde ich Ihre Antwort bearbeiten und die Bilder für Sie darin platzieren.
Karlphillip

Der Link scheint jetzt die richtigen Bilder zu enthalten.
Dennis Jaheruddin

22

Mit einem ganz anderen Ansatz als dem, was ich gesehen habe, habe ich einen erstellt Skript, das Weihnachtsbäume an ihren Lichtern erkennt. Das Ergebnis ist immer ein symmetrisches Dreieck und gegebenenfalls numerische Werte wie der Winkel ("Fett") des Baumes.

Die größte Bedrohung für diesen Algorithmus sind offensichtlich Lichter neben (in großer Anzahl) oder vor dem Baum (das größere Problem bis zur weiteren Optimierung). Bearbeiten (hinzugefügt): Was es nicht kann: Finden Sie heraus, ob es einen Weihnachtsbaum gibt oder nicht, finden Sie mehrere Weihnachtsbäume in einem Bild, erkennen Sie einen Weihnachtsbaum mitten in Las Vegas korrekt, erkennen Sie Weihnachtsbäume, die stark verbogen sind. verkehrt herum oder gehackt ...;)

Die verschiedenen Stufen sind:

  • Berechnen Sie die hinzugefügte Helligkeit (R + G + B) für jedes Pixel
  • Addieren Sie diesen Wert aller 8 benachbarten Pixel über jedem Pixel
  • Ordnen Sie alle Pixel nach diesem Wert (am hellsten zuerst) - ich weiß, nicht wirklich subtil ...
  • Wählen Sie N von diesen, beginnend von oben, und überspringen Sie diejenigen, die zu nahe sind
  • Berechne das von diesen oberen N (gibt uns die ungefähre Mitte des Baumes)
  • Beginnen Sie von der Medianposition aufwärts in einem erweiterten Suchstrahl nach dem obersten Licht der ausgewählten hellsten (Personen neigen dazu, mindestens ein Licht ganz oben zu platzieren).
  • Stellen Sie sich von dort aus Linien vor, die um 60 Grad nach links und rechts nach unten gehen (Weihnachtsbäume sollten nicht so fett sein).
  • Verringern Sie diese 60 Grad, bis 20% der hellsten Lichter außerhalb dieses Dreiecks liegen
  • Suchen Sie das Licht ganz unten im Dreieck und erhalten Sie den unteren horizontalen Rand des Baums
  • Erledigt

Erläuterung der Markierungen:

  • Großes rotes Kreuz in der Mitte des Baumes: Median der obersten N hellsten Lichter
  • Gepunktete Linie von dort nach oben: "Suchstrahl" für die Spitze des Baumes
  • Kleineres rotes Kreuz: Spitze des Baumes
  • Wirklich kleine rote Kreuze: Alle N hellsten Lichter
  • Rotes Dreieck: D'uh!

Quellcode:

<?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;
}


?>

Bilder: Oben links Untere Mitte Unten links Oben rechts Obere Mitte Rechts unten

Bonus: Ein deutscher Weihnachtsbaum aus Wikipedia Römerberg http://commons.wikimedia.org/wiki/File:Weihnachtsbaum_R%C3%B6merberg.jpg


17

Ich habe Python mit opencv verwendet.

Mein Algorithmus sieht folgendermaßen aus:

  1. Zuerst wird der rote Kanal aus dem Bild genommen
  2. Wenden Sie einen Schwellenwert (Mindestwert 200) auf den roten Kanal an
  3. Wenden Sie dann den morphologischen Gradienten an und führen Sie dann ein "Schließen" durch (Erweiterung, gefolgt von Erosion).
  4. Dann findet es die Konturen in der Ebene und wählt die längste Kontur aus.

Das Ergebnis:

Der Code:

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

Wenn ich den Kernel von (25,5) auf (10,5) ändere, erhalte ich bessere Ergebnisse für alle Bäume außer links unten. Geben Sie hier die Bildbeschreibung ein

Mein Algorithmus geht davon aus, dass der Baum beleuchtet ist, und im unteren linken Baum hat der obere weniger Licht als die anderen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.