[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
#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;
}
21
一些老式的图像处理方法...... 这个想法是基于 assumption that images depict lighted trees on typically darker and smoother backgrounds (在某些情况下是前景) . lighted tree area is more "energetic" and has higher intensity . 过程如下:
转换为graylevel
应用LoG过滤以获得最多的"active"区域
应用intentisy thresholding以获得最明亮的区域
结合前两个来获得初步掩模
应用形态扩张来扩大区域并连接相邻组件
根据区域面积消除小的候选区域
你得到的是每个图像的二进制掩码和边界框 .
Here are the results using this naive technique:
Code on MATLAB follows: 代码在包含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
% 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
结果
高分辨率结果仍然available here! Even more experiments with additional images can be found here.
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()
10 回答
我在Matlab R2007a中编写了代码 . 我用k-means粗略地提取圣诞树 . 我将仅使用一张图像显示我的中间结果,并使用所有六张图像显示最终结果 .
首先,我将RGB空间映射到Lab空间,这可以增强其b通道中红色的对比度:
除了色彩空间的特征,我还使用了与邻域相关的纹理特征而不是每个像素本身 . 在这里,我线性地组合了3个原始通道(R,G,B)的强度 . 我之所以这样格式化,是因为图片中的圣诞树上都有红灯,有时还有绿色/有时是蓝色的照明 .
我在
I0
上应用3X3局部二值模式,使用中心像素作为阈值,并通过计算高于阈值的平均像素强度值与其下方的平均值之间的差异来获得对比度 .由于我总共有4个功能,我会在我的聚类方法中选择K = 5 . k-means的代码如下所示(来自Andrew Ng博士的机器学习课程 . 我之前参加过该课程,并且我在编程任务中自己编写了代码) .
由于程序在我的计算机上运行速度很慢,我只运行了3次迭代 . 通常,停止标准是(i)迭代时间至少为10,或者(ii)质心不再有变化 . 根据我的测试,增加迭代可以更准确地区分背景(天空和树木,天空和建筑物......),但没有显示圣诞树提取的剧烈变化 . 另请注意,k-means对随机质心初始化不起作用,因此建议多次运行程序进行比较 .
在k均值之后,选择具有最大强度
I0
的标记区域 . 边界追踪用于提取边界 . 对我来说,最后一棵圣诞树是最难提取的,因为该图片中的对比度不够高,因为它们在前五个中 . 我的方法中的另一个问题是我在Matlab中使用了bwboundaries
函数来跟踪边界,但有时也会包含内部边界,因为您可以在第3,第5,第6个结果中观察到 . 圣诞树内的黑暗面不仅没有被照亮的一面聚集,而且它们也导致了许多微小的内部边界追踪(imfill
并没有很大的改善) . 在我的所有算法中仍然有很多改进空间 .一些publication s表明均值漂移可能比k均值更稳健,并且许多graph-cut based algorithms在复杂的边界分割上也非常有竞争力 . 我自己写了一个均值漂移算法,它似乎更好地提取没有足够光线的区域 . 但是平均移位有点过分,需要一些合并策略 . 它在我的计算机上跑得比k-means慢得多,我恐怕不得不放弃它 . 我热切期待看到其他人会在上面提到的那些现代算法中提供出色的结果 .
然而,我始终认为特征选择是图像分割的关键组成部分 . 通过适当的特征选择可以最大化对象和背景之间的边距,许多分割算法肯定会起作用 . 不同的算法可以将结果从1改为10,但是特征选择可以将其从0改善为1 .
圣诞节快乐 !
......另一种老式的解决方案 - 纯粹 based on HSV processing :
将图像转换为HSV颜色空间
根据HSV中的启发式创建掩码(见下文)
对面罩应用形态膨胀以连接断开的区域
丢弃小区域和水平块(记住树木是垂直块)
计算边界框
HSV处理中的单词 on the heuristics :
Hues (H) between 210 - 320 degrees 的所有内容都被丢弃为蓝色洋红色,应该是在背景中或在非相关区域
Values (V) lower that 40% 的所有内容也被丢弃,因为它太暗而无法相关
当然,人们可以尝试许多其他可能来微调这种方法......
以下是用于处理技巧的MATLAB代码(警告:代码远未被优化!!!我使用了不推荐用于MATLAB编程的技术,只是为了能够跟踪流程中的任何内容 - 这可以大大优化):
结果:
在结果中,我显示了蒙版图像和边界框 .
使用与我所见的完全不同的方法,我创建了一个php脚本,可以通过灯光检测圣诞树 . 结果总是一个对称的三角形,如果需要,数值就像树的角度("fatness") .
这种算法的最大威胁显然是(大量)或树前的灯(直到进一步优化才会出现更大的问题) . 编辑(添加):它不能做什么:找出是否有圣诞树,在一个图像中找到多个圣诞树,正确检测拉斯维加斯中间的圣诞树,检测弯曲的圣诞树,颠倒或砍倒...;)
不同的阶段是:
计算每个像素的增加亮度(R G B)
将每个像素顶部的所有8个相邻像素的值相加
按此值排列所有像素(最亮的第一个) - 我知道,不是很微妙......
从顶部开始选择其中的N个,跳过太近的那些
计算这些前N的median(给我们树的近似中心)
从一个加宽的搜索光束中的中间位置向上开始,用于选择最亮的光线的最顶部光线(人们倾向于在最顶部放置至少一个光线)
从那里,想象出向左和向下60度的线(圣诞树不应该那么胖)
减少60度,直到20%的最亮灯都在这个三角形之外
找到三角形最底部的灯光,为您提供树木的下部水平边框
完成
标记说明:
树中央的大红十字:顶部N个最亮的灯的中位数
从那里向上的虚线:"search beam"为树的顶部
较小的红十字:树的顶部
真的很小的红色十字架:所有顶部N个最亮的灯
红三角:呃呃!
源代码:
图片:
奖金:来自维基百科的德国人Weihnachtsbaum
http://commons.wikimedia.org/wiki/File:Weihnachtsbaum_R%C3%B6merberg.jpg
我的解决步骤:
获取R Channels (来自RGB) - 我们在此 Channels 上进行的所有操作:
创建兴趣区域(ROI)
阈值R通道,最小值149(右上图)
扩张结果区域(左中图)
在计算出的roi中检测到eges . 树有很多边缘(右中图)
扩张结果
更大半径的侵蚀(左下图)
选择最大(按区域)对象 - 它是结果区域
ConvexHull(树是凸多边形)(右下图)
包围框(右下图像 - grren框)
一步一步:
第一个结果 - 最简单但不是开源软件 - “自适应视觉工作室自适应视觉库”:这不是开源的,但原型很快:
整个算法检测圣诞树(11块):
下一步 . 我们想要开源解决方案 . 将AVL过滤器更改为OpenCV过滤器:这里我做了一些改动,例如边缘检测使用cvCanny过滤器,为了尊重roi我做了多个区域图像与边缘图像,选择我使用的最大元素findContours contourArea但想法是相同的 .
https://www.youtube.com/watch?v=sfjB3MigLH0&index=1&list=UUpSRrkMHNHiLDXgylwhWNQQ
我现在无法显示具有中间步骤的图像,因为我只能放置2个链接 .
好的,现在我们使用openSource过滤器,但它还不是完全开源的 . 最后一步 - 端口到c代码 . 我在版本2.4.4中使用了OpenCV
最终c代码的结果是:
c代码也很短:
一些老式的图像处理方法......
这个想法是基于 assumption that images depict lighted trees on typically darker and smoother backgrounds (在某些情况下是前景) . lighted tree area is more "energetic" and has higher intensity .
过程如下:
转换为graylevel
应用LoG过滤以获得最多的"active"区域
应用intentisy thresholding以获得最明亮的区域
结合前两个来获得初步掩模
应用形态扩张来扩大区域并连接相邻组件
根据区域面积消除小的候选区域
你得到的是每个图像的二进制掩码和边界框 .
Here are the results using this naive technique:
Code on MATLAB follows: 代码在包含JPG图像的文件夹上运行 . 加载所有图像并返回检测结果 .
这是我简单而愚蠢的解决方案 . 它基于这样的假设:树将是图片中最明亮和最重要的东西 .
第一步是检测图片中最亮的像素,但我们必须区分树本身和反射光线的雪 . 在这里,我们尝试排除雪应用颜色代码上的一个非常简单的过滤器:
然后我们找到每个“明亮”的像素:
最后我们加入了两个结果:
现在我们寻找最大的亮点:
现在我们已经差不多完成了,但是由于积雪,仍然有一些不完美的地方 . 为了剪掉它们,我们将使用圆形和矩形构建一个蒙版来近似树的形状以删除不需要的部分:
最后一步是找到树的轮廓并将其绘制在原始图片上 .
对不起,但此刻我连接不好,所以我无法上传图片 . 我会试着以后再做 .
圣诞节快乐 .
编辑:
这里有一些最终输出的图片:
我在opencv中使用了python .
我的算法是这样的:
首先从图像中获取红色通道
将阈值(最小值200)应用于红色通道
然后应用Morphological Gradient然后执行'Closing'(膨胀后跟Erosion)
然后它在平面中找到轮廓并选择最长的轮廓 .
代码:
如果我将内核从(25,5)更改为(10,5),我会在所有树上获得更好的结果,但是左下角,
我的算法假定树上有灯,而在左下角的树中,顶部的光线少于其他树 .
这是我使用传统图像处理方法的最后帖子......
在这里,我以某种方式结合我的另外两个提议, achieving even better results . 事实上,我无法看到这些结果如何更好(特别是当你看到该方法产生的蒙版图像时) .
方法的核心是 three key assumptions 的组合:
图像应该在树区域有很大的波动
图像在树区域应具有更高的强度
背景区域应该具有低强度并且主要是蓝色
考虑到这些假设,该方法的工作原理如下:
将图像转换为HSV
使用LoG滤波器过滤V通道
在LoG过滤图像上应用硬阈值以获得'activity'掩码A.
对V通道应用硬阈值以获得强度掩模B.
应用H通道阈值处理以将低强度蓝色区域捕获到背景掩模C中
使用AND组合蒙版以获取最终蒙版
扩大遮罩以扩大区域并连接分散的像素
消除小区域并获得最终仅代表树的最终掩码
这是MATLAB中的代码(同样,脚本加载当前文件夹中的所有jpg图像,再次,这远不是优化的代码片段):
结果
高分辨率结果仍然available here!
Even more experiments with additional images can be found here.
我有一种方法,我觉得它很有趣,与其他方法有点不同 . 与其他一些方法相比,我的方法的主要区别在于如何执行图像分割步骤 - 我使用了Python优化的DBSCAN聚类算法,用于查找可能不一定具有单个清晰质心的有些无定形形状 .
在顶层,我的方法相当简单,可以分解为大约3个步骤 . 首先,我应用一个阈值(或实际上,两个独立和不同的阈值的逻辑“或”) . 与许多其他答案一样,我认为圣诞树将是场景中较亮的物体之一,因此第一个阈值只是一个简单的单色亮度测试;在0-255比例(其中黑色为0,白色为255)中具有大于220的值的任何像素被保存为二进制黑白图像 . 第二个阈值试图寻找红色和黄色的灯光,这些灯光在六个图像的左上角和右下角的树木中特别突出,并且在大多数照片中普遍存在的蓝绿色背景中很好地突出 . 我将rgb图像转换为hsv空间,并要求色调在0.0-1.0范围内小于0.2(大致对应于黄色和绿色之间的边界)或大于0.95(对应于紫色和红色之间的边界)另外我需要明亮饱和的颜色:饱和度和值必须都高于0.7 . 两个阈值程序的结果在逻辑上“或”在一起,并且得到的黑白二进制图像矩阵如下所示:
你可以清楚地看到每个图像都有一个大的像素簇,大致对应于每棵树的位置,另外一些图像还有一些其他小的簇,对应于一些建筑物的窗户中的灯光,或者一个在地平线上的背景场景 . 下一步是让计算机识别出这些是独立的集群,并且使用群集成员身份标识号正确标记每个像素 .
为此,我选择了DBSCAN . 相对于其他可用的聚类算法,可以很好地直观地比较DBSCAN的行为方式here . 正如我之前所说,它非常适合无定形形状 . DBSCAN的输出,每个簇以不同的颜色绘制,如下所示:
在查看此结果时,有几点需要注意 . 首先,DBSCAN要求用户设置“接近”参数以调节其行为,这有效地控制了一对点必须分开的方式,以便算法声明一个新的独立簇而不是将测试点聚集到已经存在的集群 . 我将此值设置为沿每个图像的对角线大小的0.04倍 . 由于图像的大小从大约VGA到大约HD 1080不等,因此这种类型的比例相关定义至关重要 .
值得注意的另一点是,在scikit-learn中实现的DBSCAN算法具有内存限制,这对于此示例中的一些较大图像而言相当具有挑战性 . 因此,对于一些较大的图像,我实际上必须“抽取”(即,仅保留每个第3或第4像素并丢弃其他像素)每个簇以便保持在该限制内 . 作为这种剔除过程的结果,在一些较大的图像上难以看到剩余的单个稀疏像素 . 因此,仅出于显示目的,上述图像中的颜色编码像素已经稍微有效地“扩张”,以使它们更好地突出 . 为了叙述,它纯粹是一种整容手术;虽然有些评论在我的代码中提到了这种扩张,但请放心,它与任何实际重要的计算无关 .
一旦识别和标记了聚类,第三步也是最后一步很简单:我只是在每张图像中采用最大的聚类(在这种情况下,我选择以成员像素的总数来衡量“大小”,尽管可以同样可以轻松地使用某种类型的度量来衡量物理范围)并计算该群集的凸包 . 凸壳然后变成树边界 . 通过这种方法计算的六个凸包如下图红色所示:
源代码是为Python 2.7.6编写的,它依赖于numpy,scipy,matplotlib和scikit-learn . 我把它分成两部分 . 第一部分负责实际图像处理:
第二部分是用户级脚本,它调用第一个文件并生成上面的所有图:
EDIT NOTE: 我编辑了这篇文章,以便(i)按照要求的要求单独处理每个树形图像,(ii)同时考虑对象的亮度和形状,以提高结果的质量 .
下面介绍一种考虑物体亮度和形状的方法 . 换句话说,它寻找具有三角形形状和明显亮度的物体 . 它是使用Marvin图像处理框架在Java中实现的 .
第一步是颜色阈值处理 . 这里的目标是将分析集中在具有显着亮度的物体上 .
output images:
http://marvinproject.sourceforge.net/other/trees/tree_1threshold.png http://marvinproject.sourceforge.net/other/trees/tree_2threshold.png http://marvinproject.sourceforge.net/other/trees/tree_3threshold.png
http://marvinproject.sourceforge.net/other/trees/tree_4threshold.png http://marvinproject.sourceforge.net/other/trees/tree_5threshold.png http://marvinproject.sourceforge.net/other/trees/tree_6threshold.png
source code:
在第二步中,图像中最亮的点被扩张以形成形状 . 该过程的结果是具有显着亮度的物体的可能形状 . 应用填充填充分段,检测断开的形状 .
output images:
http://marvinproject.sourceforge.net/other/trees/tree_1_fill.png http://marvinproject.sourceforge.net/other/trees/tree_2_fill.png http://marvinproject.sourceforge.net/other/trees/tree_3_fill.png
http://marvinproject.sourceforge.net/other/trees/tree_4_fill.png http://marvinproject.sourceforge.net/other/trees/tree_5_fill.png http://marvinproject.sourceforge.net/other/trees/tree_6_fill.png
source code:
如输出图像所示,检测到多个形状 . 在这个问题中,图像中只有几个亮点 . 但是,实施此方法是为了处理更复杂的情况 .
在下一步中,分析每个形状 . 一种简单的算法检测具有类似于三角形的图案的形状 . 该算法逐行分析对象形状 . 如果每个形状线的质量的中心几乎相同(给定阈值)并且随着y的增加质量增加,则该对象具有类似三角形的形状 . 形状线的质量是该线中属于该形状的像素数 . 想象一下,您水平切割对象并分析每个水平线段 . 如果它们彼此集中并且长度从第一个段增加到线性模式中的最后一个段,则可能有一个类似于三角形的对象 .
source code:
最后,在原始图像中突出显示每个形状的类似于三角形且具有显着亮度的位置(在这种情况下为圣诞树),如下所示 .
final output images:
http://marvinproject.sourceforge.net/other/trees/tree_1_out_2.jpg http://marvinproject.sourceforge.net/other/trees/tree_2_out_2.jpg http://marvinproject.sourceforge.net/other/trees/tree_3_out_2.jpg
http://marvinproject.sourceforge.net/other/trees/tree_4_out_2.jpg http://marvinproject.sourceforge.net/other/trees/tree_5_out_2.jpg http://marvinproject.sourceforge.net/other/trees/tree_6_out_2.jpg
final source code:
这种方法的优点是它可能适用于包含其他发光物体的图像,因为它分析了物体形状 .
快活圣诞!
EDIT NOTE 2
讨论了该解决方案的输出图像与其他一些输出图像的相似性 . 实际上,它们非常相似 . 但这种方法不只是分割对象 . 它还从某种意义上分析了物体的形状 . 它可以处理同一场景中的多个发光物体 . 事实上,圣诞树不一定是最亮的 . 我只是为了丰富讨论而加以论述 . 样品中存在偏差,只是寻找最亮的物体,你会发现树木 . 但是,我们真的想在此时停止讨论吗?此时,计算机在多大程度上真正识别出类似圣诞树的物体?让我们试着弥补这个差距 .
下面的结果只是为了阐明这一点:
input image
output