Bagaimana cara mendeteksi Pohon Natal? [Tutup]

382

Teknik pemrosesan gambar apa yang dapat digunakan untuk mengimplementasikan aplikasi yang mendeteksi pohon Natal yang ditampilkan dalam gambar berikut?

Saya mencari solusi yang akan bekerja pada semua gambar ini. Oleh karena itu, pendekatan yang membutuhkan pelatihan pengklasifikasi haar cascade atau pencocokan templat tidak terlalu menarik.

Saya mencari sesuatu yang dapat ditulis dalam bahasa pemrograman apa pun , asalkan hanya menggunakan teknologi Open Source . Solusinya harus diuji dengan gambar yang dibagikan pada pertanyaan ini. Ada 6 gambar input dan jawabannya harus menampilkan hasil pemrosesan masing-masing. Akhirnya, untuk setiap gambar keluaran harus ada garis merah menggambar untuk mengelilingi pohon yang terdeteksi.

Bagaimana cara Anda mendeteksi pohon secara terprogram dalam gambar-gambar ini?

karlphillip
sumber
3
Apakah kami diizinkan menggunakan beberapa gambar untuk pelatihan, atau haruskah semua gambar yang disediakan digunakan untuk validasi? Either way, kompetisi keren: D
Hannes Ovrén
7
@karlphillip, apakah Anda ingin kami menggunakan gambar ini untuk pengujian dan gambar lainnya untuk pelatihan? Hanya saja, tidak jelas apa set pelatihan itu.
GilLevi
16
@karlphillip: Saran saya: jatuhkan persyaratan "open source". Tidak masalah bahasa / kerangka apa yang Anda gunakan. Algoritma pemrosesan gambar / visi komputer adalah bahasa agnostik, jadi jika Anda dapat menulisnya di MATLAB, Anda tentu dapat melakukannya OpenCV atau kerangka kerja lain yang Anda inginkan ... Juga saya masih tidak jelas apa yang Anda anggap pelatihan / pengujian gambar !
Amro
2
@karlphillip thanx untuk memobilisasi kami semua untuk berkontribusi pada 'pencarian' Anda! Ini merupakan peluang besar untuk menghabiskan beberapa jam secara produktif, tetapi yang paling penting, untuk melihat berapa banyak pendekatan berbeda dapat ditemukan pada satu masalah ... Semoga Anda melakukannya lagi untuk 1 Januari (mungkin giring dari Tantangan Sinterklas? ;-))
sepdek
2
OK, saya menulis ulang pertanyaan untuk menghapus elemen kompetisi. Saya pikir itu harus membiarkannya berdiri sendiri.
Brad Larson

Jawaban:

184

Saya memiliki pendekatan yang menurut saya menarik dan sedikit berbeda dari yang lain. Perbedaan utama dalam pendekatan saya, dibandingkan dengan yang lain, adalah bagaimana langkah segmentasi gambar dilakukan - saya menggunakan algoritma pengelompokan DBSCAN dari scikit-learn Python; itu dioptimalkan untuk menemukan bentuk agak amorf yang mungkin belum tentu memiliki pusat massa tunggal yang jelas.

Di tingkat atas, pendekatan saya cukup sederhana dan dapat dipecah menjadi sekitar 3 langkah. Pertama saya menerapkan ambang batas (atau sebenarnya, logika "atau" dua ambang batas yang berbeda dan berbeda). Seperti banyak jawaban lain, saya berasumsi bahwa pohon Natal akan menjadi salah satu objek yang lebih terang di tempat kejadian, jadi ambang pertama hanyalah uji kecerahan monokrom sederhana; setiap piksel dengan nilai di atas 220 pada skala 0-255 (di mana hitam 0 dan putih 255) disimpan ke gambar biner hitam-putih. Ambang kedua mencoba mencari lampu merah dan kuning, yang terutama menonjol di pohon-pohon di kiri atas dan kanan bawah dari enam gambar, dan menonjol dengan latar belakang biru-hijau yang lazim di sebagian besar foto. Saya mengkonversi gambar rgb ke ruang hsv, dan mensyaratkan bahwa rona kurang dari 0,2 pada skala 0,0-1,0 (sesuai kira-kira dengan batas antara kuning dan hijau) atau lebih besar dari 0,95 (sesuai dengan batas antara ungu dan merah) dan juga saya membutuhkan warna-warna cerah, jenuh: saturasi dan nilai keduanya harus di atas 0,7. Hasil dari dua prosedur ambang secara logis "atau" -ed bersama, dan matriks yang dihasilkan dari gambar biner hitam-putih ditunjukkan di bawah ini:

Pohon Natal, setelah ambang batas pada HSV serta kecerahan monokrom

