Як виявити ялинку? [зачинено]


382

Які методи обробки зображень можна використовувати для реалізації програми, яка визначає ялинки, відображені на наступних зображеннях?

Я шукаю рішення, які будуть працювати над усіма цими зображеннями. Тому підходи, які потребують підготовки каскадних класифікаторів Haar або відповідності шаблонів , не дуже цікаві.

Я шукаю те, що можна написати будь-якою мовою програмування, якщо воно використовує лише технології Open Source . Розв’язання має бути протестовано із зображеннями, якими ділиться в цьому питанні. Є 6 вхідних зображень, і відповідь повинна відображати результати обробки кожного з них. Нарешті, для кожного вихідного зображення повинні бути намальовані червоні лінії для оточення виявленого дерева.

Як би ви вирішили програмно виявити дерева на цих зображеннях?


3
Чи дозволяється нам використовувати деякі зображення для тренувань, або всі подані зображення повинні використовуватися для перевірки? У будь-якому випадку, крута конкуренція: D
Hannes Ovrén

7
@karlphillip, ви хочете, щоб ми використовували ці зображення для тестування та інші зображення для навчання? Просто не зрозуміло, що таке навчальний набір.
GilLevi

16
@karlphillip: Моя порада: скасуйте вимогу "відкритого коду". Це не має значення, якою мовою / рамкою ви користуєтесь. Алгоритми обробки зображень / комп'ютерного зору є мовними агностиками, тому, якщо ви можете написати це в MATLAB, ви, звичайно, можете це зробити OpenCV або будь-який інший фреймворк, який ви віддаєте перевагу ... Також мені все ще не зрозуміло, що ви вважаєте навчанням / тестуванням зображень !
Amro

2
@karlphillip thanx за те, що залучає нас до участі у ваших „пошуках”! Це була чудова можливість продуктивно провести кілька годин, але найголовніше, щоб побачити, скільки різних підходів можна знайти до однієї проблеми ... Сподіваюся, ви зробите це знову для 1-го січня (можливо, на санях Виклик Діда Мороза? ;-))
sepdek

2
Гаразд, я переформулював питання, щоб видалити елементи змагань. Я думаю, що це повинно дозволяти йому самотужки стояти самостійно.
Бред Ларсон

Відповіді:


184

У мене є підхід, який я вважаю цікавим і трохи відрізняється від решти. Основна відмінність мого підходу порівняно з деякими іншими полягає в тому, як виконується етап сегментації зображення - я використовував DBSCAN алгоритм кластеризації від Scikit-learn Python; він оптимізований для пошуку дещо аморфних форм, які не обов'язково мають єдиний чіткий центроїд.

На найвищому рівні мій підхід досить простий і його можна розділити приблизно на 3 кроки. Спочатку я застосовую поріг (або власне, логічне "або" двох окремих і виразних порогів). Як і в багатьох інших відповідях, я припускав, що ялинка буде одним із яскравих об’єктів на сцені, тому перший поріг - це просто простий монохромний тест яскравості; будь-які пікселі зі значеннями вище 220 за шкалою 0-255 (де чорний - 0, а білий - 255), зберігаються у бінарне чорно-біле зображення. Другий поріг намагається шукати червоні та жовті вогні, які особливо помітні на деревах у верхньому лівому та нижньому правому куті від шести зображень та добре виділяються на синьо-зеленому тлі, який є поширеним у більшості фотографій. Я перетворюю зображення rgb в простір hsv, і вимагати, щоб відтінок був менше 0,2 за шкалою 0,0-1,0 (що відповідає границі між жовтим та зеленим) або більше 0,95 (відповідає межі між фіолетовим та червоним), і додатково мені потрібні яскраві насичені кольори: і насиченість, і значення повинні бути вище 0,7. Результати двох порогових процедур є логічно "або" -ed разом, і отримана матриця чорно-білих бінарних зображень показана нижче:

Новорічні ялинки, після порогу на ВПГ, а також однотонної яскравості

