webentwicklung-frage-antwort-db.com.de

Wie erkennt man einen Weihnachtsbaum?

Welche Bildverarbeitungstechniken könnten verwendet werden, um eine Anwendung zu implementieren, die die in den folgenden Bildern angezeigten Weihnachtsbäume erkennt?

Ich suche nach Lösungen, die mit all diesen Bildern funktionieren. Aus diesem Grund sind Ansätze, die ein Training erfordern Haar-Kaskaden-Klassifikatoren oder Template-Matching, nicht sehr interessant.

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

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

373
karlphillip

Ich habe einen Ansatz, den ich interessant finde und der sich ein bisschen vom Rest unterscheidet. Der Hauptunterschied zwischen meinem Ansatz und einigen anderen besteht darin, wie der Bildsegmentierungsschritt durchgeführt wird - ich habe den Clustering-Algorithmus DBSCAN von Python verwendet scikit-learn; Es ist für die Suche nach etwas amorphen Formen optimiert, die nicht unbedingt einen einzigen klaren Schwerpunkt haben müssen.

Auf der obersten Ebene ist mein Ansatz ziemlich einfach und kann in etwa 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 ging ich davon aus, dass der Weihnachtsbaum eines der helleren Objekte in der Szene ist. Die erste Schwelle ist also nur ein einfacher Test der Schwarzweißhelligkeit. 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 der sechs Bilder besonders hervorzuheben sind, und hebt sich gut vom blaugrünen Hintergrund ab, der in den meisten Fotos vorherrscht. Ich konvertiere das RGB-Bild in einen HSV-Raum und fordere, dass der Farbton entweder weniger als 0,2 auf einer Skala von 0,0 bis 1,0 (entspricht in etwa 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 Schwellenwertprozeduren sind logisch "oder" verknüpft, und die resultierende Matrix von Schwarzweiß-Binärbildern ist nachstehend gezeigt:

Christmas trees, after thresholding on HSV as well as monochrome brightness

Sie können deutlich erkennen, dass jedes Bild einen großen Pixelcluster aufweist, der ungefähr der Position jedes Baums entspricht, und dass einige der Bilder auch einige andere kleine Cluster aufweisen, die entweder Lichtern in den Fenstern einiger Gebäude oder a entsprechen Hintergrundszene am Horizont. Der nächste Schritt besteht darin, den Computer zu veranlassen, zu erkennen, dass es sich um separate Cluster handelt, und jedes Pixel korrekt mit einer ID-Nummer für die Clustermitgliedschaft zu kennzeichnen.

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

DBSCAN clustering output

Bei der Betrachtung dieses Ergebnisses sind einige Dinge zu beachten. Erstens verlangt DBSCAN, dass der Benutzer einen "Proximity" -Parameter einstellt, um sein Verhalten zu regulieren, wodurch effektiv gesteuert wird, wie getrennt ein Punktepaar sein muss, damit der Algorithmus einen neuen separaten Cluster deklariert, anstatt einen Testpunkt zu agglomerieren ein bereits bestehender Cluster. Ich habe diesen Wert auf das 0,04-fache der Größe entlang der Diagonalen jedes Bildes festgelegt. Da die Größe der Bilder von ungefähr VGA bis zu 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, Speichergrenzen aufweist, die für einige der größeren Bilder in diesem Beispiel ziemlich herausfordernd sind. Daher musste ich für einige der größeren Bilder tatsächlich jeden Cluster "dezimieren" (d. H. Nur jedes dritte oder vierte Pixel beibehalten und die anderen fallen lassen), um innerhalb dieser Grenze zu bleiben. Infolge dieses Ausleseprozesses sind die verbleibenden einzelnen dünnbesetzten Pixel 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 "gedehnt", damit sie besser hervorstechen. Es ist lediglich eine kosmetische Operation, um der Erzählung willen; Obwohl es Kommentare gibt, die diese Erweiterung in meinem Code erwähnen, können Sie sicher sein, dass sie nichts mit Berechnungen zu tun hat, die tatsächlich von Bedeutung sind.

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 die "Größe" in Bezug auf die Gesamtzahl der Elementpixel gemessen, obwohl dies möglich ist Verwenden Sie stattdessen einfach eine Art Metrik, die die physikalische Ausdehnung misst, und berechnen Sie die konvexe Hülle für diesen Cluster. Die konvexe Hülle wird dann zur Baumgrenze. Die sechs nach dieser Methode berechneten konvexen Rümpfe sind unten rot dargestellt:

Christmas trees with their calculated borders

Der Quellcode ist für Python 2.7.6 geschrieben und hängt von numpy , scipy , matplotlib und ab scikit-learn . 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 oben genannten 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()
179
stachyra

BEARBEITEN HINWEIS: 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 Helligkeit zu berücksichtigen formen, 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 werden Objekte mit einer dreieckigen Form und einer signifikanten Helligkeit gesucht. Es wurde in Java unter Verwendung des Marvin Bildverarbeitungs-Frameworks implementiert.

Der erste Schritt ist die Farbschwelle. Ziel ist es hier, die Analyse auf Objekte mit signifikanter Helligkeit zu fokussieren.

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. Durch Anwenden der Flutfüllsegmentierung 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. In diesem Problem gibt es nur ein paar helle Punkte in den Bildern. Dieser Ansatz wurde jedoch implementiert, um mit komplexeren Szenarien fertig zu werden.

Im nächsten Schritt wird jede Form analysiert. Ein einfacher Algorithmus erkennt Formen mit einem Muster ähnlich einem Dreieck. Der Algorithmus analysiert die Objektform Zeile für Zeile. Wenn der Massenmittelpunkt jeder Formlinie (bei gegebenem Schwellenwert) nahezu gleich ist und die Masse mit zunehmendem y zunimmt, hat das Objekt eine dreieckige Form. Die Masse der Formlinie ist die Anzahl der Pixel in der Linie, die zur Form gehört. Stellen Sie sich vor, Sie schneiden das Objekt horizontal und analysieren jedes horizontale Segment. Wenn sie zueinander zentriert sind und die Länge vom ersten bis zum letzten Segment in einem linearen Muster 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 ANMERKUNG 2

Es gibt eine Diskussion über die Ähnlichkeit der Ausgabebilder dieser und einiger anderer Lösungen. In der Tat sind sie sehr ähnlich. Dieser Ansatz segmentiert jedoch nicht nur Objekte. In gewisser Weise werden auch die Objektformen analysiert. Es kann mit mehreren leuchtenden Objekten in derselben Szene umgehen. Tatsächlich muss der Weihnachtsbaum nicht der hellste sein. Ich nehme es nur auf, um die Diskussion zu bereichern. In den Samples gibt es eine Tendenz, dass Sie die Bäume finden, wenn Sie nur nach dem hellsten Objekt suchen. Aber wollen wir die Diskussion an dieser Stelle wirklich beenden? Inwieweit erkennt der Computer 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

enter image description here

Ausgabe

enter image description here

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

//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. Wir müssen jedoch zwischen dem Baum selbst und dem Schnee, der sein Licht reflektiert, unterscheiden. Hier versuchen wir, den Schnee auszuschließen, indem wir einen wirklich einfachen Filter für 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);

Nun 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 ist, 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 Endausgabe:

73
smeso

Ich habe den Code in Matlab R2007a geschrieben. Ich habe k-means benutzt, um den Weihnachtsbaum grob zu extrahieren. Ich zeige mein Zwischenergebnis nur mit einem Bild und das Endergebnis mit allen sechs.

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

enter image description here

Neben dem Merkmal im Farbraum habe ich auch ein Texturmerkmal verwendet, das für die Nachbarschaft relevant ist, und nicht für jedes Pixel. Hier habe ich die Intensität der 3 ursprünglichen Kanäle (R, G, B) linear kombiniert. Der Grund, warum ich so formatiert habe, ist, dass die Weihnachtsbäume auf dem Bild alle rot beleuchtet sind und manchmal auch grün/manchmal blau beleuchtet sind.

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

enter image description here

Ich habe ein lokales 3X3-Binärmuster auf I0 Angewendet, das mittlere Pixel als Schwelle verwendet und den Kontrast durch Berechnen der Differenz zwischen dem mittleren Pixelintensitätswert über der Schwelle und dem mittleren Wert darunter erhalten.

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

enter image description here

Da ich insgesamt 4 Features habe, würde ich bei meiner Clustering-Methode K = 5 wählen. Der Code für k-means ist unten gezeigt (er stammt aus dem maschinellen Lernkurs von Dr. Andrew Ng. Ich habe den Kurs zuvor besucht und den Code selbst in seiner 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 Schwerpunkten. Nach meinem Test kann durch Erhöhen der Iteration der Hintergrund (Himmel und Baum, Himmel und Gebäude, ...) genauer unterschieden werden, es wurden jedoch keine drastischen Änderungen bei der Weihnachtsbaumextraktion festgestellt. Beachten Sie auch, dass k-means nicht gegen die zufällige Initialisierung des Schwerpunkts 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 I0 Ausgewä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 Funktion bwboundaries 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 der Weihnachtsbäume wird nicht nur nicht mit der beleuchteten Seite geclustert, sondern führt auch dazu, dass so viele winzige innere Grenzen nachgezeichnet werden (imfill verbessert sich nicht sehr). Insgesamt hat mein Algorithmus noch viel Verbesserungsraum.

Einige Veröffentlichung s weisen darauf hin, dass die Mittelwertverschiebung robuster sein kann als die k-Mittelwerte, und viele auf Graphenschnitten 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 extrahiert. Die Mittelwertverschiebung ist jedoch ein wenig übersegmentiert, und es ist eine Strategie zum Zusammenführen erforderlich. Es lief sogar viel langsamer als k-means auf meinem Computer, ich fürchte, ich muss es aufgeben. Ich freue mich sehr darauf, dass andere mit den oben genannten modernen Algorithmen hier hervorragende Ergebnisse liefern.

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

Fröhliche Weihnachten !

59
lennon310

Dies ist mein letzter Beitrag mit den traditionellen Bildverarbeitungsansätzen ...

Hier verbinde 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 (besonders wenn Sie sich die maskierten Bilder ansehen, die die Methode erzeugt).

Im Mittelpunkt des Ansatzes steht die Kombination von drei Schlüsselannahmen :

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

Unter diesen Voraussetzungen 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 einen harten Schwellenwert auf den V-Kanal an, um die Intensitätsmaske B zu erhalten
  5. Wenden Sie die H-Kanal-Schwellenwerteinstellung an, um blau gefärbte Bereiche mit geringer Intensität in der Hintergrundmaske C zu erfassen
  6. Kombiniere die Masken mit AND, 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 im aktuellen Ordner und wieder ist dies alles andere als ein optimierter Code):

% 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

results

Hochauflösende Ergebnisse erhalten Sie hier noch !
Weitere Experimente mit zusätzlichen Bildern finden Sie hier.

56
sepdek

Meine Lösungsschritte:

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

  2. Region of Interest (ROI) erstellen

    • Schwelle R Kanal mit min Wert 149 (Bild oben rechts)

    • Ergebnisbereich erweitern (mittleres linkes Bild)

  3. Erkennen Sie Ränder im berechneten Roi. Baum hat viele Kanten (mittleres rechtes Bild)

    • 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. Bounding Box (Bild unten rechts - Grren Box)

Schritt für Schritt: enter image description here

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

Ganzer Algorithmus zum Erkennen von Weihnachtsbaum (11 Blöcke): AVL solution

Nächster Schritt. Wir wollen eine Open Source Lösung. Ändern Sie AVL-Filter in OpenCV-Filter: Hier habe ich kleine Änderungen vorgenommen, z. Kantenerkennung Verwenden Sie den cvCanny-Filter, um den roi zu berücksichtigen. Ich habe das Regionsbild mit dem Kantenbild multipliziert, um das größte Element auszuwählen, das ich verwendet habe. FindContours + contourArea, aber die Idee ist dieselbe.

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

OpenCV solution

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

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

Das Ergebnis des endgültigen C++ - Codes ist: enter image description here

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

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

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

Ein Wort zur Heuristik in der HSV-Bearbeitung:

  1. alles mit Farbtöne (H) zwischen 210 - 320 Grad wird als blau-magenta verworfen, das sich im Hintergrund oder in nicht relevanten Bereichen befinden soll
  2. alles mit Werte (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, der den Trick ausführt (Warnung: Der Code ist keineswegs optimiert !!! Ich habe Techniken verwendet, die für die MATLAB-Programmierung nicht empfohlen wurden, um in der Lage zu sein, irgendetwas im Prozess zu verfolgen - 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. enter image description here

30
sepdek

Ein altmodischer Bildverarbeitungsansatz ...
Die Idee basiert auf dem Annahme, dass Bilder beleuchtete Bäume auf normalerweise dunkleren und glatteren Hintergründen darstellen (oder in einigen Fällen auf den Vordergrund). Die beleuchtete Baumfläche ist "energetischer" und hat eine höhere Intensität.
Der Prozess ist wie folgt:

  1. In Graustufen konvertieren
  2. Wenden Sie die LoG-Filterung an, um die "aktivsten" Bereiche zu erhalten
  3. Wenden Sie eine absichtliche Schwellenwerteinstellung an, um die hellsten Bereiche zu erzielen
  4. Kombiniere 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 Kandidatengebiete entsprechend ihrer Gebietsgröße

Sie erhalten für jedes Bild eine Binärmaske und einen Begrenzungsrahmen.

Hier sind die Ergebnisse mit dieser naiven Technik: enter image description here

Code in 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
22
sepdek

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

Die größte Bedrohung für diesen Algorithmus sind offensichtlich Lichter neben (in großer Zahl) 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 richtig, erkennen Sie stark gebogene Weihnachtsbäume, kopfüber oder gehackt ...;)

Die verschiedenen Stadien sind:

  • Berechnen Sie die zusätzliche Helligkeit (R + G + B) für jedes Pixel
  • Addieren Sie diesen Wert aller 8 benachbarten Pixel über jedes Pixel
  • Ordnen Sie alle Pixel nach diesem Wert (hellste zuerst) - ich weiß, nicht wirklich subtil ...
  • Wählen Sie N davon, beginnend von oben, und überspringen Sie diejenigen, die zu nah sind
  • Berechnen Sie den Median dieser oberen N (gibt uns die ungefähre Mitte des Baumes)
  • Beginnen Sie von der Mittelposition nach oben in einem verbreiterten Suchstrahl für das oberste Licht der ausgewählten hellsten (die Leute neigen dazu, mindestens ein Licht ganz oben zu platzieren).
  • Stellen Sie sich vor, die Linien gehen um 60 Grad nach links und rechts nach unten (Weihnachtsbäume sollten nicht so fett sein)
  • Verringern Sie diese 60 Grad, bis sich 20% der hellsten Lichter außerhalb dieses Dreiecks befinden
  • Suchen Sie das Licht ganz unten im Dreieck, um den unteren horizontalen Rand des Baums zu erhalten
  • Getan

Erklärung der Markierungen:

  • Großes rotes Kreuz in der Mitte des Baumes: Median der Top N hellsten Lichter
  • Gepunktete Linie von dort oben: "Suchstrahl" für die Spitze des Baumes
  • Kleineres rotes Kreuz: Spitze des Baumes
  • Wirklich kleine rote Kreuze: Alle Top 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: Upper leftLower centerLower leftUpper rightUpper centerLower right

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

21
Christian

Ich habe python mit opencv verwendet.

Mein Algorithmus sieht so 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 Morphological Gradient an und führen Sie dann ein 'Closing' durch (Dilatation gefolgt von Erosion)
  4. Dann findet es die Konturen in der Ebene und wählt die längste Kontur aus.

The outcome:

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 __== "__main__":
    main()

Wenn ich den Kernel von (25,5) auf (10,5) ändere, erhalte ich bessere Ergebnisse bei allen Bäumen außer dem unteren linken Rand. enter image description here

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

16
ifryed