关于C++:如何检测圣诞树Christmas Tree?

How to detect a Christmas Tree?

可以使用哪些图像处理技术来实现一个应用程序来检测以下图像中显示的圣诞树?

我正在寻找解决方案,可以解决所有这些图像。因此,需要培训HAAR级联分类器或模板匹配的方法并不是很有趣。

我在寻找可以用任何编程语言编写的东西,只要它只使用开放源码技术。该解决方案必须使用在这个问题上共享的图像进行测试。共有6个输入图像,答案应显示每个图像的处理结果。最后,对于每个输出图像,必须绘制红线以包围检测到的树。

您将如何以编程方式检测这些图像中的树?


我有一个方法,我认为这是有趣的,有点不同于其他方法。与其他方法相比,我的方法的主要区别在于如何执行图像分割步骤——我使用了Python的SciKit Learn中的dbscan聚类算法;它优化了查找不一定有单一清晰质心的非晶形形状。好的。

在顶层,我的方法相当简单,可以分为大约3个步骤。首先,我应用一个阈值(或者实际上是两个独立的和不同的阈值的逻辑"或")。与许多其他答案一样,我假设圣诞树是场景中较亮的对象之一,因此第一个阈值只是一个简单的单色亮度测试;任何在0-255比例(其中黑色为0,白色为255)上值大于220的像素都保存到一个二进制黑白图像中。第二个门槛试图寻找红黄两色的光,这在六张图片的左上角和右下角的树上尤为突出,与大多数照片中普遍存在的蓝绿色背景形成鲜明对比。我将RGB图像转换为HSV空间,并要求色相在0.0-1.0比例下小于0.2(大致相当于黄色和绿色之间的边界),或大于0.95(相当于紫色和红色之间的边界),另外我需要明亮的饱和色:饱和度和值必须都大于0.7。这两个阈值程序的结果在逻辑上"或"结合在一起,黑白二值图像的结果矩阵如下所示:好的。

Christmas trees, after thresholding on HSV as well as monochrome brightness好的。

您可以清楚地看到,每个图像都有一个大致对应于每棵树位置的大像素簇,加上一些图像也有一些其他小的像素簇,这些像素簇要么对应于一些建筑物窗口中的灯光,要么对应于地平线上的背景场景。下一步是让计算机识别这些是单独的簇,并用簇成员身份号正确地标记每个像素。好的。

对于这个任务,我选择了DBSCAN。与这里提供的其他聚类算法相比,dbscan的典型行为有一个很好的视觉比较。正如我之前所说,它适用于非晶态形状。DBSCAN的输出(每个集群以不同的颜色绘制)如下所示:好的。

DBSCAN clustering output好的。

在查看此结果时,需要注意一些事情。首先,DBSCAN要求用户设置一个"邻近"参数以调节其行为,这有效地控制了一对点的分离程度,以便算法声明一个新的独立集群,而不是将测试点聚集到已经存在的集群上。我将这个值设置为每个图像对角线的0.04倍大小。由于图像的大小从大约vga到大约hd 1080不等,因此这种比例相对定义至关重要。好的。

另一点值得注意的是,在Scikit Learn中实现的DBSCAN算法具有内存限制,这对于本示例中的一些较大的图像来说是相当具有挑战性的。因此,对于一些较大的图像,我实际上必须"分解"(即,每3或4个像素保留一次,并丢弃其他像素)每个簇才能保持在这个限制范围内。由于这种剔除过程,在一些较大的图像上很难看到剩余的单个稀疏像素。因此,仅出于显示目的,上述图像中的彩色编码像素被有效地"放大"了一点,使其更加突出。这纯粹是为了叙述而进行的一个表面化的操作;尽管在我的代码中有评论提到了这种扩展,但请放心,它与任何实际重要的计算都没有任何关系。好的。

一旦集群被识别和标记,第三步和最后一步就很容易了:我只需取每个图像中最大的集群(在本例中,我选择以成员像素的总数来度量"大小",尽管人们可以很容易地使用某种度量物理范围的度量),并计算那个集群。凸面的外壳变成了树的边界。通过该方法计算出的六个凸壳用红色表示:好的。

Christmas trees with their calculated borders好的。