Ви чітко бачите, що кожне зображення має один великий кластер пікселів, що приблизно відповідає розташуванню кожного дерева, плюс кілька зображень також мають деякі інші невеликі кластери, що відповідають або вогням у вікнах деяких будівель, або фон сцени на горизонті. Наступним кроком є ​​те, щоб комп'ютер визнав, що це окремі кластери, і правильно позначити кожен піксель за допомогою ідентифікаційного номера членського кластера.

Для цього завдання я вибрав DBSCAN . Тут досить хороше візуальне порівняння того, як DBSCAN поводиться, як правило, стосовно інших алгоритмів кластеризації, доступних тут . Як я вже говорив раніше, це добре справляється з аморфними формами. Вихід DBSCAN з кожним кластером, побудованим в іншому кольорі, показаний тут:

Вихід кластеризації DBSCAN

Потрібно пам’ятати кілька речей, дивлячись на цей результат. По-перше, DBSCAN вимагає від користувача встановити параметр "близькості", щоб регулювати його поведінку, який ефективно контролює, наскільки має бути відокремлена пара точок, щоб алгоритм оголосив новий окремий кластер, а не агломерацію тестової точки на вже існуючий кластер. Я встановив це значення в 0,04 рази більше за діагональ кожного зображення. Оскільки зображення відрізняються за розміром від приблизно VGA до приблизно HD 1080, такий тип визначення масштабу є критичним.

Ще один момент, який варто зауважити, полягає в тому, що алгоритм DBSCAN під час його реалізації в scikit-learn має обмеження пам'яті, які є досить складними для деяких великих зображень у цьому зразку. Тому для декількох більших зображень мені довелося "зменшити" (тобто зберегти лише кожен 3-й або 4-й піксель і скинути інші) кожен кластер, щоб залишитися в межах цієї межі. В результаті цього процесу відсікання на деяких великих зображеннях залишилися окремі розріджені пікселі. Тому лише для відображення кольорові пікселі на вищезазначених зображеннях були ефективно «розширені» лише трохи, щоб вони краще виділялися. Це суто косметична операція заради розповіді; хоча в моєму кодексі є коментарі, в яких згадується про цю дилатацію,

Після того, як кластери будуть ідентифіковані та помічені, третій і останній крок стає простим: я просто беру найбільший кластер у кожному зображенні (у цьому випадку я вибрав для вимірювання "розмір" за загальною кількістю пікселів-членів, хоча можна було б настільки ж просто замість цього використовується якийсь тип метрики, яка вимірює фізичну міру) і обчислити опуклий корпус для цього кластера. Опуклий корпус потім стає межею дерева. Шість опуклих корпусів, обчислених цим методом, показані нижче червоним кольором:

Ялинки з їх розрахунковими межами

Вихідний код написаний для Python 2.7.6, і це залежить від numpy , scipy , matplotlib та scikit-learn . Я розділив його на дві частини. Перша частина відповідає за фактичну обробку зображення:

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

а друга частина - це сценарій рівня користувача, який викликає перший файл і генерує всі наведені вище сюжети:

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

Рішення @ lennon310 - це кластеризація. (k-засоби)
користувач3054997

1
@stachyra Я також думав над цим підходом, перш ніж запропонувати свої більш прості. Я думаю, що це має великий потенціал, який можна розширити і узагальнити, щоб дати хороші результати і в інших випадках. Ви можете експериментувати з нейронними мережами для кластеризації. Щось на зразок SOM або нейронного газу справили б чудову роботу. Тим не менш, від мене чудова пропозиція і великий палець!
сепдек

4
@Faust & Райан Карлсон: спасибі, хлопці! Так, я погоджуюся, що система upvote, хоча вона працює добре для присвоєння між 2 або 3 короткими відповідями, усіма поданими протягом декількох годин один одного, має серйозні упередження, коли мова йде про змагання з довгими відповідями, які розігруються протягом тривалого періоду часу . З одного боку, ранні подання починають накопичувати оновлення до того, як пізніші будуть навіть доступні для публічного огляду. І якщо відповіді є довгими, то, як тільки хтось встановить скромну роль, часто виникає "ефект смуги", оскільки люди лише підтримують перший, не намагаючись прочитати решту.
stachyra