Anda dapat dengan jelas melihat bahwa setiap gambar memiliki satu kelompok besar piksel yang secara kasar sesuai dengan lokasi setiap pohon, ditambah beberapa gambar juga memiliki beberapa kelompok kecil lainnya yang terkait dengan lampu di jendela beberapa bangunan, atau ke adegan latar belakang di cakrawala. Langkah selanjutnya adalah membuat komputer mengenali bahwa ini adalah cluster yang terpisah, dan beri label setiap piksel dengan benar dengan nomor ID keanggotaan cluster.

Untuk tugas ini saya memilih DBSCAN . Ada perbandingan visual yang cukup bagus tentang bagaimana biasanya perilaku DBSCAN, relatif terhadap algoritma pengelompokan lainnya, tersedia di sini . Seperti yang saya katakan sebelumnya, itu baik dengan bentuk amorf. Output dari DBSCAN, dengan masing-masing cluster diplot dalam warna yang berbeda, ditunjukkan di sini:

Output pengelompokan DBSCAN

Ada beberapa hal yang perlu diperhatikan ketika melihat hasil ini. Pertama adalah bahwa DBSCAN mengharuskan pengguna untuk menetapkan parameter "proximity" untuk mengatur perilakunya, yang secara efektif mengontrol bagaimana dipisahkannya sepasang titik agar algoritma dapat mendeklarasikan cluster terpisah baru daripada menggumpalkan titik uji ke cluster yang sudah ada sebelumnya. Saya menetapkan nilai ini menjadi 0,04 kali ukuran sepanjang diagonal setiap gambar. Karena ukuran gambar bervariasi dari kira-kira VGA hingga HD 1080, definisi skala-relatif ini sangat penting.

Hal lain yang patut dicatat adalah bahwa algoritma DBSCAN seperti yang diterapkan dalam scikit-learn memiliki batas memori yang cukup menantang untuk beberapa gambar yang lebih besar dalam sampel ini. Oleh karena itu, untuk beberapa gambar yang lebih besar, saya benar-benar harus "memusnahkan" (yaitu, pertahankan hanya setiap piksel ke-3 atau ke-4 dan jatuhkan yang lain) setiap cluster agar tetap dalam batas ini. Sebagai hasil dari proses pemusnahan ini, piksel jarang individu yang tersisa sulit dilihat pada beberapa gambar yang lebih besar. Oleh karena itu, hanya untuk tujuan tampilan, piksel kode warna pada gambar di atas telah secara efektif "dilatasi" hanya sedikit sehingga lebih menonjol. Ini murni operasi kosmetik demi narasi; meskipun ada komentar yang menyebutkan pelebaran ini dalam kode saya,

Setelah cluster diidentifikasi dan diberi label, langkah ketiga dan terakhir mudah: Saya cukup mengambil cluster terbesar di setiap gambar (dalam hal ini, saya memilih untuk mengukur "ukuran" dalam hal jumlah total piksel anggota, meskipun orang bisa baru saja dengan mudah menggunakan beberapa jenis metrik yang mengukur tingkat fisik) dan menghitung convex hull untuk cluster tersebut. Lambung cembung kemudian menjadi batas pohon. Enam lambung cembung yang dihitung melalui metode ini ditunjukkan di bawah ini dalam warna merah:

Pohon Natal dengan batas yang dihitung

Kode sumber ditulis untuk Python 2.7.6 dan itu tergantung pada numpy , scipy , matplotlib dan scikit-learn . Saya sudah membaginya menjadi dua bagian. Bagian pertama bertanggung jawab atas pemrosesan gambar yang sebenarnya:

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

dan bagian kedua adalah skrip tingkat pengguna yang memanggil file pertama dan menghasilkan semua plot di atas:

#!/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()
stachyra
sumber
Solusi @ lennon310 adalah clustering. (k-means)
user3054997
1
@stachyra Saya juga memikirkan pendekatan ini sebelum mengusulkan yang lebih sederhana. Saya pikir ini memiliki potensi besar untuk diperluas dan digeneralisasi untuk menghasilkan hasil yang baik dalam kasus lain juga. Anda bisa bereksperimen dengan jaring saraf untuk pengelompokan. Sesuatu seperti SOM atau gas saraf akan bekerja dengan sangat baik. Namun demikian, proposal dan acungan jempol dari saya!
sepdek
4
@Faust & Ryan Carlson: terima kasih, teman-teman! Ya, saya setuju bahwa sistem upvote, sementara itu berfungsi dengan baik untuk menilai antara 2 atau 3 jawaban singkat semua yang diajukan dalam beberapa jam satu sama lain, memiliki bias serius ketika datang ke kontes dengan jawaban panjang yang dimainkan selama periode waktu yang lama . Untuk satu hal, pengiriman awal mulai mengumpulkan upvotes sebelum yang berikutnya bahkan tersedia untuk tinjauan publik. Dan jika semua jawaban panjang, maka segera setelah seseorang menetapkan petunjuk yang sederhana, sering kali ada "efek ikut-ikutan" karena orang-orang hanya memilih yang pertama tanpa repot-repot membaca sisanya.
stachyra
2
@stachyra teman berita hebat! Selamat terpanas dan semoga ini menandai awal tahun baru Anda!
sepdek
1
@ lennon310: Saya belum mencoba filter deteksi maksimum lokal untuk masalah ini, tetapi jika Anda ingin menjelajahinya sendiri, Scipy menyertakan yang ini . Kode sumber Python saya untuk proyek ini sangat singkat sehingga saya benar-benar dapat menerbitkan 100% darinya; secara harfiah yang perlu Anda lakukan adalah menyalin dan menempelkan dua cuplikan kode saya ke file .py yang terpisah dan kemudian mengganti panggilan ke scipy.ndimage.filters.maximum_filter()tempat yang sama dengan tempat saya menggunakan ambang.
stachyra
145

