Как обнаружить елку? [закрыто]

382

Какие методы обработки изображений можно использовать для реализации приложения, которое обнаруживает рождественские елки, отображаемые на следующих изображениях?

Я ищу решения, которые будут работать на всех этих изображениях. Поэтому подходы, которые требуют обучения каскадных классификаторов Хаара или сопоставления с шаблоном , не очень интересны.

Я ищу что-нибудь, что можно написать на любом языке программирования, если в нем используются только технологии с открытым исходным кодом . Решение должно быть протестировано с изображениями, которые передаются по этому вопросу. Есть 6 входных изображений и в ответе должны отображаться результаты обработки каждого из них. Наконец, для каждого выходного изображения должны быть нарисованы красные линии, чтобы окружить обнаруженное дерево.

Как бы вы могли программно обнаружить деревья на этих изображениях?

karlphillip
источник
3
Разрешено ли нам использовать некоторые изображения для обучения или все предоставленные изображения должны использоваться для проверки? В любом случае, крутая конкуренция: D
Ханнес Оврен
7
@karlphillip, вы хотите, чтобы мы использовали эти изображения для тестирования и другие изображения для обучения? Просто не понятно, что такое тренировочный набор.
GilLevi
16
@karlphillip: Мой совет: откажитесь от требования «с открытым исходным кодом». Это действительно не имеет значения, какой язык / рамки вы используете. Алгоритмы обработки изображений / компьютерного зрения не зависят от языка, поэтому, если вы можете написать это в MATLAB, вы, безусловно, можете сделать это OpenCV или любой другой фреймворк, который вы предпочитаете ... Кроме того, я до сих пор не понимаю, что вы думаете об обучении / тестировании изображений !
Amro
2
@karlphillip спасибо за мобилизацию всех нас, чтобы внести свой вклад в этот ваш "квест"! Это была прекрасная возможность продуктивно провести несколько часов, но самое главное, узнать, сколько разных подходов можно найти для одной проблемы ... Надеюсь, вы сделаете это снова 1-го января (возможно, сани Вызов Санта-Клауса? ;-))
sepdek
2
Хорошо, я перефразировал вопрос об удалении элементов конкурса. Я думаю, что это должно позволить ему стоять самостоятельно.
Брэд Ларсон

Ответы:

184

У меня есть подход, который я считаю интересным и немного отличным от остальных. Основное отличие моего подхода по сравнению с некоторыми другими заключается в том, как выполняется этап сегментации изображения - я использовал DBSCAN алгоритм кластеризации из Python's scikit-learn; он оптимизирован для поиска несколько аморфных форм, которые могут не обязательно иметь единый четкий центроид.

На верхнем уровне мой подход довольно прост и может быть разбит на 3 этапа. Сначала я применяю порог (или фактически, логическое «или» двух отдельных и разных порогов). Как и во многих других ответах, я предположил, что рождественская елка будет одним из самых ярких объектов в сцене, поэтому первый порог - это просто простой монохромный тест яркости; любые пиксели со значениями выше 220 по шкале 0-255 (где черный равен 0, а белый - 255) сохраняются в двоичном черно-белом изображении. Второй порог пытается найти красные и желтые огни, которые особенно заметны на деревьях в верхнем левом и нижнем правом углу шести изображений и хорошо выделяются на сине-зеленом фоне, который преобладает на большинстве фотографий. Я конвертирую изображение RGB в пространство HSV, и требуют, чтобы оттенок был меньше 0,2 по шкале 0,0-1,0 (примерно соответствует границе между желтым и зеленым) или больше 0,95 (соответствует границе между фиолетовым и красным), и дополнительно мне требуются яркие насыщенные цвета: насыщенность и значение должны быть выше 0,7. Результаты двух пороговых процедур логически «или» объединены, и результирующая матрица черно-белых двоичных изображений показана ниже:

Елки, после порога на ВПГ, а также монохромный яркость