源代码是为python 2.7.6编写的,它依赖于numpy、scipy、matplotlib和scikit-learn。我把它分成了两部分。第一部分负责实际的图像处理:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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

第二部分是一个用户级脚本,调用第一个文件并生成上面的所有绘图:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#!/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)按照要求单独处理每个树图像;(i i)考虑对象亮度和形状,以提高结果的质量。

下面介绍了一种考虑物体亮度和形状的方法。换言之,它寻找具有三角形形状和显著亮度的物体。它是在Java中实现的,使用马尔文图像处理框架。

第一步是颜色阈值。这里的目标是将分析集中在具有显著亮度的物体上。

输出图像:

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
}
}

在第二步中,图像中最亮的点被放大以形成形状。这一过程的结果是可能的形状的物体具有显著的亮度。应用洪水填充分割,检测断开连接的形状。

输出图像:

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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的增加而增加,则对象具有三角形形状。形状线的质量是该线中属于形状的像素数。假设您水平地分割对象并分析每个水平段。如果它们彼此集中,并且长度在线性模式中从第一段增加到最后一段,则可能有一个类似三角形的对象。

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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;
}

最后,每个形状的位置类似于一个三角形,并且亮度很高,在本例中是一棵圣诞树,在原始图像中突出显示,如下所示。

最终输出图像:

最终源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
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

讨论了该解决方案的输出图像与其他一些图像的相似性。事实上,它们非常相似。但这种方法不只是分割对象。它还从某种意义上分析了对象的形状。它可以处理同一场景中的多个发光对象。事实上,圣诞树不需要是最亮的。我只是为了丰富讨论而放弃它。在样本中有一个偏差,只要寻找最亮的物体,你就会找到树。但是,我们真的想在这一点上停止讨论吗?在这一点上,计算机识别一个类似于圣诞树的物体到底有多远?让我们试着缩小这个差距。

下面给出的结果只是为了阐明这一点:

输入图像

enter image description here

输出

enter image description here


这是我的简单而愚蠢的解决方案。这是基于这样一个假设:这棵树将是图片中最明亮和最大的东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//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;
}

第一步是检测图片中最亮的像素,但是我们必须区分树本身和反射光的雪。在这里,我们尝试排除在颜色代码上应用非常简单的过滤器的雪:

1
2
3
4
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);

然后我们找到每个"明亮"的像素:

1
2
3
4
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);

最后,我们加入两个结果:

1
bitwise_and(tmp, tmp1, tmp1);

现在我们寻找最大的明亮物体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);

现在我们差不多做完了,但由于下雪,还是有一些瑕疵。要将它们切掉,我们将使用一个圆和一个矩形来近似树的形状来构建一个遮罩,以删除不需要的部分:

1
2
3
4
5
6
7
8
9
10
11
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);

最后一步是找到树的轮廓并在原始图片上绘制出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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粗略地提取了圣诞树。我将只显示一个图像的中间结果,以及所有六个图像的最终结果。

首先,我将RGB空间映射到实验室空间,这可以增强B通道中红色的对比度:

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

enter image description here

除了颜色空间中的特征,我还使用了与而不是每个像素本身。在这里,我将强度与3个原始频道(R、G、B)。我这样格式化的原因是因为圣诞节图中的树都有红灯,有时是绿色,有时是蓝色。照明也一样。

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

enter image description here

我在I0上应用了3x3本地二进制模式,使用中心像素作为阈值,并且通过计算平均像素强度值之间的差异获得对比度高于阈值,低于阈值的平均值。

1
2
3
4
5
6
7
8
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

enter image description here

