it-source

크리스마스 트리는 어떻게 감지하나요?

criticalcode 2022. 12. 19. 21:22
반응형

크리스마스 트리는 어떻게 감지하나요?

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

이 모든 이미지에서 사용할 수 있는 솔루션을 찾고 있습니다.따라서 haar 캐스케이드 분류자 또는 템플릿 매칭을 훈련해야 하는 접근법은 그다지 흥미롭지 않습니다.

오픈 소스 기술만 사용한다면 어떤 프로그래밍 언어로도 쓸 수 있는 것을 찾고 있습니다.이 질문에 대해 공유된 이미지를 사용하여 솔루션을 테스트해야 합니다.6개의 입력 이미지가 있으며, 각 이미지 처리 결과가 답변에 표시됩니다.마지막으로 각 출력 이미지에 대해 검출된 트리를 둘러싸는 빨간색 선이 그려져야 합니다.

이 이미지들 속의 나무를 프로그램적으로 검출하려면 어떻게 해야 할까요?

저는 흥미롭고 다른 것과는 조금 다르다고 생각하는 접근법이 있습니다.다른 접근 방식과 비교했을 때 가장 큰 차이점은 이미지 분할 단계를 수행하는 방법입니다. Python의 skit-learn에서 DBSCAN 클러스터링 알고리즘을 사용했습니다. 이 알고리즘은 하나의 명확한 중심을 가질 필요는 없는 다소 비정형적인 형태를 찾도록 최적화되어 있습니다.

상위 레벨에서는, 나의 어프로치는 꽤 심플하고, 약 3단계로 나눌 수 있습니다.먼저 임계값(또는 실제로는 두 개의 개별 임계값의 논리적 "또는")을 적용합니다.다른 많은 답변과 마찬가지로 크리스마스 트리는 장면에서 밝은 오브젝트 중 하나일 것이라고 생각했기 때문에 첫 번째 임계값은 단순한 단색 휘도 테스트입니다.0 ~ 255 스케일의 220을 넘는 픽셀(검정은 0, 흰색은 255)은 모두 2진수 흑백 이미지에 저장됩니다.두 번째 임계값은 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까지 다양하기 때문에 이러한 유형의 축척 상대적인 화질은 매우 중요합니다.

skikit-learn에 실장되어 있는 DBSCAN 알고리즘에는 메모리 제한이 있어 이 샘플의 큰 이미지 중 일부에 대해서는 상당히 어려운 점이 있습니다.따라서 일부 큰 이미지의 경우 이 제한을 유지하기 위해 실제로 각 클러스터를 "decimate"(즉, 3번째 또는 4번째 픽셀마다 유지하고 나머지 픽셀은 드롭)해야 했습니다.이 도태 처리의 결과, 큰 화상의 일부에서는, 나머지 개별의 스파스 픽셀을 보기 어려워집니다.따라서 위의 이미지에서 색상으로 구분된 픽셀은 눈에 잘 띄도록 약간만 효과적으로 "희석"되어 있습니다.내 코드에 이 확장에 대한 언급이 있지만, 실제로 중요한 계산과는 전혀 관계가 없기 때문에 안심하십시오.

클러스터를 식별하고 레이블을 지정하면 세 번째 및 마지막 단계가 쉽습니다.각 이미지에서 가장 큰 클러스터(이 경우 멤버 픽셀의 총 수로 "크기"를 측정하기로 선택했지만 물리적 범위를 측정하는 메트릭을 사용하는 대신 쉽게 사용할 수 있었습니다)를 사용하여 해당 클러스터의 볼록 선체를 계산합니다.볼록한 껍질은 나무 테두리가 됩니다.이 방법을 통해 계산된 6개의 볼록 선체는 아래 빨간색으로 표시됩니다.

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

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

