크리스마스 트리를 감지하는 방법? [닫은]


382

다음 이미지에 표시된 크리스마스 트리를 감지하는 응용 프로그램을 구현하는 데 사용할 수있는 이미지 처리 기술은 무엇입니까?

이 모든 이미지에서 작동 할 솔루션을 찾고 있습니다. 따라서 Haar 캐스케이드 분류기 또는 템플릿 일치 훈련이 필요한 접근 방식 은 그리 흥미롭지 않습니다.

난에 기록 될 수있는 무언가를 찾고 있어요 어떤 프로그래밍 언어 만큼 에만 사용하는 오픈 소스 기술을. 이 질문에서 공유 된 이미지로 솔루션을 테스트해야합니다. 거기 6 개 입력 이미지 및 응답은 그들의 각각의 처리 결과를 표시한다. 마지막으로, 각 출력 이미지 에 대해 감지 된 트리를 둘러싸려면 빨간색 선이 그려 져야합니다 .

이 이미지에서 나무를 프로그래밍 방식으로 감지하는 방법은 무엇입니까?


3
훈련을 위해 일부 이미지를 사용할 수 있습니까, 아니면 제공된 이미지를 모두 유효성 검사에 사용해야합니까? 어느 쪽이든, 멋진 경쟁 : D
Hannes Ovrén

7
@karlphillip,이 이미지를 테스트 용으로 사용하고 다른 이미지를 교육용으로 사용 하시겠습니까? 훈련 세트가 무엇인지 명확하지 않다는 것입니다.
GilLevi

16
@karlphillip : 저의 조언 : "오픈 소스"요구 사항을 삭제하십시오. 사용하는 언어 / 프레임 워크는 중요하지 않습니다. 이미지 처리 / 컴퓨터 비전 알고리즘은 언어에 구애받지 않으므로 MATLAB으로 작성할 수 있다면 OpenCV 또는 원하는 다른 프레임 워크를 사용할 수 있습니다 ... 또한 교육 / 테스트 이미지를 고려할 대상이 아직 확실하지 않습니다. !
Amro

2
우리 모두를 동원해 주신 여러분의 '퀘스트'에 동참 해 주셔서 감사합니다. 한 시간 동안 생산적으로 몇 시간을 보낼 수있는 좋은 기회 였지만, 가장 중요한 것은 한 가지 문제에 대해 여러 가지 접근 방식을 찾을 수있는 방법을 알아 보는 것입니다. 1 월 1 일에 다시 시도해보십시오. Santa Claus 'challenge? ;-))
sepdek

2
좋아, 나는 경쟁 요소를 제거하라는 질문을 다시 말했다. 나는 그것이 스스로 잘 지낼 수 있어야한다고 생각합니다.
Brad Larson

답변:


184

나는 흥미롭고 나머지와는 조금 다른 접근법을 가지고 있습니다. 다른 사람의 일부에 비해 내 접근 방식의 주요 차이점은, 이미지 분할 단계를 수행하는 방법에 - 내가 사용 DBSCAN 파이썬에서 클러스터링 알고리즘 scikit을 배우기; 반드시 하나의 명확한 중심을 가질 필요는없는 다소 비정질 형태를 찾는 데 최적화되어 있습니다.

최상위 수준에서 내 접근 방식은 상당히 간단하며 약 3 단계로 나눌 수 있습니다. 먼저 임계 값을 적용합니다 (또는 실제로 두 개의 개별적이고 고유 한 임계 값의 논리적 "또는"). 다른 많은 답변과 마찬가지로 크리스마스 트리가 장면에서 더 밝은 물체 중 하나라고 가정했기 때문에 첫 번째 임계 값은 단순한 흑백 밝기 테스트입니다. 0-255 배율 (검정은 0, 흰색은 255)에서 220보다 큰 값을 가진 모든 픽셀은 이진 흑백 이미지에 저장됩니다. 두 번째 임계 값은 6 개의 이미지 중 왼쪽 상단과 오른쪽 하단에있는 나무에서 특히 두드러지는 빨강 및 노랑색 조명을 찾으려고 시도하며 대부분의 사진에서 일반적으로 나타나는 청록색 배경과 잘 어울립니다. RGB 이미지를 hsv 공간으로 변환합니다. 색조가 0.0-1.0 스케일에서 0.2 미만 (노란색과 녹색의 경계에 해당) 또는 0.95 (보라색과 빨간색의 경계에 해당) 이상이어야하며 추가로 밝고 채도가 높은 색상이 필요합니다. 채도와 값은 0.7 이상이어야합니다. 두 임계 값 절차의 결과는 논리적으로 함께 "또는"처리되며 결과 흑백 이진 이미지 매트릭스가 아래에 표시됩니다.

