Làm thế nào để phát hiện cây Giáng sinh? [đóng cửa]


382

Những kỹ thuật xử lý hình ảnh nào có thể được sử dụng để thực hiện một ứng dụng phát hiện cây Giáng sinh được hiển thị trong các hình ảnh sau đây?

Tôi đang tìm kiếm các giải pháp sẽ hoạt động trên tất cả các hình ảnh này. Do đó, các phương pháp yêu cầu đào tạo phân loại tầng haar hoặc khớp mẫu không phải là rất thú vị.

Tôi đang tìm kiếm thứ gì đó có thể được viết bằng bất kỳ ngôn ngữ lập trình nào , miễn là nó chỉ sử dụng các công nghệ Nguồn mở . Giải pháp phải được kiểm tra với những hình ảnh được chia sẻ về câu hỏi này. Có 6 hình ảnh đầu vào và câu trả lời sẽ hiển thị kết quả xử lý từng hình ảnh. Cuối cùng, đối với mỗi hình ảnh đầu ra phải có các đường màu đỏ được vẽ để bao quanh cây được phát hiện.

Làm thế nào bạn sẽ đi về lập trình phát hiện cây trong những hình ảnh này?


3
Chúng tôi có được phép sử dụng một số hình ảnh để đào tạo không, hay tất cả các hình ảnh được cung cấp sẽ được sử dụng để xác nhận? Dù bằng cách nào, cạnh tranh tuyệt vời: D
Hannes Ovrén 25/12/13

7
@karlphillip, bạn có muốn chúng tôi sử dụng những hình ảnh này để thử nghiệm và những hình ảnh khác để đào tạo không? Chỉ là nó không rõ bộ huấn luyện là gì.
GilLevi

16
@karlphillip: Lời khuyên của tôi: bỏ yêu cầu "nguồn mở". Nó thực sự không quan trọng bạn sử dụng ngôn ngữ / khung nào. Các thuật toán xử lý hình ảnh / thị giác máy tính là bất khả tri về ngôn ngữ, vì vậy nếu bạn có thể viết nó bằng MATLAB, bạn chắc chắn có thể thực hiện OpenCV hoặc bất kỳ khuôn khổ nào khác mà bạn thích ... Ngoài ra tôi vẫn chưa rõ bạn xem xét những gì về hình ảnh đào tạo / kiểm tra !
Amro