2
@stachyra чудовий друг новин! Найтепліші привітання, і це може позначити початок вашого нового року!
сепдек

1
@ lennon310: Я ще не пробував локальний фільтр максимального виявлення щодо цієї проблеми, але якщо ви хочете вивчити його самостійно, scipy включає цей . Мій вихідний код Python для цього проекту був таким коротким, що я насправді змогла опублікувати 100%; буквально все, що вам потрібно буде зробити, - скопіювати та вставити два мої фрагменти коду в окремі файли .py, а потім замінити виклик scipy.ndimage.filters.maximum_filter()у тому самому місці, де я використав поріг.
stachyra

145

РЕДАКТУВАЛЬНА ПРИМІТКА. Я редагував цю публікацію, щоб (i) обробляти кожне зображення дерева окремо, як цього вимагають вимоги;


Нижче представлений підхід, який враховує яскравість та форму об'єкта. Іншими словами, він шукає об’єкти, що мають форму трикутника та зі значною яскравістю. Він був реалізований в Java, використовуючи рамки обробки зображень Marvin .

Перший крок - кольорове обмеження. Завдання тут - зосередити аналіз на об'єктах зі значною яскравістю.

вихідні зображення:

вихідний код:

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

На другому кроці найяскравіші точки зображення розширюються з метою формування фігур. Результатом цього процесу є ймовірна форма предметів зі значною яскравістю. Застосовуючи сегментацію заливного заливу, виявляються від’єднані форми.

вихідні зображення:

вихідний код:

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

Як показано на вихідному зображенні, було виявлено кілька фігур. У цій проблемі на зображеннях є лише кілька яскравих точок. Однак такий підхід був застосований для вирішення більш складних сценаріїв.

На наступному етапі кожна форма аналізується. Простий алгоритм виявляє фігури з малюнком, схожим на трикутник. Алгоритм аналізує об’єктну форму за рядком. Якщо центр маси кожної лінії фігури майже однаковий (заданий поріг), а маса збільшується в міру збільшення y, об'єкт має форму трикутника. Маса лінії фігури - це кількість пікселів у тому рядку, який належить фігурі. Уявіть, що ви нарізаєте об'єкт по горизонталі та аналізуєте кожен горизонтальний сегмент. Якщо вони централізовані один до одного і довжина збільшується від першого сегмента до останнього за лінійним малюнком, напевно, у вас є об'єкт, що нагадує трикутник.

вихідний код:

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

Нарешті, положення кожної форми, подібне до трикутника та зі значною яскравістю, в даному випадку ялинку, виділяється на оригінальному зображенні, як показано нижче.

кінцеві вихідні зображення:

остаточний вихідний код:

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

Перевагою такого підходу є той факт, що він, ймовірно, працюватиме із зображеннями, що містять інші світяться об'єкти, оскільки він аналізує форму об'єкта.

Щасливого Різдва!


РЕДАКЦІЯ ПРИМІТКА 2

Йде дискусія про схожість вихідних зображень цього рішення та деяких інших. Насправді вони дуже схожі. Але такий підхід не просто сегментує об’єкти. Він також аналізує фігури об'єкта в певному сенсі. Він може обробляти кілька світних предметів в одній сцені. Насправді ялинку не потрібно бути найяскравішою. Я просто перекладаю це, щоб збагатити дискусію. У зразках є упередженість, що просто шукаючи найяскравіший об’єкт, ви знайдете дерева. Але чи справді ми хочемо зупинити дискусію на цьому етапі? У цей момент, наскільки комп'ютер насправді розпізнає предмет, що нагадує ялинку? Спробуємо закрити цю прогалину.

Нижче представлений результат лише для з'ясування цього моменту:

вхідне зображення

введіть тут опис зображення

вихід

введіть тут опис зображення


2
Це цікаво. Сподіваюся, ви зможете отримати однакові результати, коли кожне зображення обробляється індивідуально. Я раніше відредагував це питання 4 години раніше, коли ви розмістили відповідь, щоб вказати це конкретно. Було б дивним, якби ви могли оновити свою відповідь за допомогою цих результатів.
karlphillip