HSV 및 흑백 밝기에서 임계 값을 지정한 후 크리스마스 트리

각 이미지에는 대략 각 나무의 위치에 해당하는 하나의 큰 픽셀 클러스터가 있으며, 일부 이미지에는 건물의 창문에있는 조명에 해당하는 다른 작은 클러스터도 있습니다. 수평선에 배경 장면입니다. 다음 단계는 컴퓨터가 별도의 클러스터임을 인식하고 클러스터 구성원 ID 번호로 각 픽셀에 레이블을 올바르게 지정하는 것입니다.

이 작업을 위해 DBSCAN을 선택 했습니다 . 여기 에서 사용 가능한 다른 클러스터링 알고리즘과 비교하여 DBSCAN이 일반적으로 작동하는 방식을 상당히 잘 비교할 수 있습니다 . 앞서 말했듯이 비정질 모양과 잘 어울립니다. 각 클러스터가 다른 색상으로 플롯 된 DBSCAN의 출력은 다음과 같습니다.

DBSCAN 클러스터링 출력

이 결과를 볼 때 알아야 할 사항이 몇 가지 있습니다. 첫째, DBSCAN은 사용자가 동작을 조절하기 위해 "근접성"매개 변수를 설정해야하는데, 이는 알고리즘이 테스트 포인트를 통합하지 않고 새로운 별도의 클러스터를 선언하기 위해 한 쌍의 포인트를 어떻게 분리해야 하는지를 효과적으로 제어합니다. 이미 존재하는 클러스터 이 값을 각 이미지의 대각선을 따라 크기의 0.04 배로 설정했습니다. 이미지의 크기는 대략 VGA에서 최대 HD 1080까지 다양하므로 이러한 유형의 스케일 기준 정의는 매우 중요합니다.

주목할 가치가있는 또 다른 요점은 scikit-learn에서 구현되는 DBSCAN 알고리즘은이 샘플에서 더 큰 일부 이미지에는 상당히 어려운 메모리 제한이 있다는 것입니다. 따라서 더 큰 이미지 중 일부의 경우이 제한을 유지하기 위해 각 클러스터를 실제로 "결정"해야합니다 (즉, 모든 3 번째 또는 4 번째 픽셀 만 유지하고 나머지는 삭제). 이 컬링 프로세스의 결과로, 나머지 개별 스파 스 픽셀은 더 큰 이미지 중 일부에서 잘 보이지 않습니다. 따라서, 표시 목적으로 만, 상기 이미지에서 컬러 코딩 된 픽셀은 더 잘 두드러 지도록 효과적으로 "확장"되었다. 그것은 이야기를 위해 순수하게 성형 수술입니다. 내 코드 에서이 확장에 대해 언급하는 의견이 있지만,

클러스터가 식별되고 레이블이 지정되면 세 번째이자 마지막 단계는 간단합니다. 각 이미지에서 가장 큰 클러스터를 가져옵니다 (이 경우 총 멤버 픽셀 수로 "크기"를 측정하기로 결정했습니다. 물리적 범위를 측정하는 일부 측정 항목 유형을 쉽게 사용하고 해당 클러스터의 볼록 껍질을 계산했습니다. 볼록 껍질은 나무 테두리가됩니다. 이 방법으로 계산 된 6 개의 볼록 껍질은 아래에 빨간색으로 표시됩니다.

계산 된 테두리가있는 크리스마스 트리

소스 코드는 Python 2.7.6 용으로 작성되었으며 numpy , scipy , matplotlibscikit-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-means)
user3054997