편집 메모: 이 투고는 (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 편집

이 솔루션과 다른 솔루션의 출력 이미지의 유사성에 대해 설명합니다.사실, 그들은 매우 비슷합니다.그러나 이 접근법은 단순히 객체를 세그먼트화하는 것이 아닙니다.또한 어떤 의미에서 물체 모양을 분석합니다.한 장면에서 여러 개의 발광 물체를 처리할 수 있습니다.사실, 크리스마스트리가 가장 밝을 필요는 없습니다.나는 단지 논의를 풍부하게 하기 위해 그것을 중단하는 것이다.샘플에는 가장 밝은 물체를 찾기만 하면 나무를 찾을 수 있다는 편견이 있습니다.하지만, 이 시점에서 정말 논의를 중단하고 싶은가?이 시점에서 컴퓨터는 크리스마스 트리와 비슷한 물체를 실제로 얼마나 멀리까지 인식하고 있을까?이 틈새를 메우도록 합시다.

다음은 이 점을 설명하기 위한 결과입니다.

입력 이미지

여기에 이미지 설명 입력

산출량

여기에 이미지 설명 입력

여기 저의 단순하고 멍청한 해결책이 있습니다.그것은 그 트리가 그림에서 가장 밝고 큰 것이라는 가정에 기초하고 있다.

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

죄송하지만 현재 접속 상태가 좋지 않아 사진을 올릴 수 없습니다.나중에 해보도록 하겠습니다.

메리 크리스마스.

편집:

다음은 최종 출력의 몇 가지 그림입니다.

저는 Matlab R2007a에 코드를 썼습니다.나는 크리스마스 트리를 대충 뽑기 위해 k-means를 사용했다.중간결과는 1장의 이미지로, 최종결과는 6장의 이미지로 보여드릴게요.

먼저 RGB 공간을 랩 공간에 매핑했습니다. 그러면 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-means의 코드는 다음과 같습니다(Dr. 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-평균은 무작위 중심 초기화의 영향을 받지 않으므로 비교하기 위해 프로그램을 여러 번 실행하는 것이 좋습니다.

의 최대 강도를 I0선택되었습니다.경계 추적은 경계를 추출하기 위해 사용되었습니다.저는 마지막 크리스마스 트리가 처음 5번째 크리스마스 트리처럼 대비가 높지 않기 때문에 추출하기 가장 어렵습니다.는 제가 사용했다는 입니다.bwboundariesMatlab에서 경계를 추적하는 기능을 수행하지만, 세 번째, 다섯 번째, 여섯 번째 결과에서 관찰할 수 있듯이 내부 경계도 포함될 수 있습니다. 내의 면과 뿐만 작은 내부 .imfill별로 개선되지 않습니다.)내 알고리즘은 여전히 많은 개선 공간을 가지고 있다.

일부 간행물은 평균 이동이 k-평균보다 더 강력할 수 있으며, 많은 그래프 기반 알고리즘은 복잡한 경계 분할에 대해서도 매우 경쟁력이 있다고 나타낸다.평균 이동 알고리즘을 직접 작성했는데, 충분한 빛이 없는 영역을 추출하는 것이 더 나은 것 같습니다.그러나 평균 이동은 약간 지나치게 세분화되어 있기 때문에 병합 전략이 필요합니다.내 컴퓨터의 k-means보다 훨씬 느리게 실행되어서 포기해야 할 것 같아.저는 위에서 언급한 최신 알고리즘으로 다른 사람들이 이곳에서 훌륭한 결과를 제출하기를 고대하고 있습니다.

그러나 저는 항상 기능 선택이 이미지 분할의 핵심 컴포넌트라고 생각합니다.오브젝트와 배경 사이의 마진을 최대화할 수 있는 적절한 피쳐를 선택하면 많은 세그멘테이션알고리즘이 확실하게 동작합니다.다른 알고리즘을 사용하면 결과가 1에서 10으로 개선될 수 있지만 기능을 선택하면 결과가 0에서 1로 개선될 수 있습니다.

메리 크리스마스!

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

여기에서는 다른 두 가지 제안을 조합하여 더 나은 결과를 얻을있습니다.사실 이러한 결과가 어떻게 더 좋을 수 있는지 알 수 없습니다(특히 이 방법이 생성하는 마스크 이미지를 보면).

이 접근방식의 핵심은 다음 3가지 주요 전제 조건의 조합입니다.

  1. 이미지는 트리 영역에서 큰 변동이 있어야 합니다.
  2. 트리 영역에서 이미지의 명암을 높여야 합니다.
  3. 배경 영역은 강도가 낮고 대부분 파란색이어야 합니다.

이러한 전제를 염두에 두고, 이 방법은 다음과 같이 동작합니다.

  1. 이미지를 HSV로 변환합니다.
  2. LoG 필터를 사용하여 V 채널 필터링
  3. LoG 필터링된 이미지에 하드 임계값을 적용하여 '활동' 마스크 A 획득
  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

결과.

결과.

고해상도 화면은 이쪽에서 보실 수 있습니다!
여기에서 추가 이미지를 사용한 더 많은 실험을 볼 수 있습니다.

솔루션 단계:

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

  2. 관심 영역(ROI) 생성

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

    • 결과 영역 확장(왼쪽 가운데 영상)

  3. 계산된 ROI에서 ege를 검출합니다.트리에 가장자리가 많다(오른쪽 중간 이미지)

    • 결과 확장

    • 반지름이 큰 침식(왼쪽 아래 이미지)

  4. 가장 큰(면적별) 개체를 선택합니다. 결과 영역입니다.

  5. Colvess Hull (트리는 볼록 폴리곤)(오른쪽 아래 이미지)

  6. 경계 상자(오른쪽 하단 이미지 - gren 상자)

스텝: ★★★★★★★★★★★★★★★:여기에 이미지 설명 입력

첫 번째 결과 - 가장 단순하지만 오픈 소스 소프트웨어에는 없는 "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++ 코드로의 포트.버전 2.4.4에서 OpenCV를 사용했습니다.

c는 다음과 같습니다 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;
}