EDIT CATATAN: Saya mengedit posting ini untuk (i) memproses setiap gambar pohon secara terpisah, seperti yang diminta dalam persyaratan, (ii) untuk mempertimbangkan kecerahan dan bentuk objek untuk meningkatkan kualitas hasil.


Di bawah ini disajikan pendekatan yang mempertimbangkan kecerahan dan bentuk objek. Dengan kata lain, ia mencari objek dengan bentuk seperti segitiga dan dengan kecerahan yang signifikan. Itu diimplementasikan di Jawa, menggunakan kerangka kerja pemrosesan gambar Marvin .

Langkah pertama adalah ambang warna. Tujuannya di sini adalah untuk memfokuskan analisis pada objek dengan kecerahan yang signifikan.

gambar output:

Kode sumber:

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

Pada langkah kedua, titik paling terang pada gambar dilebarkan untuk membentuk bentuk. Hasil dari proses ini adalah kemungkinan bentuk objek dengan kecerahan yang signifikan. Menerapkan segmentasi pengisian banjir, bentuk terputus terdeteksi.

gambar output:

Kode sumber:

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

Seperti yang ditunjukkan pada gambar output, banyak bentuk terdeteksi. Dalam masalah ini, hanya ada beberapa titik terang dalam gambar. Namun, pendekatan ini diimplementasikan untuk menangani skenario yang lebih kompleks.

Pada langkah berikutnya setiap bentuk dianalisis. Algoritma sederhana mendeteksi bentuk dengan pola yang mirip dengan segitiga. Algoritma ini menganalisis objek bentuk garis demi garis. Jika pusat massa masing-masing garis bentuk hampir sama (diberi ambang batas) dan massa bertambah seiring y bertambah, objek memiliki bentuk seperti segitiga. Massa garis bentuk adalah jumlah piksel dalam garis yang termasuk dalam bentuk itu. Bayangkan Anda mengiris objek secara horizontal dan menganalisis setiap segmen horizontal. Jika mereka terpusat satu sama lain dan panjangnya bertambah dari segmen pertama ke yang terakhir dalam pola linier, Anda mungkin memiliki objek yang menyerupai segitiga.

Kode sumber:

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

Akhirnya, posisi setiap bentuk mirip dengan segitiga dan dengan kecerahan yang signifikan, dalam hal ini pohon Natal, disorot dalam gambar aslinya, seperti yang ditunjukkan di bawah ini.

gambar hasil akhir:

kode sumber akhir:

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

Keuntungan dari pendekatan ini adalah fakta bahwa ia mungkin akan bekerja dengan gambar yang mengandung objek bercahaya lain karena menganalisis bentuk objek.

Selamat Natal!


EDIT CATATAN 2

Ada diskusi tentang kesamaan gambar output dari solusi ini dan beberapa yang lain. Bahkan, mereka sangat mirip. Namun pendekatan ini tidak hanya mensegmentasi objek. Ini juga menganalisis bentuk objek dalam beberapa hal. Itu dapat menangani beberapa objek bercahaya dalam adegan yang sama. Faktanya, pohon Natal tidak perlu menjadi yang paling terang. Saya hanya membahasnya untuk memperkaya diskusi. Ada bias dalam sampel yang hanya mencari objek paling terang, Anda akan menemukan pohon. Tetapi, apakah kita benar-benar ingin menghentikan diskusi pada titik ini? Pada titik ini, seberapa jauh komputer benar-benar mengenali objek yang menyerupai pohon Natal? Mari kita coba untuk menutup celah ini.

Di bawah ini disajikan hasil hanya untuk menjelaskan hal ini:

masukan gambar

masukkan deskripsi gambar di sini

keluaran

masukkan deskripsi gambar di sini