1
@stachyra 나는 또한 간단한 접근법을 제안하기 전에이 접근법에 대해 생각했습니다. 나는 이것이 다른 경우에도 좋은 결과를 내기 위해 확장되고 일반화 될 큰 잠재력을 가지고 있다고 생각합니다. 클러스터링을 위해 신경망을 실험 할 수 있습니다. SOM 또는 신경 가스와 같은 것이 훌륭한 작업을 수행합니다. 그럼에도 불구하고, 큰 제안과 나에게서 엄지 손가락!
sepdek

4
@Faust & Ryan Carlson : 감사합니다. 예, 공감 시스템은 서로 몇 시간 내에 제출 된 2 ~ 3 개의 짧은 답변을 판결하는 데 효과적이지만 오랜 기간 동안 진행되는 긴 답변이있는 컨테스트에는 심각한 편견이 있음에 동의합니다. . 우선, 초기 제출물은 이후의 검토가 공개 검토를하기 전에 상향 투표를 누적하기 시작합니다. 그리고 답이 모두 길다면, 보통의 단서를 세우 자마자 사람들이 나머지 부분을 읽지 않고 첫 번째 것만 찬성하기 때문에 "대역폭 효과"가 종종 있습니다.
stachyra

2
@stachyra 좋은 소식 친구! 가장 따뜻한 축하 인사이며 새해를 맞이할 수 있습니다.
sepdek

1
@ lennon310 : 아직이 문제에 대해 로컬 최대 감지 필터를 시도하지 않았지만 직접 탐색하려면 scipy 에이 필터를 포함 하십시오 . 이 프로젝트의 Python 소스 코드가 너무 짧아서 실제로 100 %를 게시 할 수있었습니다. 말 그대로 당신이해야 할 일은 두 개의 코드 스 니펫을 복사하여 별도의 .py 파일에 붙여 넣은 다음 scipy.ndimage.filters.maximum_filter()임계 값을 사용한 동일한 위치에서 호출을 대체하는 것 입니다.
stachyra

145

편집 참고 사항 : 나는이 게시물을 편집하여 (i) 요구 사항에 따라 각 트리 이미지를 개별적으로 처리하고 (ii) 결과의 품질을 향상시키기 위해 객체 밝기와 모양을 모두 고려합니다.


아래는 물체의 밝기와 모양을 고려한 접근법입니다. 다시 말해, 삼각형과 같은 모양과 상당한 밝기를 가진 물체를 찾습니다. Marvin 이미지 처리 프레임 워크를 사용하여 Java로 구현되었습니다 .

첫 번째 단계는 색상 임계 값입니다. 여기에서 목표는 밝기가 큰 물체에 대한 분석에 집중하는 것입니다.

출력 이미지 :

소스 코드:

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

제 2 단계에서, 형상을 형성하기 위해 이미지에서 가장 밝은 점이 확장된다. 이 과정의 결과는 상당한 밝기를 가진 물체의 가능한 모양입니다. 플러드 필 분할을 적용하면 연결이 끊어진 모양이 감지됩니다.

출력 이미지 :

소스 코드:

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

삼각형 감지에서 @Marvin, 질량의 변동을 어떻게 처리 했습니까? 그것은 엄격한 삼각형이 아니며, y가 변함에 따라 질량은 모노가 아닙니다
user3054997

2
@ user3054997 : 또 다른 포인트입니다. 내가 게시 한 것처럼 알고리즘은 엄격한 삼각형 모양을 찾지 않습니다. 각 객체를 분석하고 간단한 기준으로 삼각형을 "닮은"트리를 고려합니다. y가 증가함에 따라 객체의 질량이 증가하고 각 수평 객체 세그먼트의 질량 중심이 서로 거의 중앙 집중화됩니다 .
Gabriel Ambrósio Archanjo