...또 다른 구식 솔루션 - 순수하게 HSV 처리를 기반으로 합니다.

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

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

  1. Hues(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

결과:

결과에 마스크된 이미지와 경계 상자가 표시됩니다. 여기에 이미지 설명 입력

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
이 아이디어는 이미지가 일반적으로 어둡고 부드러운 배경(또는 경우에 따라서는 전경)에 밝은 나무를 묘사한다는 가정에 기초하고 있습니다.이 켜진 나무 영역은 더 "에너지" 있고 강도가 더 높습니다.
츠키다

  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

지금까지와는 전혀 다른 방법으로 크리스마스 트리를 빛으로 검출하는 php 를 작성했습니다.결과는 항상 대칭 삼각형이며 필요한 경우 트리의 각도("지방도")와 같은 숫자 값이 됩니다.

이 알고리즘에 대한 가장 큰 위협은 (대부분) 트리 옆 또는 앞에 있는 조명입니다(추가 최적화가 이루어질 때까지 더 큰 문제).편집(추가):할 수 없는 일:크리스마스 트리가 있는지 없는지, 하나의 이미지에서 여러 개의 크리스마스 트리를 찾아 라스베이거스 한복판에 있는 크리스마스 트리를 정확하게 감지하고, 심하게 구부러지거나 뒤집히거나 잘린 크리스마스 트리를 감지합니다.;)

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

  • 각 픽셀의 추가된 밝기(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;
}


?>

이미지:왼쪽 위 아래쪽 중심 왼쪽 아래 오른쪽 위 상부 중앙 오른쪽 아래

보너스: 위키피디아에서 온 독일인 Weihnachtsbaum뢰메르베르크 http://commons.wikimedia.org/wiki/File:Weihnachtsbaum_R%C3%B6merberg.jpg

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

에서 더 수 .여기에 이미지 설명 입력

내 알고리즘은 트리에 불이 켜져 있고 왼쪽 아래 트리는 다른 트리에 비해 위쪽에 불이 덜 들어온다고 가정합니다.

언급URL : https://stackoverflow.com/questions/20772893/how-to-detect-a-christmas-tree

반응형