Gabriel Ambrósio Archanjo
sumber
2
Itu menarik. Saya harap Anda bisa mendapatkan hasil yang sama ketika setiap gambar diproses secara individual. Saya telah mengedit pertanyaan 4 jam sebelumnya untuk Anda memposting jawaban untuk menyatakan ini secara khusus. Akan luar biasa jika Anda dapat memperbarui jawaban Anda dengan hasil ini.
karlphillip
@Marvin dalam deteksi segitiga Anda, bagaimana Anda menangani fluktuasi massa? Ini bukan segitiga yang ketat, massa bukan mono karena perubahan y
user3054997
2
@ user3054997: Itu poin lain. Seperti yang saya posting, algoritme tidak mencari bentuk segitiga yang ketat. Ini menganalisis setiap objek dan mempertimbangkan pohon yang "menyerupai" segitiga dengan kriteria sederhana: massa objek digunakan untuk meningkat seiring bertambahnya y dan pusat massa dari setiap segmen objek horizontal hampir terpusat satu sama lain .
Gabriel Ambrósio Archanjo
@Marvin Solusi saya sangat sederhana, saya menyatakannya dalam jawaban saya juga. Ngomong-ngomong itu bekerja lebih baik daripada solusi pertama Anda. Jika saya ingat dengan benar, pada jawaban pertama Anda, Anda berbicara tentang deskriptor fitur untuk mendeteksi tekstur cahaya kecil, yang bukan apa yang Anda lakukan di sini. Saya hanya mengatakan bahwa pendekatan Anda saat ini dan hasilnya jauh lebih mirip dengan saya daripada solusi pertama Anda. Tentu saja saya tidak berharap Anda mengakuinya, saya menyatakannya hanya sebagai catatan.
smeso
1
@sepdek Ada beberapa solusi di sini yang benar-benar jauh lebih baik daripada milik saya dan mereka masih mendapatkan setengah dari upvotes saya. Tidak ada yang salah dalam "terinspirasi" oleh solusi lain. Saya melihat solusi Anda juga, saya tidak punya alasan untuk menentang Anda, Anda mempostingnya setelah saya dan "ide" saya tidak terlalu orisinal untuk mengatakan bahwa Anda baru saja menyalin saya. Tapi Marvin adalah satu-satunya yang memposting sebelum saya dan diedit adalah solusi setelah melihat saya menggunakan algoritma yang sama ... setidaknya dia bisa mengatakan "Ya, saya menyukai solusi Anda dan saya menggunakannya kembali" tidak ada yang salah, hanya saja permainan.
smeso
75

Ini solusi sederhana dan bodoh saya. Ini didasarkan pada asumsi bahwa pohon itu akan menjadi hal yang paling terang dan besar dalam gambar.

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

Langkah pertama adalah mendeteksi piksel paling terang dalam gambar, tetapi kita harus melakukan pembedaan antara pohon itu sendiri dan salju yang memantulkan cahayanya. Di sini kami mencoba untuk mengecualikan salju yang menerapkan filter yang sangat sederhana pada kode warna:

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

Lalu kami menemukan setiap piksel "cerah":

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

Akhirnya kami bergabung dengan dua hasil:

bitwise_and(tmp, tmp1, tmp1);

Sekarang kita mencari objek terang terbesar:

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

Sekarang kita hampir selesai, tetapi masih ada beberapa ketidaksempurnaan karena salju. Untuk memotongnya, kami akan membuat topeng menggunakan lingkaran dan persegi panjang untuk memperkirakan bentuk pohon untuk menghapus potongan yang tidak diinginkan:

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

Langkah terakhir adalah menemukan kontur pohon kami dan menggambarnya di gambar aslinya.

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

Maaf, tetapi saat ini saya memiliki koneksi yang buruk sehingga tidak mungkin bagi saya untuk mengunggah gambar. Saya akan coba melakukannya nanti.

Selamat Natal.

EDIT:

Berikut beberapa gambar hasil akhir:

smeso
sumber
1
Halo! Pastikan jawaban Anda mengikuti semua persyaratan: Ada 6 gambar input dan jawabannya harus menampilkan hasil pemrosesan masing-masing; .
karlphillip
Hai! Anda dapat melewati nama file sebagai argumen CLI untuk program saya: ./christmas_tree ./*.png. Mereka dapat sebanyak yang Anda inginkan, hasilnya akan ditampilkan satu demi satu dengan menekan tombol apa saja. Apakah ini salah?
smeso
Tidak apa-apa, tetapi Anda masih perlu mengunggah gambar dan membagikannya di pertanyaan Anda sehingga pemirsa utas dapat benar-benar melihat hasil Anda. Membiarkan orang melihat apa yang Anda lakukan akan meningkatkan peluang Anda untuk mendapatkan suara;)
karlphillip
Saya mencoba mencari solusi untuk ini, saya punya beberapa masalah konektivitas.
smeso
2
Bagus! Sekarang Anda dapat <img src="http://i.stack.imgur.com/nmzwj.png" width="210" height="150">mengubah
skala
60

Saya menulis kode di Matlab R2007a. Saya menggunakan k-means untuk mengekstrak pohon natal secara kasar. Saya akan menunjukkan hasil antara saya hanya dengan satu gambar, dan hasil akhir dengan semua enam.

Pertama, saya memetakan ruang RGB ke ruang Lab, yang dapat meningkatkan kontras merah di saluran b-nya:

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

masukkan deskripsi gambar di sini

Selain fitur dalam ruang warna, saya juga menggunakan fitur tekstur yang relevan dengan lingkungan daripada setiap piksel itu sendiri. Di sini saya secara linear menggabungkan intensitas dari 3 saluran asli (R, G, B). Alasan mengapa saya diformat dengan cara ini adalah karena pohon-pohon natal di gambar semua memiliki lampu merah pada mereka, dan kadang-kadang pencahayaan hijau / kadang-kadang biru juga.

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

masukkan deskripsi gambar di sini

Saya menerapkan pola biner lokal 3X3 pada I0, menggunakan piksel tengah sebagai ambang, dan memperoleh kontras dengan menghitung perbedaan antara nilai intensitas piksel rata-rata di atas ambang batas dan nilai rata-rata di bawahnya.

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

masukkan deskripsi gambar di sini

Karena saya memiliki total 4 fitur, saya akan memilih K = 5 dalam metode pengelompokan saya. Kode untuk k-means ditunjukkan di bawah ini (ini dari kursus pembelajaran mesin Dr. Andrew Ng. Saya mengambil kursus sebelumnya, dan saya menulis kode sendiri dalam tugas pemrogramannya).

[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

Karena program ini berjalan sangat lambat di komputer saya, saya hanya menjalankan 3 iterasi. Biasanya kriteria berhenti adalah (i) waktu iterasi setidaknya 10, atau (ii) tidak ada perubahan pada centroid lagi. Untuk pengujian saya, meningkatkan iterasi dapat membedakan latar belakang (langit dan pohon, langit dan bangunan, ...) lebih akurat, tetapi tidak menunjukkan perubahan drastis dalam ekstraksi pohon natal. Juga perhatikan k-means tidak kebal terhadap inisialisasi centroid acak, sehingga menjalankan program beberapa kali untuk membuat perbandingan direkomendasikan.

Setelah k-means, wilayah berlabel dengan intensitas maksimum I0dipilih. Dan penelusuran batas digunakan untuk mengekstraksi batas. Bagi saya, pohon natal terakhir adalah yang paling sulit untuk diekstraksi karena kontras dalam gambar itu tidak cukup tinggi seperti pada lima pohon pertama. Masalah lain dalam metode saya adalah bahwa saya menggunakan bwboundariesfungsi di Matlab untuk melacak batas, tetapi kadang-kadang batas-batas bagian dalam juga dimasukkan karena Anda dapat mengamati dalam hasil 3, 5, 6. Sisi gelap di dalam pohon natal tidak hanya gagal untuk dikelompokkan dengan sisi yang diterangi, tetapi mereka juga menyebabkan begitu banyak batasan batin kecil yang ditelusuri ( imfilltidak bertambah banyak). Secara keseluruhan, algoritma saya masih memiliki banyak ruang perbaikan.

Beberapa publikasi menunjukkan bahwa mean-shift mungkin lebih kuat daripada k-means, dan banyak algoritma berbasis grafik-cut juga sangat kompetitif pada segmentasi batas yang rumit. Saya menulis algoritma mean-shift sendiri, sepertinya lebih baik mengekstrak wilayah tanpa cukup cahaya. Tetapi pergeseran berarti sedikit lebih tersegmentasi, dan beberapa strategi penggabungan diperlukan. Itu berjalan jauh lebih lambat daripada k-berarti di komputer saya, saya takut saya harus menyerah. Saya berharap dapat melihat orang lain akan mengirimkan hasil yang sangat baik di sini dengan algoritma modern yang disebutkan di atas.

Namun saya selalu percaya pemilihan fitur adalah komponen kunci dalam segmentasi gambar. Dengan pemilihan fitur yang tepat yang dapat memaksimalkan margin antara objek dan latar belakang, banyak algoritma segmentasi pasti akan berfungsi. Algoritme yang berbeda dapat meningkatkan hasil dari 1 hingga 10, tetapi pemilihan fitur dapat meningkatkannya dari 0 hingga 1.

Selamat Natal !

lennon310
sumber
2
Terima kasih atas jawabannya! Saya hanya ingin menunjukkan bahwa Matlab bukan open source , tetapi Scilab . Saya juga ingin melihat jawaban ini bersaing dengan yang lain. ;)
karlphillip
6
Karl terima kasih. Octave adalah perangkat lunak open source lain yang berbagi tata bahasa pengkodean yang hampir sama dengan Matlab: mathworks.fr/matlabcentral/answers/14399-gnu-octave-vs-matlab .
lennon310
Menarik, saya tidak tahu itu, terima kasih! Apakah kode Anda berfungsi pada Oktaf?
karlphillip
Saya belum menguji, tapi saya pikir itu tidak masalah :)
lennon310
@ lennon310 Saya pikir jika Anda menjatuhkan batas dan mendapatkan lambung cembung Anda akan menyingkirkan masalah lubang. Ingat bahwa convex hull adalah area terkecil yang mencakup semua titik dalam satu set.
sepdek
57

Ini adalah posting terakhir saya menggunakan pendekatan pemrosesan gambar tradisional ...

Di sini saya entah bagaimana menggabungkan dua proposal saya yang lain, mencapai hasil yang lebih baik . Sebenarnya saya tidak bisa melihat bagaimana hasil ini bisa lebih baik (terutama ketika Anda melihat gambar bertopeng yang dihasilkan metode ini).

Inti dari pendekatan ini adalah kombinasi dari tiga asumsi utama :

  1. Gambar harus memiliki fluktuasi tinggi di wilayah pohon
  2. Gambar harus memiliki intensitas yang lebih tinggi di wilayah pohon
  3. Daerah latar belakang harus memiliki intensitas rendah dan sebagian besar biru-ish

Dengan asumsi ini, metode ini berfungsi sebagai berikut:

  1. Konversi gambar ke HSV
  2. Saring saluran V dengan filter LoG
  3. Terapkan ambang keras pada gambar yang difilter LoG untuk mendapatkan topeng 'aktivitas' A
  4. Terapkan ambang keras ke saluran V untuk mendapatkan topeng intensitas B
  5. Terapkan ambang saluran H untuk menangkap daerah blue-ish intensitas rendah ke dalam topeng latar belakang C
  6. Gabungkan topeng menggunakan DAN untuk mendapatkan topeng terakhir
  7. Lebarkan topeng untuk memperbesar wilayah dan menghubungkan piksel yang tersebar
  8. Hilangkan daerah kecil dan dapatkan topeng terakhir yang pada akhirnya hanya akan mewakili pohon

Berikut adalah kode dalam MATLAB (sekali lagi, skrip memuat semua gambar jpg di folder saat ini dan, sekali lagi, ini masih jauh dari sepotong kode yang dioptimalkan):

% 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

Hasil

hasil

Hasil resolusi tinggi masih tersedia di sini!
Bahkan lebih banyak percobaan dengan gambar tambahan dapat ditemukan di sini.

sepdek
sumber
1
Barang bagus! Pastikan jawaban Anda yang lain juga mengikuti format ini. Untuk bersaing mendapatkan hadiah, Anda harus menggunakan teknologi open source , dan sayangnya Matlab bukan salah satunya. Namun, SciLab dan Octave adalah dan mereka menyediakan sintaks dan fungsi yang sama. ;)
karlphillip
Kode oktaf sama ...
sepdek
@karlphillip Entah bagaimana pertanyaan ini akhirnya memiliki tag Matlab. Jika open source benar-benar harus saya sarankan menghapusnya.
Dennis Jaheruddin
@sepdek Sangat bagus, mungkin ada sesuatu yang masih bisa dilakukan untuk memasukkan 'lubang' di gambar terakhir. (Tambahkan semua piksel yang sepenuhnya dikelilingi oleh piksel yang disetujui ?!)
Dennis Jaheruddin
1
@karlphillip thanx man! Saya senang Anda menemukan pendekatan saya menarik. Selain itu, saya ingin mengucapkan selamat kepada Anda karena memilih solusi paling elegan dan bukan solusi dengan suara terbanyak !!!
sepdek
36

Langkah solusi saya:

  1. Dapatkan saluran R (dari RGB) - semua operasi yang kami lakukan di saluran ini:

  2. Buat Wilayah Bunga (ROI)

    • Saluran R ambang batas dengan nilai min 149 (gambar kanan atas)

    • Melebarkan wilayah hasil (gambar kiri tengah)

  3. Mendeteksi eges dalam roi yang dihitung. Pohon memiliki banyak sisi (gambar kanan tengah)

    • Dilatasi hasilnya

    • Melaju dengan radius lebih besar (gambar kiri bawah)

  4. Pilih objek (berdasarkan wilayah) terbesar - ini adalah wilayah hasil

  5. ConvexHull (pohon adalah cembung poligon) (gambar kanan bawah)

  6. Bounding box (gambar kanan bawah - kotak grren)

Selangkah demi selangkah: masukkan deskripsi gambar di sini

Hasil pertama - paling sederhana tetapi tidak dalam perangkat lunak sumber terbuka - "Adaptive Vision Studio + Adaptive Vision Library": Ini bukan open source tetapi sangat cepat untuk prototipe:

Seluruh algoritme untuk mendeteksi pohon natal (11 blok): Solusi AVL

Langkah berikutnya. Kami menginginkan solusi open source. Ubah filter AVL menjadi filter OpenCV: Di sini saya melakukan sedikit perubahan misalnya Deteksi Tepi menggunakan filter cvCanny, untuk menghormati roi, saya melakukan penggandaan gambar wilayah dengan gambar tepi, untuk memilih elemen terbesar yang saya gunakan findContours + contourArea tetapi gagasannya sama.

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

Solusi OpenCV

Saya tidak dapat menampilkan gambar dengan langkah perantara sekarang karena saya hanya dapat menempatkan 2 tautan.

Ok sekarang kita menggunakan filter openSource tetapi itu belum sepenuhnya open source. Langkah terakhir - port ke kode c ++. Saya menggunakan OpenCV dalam versi 2.4.4

Hasil akhir kode c ++ adalah: masukkan deskripsi gambar di sini

kode c ++ juga cukup pendek:

#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;
}
AdamF
sumber
Kompiler mana yang dapat membangun program ini tanpa kesalahan?
karlphillip
Saya menggunakan Visual Studio 2012 untuk membangunnya. Anda harus menggunakan kompiler c ++ dengan dukungan c ++ 11.
AdamF
Saya tidak punya sistem yang saya inginkan. Bisakah Anda menulis ulang std::max_element()panggilan? Saya ingin menghargai jawaban Anda juga. Saya pikir saya punya gcc 4.2.
karlphillip
Ok ini fitur c ++ 11;) Saya mengubah kode sumber di atas. Silakan coba sekarang.
AdamF
Baik terima kasih. Saya mengujinya dan itu indah. Segera setelah pertanyaan ini dibuka kembali (pengguna lain harus membantu saya dengan hal itu) saya dapat menetapkan hadiah lain untuk memberi hadiah kepada Anda. Selamat!
karlphillip
31

... solusi kuno lain - murni berdasarkan pada pemrosesan HSV :

  1. Konversi gambar ke ruang warna HSV
  2. Buat topeng sesuai dengan heuristik di HSV (lihat di bawah)
  3. Terapkan pelebaran morfologis ke topeng untuk menghubungkan area yang terputus
  4. Buang area kecil dan blok horizontal (ingat pohon adalah blok vertikal)
  5. Hitung kotak pembatas

Sepatah kata tentang heuristik dalam pemrosesan HSV:

  1. semuanya dengan Hues (H) antara 210 - 320 derajat dibuang sebagai magenta biru yang seharusnya berada di latar belakang atau di area yang tidak relevan
  2. semuanya dengan Nilai (V) lebih rendah dari 40% juga dibuang sebagai terlalu gelap untuk menjadi relevan

Tentu saja seseorang dapat bereksperimen dengan berbagai kemungkinan lain untuk menyempurnakan pendekatan ini ...

Berikut adalah kode MATLAB untuk melakukan trik (peringatan: kode ini jauh dari dioptimalkan !!! Saya menggunakan teknik yang tidak disarankan untuk pemrograman MATLAB hanya untuk dapat melacak apa pun dalam proses-ini dapat sangat dioptimalkan):

% 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

Hasil:

Dalam hasil saya menunjukkan gambar bertopeng dan kotak pembatas. masukkan deskripsi gambar di sini

sepdek
sumber
Halo, terima kasih atas jawabannya. Silakan luangkan waktu sejenak untuk membaca bagian Persyaratan untuk memastikan jawaban Anda mengikuti semua instruksi. Anda lupa membagikan gambar yang dihasilkan. ;)
karlphillip
2
@karlphillip sepdek tidak memiliki reputasi yang cukup untuk berbagi gambar, saya memindahkan gambar ke badan jawaban sesuai dengan tautan dan instruksinya. Namun tidak yakin, bahwa itu adalah yang benar, jangan ragu untuk berkomentar bagian ini.
alko
@alko saya tahu, terima kasih. Tetapi beberapa gambar yang Anda bagikan tidak ada dalam set input . Jawabannya harus menunjukkan hasil pemrosesan semua 6 gambar yang dibagikan pada pertanyaan.
karlphillip
@karlphillip itu fotonya, bukan milikku. itulah yang saya maksud dengan "komentari bagian ini";)
alko
2
Maaf karena menyebabkan masalah ... bukan niat saya. Saya telah memasukkan semua gambar dalam
set
23

Beberapa pendekatan pemrosesan gambar kuno ...
Idenya didasarkan pada asumsi bahwa gambar menggambarkan pohon terang pada latar belakang yang biasanya lebih gelap dan lebih halus (atau latar depan dalam beberapa kasus). Area pohon yang terang lebih "energik" dan memiliki intensitas yang lebih tinggi .
Prosesnya adalah sebagai berikut:

  1. Konversikan ke tingkat abu-abu
  2. Terapkan pemfilteran LoG untuk mendapatkan area yang paling "aktif"
  3. Terapkan ambang batas yang disengaja untuk mendapatkan area yang paling terang
  4. Gabungkan 2 sebelumnya untuk mendapatkan topeng pendahuluan
  5. Terapkan pelebaran morfologis untuk memperbesar area dan menghubungkan komponen yang berdekatan
  6. Hilangkan area kandidat kecil sesuai dengan ukuran area mereka

Apa yang Anda dapatkan adalah topeng biner dan kotak pembatas untuk setiap gambar.

Berikut adalah hasil menggunakan teknik naif ini: masukkan deskripsi gambar di sini

Kode pada MATLAB berikut: Kode ini berjalan pada folder dengan gambar JPG. Memuat semua gambar dan mengembalikan hasil yang terdeteksi.

% 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
sepdek
sumber
Jangan lupa untuk mengunggah gambar yang dihasilkan, seperti yang dilakukan Faust.
karlphillip
Saya seorang noob di sini sehingga saya tidak dapat mengunggah gambar. Silakan lihat hasil pada tautan yang disediakan dalam uraian saya.
sepdek
Oke, tetapi Anda masih harus menggunakan gambar yang dibagikan pada pertanyaan seperti yang dilakukan orang lain. Setelah Anda memprosesnya, unggah di suatu tempat dan edit jawaban Anda untuk menambahkan tautan. Kemudian saya akan mengedit jawaban Anda dan menempatkan gambar di dalamnya untuk Anda.
karlphillip
Tautan tampaknya berisi gambar yang benar sekarang.
Dennis Jaheruddin
22

Menggunakan pendekatan yang sangat berbeda dari apa yang saya lihat, saya membuat skrip yang mendeteksi pohon natal dengan lampu mereka. Hasilnya adalah selalu segitiga simetris, dan jika perlu nilai numerik seperti sudut ("kegemukan") pohon.

Ancaman terbesar terhadap algoritma ini jelas adalah lampu di sebelah (dalam jumlah besar) atau di depan pohon (masalah yang lebih besar hingga optimasi lebih lanjut). Sunting (ditambahkan): Apa yang tidak bisa dilakukan: Cari tahu apakah ada pohon natal atau tidak, temukan beberapa pohon natal dalam satu gambar, mendeteksi pohon cristmas dengan benar di tengah Las Vegas, mendeteksi pohon natal yang sangat bengkok, terbalik atau dicincang ...;)

Tahapan yang berbeda adalah:

  • Hitung kecerahan yang ditambahkan (R + G + B) untuk setiap piksel
  • Tambahkan nilai ini dari semua 8 piksel tetangga di atas setiap piksel
  • Beri peringkat semua piksel berdasarkan nilai ini (paling terang dulu) - Saya tahu, tidak terlalu halus ...
  • Pilih N dari ini, mulai dari atas, lewati yang terlalu dekat
  • Hitung dari top N ini (memberi kita perkiraan pusat pohon)
  • Mulai dari posisi tengah ke atas dalam sinar pencarian yang melebar untuk cahaya paling atas dari yang paling terang terpilih (orang cenderung menempatkan setidaknya satu cahaya di bagian paling atas)
  • Dari sana, bayangkan garis-garis mengarah ke 60 derajat ke kiri dan ke kanan ke bawah (pohon natal tidak boleh terlalu gemuk)
  • Kurangi 60 derajat hingga 20% dari lampu paling terang berada di luar segitiga ini
  • Temukan cahaya di bagian paling bawah segitiga, memberi Anda batas horizontal bawah pohon
  • Selesai

Penjelasan tanda:

  • Salib merah besar di tengah-tengah pohon: Median atas N lampu paling terang
  • Garis putus-putus dari sana ke atas: "balok pencarian" untuk bagian atas pohon
  • Palang merah kecil: bagian atas pohon
  • Salib merah yang sangat kecil: Semua lampu paling terang di bagian atas N
  • Segitiga merah: Duh!

Kode sumber:

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


?>

Gambar-gambar: Kiri atas Pusat bawah Kiri bawah Pojok kanan atas Pusat atas Kanan bawah

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

Kristen
sumber
17

Saya menggunakan python dengan opencv.

Algoritma saya berjalan seperti ini:

  1. Pertama mengambil saluran merah dari gambar
  2. Terapkan ambang (nilai minimum 200) ke saluran Merah
  3. Kemudian oleskan Gradient Morfologis dan kemudian lakukan 'Penutupan' (pelebaran diikuti oleh Erosi)
  4. Kemudian ia menemukan kontur di pesawat dan mengambil kontur terpanjang.

Hasilnya:

Kode:

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

Jika saya mengubah kernel dari (25,5) menjadi (10,5) saya mendapatkan hasil yang lebih bagus di semua pohon tetapi di kiri bawah, masukkan deskripsi gambar di sini

algoritma saya berasumsi bahwa pohon memiliki lampu di atasnya, dan di pohon kiri bawah, bagian atas memiliki lebih sedikit cahaya daripada yang lain.

ifryed
sumber