@ Марвін у вашому виявленні трикутника, як ви впоралися з коливанням маси? Це не суворий трикутник, маса не є моно, оскільки y змінюється
user3054997

2
@ user3054997: Це ще один момент. Як я розміщував, алгоритм не шукає строгих фігур трикутника. Він аналізує кожен об'єкт і розглядає дерево, яке «нагадує» трикутник з простими критеріями: маса об’єкта використовується для збільшення, оскільки y збільшується, а центр маси кожного горизонтального сегмента об'єкта майже централізований один до одного .
Габріель Амбрусіо Арчанджо

@Marvin Моє рішення дуже просте, про це я і заявив у своїй відповіді. До речі, це працювало краще, ніж ваше перше рішення. Якщо я правильно пригадую, у першій своїй відповіді ви говорили про дескриптори функцій для виявлення невеликих легких текстур, які ви не робите тут. Я просто сказав, що ваш теперішній підхід та результати набагато більше схожі на мої, ніж на ваше перше рішення. Звичайно, я не чекаю, що ти це признаєш, я це заявив лише для запису.
smeso

1
@sepdek Тут є кілька рішень, які справді набагато кращі, ніж мої, і вони все ще отримують половину моїх результатів. Немає нічого поганого в тому, щоб "надихнутися" іншими рішеннями. Я також бачив ваші рішення, я нічого не маю сказати проти вас, ви розмістили їх після мене, і моя "ідея" була не такою оригінальною, щоб сказати, що ви просто скопіювали мене. Але Марвін був єдиним, хто розмістив переді мною і відредагував це рішення після того, як побачив шахту за тим самим алгоритмом ... принаймні, він міг би сказати "Так, мені сподобалось ваше рішення, і я його повторно використав" нічого поганого немає, це просто Гра.
smeso

75

Ось моє просте і німе рішення. Він заснований на припущенні, що дерево буде найяскравішою і великою річчю на малюнку.

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

Перший крок - виявити найяскравіші пікселі на малюнку, але ми повинні зробити відмінність між самим деревом та снігом, який відбиває його світло. Тут ми намагаємось виключити сніг, застосувавши дійсно простий фільтр кольорових кодів:

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

Вибачте, але на даний момент у мене поганий зв’язок, тому неможливо завантажувати фотографії. Я спробую це зробити пізніше.

Щасливого Різдва.

Редагувати:

Ось кілька знімків підсумкового результату:


1
Привіт! Переконайтесь, що ваша відповідь відповідає всім вимогам: Є 6 вхідних зображень, і відповідь має відображати результати обробки кожного з них; .
karlphillip