Вы можете четко видеть, что каждое изображение имеет один большой кластер пикселей, приблизительно соответствующий местоположению каждого дерева, плюс несколько изображений также имеют некоторые другие небольшие кластеры, соответствующие либо огням в окнах некоторых зданий, либо фоновая сцена на горизонте. Следующий шаг - заставить компьютер распознать, что это отдельные кластеры, и правильно пометить каждый пиксель идентификатором членства в кластере.

Для этой задачи я выбрал 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()
stachyra
источник
Решение @ lennon310 - кластеризация. (k-means)
user3054997
1
@stachyra Я также подумал об этом подходе, прежде чем предлагать свои более простые. Я думаю, что это имеет большой потенциал для расширения и обобщения, чтобы получить хорошие результаты и в других случаях. Вы можете поэкспериментировать с нейронными сетями для кластеризации. Что-то вроде SOM или нейронного газа сделало бы отличную работу. Тем не менее, отличное предложение и большие пальцы от меня!
Sepdek
4
@Faust & Ryan Carlson: спасибо, ребята! Да, я согласен, что система upvote, хотя она хорошо работает для оценки 2 или 3 коротких ответов, представленных в течение нескольких часов друг от друга, имеет серьезные предубеждения, когда речь идет о конкурсах с длинными ответами, которые разыгрываются в течение длительных периодов времени. , Во-первых, ранние представления начинают накапливать положительные отзывы еще до того, как более поздние становятся доступными для публичного просмотра. И если ответы все длинны, то, как только кто-то устанавливает скромное лидерство, часто возникает «эффект побочного эффекта», так как люди только повышают голос первого, не удосужившись прочитать остальное.
Стахира
2
@stachyra отличная новость друг! Поздравляю вас с теплом, и пусть это станет началом вашего нового года!
sepdek
1
@ lennon310: Я еще не пробовал локальный фильтр обнаружения максимума по этой проблеме, но если вы хотите исследовать его самостоятельно, Сципи включает его . Мой исходный код Python для этого проекта был настолько коротким, что я смог опубликовать его на 100%; буквально все, что вам нужно сделать, это скопировать и вставить два моих фрагмента кода в отдельные файлы .py, а затем заменить вызов scipy.ndimage.filters.maximum_filter()в том же месте, где я использовал порог.
Стачира
145

ПРИМЕЧАНИЕ ПО РЕДАКТИРОВАНИЮ: я отредактировал этот пост, чтобы (i) обрабатывать каждое древовидное изображение отдельно, как того требуют требования, (ii) учитывать яркость и форму объекта, чтобы улучшить качество результата.


Ниже представлен подход, который учитывает яркость и форму объекта. Другими словами, он ищет объекты треугольной формы и со значительной яркостью. Это было реализовано на 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 часа до того, как вы опубликовали ответ, чтобы указать это конкретно. Было бы здорово, если бы вы могли обновить свой ответ с этими результатами.
Карлфиллип
@ Марвин, в своем обнаружении треугольника, как ты справился с колебаниями массы? Это не строгий треугольник, масса не моно, как у изменений
user3054997
2
@ user3054997: Это еще один момент. Как я уже писал, алгоритм не ищет строгие треугольные формы. Он анализирует каждый объект и рассматривает дерево, которое «напоминает» треугольник с простыми критериями: масса объекта используется для увеличения по мере увеличения y, а центр масс каждого горизонтального сегмента объекта практически централизован друг для друга ,
Габриэль Амброзио Арчанхо,
@ Марвин Мое решение действительно простое, я также изложил это в своем ответе. Кстати, это сработало лучше, чем ваше первое решение. Если я правильно помню, в своем первом ответе вы говорили о дескрипторах объектов для обнаружения небольших световых текстур, что вы здесь не делаете. Я просто сказал, что ваш текущий подход и результаты намного больше похожи на мои, чем на ваше первое решение. Конечно, я не ожидаю, что вы это признаете, я изложил это только для протокола.
Смесо
1
@sepdek Здесь есть пара решений, которые действительно намного лучше, чем у меня, и они все еще получают половину моих голосов. Нет ничего плохого в том, чтобы «вдохновляться» другими решениями. Я тоже видел ваши решения, мне нечего сказать против вас, вы разместили их после меня, и моя «идея» не была настолько оригинальной, чтобы сказать, что вы просто скопировали меня. Но Марвин был единственным, кто опубликовал передо мной и отредактировал решение, увидев мое, используя тот же алгоритм ... по крайней мере, он мог бы сказать: «Да, мне понравилось ваше решение, и я использовал его повторно», в этом нет ничего плохого, это просто игра.
Смесо
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);