2
@karlphillip thanx vì đã huy động tất cả chúng ta đóng góp cho 'nhiệm vụ' này của bạn! Đây là một cơ hội tuyệt vời để dành một vài giờ hiệu quả, nhưng quan trọng nhất là để xem có bao nhiêu cách tiếp cận khác nhau có thể được tìm thấy cho một vấn đề duy nhất ... Hy vọng bạn sẽ làm lại vào ngày 1 tháng 1 (có thể là một ' Thử thách của ông già Noel? ;-))
sepdek 30/12/13

2
OK, tôi đã đặt lại câu hỏi để loại bỏ các yếu tố cạnh tranh. Tôi nghĩ rằng nó nên cho phép nó tự đứng vững.
Brad Larson

Câu trả lời:


184

Tôi có một cách tiếp cận mà tôi nghĩ là thú vị và một chút khác biệt so với phần còn lại. Sự khác biệt chính trong cách tiếp cận của tôi, so với một số khác, là ở cách thực hiện bước phân đoạn hình ảnh - Tôi đã sử dụng thuật toán phân cụm DBSCAN từ scikit-learn của Python; nó được tối ưu hóa để tìm các hình dạng hơi vô định hình có thể không nhất thiết phải có một tâm rõ ràng.

Ở cấp độ cao nhất, cách tiếp cận của tôi khá đơn giản và có thể được chia thành khoảng 3 bước. Đầu tiên tôi áp dụng một ngưỡng (hoặc thực tế, "hoặc" logic của hai ngưỡng riêng biệt và riêng biệt). Cũng như nhiều câu trả lời khác, tôi cho rằng cây Giáng sinh sẽ là một trong những vật thể sáng hơn trong cảnh, vì vậy ngưỡng đầu tiên chỉ là một thử nghiệm độ sáng đơn sắc đơn giản; bất kỳ pixel nào có giá trị trên 220 theo tỷ lệ 0-255 (trong đó màu đen là 0 và màu trắng là 255) được lưu vào hình ảnh đen trắng nhị phân. Ngưỡng thứ hai cố gắng tìm kiếm ánh sáng màu đỏ và màu vàng, đặc biệt nổi bật trong các cây ở phía trên bên trái và bên phải của sáu hình ảnh, và nổi bật trên nền màu xanh lục phổ biến trong hầu hết các bức ảnh. Tôi chuyển đổi hình ảnh rgb sang không gian hsv, và yêu cầu màu sắc phải nhỏ hơn 0,2 trên thang điểm 0,0-1 (tương ứng với đường viền giữa màu vàng và màu xanh lá cây) hoặc lớn hơn 0,95 (tương ứng với đường viền giữa màu tím và màu đỏ) và ngoài ra tôi yêu cầu màu sáng, bão hòa: Độ bão hòa và giá trị phải cao hơn 0,7. Kết quả của hai thủ tục ngưỡng là "hoặc" được kết hợp một cách hợp lý và ma trận kết quả của hình ảnh nhị phân đen trắng được hiển thị bên dưới:

Cây thông Noel, sau khi đập vào HSV cũng như độ sáng đơn sắc

Bạn có thể thấy rõ rằng mỗi hình ảnh có một cụm pixel lớn tương ứng với vị trí của từng cây, cộng với một số hình ảnh cũng có một số cụm nhỏ khác tương ứng với ánh sáng trong cửa sổ của một số tòa nhà hoặc với cảnh nền trên đường chân trời. Bước tiếp theo là làm cho máy tính nhận ra rằng đây là các cụm riêng biệt và gắn nhãn cho từng pixel một cách chính xác bằng số ID thành viên của cụm.

Đối với nhiệm vụ này, tôi đã chọn DBSCAN . Có một so sánh trực quan khá tốt về cách DBSCAN thường hành xử, so với các thuật toán phân cụm khác, có sẵn ở đây . Như tôi đã nói trước đó, nó rất tốt với các hình dạng vô định hình. Đầu ra của DBSCAN, với mỗi cụm được vẽ trong một màu khác nhau, được hiển thị ở đây:

Đầu ra phân cụm DBSCAN

Có một vài điều cần lưu ý khi nhìn vào kết quả này. Đầu tiên là DBSCAN yêu cầu người dùng thiết lập tham số "độ gần" để điều chỉnh hành vi của nó, điều khiển hiệu quả cách tách một cặp điểm để thuật toán khai báo một cụm riêng biệt mới thay vì kết hợp điểm kiểm tra vào một cụm đã có sẵn. Tôi đặt giá trị này là 0,04 lần kích thước dọc theo đường chéo của mỗi hình ảnh. Do hình ảnh có kích thước khác nhau từ khoảng VGA cho đến khoảng HD 1080, loại định nghĩa tương đối tỷ lệ này là rất quan trọng.

Một điểm đáng chú ý khác là thuật toán DBSCAN khi được triển khai trong scikit-learn có giới hạn bộ nhớ khá khó khăn đối với một số hình ảnh lớn hơn trong mẫu này. Do đó, đối với một số hình ảnh lớn hơn, tôi thực sự phải "khử" (nghĩa là chỉ giữ lại mỗi pixel thứ 3 hoặc thứ 4 và thả các hình ảnh khác) mỗi cụm để ở trong giới hạn này. Kết quả của quá trình loại bỏ này, các pixel thưa thớt riêng lẻ còn lại rất khó nhìn thấy trên một số hình ảnh lớn hơn. Do đó, chỉ dành cho mục đích hiển thị, các pixel được mã hóa màu trong các hình ảnh trên đã được "giãn" một cách hiệu quả chỉ một chút để chúng nổi bật hơn. Đó hoàn toàn là một hoạt động thẩm mỹ vì lợi ích của câu chuyện; mặc dù có những bình luận đề cập đến sự giãn nở này trong mã của tôi,

Khi các cụm được xác định và dán nhãn, bước thứ ba và cuối cùng rất dễ dàng: Tôi chỉ cần lấy cụm lớn nhất trong mỗi hình ảnh (trong trường hợp này, tôi đã chọn đo "kích thước" theo tổng số pixel thành viên, mặc dù có thể thay vào đó, dễ dàng sử dụng một số loại số liệu đo mức độ vật lý) và tính toán vỏ lồi cho cụm đó. Thân tàu lồi sau đó trở thành viền cây. Sáu thân tàu lồi được tính toán thông qua phương pháp này được hiển thị bên dưới màu đỏ:

Cây Giáng sinh với đường viền được tính toán của họ

Mã nguồn được viết cho Python 2.7.6 và nó phụ thuộc vào numpy , scipy , matplotlibscikit-learn . Tôi đã chia nó thành hai phần. Phần đầu tiên chịu trách nhiệm xử lý hình ảnh thực tế:

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

và phần thứ hai là một kịch bản cấp người dùng gọi tệp đầu tiên và tạo ra tất cả các lô ở trên:

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

Giải pháp của @ lennon 310 là phân cụm. (k-nghĩa)
dùng3054997

1
@stachyra Tôi cũng đã nghĩ về phương pháp này trước khi đề xuất những cách đơn giản hơn. Tôi nghĩ rằng điều này có một tiềm năng lớn để được mở rộng và khái quát hóa để tạo ra kết quả tốt trong các trường hợp khác. Bạn có thể thử nghiệm với các mạng lưới thần kinh để phân cụm. Một cái gì đó như SOM hoặc khí thần kinh sẽ làm công việc tuyệt vời. Tuy nhiên, đề nghị tuyệt vời và ngón tay cái lên từ tôi!
sepdek

4
@Faust & Ryan Carlson: cảm ơn các bạn! Có, tôi đồng ý rằng hệ thống upvote, trong khi nó hoạt động tốt để phân xử giữa 2 hoặc 3 câu trả lời ngắn, tất cả được gửi trong vòng vài giờ với nhau, có những thành kiến ​​nghiêm trọng khi tham gia các cuộc thi với câu trả lời dài diễn ra trong thời gian dài . Đối với một điều, đệ trình sớm bắt đầu tích lũy upvote trước khi những người sau đó thậm chí có sẵn để xem xét công khai. Và nếu các câu trả lời dài dòng, thì ngay khi người ta thiết lập được vị trí dẫn đầu khiêm tốn, thường sẽ có "hiệu ứng bandwagon" khi mọi người chỉ nâng cao câu đầu tiên mà không cần đọc phần còn lại.
stachyra

2
@stachyra người bạn tin tức tuyệt vời! Chúc mừng ấm áp nhất và có thể điều này đánh dấu một khởi đầu cho năm mới của bạn!
sepdek

1
@ lennon 310: Tôi chưa thử bộ lọc phát hiện tối đa cục bộ về vấn đề này, nhưng nếu bạn muốn tự mình khám phá nó, scipy bao gồm bộ lọc này . Mã nguồn Python của tôi cho dự án này quá ngắn đến nỗi tôi thực sự có thể xuất bản 100% của nó; theo nghĩa đen, tất cả những gì bạn cần làm là sao chép và dán hai đoạn mã của tôi vào các tệp .py riêng biệt và sau đó thay thế một cuộc gọi đến scipy.ndimage.filters.maximum_filter()cùng một nơi mà tôi đã sử dụng một ngưỡng.
stachyra

145

EDIT LƯU Ý: Tôi đã chỉnh sửa bài đăng này để (i) xử lý từng hình ảnh cây riêng lẻ, theo yêu cầu trong yêu cầu, (ii) để xem xét cả độ sáng và hình dạng đối tượng để cải thiện chất lượng của kết quả.


Dưới đây được trình bày một cách tiếp cận có tính đến độ sáng và hình dạng của đối tượng. Nói cách khác, nó tìm kiếm các vật thể có hình dạng giống như hình tam giác và có độ sáng đáng kể. Nó được triển khai trong Java, sử dụng khung xử lý ảnh Marvin .

Bước đầu tiên là ngưỡng màu. Mục tiêu ở đây là tập trung phân tích vào các vật thể có độ sáng đáng kể.

hình ảnh đầu ra:

mã nguồn:

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

Trong bước thứ hai, các điểm sáng nhất trong ảnh được giãn ra để tạo thành các hình dạng. Kết quả của quá trình này là hình dạng có thể xảy ra của các đối tượng có độ sáng đáng kể. Áp dụng phân đoạn lấp lũ, hình dạng bị ngắt kết nối được phát hiện.

hình ảnh đầu ra:

mã nguồn:

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

Như thể hiện trong hình ảnh đầu ra, nhiều hình dạng đã được phát hiện. Trong vấn đề này, chỉ có một vài điểm sáng trong ảnh. Tuy nhiên, phương pháp này đã được thực hiện để đối phó với các kịch bản phức tạp hơn.

Trong bước tiếp theo, mỗi hình dạng được phân tích. Một thuật toán đơn giản phát hiện các hình dạng với một mô hình tương tự như một hình tam giác. Thuật toán phân tích các hình dạng đối tượng theo dòng. Nếu tâm khối lượng của mỗi đường hình gần như nhau (đưa ra một ngưỡng) và khối lượng tăng khi y tăng, thì vật thể có hình dạng giống như hình tam giác. Khối lượng của đường hình là số pixel trong đường đó thuộc về hình. Hãy tưởng tượng bạn cắt đối tượng theo chiều ngang và phân tích từng phân đoạn ngang. Nếu chúng được tập trung vào nhau và chiều dài tăng từ đoạn đầu tiên đến đoạn cuối theo mô hình tuyến tính, bạn có thể có một đối tượng giống như một hình tam giác.

mã nguồn:

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

Cuối cùng, vị trí của mỗi hình dạng tương tự như một hình tam giác và với độ sáng đáng kể, trong trường hợp này là cây Giáng sinh, được tô sáng trong hình ảnh gốc, như được hiển thị bên dưới.

hình ảnh đầu ra cuối cùng:

mã nguồn cuối cùng:

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

Ưu điểm của phương pháp này là thực tế có thể nó sẽ hoạt động với các hình ảnh có chứa các vật thể phát sáng khác vì nó phân tích hình dạng đối tượng.

Giáng sinh vui vẻ!


EDIT LƯU Ý 2

Có một cuộc thảo luận về sự giống nhau của hình ảnh đầu ra của giải pháp này và một số hình ảnh khác. Trên thực tế, chúng rất giống nhau. Nhưng cách tiếp cận này không chỉ phân khúc các đối tượng. Nó cũng phân tích các hình dạng đối tượng trong một số ý nghĩa. Nó có thể xử lý nhiều vật thể phát sáng trong cùng một cảnh. Trong thực tế, cây Giáng sinh không cần phải là cây sáng nhất. Tôi chỉ từ bỏ nó để làm phong phú thêm các cuộc thảo luận. Có một sự thiên vị trong các mẫu chỉ cần tìm kiếm vật thể sáng nhất, bạn sẽ tìm thấy những cái cây. Nhưng, chúng ta có thực sự muốn dừng cuộc thảo luận vào thời điểm này không? Tại thời điểm này, máy tính thực sự nhận ra một vật giống với cây Giáng sinh đến mức nào? Hãy thử thu hẹp khoảng cách này.

Dưới đây được trình bày một kết quả chỉ để làm sáng tỏ điểm này:

hình ảnh đầu vào

nhập mô tả hình ảnh ở đây

đầu ra

nhập mô tả hình ảnh ở đây


2
Nó thật thú vị. Tôi hy vọng bạn có thể nhận được kết quả tương tự khi mỗi hình ảnh được xử lý riêng lẻ. Tôi đã chỉnh sửa câu hỏi 4 giờ trước để bạn đăng câu trả lời để nêu cụ thể điều này. Sẽ thật tuyệt vời nếu bạn có thể cập nhật câu trả lời của mình với những kết quả này.
karlphillip

@Marvin trong phát hiện tam giác của bạn, làm thế nào bạn xử lý sự biến động của khối lượng? Đó không phải là một tam giác nghiêm ngặt, khối lượng không phải là đơn sắc khi y thay đổi
user3054997 31/12/13

2
@ user3054997: Đó là một điểm khác. Như tôi đã đăng, thuật toán không tìm kiếm các hình tam giác nghiêm ngặt. Nó phân tích từng đối tượng và xem xét một cây "giống" một tam giác với một tiêu chí đơn giản: khối lượng của đối tượng được sử dụng để tăng khi y tăng và tâm khối lượng của mỗi phân đoạn đối tượng ngang gần như tập trung vào nhau .
Gabriel Ambrósio Archanjo 31/12/13

@Marvin Giải pháp của tôi rất đơn giản, tôi cũng đã nêu nó trong câu trả lời của mình. Bằng cách này, nó làm việc tốt hơn so với giải pháp đầu tiên của bạn. Nếu tôi nhớ lại một cách chính xác, trong câu trả lời đầu tiên của bạn, bạn đã nói về các mô tả tính năng để phát hiện kết cấu ánh sáng nhỏ, đó không phải là điều bạn đang làm ở đây. Tôi chỉ đơn giản nói rằng cách tiếp cận và kết quả hiện tại của bạn giống với tôi hơn là giải pháp đầu tiên của bạn. Tất nhiên tôi không mong đợi bạn thừa nhận nó, tôi đã tuyên bố nó chỉ cho hồ sơ.
smeso

1
@sepdek Ở đây có một vài giải pháp thực sự tốt hơn tôi rất nhiều và họ vẫn nhận được một nửa số lượt ủng hộ của tôi. Không có gì sai trong việc "lấy cảm hứng" bởi các giải pháp khác. Tôi cũng thấy giải pháp của bạn, tôi không có gì để nói với bạn, bạn đã đăng chúng sau khi tôi và "ý tưởng" của tôi không quá độc đáo để nói rằng bạn vừa sao chép tôi. Nhưng Marvin là người duy nhất đăng bài trước tôi và chỉnh sửa là giải pháp sau khi thấy tôi sử dụng thuật toán tương tự ... ít nhất anh ta có thể nói "Vâng, tôi thích giải pháp của bạn và tôi đã sử dụng lại" không có gì sai, đó chỉ là một trò chơi.
smeso

75

Đây là giải pháp đơn giản và ngu ngốc của tôi. Nó dựa trên giả định rằng cây sẽ là thứ sáng nhất và to nhất trong bức tranh.

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

Bước đầu tiên là phát hiện các pixel sáng nhất trong ảnh, nhưng chúng ta phải phân biệt giữa chính cái cây và tuyết phản chiếu ánh sáng của nó. Ở đây chúng tôi cố gắng loại trừ tuyết táo một bộ lọc thực sự đơn giản về mã màu:

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

Sau đó, chúng tôi tìm thấy mọi pixel "sáng":

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

Cuối cùng, chúng tôi tham gia hai kết quả:

bitwise_and(tmp, tmp1, tmp1);

Bây giờ chúng tôi tìm kiếm đối tượng sáng lớn nhất:

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

Bây giờ chúng tôi đã gần như hoàn thành, nhưng vẫn còn một số khiếm khuyết do tuyết. Để cắt chúng, chúng ta sẽ tạo mặt nạ bằng hình tròn và hình chữ nhật để xấp xỉ hình dạng của cây để xóa các phần không mong muốn:

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

Bước cuối cùng là tìm đường viền của cây của chúng ta và vẽ nó lên bức tranh gốc.

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

Tôi xin lỗi nhưng hiện tại tôi có kết nối không tốt nên tôi không thể tải ảnh lên. Tôi sẽ cố gắng làm điều đó sau.

Giáng sinh vui vẻ.

BIÊN TẬP:

Dưới đây là một số hình ảnh của đầu ra cuối cùng:


1
Xin chào! Đảm bảo câu trả lời của bạn tuân theo tất cả các yêu cầu: Có 6 hình ảnh đầu vào và câu trả lời sẽ hiển thị kết quả xử lý từng hình ảnh; .
karlphillip

Chào! Bạn có thể chuyển tên tệp dưới dạng đối số CLI cho chương trình của tôi : ./christmas_tree ./*.png. Chúng có thể nhiều như bạn muốn, kết quả sẽ được hiển thị lần lượt sau khi nhấn phím khác. Điều này có sai không?
smeso

Không sao, nhưng bạn vẫn cần tải lên hình ảnh và chia sẻ chúng trong câu hỏi của bạn để người xem chủ đề thực sự có thể thấy kết quả của bạn. Để mọi người thấy những gì bạn đã làm sẽ cải thiện cơ hội nhận được phiếu bầu;)
karlphillip 26/12/13

Tôi đang cố gắng tìm một giải pháp cho vấn đề này, tôi có một số vấn đề về kết nối.
smeso

2
Tuyệt quá! Bây giờ bạn có thể bán lại chúng bên trong câu trả lời bằng mã sau: <img src="http://i.stack.imgur.com/nmzwj.png" width="210" height="150">Chỉ cần thay đổi liên kết đến hình ảnh;)
karlphillip

60

Tôi đã viết mã trong Matlab R2007a. Tôi đã sử dụng phương tiện k để trích xuất cây Giáng sinh. Tôi sẽ chỉ hiển thị kết quả trung gian của mình với một hình ảnh và kết quả cuối cùng với tất cả sáu hình ảnh.

Đầu tiên, tôi ánh xạ không gian RGB lên không gian Lab, có thể tăng cường độ tương phản của màu đỏ trong kênh b của nó:

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

nhập mô tả hình ảnh ở đây

Bên cạnh tính năng trong không gian màu, tôi cũng sử dụng tính năng kết cấu có liên quan đến vùng lân cận hơn là từng pixel. Ở đây tôi kết hợp tuyến tính cường độ từ 3 kênh gốc (R, G, B). Lý do tại sao tôi định dạng theo cách này là vì các cây Giáng sinh trong ảnh đều có đèn đỏ trên chúng, và đôi khi cũng chiếu sáng màu xanh lá cây / đôi khi là màu xanh.

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

nhập mô tả hình ảnh ở đây

Tôi đã áp dụng mô hình nhị phân cục bộ 3X3 trên I0, sử dụng pixel trung tâm làm ngưỡng và thu được độ tương phản bằng cách tính chênh lệch giữa giá trị cường độ pixel trung bình trên ngưỡng và giá trị trung bình bên dưới ngưỡng đó.

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

nhập mô tả hình ảnh ở đây

Vì tôi có tổng cộng 4 tính năng, tôi sẽ chọn K = 5 trong phương pháp phân cụm của mình. Mã cho phương tiện k được hiển thị bên dưới (đó là từ khóa học máy của Tiến sĩ Andrew Ng. Tôi đã tham gia khóa học trước đó và tôi đã tự viết mã trong bài tập lập trình của mình).

[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

Vì chương trình chạy rất chậm trong máy tính của tôi, tôi chỉ chạy 3 lần lặp. Thông thường, tiêu chí dừng là (i) thời gian lặp lại ít nhất là 10, hoặc (ii) không có thay đổi nào trên nhân nữa. Theo thử nghiệm của tôi, việc tăng số lần lặp có thể phân biệt nền (bầu trời và cây, bầu trời và tòa nhà, ...) chính xác hơn, nhưng không cho thấy sự thay đổi mạnh mẽ trong khai thác cây Giáng sinh. Cũng lưu ý rằng k-mean không tránh khỏi việc khởi tạo centroid ngẫu nhiên, vì vậy nên chạy chương trình nhiều lần để so sánh.

Sau phương tiện k, vùng được dán nhãn với cường độ tối đa I0được chọn. Và truy tìm ranh giới đã được sử dụng để trích xuất các ranh giới. Đối với tôi, cây Giáng sinh cuối cùng là cây khó khai thác nhất vì độ tương phản trong bức tranh đó không đủ cao như trong năm đầu tiên. Một vấn đề khác trong phương pháp của tôi là tôi đã sử dụng bwboundarieshàm trong Matlab để theo dõi ranh giới, nhưng đôi khi các ranh giới bên trong cũng được đưa vào như bạn có thể quan sát trong các kết quả thứ 3, 5, 6. Mặt tối bên trong cây thông giáng sinh không chỉ không được phân cụm với mặt được chiếu sáng, mà còn dẫn đến rất nhiều dấu vết ranh giới bên trong nhỏ bé ( imfillkhông cải thiện rất nhiều). Trong tất cả các thuật toán của tôi vẫn có một không gian cải tiến rất nhiều.

Một số ấn phẩm chỉ ra rằng dịch chuyển trung bình có thể mạnh hơn phương tiện k và nhiều thuật toán dựa trên biểu đồ cũng rất cạnh tranh trên phân khúc ranh giới phức tạp. Tôi đã tự viết một thuật toán dịch chuyển trung bình, nó dường như trích xuất tốt hơn các vùng không có đủ ánh sáng. Nhưng sự thay đổi trung bình là một chút phân đoạn quá mức, và một số chiến lược hợp nhất là cần thiết. Nó chạy thậm chí chậm hơn nhiều so với k-nghĩa trong máy tính của tôi, tôi sợ rằng tôi phải từ bỏ nó. Tôi háo hức mong chờ để thấy những người khác sẽ gửi kết quả tuyệt vời ở đây với những thuật toán hiện đại được đề cập ở trên.

Tuy nhiên, tôi luôn tin rằng lựa chọn tính năng là thành phần chính trong phân đoạn hình ảnh. Với một lựa chọn tính năng phù hợp có thể tối đa hóa lề giữa đối tượng và nền, nhiều thuật toán phân đoạn chắc chắn sẽ hoạt động. Các thuật toán khác nhau có thể cải thiện kết quả từ 1 đến 10, nhưng lựa chọn tính năng có thể cải thiện nó từ 0 đến 1.

Giáng sinh vui vẻ !


2
Cảm ơn câu trả lời! Tôi chỉ muốn chỉ ra rằng Matlab không phải là nguồn mở , nhưng Scilab thì có. Tôi cũng thích xem câu trả lời này cạnh tranh với những người khác. ;)
karlphillip 27/12/13

6
Cảm ơn bạn Karl. Octave là một phần mềm mã nguồn mở khác có chung ngữ pháp mã hóa với Matlab: mathworks.fr/matlabcentral/answers/14399-gnu-octave-vs-matlab .
lennon 310

Thật thú vị, tôi không biết điều đó, cảm ơn! Mã của bạn có hoạt động trên Octave không?
karlphillip

Tôi chưa thử nghiệm, nhưng tôi nghĩ không có vấn đề gì :)
lennon 310 27/12/13

@ lennon 310 Tôi nghĩ rằng nếu bạn bỏ qua các ranh giới và có được thân tàu lồi, bạn sẽ thoát khỏi vấn đề lỗ hổng. Hãy nhớ rằng thân tàu lồi là khu vực nhỏ nhất bao gồm tất cả các điểm trong một tập hợp.
sepdek

57

Đây là bài viết cuối cùng của tôi bằng cách sử dụng các phương pháp xử lý hình ảnh truyền thống ...

Ở đây tôi bằng cách nào đó kết hợp hai đề xuất khác của tôi, đạt được kết quả thậm chí tốt hơn . Vì thực tế tôi không thể thấy những kết quả này có thể tốt hơn như thế nào (đặc biệt là khi bạn nhìn vào những hình ảnh đeo mặt nạ mà phương thức tạo ra).

Trọng tâm của cách tiếp cận là sự kết hợp của ba giả định chính :

  1. Hình ảnh nên có dao động cao trong khu vực cây
  2. Hình ảnh nên có cường độ cao hơn trong các vùng cây
  3. Các vùng nền nên có cường độ thấp và chủ yếu là màu xanh lam

Với các giả định này, phương pháp này hoạt động như sau:

  1. Chuyển đổi hình ảnh sang HSV
  2. Lọc kênh V bằng bộ lọc LoG
  3. Áp dụng ngưỡng mạnh trên hình ảnh được lọc LoG để có mặt nạ 'hoạt động'
  4. Áp dụng ngưỡng mạnh vào kênh V để lấy mặt nạ cường độ B
  5. Áp dụng ngưỡng kênh H để chụp các vùng màu xanh lam cường độ thấp vào mặt nạ nền C
  6. Kết hợp mặt nạ bằng AND để có được mặt nạ cuối cùng
  7. Mở rộng mặt nạ để phóng to các vùng và kết nối các pixel phân tán
  8. Loại bỏ các vùng nhỏ và lấy mặt nạ cuối cùng sẽ chỉ đại diện cho cây

Đây là mã trong MATLAB (một lần nữa, tập lệnh tải tất cả các hình ảnh jpg trong thư mục hiện tại và, một lần nữa, đây không phải là một đoạn mã được tối ưu hóa):

% 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

Các kết quả

các kết quả

Kết quả độ phân giải cao vẫn có sẵn ở đây!
Thậm chí nhiều thí nghiệm với hình ảnh bổ sung có thể được tìm thấy ở đây.


1
Công cụ tuyệt vời! Hãy chắc chắn rằng các câu trả lời khác của bạn cũng theo định dạng này. Để cạnh tranh tiền thưởng, bạn phải sử dụng công nghệ nguồn mở và thật không may, Matlab không phải là một trong số họ. Tuy nhiên, SciLab và Octave là và chúng cung cấp các cú pháp và hàm tương tự. ;)
karlphillip

Mã Octave giống nhau ...
sepdek 27/12/13

@karlphillip Bằng cách nào đó, câu hỏi này đã có thẻ Matlab. Nếu nguồn mở thực sự là điều bắt buộc tôi sẽ khuyên bạn nên loại bỏ nó.
Dennis Jaheruddin

@sepdek Rất đẹp, có lẽ vẫn có thể làm gì đó để bao gồm các 'lỗ hổng' trong bức ảnh cuối cùng. (Thêm tất cả các pixel được bao quanh hoàn toàn bởi các pixel được phê duyệt?!)
Dennis Jaheruddin 30/12/13

1
@karlphillip thanx đàn ông! Tôi vui vì bạn thấy cách tiếp cận của tôi thú vị. Ngoài ra, tôi muốn chúc mừng bạn vì đã chọn giải pháp thanh lịch nhất và không phải là giải pháp có nhiều phiếu bầu nhất !!!
sepdek

36

Các bước giải pháp của tôi:

  1. Nhận kênh R (từ RGB) - tất cả các hoạt động chúng tôi thực hiện trên kênh này:

  2. Tạo Vùng quan tâm (ROI)

    • Kênh ngưỡng R với giá trị tối thiểu 149 (ảnh trên cùng bên phải)

    • Làm giãn vùng kết quả (ảnh giữa bên trái)

  3. Phát hiện eges trong roi tính toán. Cây có rất nhiều cạnh (ảnh phải ở giữa)

    • Pha loãng kết quả

    • Erode với bán kính lớn hơn (hình dưới bên trái)

  4. Chọn đối tượng lớn nhất (theo khu vực) - đó là vùng kết quả

  5. ConvexHull (cây là đa giác lồi) (ảnh dưới cùng bên phải)

  6. Hộp giới hạn (hình dưới cùng bên phải - hộp grren)

Từng bước một: nhập mô tả hình ảnh ở đây

Kết quả đầu tiên - đơn giản nhất nhưng không có trong phần mềm nguồn mở - "Thư viện Tầm nhìn Thích ứng + Thư viện Tầm nhìn Thích ứng": Đây không phải là nguồn mở mà thực sự nhanh chóng để tạo nguyên mẫu:

Toàn bộ thuật toán để phát hiện cây Giáng sinh (11 khối): Giải pháp AVL

Bước tiếp theo. Chúng tôi muốn giải pháp nguồn mở. Thay đổi bộ lọc AVL thành bộ lọc OpenCV: Ở đây tôi đã thực hiện một số thay đổi, ví dụ: Phát hiện cạnh sử dụng bộ lọc cvCanny, để tôn trọng tôi đã nhân hình ảnh vùng với hình ảnh cạnh, để chọn phần tử lớn nhất tôi sử dụng findContours + contourArea nhưng ý tưởng là như nhau.

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

Giải pháp OpenCV

Bây giờ tôi không thể hiển thị hình ảnh với các bước trung gian vì tôi chỉ có thể đặt 2 liên kết.

Ok bây giờ chúng tôi sử dụng các bộ lọc openSource nhưng nó vẫn không phải là toàn bộ nguồn mở. Bước cuối cùng - cổng vào mã c ++. Tôi đã sử dụng OpenCV trong phiên bản 2.4.4

Kết quả của mã c ++ cuối cùng là: nhập mô tả hình ảnh ở đây

Mã c ++ cũng khá ngắn:

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

Trình biên dịch nào có thể xây dựng chương trình này mà không có lỗi?
karlphillip

Tôi đã sử dụng Visual Studio 2012 để xây dựng nó. Bạn nên sử dụng trình biên dịch c ++ có hỗ trợ c ++ 11.
AdamF

Tôi không có một hệ thống theo ý của tôi với điều đó. Bạn có thể viết lại std::max_element()cuộc gọi? Tôi muốn thưởng cho câu trả lời của bạn là tốt. Tôi nghĩ rằng tôi có gcc 4.2.
karlphillip

Ok đây là tính năng c ++ 11;) Tôi đã thay đổi mã nguồn ở trên. Hãy cố gắng bây giờ.
AdamF

Được rồi, cám ơn. Tôi đã thử nó và nó thật đẹp. Ngay sau khi câu hỏi này được mở lại (những người dùng khác phải giúp tôi điều đó) tôi có thể đặt một tiền thưởng khác để thưởng cho bạn. Xin chúc mừng!
karlphillip

31

... Một giải pháp lỗi thời khác - hoàn toàn dựa trên xử lý HSV :

  1. Chuyển đổi hình ảnh sang không gian màu HSV
  2. Tạo mặt nạ theo heuristic trong HSV (xem bên dưới)
  3. Áp dụng sự giãn nở hình thái cho mặt nạ để kết nối các khu vực bị ngắt kết nối
  4. Loại bỏ các khu vực nhỏ và khối ngang (nhớ cây là khối dọc)
  5. Tính hộp giới hạn

Một từ về các heuristic trong xử lý HSV:

  1. mọi thứ có Hues (H) trong khoảng 210 - 320 độ bị loại bỏ dưới dạng màu xanh đỏ được cho là ở hậu cảnh hoặc trong các khu vực không liên quan
  2. mọi thứ có Giá trị (V) thấp hơn 40% cũng bị loại bỏ vì quá tối không liên quan

Tất nhiên người ta có thể thử nghiệm nhiều khả năng khác để tinh chỉnh phương pháp này ...

Đây là mã MATLAB để thực hiện thủ thuật (cảnh báo: mã không được tối ưu hóa !!! Tôi đã sử dụng các kỹ thuật không được khuyến nghị cho lập trình MATLAB chỉ để có thể theo dõi bất cứ điều gì trong quy trình - điều này có thể được tối ưu hóa rất nhiều):

% 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

Các kết quả:

Trong kết quả, tôi hiển thị hình ảnh đeo mặt nạ và khung giới hạn. nhập mô tả hình ảnh ở đây


Xin chào, cảm ơn câu trả lời. Vui lòng dành chút thời gian để đọc phần Yêu cầu để đảm bảo câu trả lời của bạn tuân theo tất cả các hướng dẫn. Bạn quên chia sẻ những hình ảnh kết quả. ;)
karlphillip 27/12/13

2
@karlphillip sepdek không có đủ danh tiếng để chia sẻ hình ảnh, tôi đã chuyển hình ảnh vào phần thân câu trả lời theo liên kết và hướng dẫn của anh ấy. Mặc dù không chắc chắn, đó là những người chính xác, hãy bình luận phần này.
alko

@alko Mình biết, cảm ơn. Nhưng một số hình ảnh bạn chia sẻ không có trong bộ đầu vào . Câu trả lời phải hiển thị kết quả xử lý tất cả 6 hình ảnh được chia sẻ trên câu hỏi.
karlphillip 27/12/13

@karlphillip đó là hình ảnh của anh ấy, không phải của tôi. đó là ý nghĩa của "bình luận phần này";)
alko

2
Xin lỗi vì đã gây ra vấn đề ... không phải ý định của tôi. Tôi đã bao gồm tất cả các hình ảnh trong bộ dữ liệu ban đầu và nâng cao nó hơn nữa chỉ để chứng minh rằng khái niệm của tôi mạnh mẽ ...
sepdek

23

Một số phương pháp xử lý hình ảnh lỗi thời ...
Ý tưởng này dựa trên giả định rằng hình ảnh mô tả cây phát sáng trên nền thường tối hơn và mịn hơn (hoặc tiền cảnh trong một số trường hợp). Khu vực cây được chiếu sáng "nhiều năng lượng" hơn và có cường độ cao hơn .
Quá trình này như sau:

  1. Chuyển đổi sang Graylevel
  2. Áp dụng lọc LoG để có được các khu vực "hoạt động" nhất
  3. Áp dụng một ngưỡng có ý định để có được các khu vực sáng nhất
  4. Kết hợp 2 phần trước để có mặt nạ sơ bộ
  5. Áp dụng sự giãn nở hình thái cho các khu vực phóng to và kết nối các thành phần lân cận
  6. Loại bỏ các khu vực ứng cử viên nhỏ theo kích thước khu vực của họ

Những gì bạn nhận được là một mặt nạ nhị phân và một hộp giới hạn cho mỗi hình ảnh.

Dưới đây là kết quả sử dụng kỹ thuật ngây thơ này: nhập mô tả hình ảnh ở đây

Mã trên MATLAB theo sau: Mã chạy trên một thư mục có hình ảnh JPG. Tải tất cả các hình ảnh và trả về kết quả được phát hiện.

% 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

Đừng quên tải lên hình ảnh kết quả, giống như Faust đã làm.
karlphillip

Tôi là một người mới ở đây vì vậy tôi không thể tải lên hình ảnh. Xin vui lòng xem kết quả trên các liên kết được cung cấp trong mô tả của tôi.
sepdek 27/12/13

Ok, nhưng bạn vẫn phải sử dụng những hình ảnh được chia sẻ trong câu hỏi như mọi người khác đang làm. Khi bạn xử lý chúng, hãy tải nó lên một nơi nào đó và chỉnh sửa câu trả lời của bạn để thêm các liên kết. Sau đó tôi sẽ chỉnh sửa câu trả lời của bạn và đặt hình ảnh bên trong nó cho bạn.
karlphillip

Liên kết dường như chứa các hình ảnh chính xác bây giờ.
Dennis Jaheruddin

22

Sử dụng một cách tiếp cận hoàn toàn khác với những gì tôi đã thấy, tôi đã tạo ra một kịch bản phát hiện cây Giáng sinh bằng ánh sáng của họ. Kết quả ist luôn là một tam giác đối xứng và nếu các giá trị số cần thiết như góc ("độ béo") của cây.

Mối đe dọa lớn nhất đối với thuật toán này rõ ràng là đèn bên cạnh (với số lượng lớn) hoặc ở phía trước cây (vấn đề lớn hơn cho đến khi tối ưu hóa hơn nữa). Chỉnh sửa (đã thêm): Điều không thể làm: Tìm hiểu xem có cây Giáng sinh hay không, tìm nhiều cây Giáng sinh trong một hình ảnh, phát hiện chính xác một cây cristmas ở giữa Las Vegas, phát hiện cây Giáng sinh bị uốn cong nặng, lộn ngược hoặc cắt nhỏ ...;)

Các giai đoạn khác nhau là:

  • Tính độ sáng được thêm vào (R + G + B) cho mỗi pixel
  • Thêm giá trị này của tất cả 8 pixel lân cận lên trên mỗi pixel
  • Xếp hạng tất cả các pixel theo giá trị này (sáng nhất trước tiên) - Tôi biết, không thực sự tinh tế ...
  • Chọn N trong số này, bắt đầu từ đầu, bỏ qua những cái quá gần
  • Tính toán trong số N hàng đầu này (cung cấp cho chúng tôi trung tâm gần đúng của cây)
  • Bắt đầu từ vị trí trung bình trở lên trong chùm tìm kiếm mở rộng để có ánh sáng cao nhất từ ​​những điểm sáng nhất được chọn (mọi người có xu hướng đặt ít nhất một ánh sáng ở trên cùng)
  • Từ đó, hãy tưởng tượng các đường đi 60 độ trái và phải xuống (cây Giáng sinh không nên mập như vậy)
  • Giảm 60 độ cho đến khi 20% ánh sáng rực rỡ nhất nằm ngoài tam giác này
  • Tìm ánh sáng ở dưới cùng của hình tam giác, cho bạn đường viền ngang dưới của cây
  • Làm xong

Giải thích về các dấu hiệu:

  • Cây thánh giá lớn màu đỏ ở trung tâm của cây: Trung bình của ngọn N sáng nhất
  • Đường chấm chấm từ đó trở lên: "chùm tìm kiếm" cho ngọn cây
  • Chữ thập đỏ nhỏ hơn: ngọn cây
  • Chữ thập đỏ thực sự nhỏ: Tất cả các đèn N sáng nhất
  • Tam giác đỏ: D'uh!

Mã nguồn:

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


?>

Hình ảnh: Phía trên bên trái Trung tâm dưới Thấp hơn bên trái Phía trên bên phải Trung tâm phía trên Góc dưới bên phải

Phần thưởng: Một người Đức Weihnachtsbaum, từ Wikipedia http : //commons.wik mega.org/wiki/File:Weihnachtsbaum_R%C3%B6merberg.jpg Römerberg


17

Tôi đã sử dụng python với opencv.

Thuật toán của tôi diễn ra như sau:

  1. Đầu tiên, nó lấy kênh màu đỏ từ hình ảnh
  2. Áp dụng ngưỡng (giá trị tối thiểu 200) cho kênh Đỏ
  3. Sau đó, áp dụng Gradient Hình thái và sau đó thực hiện 'Đóng' (sự giãn nở theo sau là Xói mòn)
  4. Sau đó, nó tìm thấy các đường viền trong mặt phẳng và nó chọn đường viền dài nhất.

Kết quả:

Mật mã:

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

Nếu tôi thay đổi kernel từ (25,5) thành (10,5), tôi sẽ nhận được kết quả đẹp hơn trên tất cả các cây nhưng phía dưới bên trái, nhập mô tả hình ảnh ở đây

Thuật toán của tôi giả định rằng cây có đèn chiếu sáng, và ở cây dưới cùng bên trái, ngọn có ít ánh sáng hơn các cây khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.