Привіт! Ви можете передати імена файлів в якості аргументів CLI до моєї програми: ./christmas_tree ./*.png. Їх може бути стільки, скільки вам потрібно, результати показуватимуться один за одним натисканням будь-якої клавіші. Це неправильно?
smeso

Це нормально, але вам все одно потрібно завантажувати зображення та ділитися ними у своєму запитанні, щоб глядачі нитки насправді побачили ваш результат. Якщо люди побачать, що ви зробили, це покращить ваші шанси отримати голоси;)
karlphillip

Я намагаюся знайти рішення для цього, у мене є проблеми з підключенням.
smeso

2
Чудово! Тепер ви можете змінити їх масштаб всередині відповіді з таким кодом: <img src="http://i.stack.imgur.com/nmzwj.png" width="210" height="150">Просто змініть посилання на картинку;)
karlphillip

60

Я написав код у Matlab R2007a. Я використовував k-засоби, щоб грубо витягти ялинку. Я покажу проміжний результат лише одним зображенням, а кінцеві результати - всіма шістьма.

По-перше, я намалював простір RGB на простір лабораторії, що може посилити контраст червоного кольору в його b-каналі:

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

введіть тут опис зображення

Окрім функції у кольоровому просторі, я також використовував функцію текстури, яка більше стосується сусідства, а не кожного пікселя. Тут я лінійно поєднав інтенсивність з трьох вихідних каналів (R, G, B). Причина, чому я відформатував цей спосіб, полягає в тому, що на ялинках на зображенні всі червоні вогні на них, а іноді і зелене / іноді синє освітлення.

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

введіть тут опис зображення

Я застосував локальну бінарну схему 3X3 I0, використав центральний піксель як поріг і отримав контраст, обчисливши різницю між середнім значенням інтенсивності пікселя над порогом і середнім значенням під ним.

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

введіть тут опис зображення

Оскільки у мене всього 4 функції, я вибрав би K = 5 у своєму методі кластеризації. Код k-засобів показаний нижче (це з курсу машинного навчання доктора Ендрю Нг. Я пройшов курс раніше, і сам написав код у своєму завданні з програмування).

[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

Оскільки програма працює на моєму комп’ютері дуже повільно, я просто запустив 3 ітерації. Зазвичай критеріями зупинки є (i) час ітерації принаймні 10, або (ii) більше не змінюється на центроїдах. На мій тест, збільшення ітерації може диференціювати тло (небо та дерево, небо та будівля, ...) точніше, але не показало кардинальних змін у видобутку ялинки. Також зауважте, що k-засоби не застраховані від випадкової ініціалізації центроїдів, тому рекомендується запускати програму кілька разів для порівняння.

Після k-засобів I0була обрана мічена область з максимальною інтенсивністю . І граничне трасування було використано для вилучення кордонів. Мені остання ялинка є найскладнішою для видобутку, оскільки контраст на цій картині недостатньо високий, оскільки вони є в першій п'ятірці. Інша проблема мого методу полягає в тому, що я використовував bwboundariesфункцію в Matlab для відстеження меж, але іноді також включаються внутрішні межі, як ви можете спостерігати в результатах 3-го, 5-го, 6-го. Темна сторона в ялинках не тільки не змогла бути згрупована підсвіченою стороною, але й призводить до такої кількості крихітних внутрішніх меж ( imfillне дуже покращується). У всьому моєму алгоритмі ще багато місця для вдосконалення.

Деякі публікації вказують, що середній зсув може бути більш надійним, ніж k-засоби, і багато алгоритмів на основі графіків також дуже конкурентоспроможні за складної сегментації меж. Я написав алгоритм середнього зсуву сам, здається, краще витягувати регіони без достатнього світла. Але середній зсув дещо сегментований, і потрібна певна стратегія об'єднання. Він працював навіть набагато повільніше, ніж k-означає, на моєму комп’ютері, я боюся, що мені доведеться його відмовити. Я з нетерпінням чекаю, що інші представлять тут чудові результати за допомогою цих сучасних алгоритмів, згаданих вище.

Але я завжди вважаю, що вибір функції є ключовим компонентом сегментації зображення. При правильному підборі функції, який може максимально збільшити межу між об'єктом і фоном, безліч алгоритмів сегментації обов'язково спрацює. Різні алгоритми можуть покращити результат від 1 до 10, але вибір функції може покращити його від 0 до 1.

Щасливого Різдва !


2
Дякую за відповідь! Я просто хотів зазначити, що Matlab не є відкритим кодом , але Scilab є. Я також хотів би бачити, як ця відповідь конкурує з іншими. ;)
karlphillip

6
Дякую, Карле. Octave - це ще одне програмне забезпечення з відкритим кодом, яке поділяє майже таку ж граматику кодування з Matlab: mathworks.fr/matlabcentral/answers/14399-gnu-octave-vs-matlab .
lennon310

Цікаво, я цього не знав, дякую! Чи працює ваш код на Octave?
karlphillip

Я ще не
тестував

@ lennon310 Я думаю, якщо скинути межі та отримати опуклий корпус, ти позбудешся проблеми з отворами. Пам'ятайте, що опуклий корпус - найменша площа, яка включає всі точки в наборі.
сепдек

57

Це моя остання публікація з використанням традиційних підходів до обробки зображень ...

Тут я якось поєдную свої дві інші пропозиції, домагаючись ще кращих результатів . Власне кажучи, я не бачу, як ці результати можуть бути кращими (особливо, коли ви дивитесь на масковані зображення, які створює метод).

В основі підходу лежить поєднання трьох ключових припущень :

  1. Зображення повинні мати великі коливання в областях дерева
  2. Зображення повинні мати більш високу інтенсивність у регіонах дерева
  3. Фонові регіони повинні мати низьку інтенсивність і бути переважно блакитними

Маючи на увазі ці припущення, метод працює наступним чином:

  1. Перетворення зображень у HSV
  2. Відфільтруйте V канал за допомогою фільтра LoG
  3. Застосовуйте жорстке порогове значення на відфільтрованому LoG зображенні, щоб отримати маску "Активність"
  4. Застосуйте жорсткий поріг до V каналу, щоб отримати маску інтенсивності B
  5. Застосовуйте порогове значення H-каналу для зйомки областей низької інтенсивності синього кольору у маску С
  6. Поєднайте маски за допомогою AND, щоб отримати остаточну маску
  7. Розширіть маску, щоб збільшити регіони та з'єднати дисперсні пікселі
  8. Усуньте невеликі регіони і отримайте остаточну маску, яка в кінцевому підсумку буде представляти лише дерево

Ось код у MATLAB (знову ж таки, скрипт завантажує всі зображення jpg у поточну папку, і, знову ж таки, це далеко не оптимізований фрагмент коду):

% 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

Результати

результати

Результати з високою роздільною здатністю ще доступні тут!
Ще більше експериментів з додатковими зображеннями можна знайти тут.


1
Чудові речі! Переконайтеся, що інші відповіді також відповідають цьому формату. Щоб змагатись за виграш, ви повинні використовувати технологію з відкритим кодом , і, на жаль, Matlab не є однією з них. Однак SciLab та Octave є, і вони забезпечують подібний синтаксис та функції. ;)
karlphillip

Код Октави той самий ...
sepdek

@karlphillip Якось у цьому питанні з’явився тег Matlab. Якщо відкритий код справді є обов'язковим, я рекомендую його видалити.
Денніс Джахеруддін

@sepdek Дуже приємно, можливо, все-таки можна було б зробити щось, щоб включити "дірки" у фінальну картину. (Додайте всі пікселі, які повністю оточені затвердженими пікселями ?!)
Dennis Jaheruddin

1
@karlphillip thanx man! Я радий, що ти знайшов мій підхід цікавим. Крім того, я хотів би привітати вас за те, що ви вибрали найелегантніше рішення, а не те, що має найбільше голосів !!!
сепдек

36

Мої кроки вирішення:

  1. Отримати R-канал (від RGB) - всі операції, які ми робимо на цьому каналі:

  2. Створіть цікавий регіон (ROI)

    • Поріг каналу R з мінімальним значенням 149 (верхнє право зображення)

    • Розширити область результатів (зображення зліва вліво)

  3. Виявити егези в обчислених роях. Дерево має багато країв (зображення в середньому правому куті)

    • Розширити результат

    • Ерозія з більшим радіусом (зображення внизу ліворуч)

  4. Виберіть найбільший (за площею) об’єкт - це область результатів

  5. ConvexHull (дерево - опуклий багатокутник) (зображення праворуч внизу)

  6. Обв'язувальний ящик (зображення праворуч внизу - скринька грина)

Крок за кроком: введіть тут опис зображення

Перший результат - найпростіший, але не в програмному забезпеченні з відкритим кодом - "Адаптивна Vision Studio + Бібліотека адаптивних бачень": Це не з відкритим кодом, але дуже швидко прототипується:

Весь алгоритм виявлення ялинки (11 блоків): Рішення AVL

Наступний крок. Ми хочемо рішення з відкритим кодом. Змініть AVL-фільтри на OpenCV-фільтри: Тут я зробив невеликі зміни, наприклад, для виявлення країв використовується фільтр cvCanny, щоб поважати Рой, я помножив зображення регіону на зображення ребер, щоб вибрати найбільший елемент, який я використав findContours + contourArea, але ідея така ж.

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

Рішення OpenCV

Зараз я не можу показувати зображення з проміжними кроками, оскільки я можу розмістити лише 2 посилання.

Гаразд, ми використовуємо фільтри openSource, але це ще не весь відкритий код. Останній крок - порт до коду c ++. Я використовував OpenCV у версії 2.4.4

Результатом остаточного коду c ++ є: введіть тут опис зображення

Код c ++ також досить короткий:

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

Який компілятор може створити цю програму без помилок?
karlphillip

Я використовував Visual Studio 2012 для його побудови. Слід використовувати компілятор c ++ із підтримкою c ++ 11.
АдамФ

У мене немає такої системи в моєму розпорядженні. Не могли б ви переписати std::max_element()дзвінок? Я також хотів би нагородити вашу відповідь. Я думаю, що у мене є gcc 4.2.
karlphillip

Добре це функція c ++ 11;) я змінив вихідний код вище. Спробуйте зараз.
AdamF

Добре, дякую. Я тестував це, і це прекрасно. Як тільки це питання буде відкрито (інші користувачі повинні мені допомогти у цьому), я можу встановити ще одну винагороду, щоб винагородити вас. Вітаємо!
karlphillip

31

... ще одне старомодне рішення - суто на основі обробки ВПГ :

  1. Перетворення зображень у простор кольорів HSV
  2. Створіть маски відповідно до евристики у ВПГ (див. Нижче)
  3. Нанесіть морфологічну дилатацію на маску для з'єднання роз'єднаних ділянок
  4. Відкиньте невеликі площі та горизонтальні блоки (пам’ятайте, що дерева - це вертикальні блоки)
  5. Обчисліть обмежувальне поле

Слово про евристику при обробці ВПГ:

  1. все з відтінками (H) між 210 - 320 градусами відкидається як синьо-пурпурова, яка повинна бути на задньому плані або в невідповідних областях
  2. все зі значеннями (V) нижче 40% також викидається як занадто темне, щоб бути актуальним

Звичайно, можна експериментувати з численними іншими можливостями, щоб налагодити цей підхід ...

Ось код MATLAB, щоб виконати трюк (попередження: код далеко не оптимізований !!! Я використовував методи, не рекомендовані для програмування MATLAB, просто щоб мати можливість відстежувати що-небудь у процесі - це може бути значно оптимізовано):

% 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

Результати:

В результатах я показую замасковане зображення та обмежувальний ящик. введіть тут опис зображення


Привіт, дякую за відповідь. Будь ласка, знайдіть хвилину, щоб прочитати розділ « Вимоги», щоб переконатися, що ваша відповідь відповідає всім інструкціям. Ви забули поділитися отриманими зображеннями. ;)
karlphillip

2
@karlphillip sepdek не має достатньої репутації для обміну зображеннями, я перемістив зображення в тіло відповідей відповідно до його посилання та інструкцій. Не впевнений, що це правильні, сміливо коментуйте цю частину.
алко

@alko Я знаю, дякую. Але деякі зображення, якими ви поділилися, не були у наборі вводу . У відповіді повинен бути відображений результат обробки всіх 6 зображень, поділених у питанні.
karlphillip

@karlphillip - це його образи, а не мої. це якраз те, що я мав на увазі під «прокоментувати цю частину»;)
алко

2
Вибачте за те, що викликали проблеми ... не мій намір. Я включив усі зображення в початковий набір даних і вдосконалив його ще більше, щоб довести, що моя концепція є надійною ...
sepdek

23

Деякий старомодний підхід до обробки зображень ...
Ідея ґрунтується на припущенні, що зображення зображують освітлені дерева на типово більш темних і плавних фонах (або фонах у деяких випадках). Освітлена область дерева є більш «енергійної» і має більш високу інтенсивність .
Процес такий:

  1. Перетворити на грайвел
  2. Застосуйте LoG-фільтрацію, щоб отримати найбільш "активні" області
  3. Застосуйте порогове значення, щоб отримати найсвітліші ділянки
  4. Поєднайте попередні 2, щоб отримати попередню маску
  5. Застосовуйте морфологічну дилатацію для збільшення ділянок та з'єднання сусідніх компонентів
  6. Усуньте невеликі області кандидатів відповідно до їх розміру

Ви отримуєте двійкову маску та обмежувальний ящик для кожного зображення.

Ось результати за допомогою цієї наївної техніки: введіть тут опис зображення

Код на MATLAB наступний: Код працює у папці із зображеннями JPG. Завантажує всі зображення та повертає виявлені результати.

% 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

Не забувайте завантажувати отримані зображення, як це робив Фауст.
karlphillip

Я тут ноб, тому не можу завантажувати зображення. Будь ласка, подивіться результати за наданими посиланнями в моєму описі.
сепдек

Гаразд, але ви все одно повинні використовувати зображення, що ділиться у питанні, як і всі інші. Після їх обробки завантажте його кудись і відредагуйте свою відповідь, щоб додати посилання. Пізніше я відредагую вашу відповідь і розміщу зображення всередині неї.
karlphillip

Здається, посилання містить правильні зображення зараз.
Денніс Джахеруддін

22

Використовуючи зовсім інший підхід від того, що я бачив, я створив а сценарій, який виявляє ялинки за їх вогнями. Результат є завжди симетричним трикутником, а при необхідності числовими значеннями, такими як кут ("жирність") дерева.

Найбільшою загрозою цього алгоритму, очевидно, є вогні поруч (у великій кількості) або перед деревом (чим більша проблема до подальшої оптимізації). Редагувати (додано): Що робити не можна: з’ясуйте, чи є ялинка чи ні, знайдіть кілька ялинок в одному зображенні, правильно виявіть ялинку посеред Лас-Вегаса, виявіть ялинки, які сильно зігнуті, догори дном або рубати ...;)

Різні етапи:

  • Обчисліть додану яскравість (R + G + B) для кожного пікселя
  • Додайте це значення всіх 8 сусідніх пікселів зверху кожного пікселя
  • Рейтинг усіх пікселів за цим значенням (найяскравіший перший) - я знаю, не дуже тонкий ...
  • Виберіть N із них, починаючи з верху, пропускаючи занадто близько
  • Обчисліть цих вершин N (дає нам приблизний центр дерева)
  • Почніть з медіанного положення вгору в розширюваному промені пошуку для самого верхнього світла від вибраних найяскравіших (люди, як правило, кладуть принаймні одне світло в саму верхню частину)
  • Звідти уявіть лінії, що йдуть на 60 градусів вліво і вправо вниз (ялинки не повинні бути такими жирними)
  • Зменшіть ці 60 градусів, поки 20% найяскравіших вогнів не знаходяться поза цим трикутником
  • Знайдіть світло в самому дні трикутника, даючи нижню горизонтальну межу дерева
  • Зроблено

Пояснення маркування:

  • Великий червоний хрест у центрі дерева: медіана верхнього N найяскравіших вогнів
  • Пунктирною лінією звідти вгору: "промінь пошуку" для верху дерева
  • Менший червоний хрест: верхівка дерева
  • Дійсно маленькі червоні хрести: усі вершини N найяскравіших вогнів
  • Червоний трикутник: D'uh!

Вихідний код:

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


?>

Зображення: Лівий верхній Нижній центр Внизу зліва Правий верхній кут Верхній центр Праворуч внизу

Бонус: німецький Weihnachtsbaum, з Вікіпедії Römerberg http://commons.wikimedia.org/wiki/File:Weihnachtsbaum_R%C3%B6merberg.jpg


17

Я використовував python з opencv.

Мій алгоритм іде так:

  1. Спочатку він знімає червоний канал із зображення
  2. Застосуйте поріг (мінімальне значення 200) до Червоного каналу
  3. Потім нанесіть морфологічний градієнт, а потім зробіть «закриття» (розширення з подальшим ерозією)
  4. Потім він знаходить контури в площині, і він підбирає найдовший контур.

Результат:

Код:

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

Якщо я поміняю ядро ​​з (25,5) на (10,5), я отримаю кращі результати на всіх деревах, але внизу зліва, введіть тут опис зображення

мій алгоритм передбачає, що дерево має світло на ньому, а в нижньому лівому дереві вгорі менше світла, ніж у інших.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.