Извините, но сейчас у меня плохое соединение, поэтому я не могу загрузить фотографии. Я постараюсь сделать это позже.

Счастливого Рождества.

РЕДАКТИРОВАТЬ:

Вот несколько картинок с окончательным выводом:

smeso
источник
1
Здравствуйте! Убедитесь, что ваш ответ соответствует всем требованиям: имеется 6 входных изображений, и в ответе должны отображаться результаты обработки каждого из них; ,
karlphillip
Привет! Вы можете передать имена файлов в качестве аргументов CLI к моей программе: ./christmas_tree ./*.png. Их может быть сколько угодно, результаты будут показываться один за другим нажатием любой клавиши. Это неправильно?
Смесо
Это нормально, но вам все равно нужно загрузить изображения и поделиться ими в своем вопросе, чтобы зрители цепочки могли увидеть ваш результат. Позволение людям увидеть, что вы сделали, увеличит ваши шансы
набрать
Я пытаюсь найти решение для этого, у меня есть некоторые проблемы с подключением.
Смесо
2
Большой! Теперь вы можете перемасштабировать их внутри ответа с помощью следующего кода: <img src="http://i.stack.imgur.com/nmzwj.png" width="210" height="150">просто измените ссылку на картинку;)
karlphillip
60

Я написал код в Matlab R2007a. Я использовал k-средства, чтобы примерно извлечь елку. Я покажу свой промежуточный результат только с одним изображением, а окончательные результаты со всеми шестью.

Во-первых, я сопоставил пространство RGB с пространством Lab, что может повысить контраст красного в его b-канале:

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

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

Помимо функции в цветовом пространстве, я также использовал функцию текстуры, которая связана с соседством, а не с самим пикселем. Здесь я линейно объединил интенсивность от 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-means не защищен от случайной инициализации центроида, поэтому рекомендуется запускать программу несколько раз для сравнения.

После k-средних I0была выбрана область с максимальной интенсивностью . И для отслеживания границ использовалась трассировка границ. Для меня последняя рождественская елка - самая трудная для извлечения, поскольку контраст на этой картинке недостаточно высок, как в первых пяти. Другой проблемой в моем методе является то, что я использовал bwboundariesфункцию в Matlab для трассировки границы, но иногда внутренние границы также включаются, как вы можете наблюдать в 3-м, 5-м, 6-м результатах. Темная сторона в рождественских елках не только не может быть сгруппирована с освещенной стороной, но они также приводят к такому количеству прослеживания крошечных внутренних границ ( imfillне очень улучшается). Во всем моем алгоритме еще много места для улучшения.

В некоторых публикациях указывается, что среднее смещение может быть более устойчивым, чем k-среднее, и многие алгоритмы, основанные на срезе графа , также очень конкурентоспособны при сложной сегментации границ. Я сам написал алгоритм среднего смещения, кажется, что лучше выделять области без достаточного количества света. Но среднее смещение немного чрезмерно сегментировано, и необходима некоторая стратегия слияния. Он работал даже намного медленнее, чем k-means на моем компьютере, боюсь, мне придется отказаться от него. Я с нетерпением жду возможности увидеть, что другие представят здесь отличные результаты с этими современными алгоритмами, упомянутыми выше.

Тем не менее, я всегда считаю, что выбор функции является ключевым компонентом в сегментации изображения. При правильном выборе функции, которая может максимизировать разницу между объектом и фоном, многие алгоритмы сегментации определенно будут работать. Различные алгоритмы могут улучшить результат с 1 до 10, но выбор функции может улучшить его с 0 до 1.

Счастливого Рождества !

lennon310
источник
2
Спасибо за ответ! Я просто хотел отметить, что Matlab - это не открытый код , а Scilab . Я бы тоже хотел, чтобы этот ответ конкурировал с остальными. ;)
karlphillip
6
Спасибо, Карл. Octave - еще одно программное обеспечение с открытым исходным кодом, которое использует почти ту же грамматику кодирования, что и Matlab: mathworks.fr/matlabcentral/answers/14399-gnu-octave-vs-matlab .
lennon310
Интересно, я этого не знал, спасибо! Ваш код работает на Octave?
karlphillip
Я еще не тестировал, но думаю, что это не проблема :)
lennon310
@ lennon310 Я думаю, что если вы опустите границы и получите выпуклый корпус, вы избавитесь от проблемы с дырами. Помните, что выпуклая оболочка - это самая маленькая область, которая включает все точки в наборе.
sepdek
57

Это мой последний пост с использованием традиционных подходов к обработке изображений ...

Здесь я как-то объединяю два других моих предложения, добиваясь даже лучших результатов . На самом деле я не могу понять, как эти результаты могут быть лучше (особенно когда вы смотрите на маскированные изображения, которые создает метод).

В основе подхода лежит комбинация трех ключевых допущений :

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

С учетом этих допущений метод работает следующим образом:

  1. Конвертировать изображения в HSV
  2. Фильтрация V-канала с помощью фильтра LoG
  3. Примените жесткий порог к фильтрованному изображению LoG, чтобы получить маску активности
  4. Примените жесткий порог к каналу V, чтобы получить маску интенсивности B
  5. Примените пороговое значение H-канала для захвата областей с низкой интенсивностью синего цвета в фоновую маску C
  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

Результаты

Результаты

Результаты высокого разрешения все еще доступны здесь!
Еще больше экспериментов с дополнительными изображениями можно найти здесь.

sepdek
источник
1
Качественный товар! Пожалуйста, убедитесь, что ваши другие ответы также следуют этому формату. Чтобы побороться за награду, вы должны использовать технологию с открытым исходным кодом , и, к сожалению, Matlab не является одним из них. Тем не менее, SciLab и Octave есть, и они предоставляют сходный синтаксис и функции. ;)
karlphillip
Код октавы такой же ...
sepdek
@karlphillip Каким-то образом этот вопрос имел тег Matlab. Если открытый исходный код действительно необходим, я бы порекомендовал удалить его.
Деннис Джаэруддин
@sepdek Очень хорошо, возможно, что-то еще можно сделать, чтобы включить «дыры» в окончательную картинку. (Добавить все пиксели, которые полностью окружены утвержденными пикселями ?!)
Деннис Джарудудин
1
@karlphillip спасибо человек! Я рад, что вы нашли мой подход интересным. Кроме того, я хотел бы поздравить вас с выбором наиболее элегантного решения, а не с большинством голосов !!!
sepdek
36

Мои шаги решения:

  1. Получить канал R (из RGB) - все операции, которые мы выполняем на этом канале:

  2. Создать область интересов (ROI)

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

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

  3. Обнаружение возраста в компьютерных роях. Дерево имеет много краев (среднее правое изображение)

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

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

  4. Выберите самый большой (по площади) объект - это область результата

  5. ConvexHull (дерево является выпуклым многоугольником) (нижнее правое изображение)

  6. Ограничительная рамка (нижнее правое изображение - grren box)

Шаг за шагом: введите описание изображения здесь

Первый результат - самый простой, но не в программном обеспечении с открытым исходным кодом - «Adaptive Vision Studio + Adaptive Vision Library»: это не открытый исходный код, а действительно быстрое создание прототипа:

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

Следующий шаг. Мы хотим, чтобы решение с открытым исходным кодом. Измените фильтры AVL на фильтры OpenCV: здесь я сделал небольшие изменения, например, Edge Detection использует фильтр 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;
}
AdamF
источник
Какой компилятор может собрать эту программу без ошибок?
Карлфиллип
Я использовал Visual Studio 2012 для его создания. Вы должны использовать компилятор c ++ с поддержкой c ++ 11.
AdamF
У меня нет системы в моем распоряжении с этим. Не могли бы вы переписать std::max_element()звонок? Я хотел бы также вознаграждать ваш ответ. Я думаю, что у меня есть GCC 4.2.
karlphillip
Хорошо, это особенность C ++ 11;) Я изменил исходный код выше. Пожалуйста, попробуйте сейчас.
AdamF
Хорошо, спасибо. Я проверил это, и это красиво. Как только этот вопрос будет вновь открыт (другие пользователи должны помочь мне в этом), я могу назначить еще одну награду, чтобы вознаградить вас. Поздравляем!
karlphillip
31

... еще одно старомодное решение, основанное исключительно на обработке HSV :

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

Несколько слов об эвристике при обработке HSV:

  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

Результаты:

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

sepdek
источник
Здравствуйте, спасибо за ответ. Пожалуйста, найдите время, чтобы прочитать раздел « Требования », чтобы убедиться, что ваш ответ соответствует всем инструкциям. Вы забыли поделиться полученными изображениями. ;)
karlphillip
2
У @karlphillip sepdek недостаточно репутации, чтобы делиться изображениями. Я переместил изображения в тело ответа в соответствии с его ссылкой и инструкциями. Не уверен, однако, что это правильные, не стесняйтесь комментировать эту часть.
Alko
@ Алко, я знаю, спасибо. Но некоторые изображения, которыми вы поделились, не были во входном наборе . В ответе должен быть показан результат обработки всех 6 изображений, поделенных на вопрос.
karlphillip
@karlphillip это его изображения, а не мои. это именно то, что я имел в виду под «комментировать эту часть»;)
alko
2
Извините за причиненные проблемы ... не мое намерение. Я включил все изображения в исходный набор данных и еще больше расширил его, чтобы доказать, что моя концепция
надежна
23

Какой-то старомодный подход к обработке изображений ...
Идея основана на предположении, что изображения изображают освещенные деревья, как правило, на более темном и более гладком фоне (или в некоторых случаях на переднем плане). Освещенная область дерева является более «энергичной» и имеет более высокую интенсивность .
Процесс выглядит следующим образом:

  1. Конвертировать в graylevel
  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
sepdek
источник
Не забудьте загрузить полученные изображения, как это сделал Фауст.
karlphillip
Я нуб, поэтому я не могу загружать изображения. Пожалуйста, смотрите результаты по предоставленным ссылкам в моем описании.
sepdek
Хорошо, но вы все равно должны использовать изображения, которыми поделились в вопросе, как и все остальные. Как только вы их обработаете, загрузите его куда-нибудь и отредактируйте свой ответ, добавив ссылки. Позже я отредактирую ваш ответ и поместу изображения внутри него для вас.
karlphillip
Ссылка теперь содержит правильные изображения.
Деннис Джаэруддин
22

Используя совершенно иной подход, чем я видел, я создал скрипт, который обнаруживает рождественские елки по их огням. Результатом является всегда симметричный треугольник и, при необходимости, числовые значения, такие как угол ("упитанность") дерева.

Самой большой угрозой для этого алгоритма, очевидно, являются огни рядом (в большом количестве) или перед деревом (большая проблема до дальнейшей оптимизации). Редактировать (добавлено): Что он не может сделать: выяснить, есть ли рождественская елка или нет, найти несколько рождественских елок на одном изображении, правильно обнаружить рождественскую елку в центре Лас-Вегаса, обнаружить рождественские елки, которые сильно согнуты, перевернутый или срубленный ...;)

Различные этапы:

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

Объяснение маркировки:

  • Большой красный крест в центре дерева: медиана верхних N ярких огней
  • Пунктирная линия оттуда вверх: «Поиск луча» для вершины дерева
  • Меньший красный крест: верхушка дерева
  • Маленькие красные крестики: все самые яркие и яркие огни
  • Красный треугольник: D'у!

Исходный код:

<?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), я получу более хорошие результаты на всех деревьях, кроме нижнего левого, введите описание изображения здесь

Мой алгоритм предполагает, что на дереве есть источники света, а в нижнем левом дереве верхняя часть имеет меньше света, чем остальные.

ifryed
источник