@Marvin 내 솔루션은 정말 간단합니다. 내 답변에도 언급했습니다. 그건 그렇고 그것은 첫 번째 솔루션보다 잘 작동했습니다. 내가 정확하게 기억한다면, 첫 번째 대답에서, 당신은 당신이하고있는 것이 아닌 작은 가벼운 질감을 감지하는 기능 설명자에 대해 이야기했습니다. 나는 단순히 현재의 접근법과 결과가 첫 번째 솔루션보다 내 것과 훨씬 유사하다고 말했다. 물론 나는 당신이 그것을 인정할 것을 기대하지 않습니다, 나는 단지 기록을 위해 그것을 언급했습니다.
smeso

1
@ sepdek 여기 내 것보다 훨씬 더 나은 몇 가지 솔루션이 있으며 여전히 내 의견의 절반을 받고 있습니다. 다른 솔루션에서 "영감을 얻는 것"에는 잘못된 것이 없습니다. 나는 당신의 해결책도 보았습니다. 나는 당신에 대해 말할 것이 없습니다. 그러나 Marvin은 내 앞에 게시하고 편집 한 유일한 사람은 동일한 알고리즘을 사용하여 내 것을 본 후에 해결책입니다 ... 적어도 그는 "그렇습니다, 나는 당신의 해결책을 좋아하고 재사용했습니다"라고 아무 잘못이 없습니다. 게임.
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- 평균을 사용하여 크리스마스 트리를 대략 추출했습니다. 중간 결과는 하나의 이미지에만 표시하고 최종 결과는 6 개 모두에 표시합니다.

먼저 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- 평균의 코드는 다음과 같습니다 (앤드류 응 (Andrew Ng) 박사의 기계 학습 과정에서 발췌 한 것입니다. 전 과정을 수강했으며 프로그래밍 과제에서 직접 코드를 작성했습니다).

[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이 선택되었다. 그리고 경계 추적을 사용하여 경계를 추출했습니다. 나에게, 마지막 크리스마스 트리는 그림의 대비가 처음 5에서와 같이 충분히 높지 않기 때문에 추출하기가 가장 어렵습니다. 내 방법의 또 다른 문제는 bwboundariesMatlab의 함수를 사용 하여 경계를 추적하지만 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

흥미 롭습니다. 고마워요! 옥타브에서 코드가 작동합니까?
karlphillip

아직 테스트하지는 않았지만 문제가 없다고 생각합니다.)
lennon310

@ lennon310 난 당신이 경계를 삭제하고 볼록 껍질을 얻을 경우 구멍 문제를 제거 할 것이라고 생각합니다. 볼록 껍질은 세트의 모든 점을 포함하는 가장 작은 영역입니다.
sepdek

57

이것은 전통적인 이미지 처리 방식을 사용하는 마지막 게시물입니다 ...

여기서 나는 다른 두 가지 제안을 어떻게 결합하여 더 나은 결과를 얻습니다 . 사실, 나는이 결과가 어떻게 더 나은지 알 수 없습니다 (특히 메소드가 생성하는 마스크 된 이미지를 볼 때).

이 접근법의 핵심은 다음 세 가지 주요 가정 의 조합입니다 .

  1. 나무 영역에서 이미지의 변동이 커야합니다
  2. 트리 영역에서 이미지의 강도가 높아야합니다
  3. 배경 영역은 강도가 낮아야하며 대부분 푸른 색이어야합니다.

이러한 가정을 염두에두고이 방법은 다음과 같이 작동합니다.

  1. 이미지를 HSV로 변환
  2. LoG 필터로 V 채널 필터링
  3. LoG 필터링 된 이미지에 엄격한 임계 값을 적용하여 '활동'마스크 A를 얻습니다.
  4. 강도 마스크 B를 얻기 위해 V 채널에 하드 임계 값 적용
  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

결과

결과

고해상도 결과는 여전히 여기에 있습니다!
추가 이미지에 대한 더 많은 실험이 여기에서 찾을 수 있습니다.


1
좋은 물건! 다른 답변도이 형식을 따르십시오. 현상금과 경쟁하려면 오픈 소스 기술을 사용해야 하며 불행히도 Matlab은 그 중 하나가 아닙니다. 그러나 SciLab과 Octave는 유사한 구문과 기능을 제공합니다. ;)
karlphillip

옥타브 코드는 동일합니다 ...
sepdek