因为我总共有4个特性,所以我在集群方法中选择k=5。代码k-均值如下所示(它来自Andrew Ng博士的机器学习课程)。我拿了之前的课程,我在他的编程作业中自己写了代码)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[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次,或(i i)质心不再发生变化。到我的测试,增加迭代可能会区分背景(天空和树,天空和建筑,…)更准确地说,但圣诞树并没有表现出巨大的变化。提取。同时注意k-means不免疫随机质心初始化,因此建议运行程序几次进行比较。

在k均值之后,选择最大强度为I0的标记区域。和边界跟踪法用于提取边界。对我来说,最后一棵圣诞树是最难提取的,因为照片中的对比度不够高,因为它们在前五棵。我方法中的另一个问题是,我使用了Matlab中的bwboundaries函数来跟踪边界,但有时也包括了内边界,正如您在第3、5、6个结果中看到的那样。圣诞树的黑暗面不仅没有与被照亮的一面聚集在一起,而且还导致了许多微小的内部边界追踪(imfill并没有改善很多)。总之,我的算法还有很大的改进空间。

一些出版物指出,均值漂移可能比k均值更为强劲,而且许多基于图割的算法在复杂边界上也具有很强的竞争力。分割。我自己写了一个均值偏移算法,它似乎能更好地提取区域没有足够的光。但是平均值的变化有点过于分段,而且有些策略需要合并。恐怕我的电脑比K-means慢得多放弃它。我热切期待看到其他人在这里提交出色的结果。上面提到的现代算法。

然而,我一直认为特征选择是图像分割的关键。用一个适当的特征选择,可以最大化对象和背景之间的边界,很多分割算法肯定会起作用。不同的算法可以提高结果从1到10,但功能选择可能会将其从0改进为1。

圣诞快乐!


这是我最后一篇使用传统图像处理方法的文章…

在这里,我以某种方式结合了我的另外两个建议,取得了更好的结果。事实上,我看不出这些结果是如何变得更好的(尤其是当您查看该方法生成的遮罩图像时)。

该方法的核心是三个关键假设的组合:

  • 图像在树区域应该有很高的波动
  • 在树区域图像应该具有更高的强度
  • 背景区域应具有低强度,且大部分为蓝色。
  • 考虑到这些假设,该方法的工作原理如下:

  • 将图像转换为hsv
  • 用对数滤波器过滤V通道
  • 在日志筛选图像上应用硬阈值以获取"活动"掩码A
  • 对V通道应用硬阈值得到强度掩模B
  • 应用H通道阈值捕获低强度蓝色区域到背景遮罩C中
  • 使用和组合遮罩以获得最终遮罩
  • 展开遮罩以放大区域并连接分散的像素
  • 消除小区域并获得最终的遮罩,最终只表示树
  • 以下是matlab中的代码(同样,脚本会加载当前文件夹中的所有JPG图像,而且,这远不是优化代码):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    % 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

    结果

    results

    高分辨率结果仍然在这里可用!< BR>在这里可以找到更多关于附加图像的实验。


    我的解决方案步骤:

  • 获取R通道(来自RGB)-我们在此通道上进行的所有操作:

  • 创建感兴趣的区域(ROI)

    • 阈值R通道,最小值为149(右上角图像)

    • 扩大结果区域(左中图像)

  • 在计算的ROI中检测EGE。树有很多边(右中图像)

    • 扩张结果

    • 半径较大的侵蚀(左下角图像)

  • 选择最大的(按区域)对象-它是结果区域

  • 凸形(树是凸多边形)(右下角图像)

  • 边界框(右下角图像-grren框)

  • 一步一步地:enter image description here

    第一个结果-最简单但不在开源软件中-"自适应视觉工作室+自适应视觉库":这不是开源的,但很快就可以实现原型:

    整个算法检测圣诞树(11块):AVL solution

    下一步。我们需要开源解决方案。将AVL过滤器更改为OpenCV过滤器:这里我做了一些小的改变,例如边缘检测使用cvcanny过滤器,为了尊重投资回报率,我用边缘图像乘以区域图像,选择我使用findcontours+Contourarea的最大元素,但想法是一样的。

    https://www.youtube.com/watch?v=sfjb3miglh0&index=1&list=uupsrkmhnhildxgylwwwnqq

    OpenCV solution

    我现在不能用中间步骤显示图像,因为我只能放置两个链接。

    好的,现在我们使用开源过滤器,但它还不是整个开源的。最后一步-端口到C++代码。我在2.4.4版中使用了opencv

    最终C++代码的结果是:enter image description here

    C++代码也很短:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    #include"opencv2/highgui/highgui.hpp"
    #include"opencv2/opencv.hpp"
    #include
    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处理:

  • 将图像转换为HSV颜色空间
  • 根据HSV中的启发式方法创建遮罩(见下文)
  • 将形态扩张应用于面膜以连接断开区域
  • 丢弃小面积和水平块(记住树是垂直块)
  • 计算边界框
  • 关于HSV处理中的启发式方法的一个词:

  • 所有颜色(H)在210-320度之间的物体都被视为背景或非相关区域中的蓝色洋红。
  • 所有值(v)低于40%的东西也会因为太暗而不相关而被丢弃。
  • 当然,我们可以尝试许多其他的可能性来微调这种方法…

    下面是matlab代码做的诀窍(警告:代码远没有被优化!!!!我使用了Matlab编程中不推荐的技术,只是为了跟踪过程中的任何事情,这可以大大优化):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    % 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

    结果:

    在结果中,我显示了遮罩图像和边界框。enter image description here


    一些老式的图像处理方法…
    这个想法是基于这样一个假设:图像描绘的是通常较暗和光滑的背景(或者在某些情况下是前场)上被照亮的树木。采光的树区更"有活力",强度更高。< BR>流程如下:

  • 转换为灰度
  • 应用日志筛选以获取最"活动"的区域
  • 使用密集的阈值以获得最亮的区域
  • 结合前2个得到一个初步的蒙版
  • 应用形态扩张扩大区域并连接相邻部件
  • 根据区域大小消除小的候选区域
  • 你得到的是一个二进制的遮罩和每个图像的边界框。

    以下是使用这种幼稚技术的结果:enter image description here

    Matlab上的代码如下:代码运行在包含JPG图像的文件夹上。加载所有图像并返回检测到的结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    % 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度(圣诞树不应该那么胖)
    • 减少60度,直到这个三角形外有20%的最亮的灯光。
    • 找到三角形最底部的灯,给你树的下水平边界。
    • 多恩

    标记说明:

    • 树中央的大红十字:最亮的n盏灯的中间
    • 从那里向上的虚线:"搜索梁"寻找树的顶部
    • 小红十字会:树顶
    • 非常小的红色十字:所有前n个最亮的灯
    • 红色三角:呃!

    源代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    <?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,"

     STAGE 5"
    );

        $treetopX = $medianXvalue;
        $treetopY = $medianYvalue;

        $searchXmin = $medianXvalue;
        $searchXmax = $medianXvalue;

        $width = 0;
        for($y = $medianYvalue; $y >= 0; $y--) {
            fwrite($handle,"
    At 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,"

     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,"

    We're at angle"
    .$treesideAngle);
            $xymultiplier = sin(deg2rad($treesideAngle));
            fwrite($handle,"
    Multiplier:"
    .$xymultiplier);

            $topPositionsExcluded = 0;
            foreach ($topNused as $key => $val) {
                $position = explode(":", $key);
                fwrite($handle,"
    At 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,"
    TOO HIGH"
    );
                    continue;
                }

                // Top light will generate division by zero
                if($treetopY-$position[1] == 0) {
                    fwrite($handle,"
    DIVISION BY ZERO"
    );
                    continue;
                }

                // Lights left end right of it are also not inside
                fwrite($handle,"
    Light position factor:"
    .(abs($treetopX-$position[0]) / abs($treetopY-$position[1])));
                if((abs($treetopX-$position[0]) / abs($treetopY-$position[1])) > $xymultiplier){
                    $topPositionsExcluded++;
                    fwrite($handle,"
     --- 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;
    }


    ?>

    图像:Upper leftLower centerLower leftUpper rightUpper centerLower right

    奖金:一个来自维基百科的德国韦纳切茨鲍姆R?merberghttp://commons.wikimedia.org/wiki/file:weihnachtsbaum_r%c3%b6merberg.jpg


    我用了python和opencv。

    我的算法是这样的:

  • 首先,它从图像中获取红色通道
  • 对红色通道应用阈值(最小值200)
  • 然后应用形态梯度,然后进行"闭合"(扩张后腐蚀)
  • 然后找到平面上的轮廓,选择最长的轮廓。
  • The outcome:

    代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    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)除了左下角,所有的树都有更好的结果,enter image description here

    我的算法假设树上有灯,并且在左下角的树上,顶部的光比其他树少。