@karlphillip 어떻게 든이 질문에 Matlab 태그가 생겼습니다. 오픈 소스가 꼭 필요한 경우 제거하는 것이 좋습니다.
Dennis Jaheruddin

@sepdek 아주 좋은, 아마도 마지막 그림에 '구멍'을 포함시키기 위해 무언가를 할 수있을 것입니다. (완전히 승인 픽셀로 둘러싸인 모든 픽셀을 추가!)
데니스 Jaheruddin에게

1
트윗 담아 가기 내 접근 방식이 흥미 롭다는 것을 알게되어 기쁩니다. 또한 가장 투표가 많은 솔루션이 아닌 가장 우아한 솔루션을 선택해 주셔서 감사합니다 !!!
sepdek

36

내 솔루션 단계 :

  1. R 채널 가져 오기 (RGB에서)-이 채널에서 수행하는 모든 작업 :

  2. 관심 영역 (ROI) 생성

    • 최소값이 149 인 임계 값 R 채널 (오른쪽 상단 이미지)

    • 결과 영역 확장 (왼쪽 중간 이미지)

  3. 계산 된 ROI에서 eges를 탐지합니다. 나무에 가장자리가 많이 있습니다 (오른쪽 가운데 이미지)

    • 결과 확장

    • 더 큰 반경의 침식 (왼쪽 아래 이미지)

  4. 가장 큰 (영역 별) 개체를 선택하십시오-결과 영역입니다

  5. ConvexHull (나무는 볼록 다각형 임) (오른쪽 아래 이미지)

  6. 경계 상자 (오른쪽 아래 이미지-grren 상자)

단계별 : 여기에 이미지 설명을 입력하십시오

첫 번째 결과-가장 단순하지만 오픈 소스 소프트웨어에서는 아님- "Adaptive Vision Studio + Adaptive Vision Library": 오픈 소스는 아니지만 프로토 타입을 만드는 데 매우 빠릅니다.

크리스마스 트리를 감지하는 전체 알고리즘 (11 블록) : AVL 솔루션

다음 단계. 우리는 오픈 소스 솔루션을 원합니다. AVL 필터를 OpenCV 필터로 변경하십시오. 여기서 가장자리 감지와 cvCanny 필터를 사용하여 약간의 변경을 수행했습니다 .roi를 존중하기 위해 영역 이미지에 가장자리 이미지를 곱하고 findContours + contourArea를 사용한 가장 큰 요소를 선택했지만 아이디어는 동일합니다.

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

OpenCV 솔루션

링크를 2 개만 넣을 수 있으므로 중간 단계의 이미지를 표시 할 수 없습니다.

이제 우리는 오픈 소스 필터를 사용하지만 여전히 전체 오픈 소스는 아닙니다. 마지막 단계-C ++ 코드로 포트하십시오. 버전 2.4.4에서 OpenCV를 사용했습니다.

최종 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 ++ 11을 지원하는 c ++ 컴파일러를 사용해야합니다.
AdamF

나는 그것을 처리 할 수있는 시스템이 없습니다. std::max_element()전화 를 다시 쓸 수 있습니까? 귀하의 답변에 대해서도 보상하고 싶습니다. gcc 4.2가 있다고 생각합니다.
karlphillip

Ok 이것은 c ++ 11 기능입니다.) 위의 소스 코드를 변경했습니다. 지금 시도하십시오.
AdamF

알았어, 고마워 나는 그것을 테스트했고 그것은 아름답다. 이 질문이 다시 열리 자마자 (다른 사용자가 저를 도와 줘야합니다) 보상을주기 위해 다른 현상금을 설정할 수 있습니다. 축하합니다!
karlphillip

31

... 또 다른 구식 솔루션-순전히 HSV 처리 기반 :

  1. 이미지를 HSV 색 공간으로 변환
  2. HSV의 휴리스틱에 따라 마스크 만들기 (아래 참조)
  3. 분리 된 영역을 연결하기 위해 마스크에 형태 확장을 적용
  4. 작은 영역과 가로 블록을 버립니다 (나무는 세로 블록임을 기억하십시오)
  5. 경계 상자 계산

HSV 처리 의 휴리스틱 에 대한 단어 :

  1. 와 모든 색채 (H) (210) 사이에서 -도 320은 백그라운드 또는 비 관련 분야에 있어야하는데 블루 마젠타로서 폐기
  2. 40 %보다 낮은 값 (V)을 가진 모든 항목 은 너무 어두워 폐기 할 수 없습니다.

물론이 방법을 미세 조정할 수있는 다른 많은 가능성을 실험 해 볼 수도 있습니다.

다음은 트릭을 수행하는 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 그것은 내 이미지가 아닌 그의 이미지입니다. 그것은 내가 "이 부분을 주석으로 처리했다"라는 뜻의 exatly;)
alko

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

Faust처럼 결과 이미지를 업로드하는 것을 잊지 마십시오.
karlphillip

나는 멍청한 놈이므로 이미지를 업로드 할 수 없습니다. 내 설명에 제공된 링크의 결과를 참조하십시오.
sepdek December

좋아,하지만 다른 사람들이하는 것처럼 여전히 질문에 공유 된 이미지를 사용해야합니다. 처리 한 후 어딘가에 업로드하고 답변을 편집하여 링크를 추가하십시오. 나중에 답을 편집하고 그 안에 이미지를 넣겠습니다.
karlphillip

링크에 올바른 이미지가 포함되어 있습니다.
Dennis Jaheruddin

22

내가 본 것과는 매우 다른 접근법을 사용하여 그들의 빛으로 크리스마스 트리를 감지하는 스크립트. 결과는 항상 대칭 삼각형이 아니며 필요한 경우 나무의 각도 ( "비만")와 같은 숫자 값입니다.

이 알고리즘에 대한 가장 큰 위협은 분명히 (옆으로) 또는 나무 앞의 조명입니다 (추가 최적화까지 더 큰 문제). 편집 (추가) : 할 수없는 작업 : 크리스마스 트리가 있는지 확인하고, 하나의 이미지에서 여러 개의 크리스마스 트리를 찾거나, 라스 베이거스 한가운데에있는 cristmas 트리를 정확하게 감지하고, 심하게 구부러진 크리스마스 트리를 감지하고, 거꾸로 또는 다진 ...;)

다른 단계는 다음과 같습니다.

  • 각 픽셀에 대해 추가 된 밝기 (R + G + B)를 계산
  • 각 픽셀 위에 8 개의 인접 픽셀의이 값을 더합니다
  • 이 값으로 모든 픽셀의 순위를 지정하십시오 (가장 밝은 것부터)-나는 정말로 미묘하지는 않습니다 ...
  • 너무 가까이있는 것을 건너 뛰고 맨 위에서부터 N을 선택하십시오.
  • 계산 이 상위 N의 (우리에게 나무의 대략적인 중심을 제공합니다)
  • 선택된 가장 밝은 것에서 가장 높은 빛을 찾기 위해 넓은 검색 빔에서 중간 위치부터 위쪽으로 시작합니다 (사람은 맨 위에 하나 이상의 빛을 두는 경향이 있습니다)
  • 거기에서 60도 왼쪽과 오른쪽으로가는 선을 상상하십시오
  • 가장 밝은 조명의 20 %가이 삼각형 밖에있을 때까지 60도를 줄이십시오.
  • 삼각형의 맨 아래에서 빛을 찾으면 나무의 아래쪽 가로 테두리가 나타납니다.
  • 끝난

표시 설명 :

  • 나무의 중심에있는 큰 적십자 : 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;
}


?>

이미지 : 왼쪽 위 더 낮은 센터 왼쪽 아래 오른쪽 위 상단 중앙 오른쪽 아래

보너스 : Wikipedia의 독일 Weihnachtsbaum 로메르 베르크 http://commons.wikimedia.org/wiki/File:Weihnachtsbaum_R%C3%B6merberg.jpg


17

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)로 변경하면 왼쪽 아래를 제외한 모든 나무에서 더 좋은 결과를 얻습니다. 여기에 이미지 설명을 입력하십시오

내 알고리즘은 트리에 조명이 있다고 가정하고 왼쪽 하단 트리에서 상단은 다른 조명보다 조명이 적습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.