有哪些技术能够用来检测下图中出现的圣诞树?

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

我正在寻找解决方案,将工作在所有这些图像。因此,需要训练哈尔级联分类器模板匹配的方法不是很有趣。

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

您将如何通过编程来检测这些图像中的树木呢?

25349 次浏览

编辑注:我编辑这篇文章是为了(I)按照要求单独处理每个树图像,(ii)考虑物体的亮度和形状,以提高结果的质量。


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

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

输出图片:

< img src = " https://i.imgur.com/7dY2GCT.png " width = " 200 " > < / p >

< img src = " https://i.imgur.com/pLUfa6l.png " width = " 200 " > < / p >

源代码:

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

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

输出图片:

< img src = " https://i.imgur.com/G4fzxv6.png " width = " 200 " > < / p >

< img src = " https://i.imgur.com/H9IYmJh.png " width = " 200 " > < / p >

源代码:

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

最后,每个形状的位置类似于一个三角形,并具有显著的亮度,在这种情况下,圣诞树,突出显示在原图中,如下所示。

最终输出图片:

< img src = " https://i.imgur.com/Y87W0TJ.jpg " width = " 200 " > < / p >

< img src = " https://i.imgur.com/i9i8aBs.jpg " width = " 200 " > < / p >

最终源代码:

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

这是我的简单而愚蠢的解决方案。 它是基于这样一个假设,那棵树将是图片中最明亮、最大的东西
//g++ -Wall -pedantic -ansi -O2 -pipe -s -o christmas_tree christmas_tree.cpp `pkg-config --cflags --libs opencv`
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>


using namespace cv;
using namespace std;


int main(int argc,char *argv[])
{
Mat original,tmp,tmp1;
vector <vector<Point> > contours;
Moments m;
Rect boundrect;
Point2f center;
double radius, max_area=0,tmp_area=0;
unsigned int j, k;
int i;


for(i = 1; i < argc; ++i)
{
original = imread(argv[i]);
if(original.empty())
{
cerr << "Error"<<endl;
return -1;
}


GaussianBlur(original, tmp, Size(3, 3), 0, 0, BORDER_DEFAULT);
erode(tmp, tmp, Mat(), Point(-1, -1), 10);
cvtColor(tmp, tmp, CV_BGR2HSV);
inRange(tmp, Scalar(0, 0, 0), Scalar(180, 255, 200), tmp);


dilate(original, tmp1, Mat(), Point(-1, -1), 15);
cvtColor(tmp1, tmp1, CV_BGR2HLS);
inRange(tmp1, Scalar(0, 185, 0), Scalar(180, 255, 255), tmp1);
dilate(tmp1, tmp1, Mat(), Point(-1, -1), 10);


bitwise_and(tmp, tmp1, tmp1);


findContours(tmp1, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
max_area = 0;
j = 0;
for(k = 0; k < contours.size(); k++)
{
tmp_area = contourArea(contours[k]);
if(tmp_area > max_area)
{
max_area = tmp_area;
j = k;
}
}
tmp1 = Mat::zeros(original.size(),CV_8U);
approxPolyDP(contours[j], contours[j], 30, true);
drawContours(tmp1, contours, j, Scalar(255,255,255), CV_FILLED);


m = moments(contours[j]);
boundrect = boundingRect(contours[j]);
center = Point2f(m.m10/m.m00, m.m01/m.m00);
radius = (center.y - (boundrect.tl().y))/4.0*3.0;
Rect heightrect(center.x-original.cols/5, boundrect.tl().y, original.cols/5*2, boundrect.size().height);


tmp = Mat::zeros(original.size(), CV_8U);
rectangle(tmp, heightrect, Scalar(255, 255, 255), -1);
circle(tmp, center, radius, Scalar(255, 255, 255), -1);


bitwise_and(tmp, tmp1, tmp1);


findContours(tmp1, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
max_area = 0;
j = 0;
for(k = 0; k < contours.size(); k++)
{
tmp_area = contourArea(contours[k]);
if(tmp_area > max_area)
{
max_area = tmp_area;
j = k;
}
}


approxPolyDP(contours[j], contours[j], 30, true);
convexHull(contours[j], contours[j]);


drawContours(original, contours, j, Scalar(0, 0, 255), 3);


namedWindow(argv[i], CV_WINDOW_NORMAL|CV_WINDOW_KEEPRATIO|CV_GUI_EXPANDED);
imshow(argv[i], original);


waitKey(0);
destroyWindow(argv[i]);
}


return 0;
}

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

GaussianBlur(original, tmp, Size(3, 3), 0, 0, BORDER_DEFAULT);
erode(tmp, tmp, Mat(), Point(-1, -1), 10);
cvtColor(tmp, tmp, CV_BGR2HSV);
inRange(tmp, Scalar(0, 0, 0), Scalar(180, 255, 200), tmp);

然后我们找到每个“亮”像素:

dilate(original, tmp1, Mat(), Point(-1, -1), 15);
cvtColor(tmp1, tmp1, CV_BGR2HLS);
inRange(tmp1, Scalar(0, 185, 0), Scalar(180, 255, 255), tmp1);
dilate(tmp1, tmp1, Mat(), Point(-1, -1), 10);

最后我们将两个结果结合起来:

bitwise_and(tmp, tmp1, tmp1);

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

findContours(tmp1, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
max_area = 0;
j = 0;
for(k = 0; k < contours.size(); k++)
{
tmp_area = contourArea(contours[k]);
if(tmp_area > max_area)
{
max_area = tmp_area;
j = k;
}
}
tmp1 = Mat::zeros(original.size(),CV_8U);
approxPolyDP(contours[j], contours[j], 30, true);
drawContours(tmp1, contours, j, Scalar(255,255,255), CV_FILLED);
现在我们已经基本完成了,但是由于下雪,还有一些不完美的地方。 为了切断它们,我们将使用一个圆形和一个矩形来近似树的形状来创建一个蒙版,以删除不需要的片段:

m = moments(contours[j]);
boundrect = boundingRect(contours[j]);
center = Point2f(m.m10/m.m00, m.m01/m.m00);
radius = (center.y - (boundrect.tl().y))/4.0*3.0;
Rect heightrect(center.x-original.cols/5, boundrect.tl().y, original.cols/5*2, boundrect.size().height);


tmp = Mat::zeros(original.size(), CV_8U);
rectangle(tmp, heightrect, Scalar(255, 255, 255), -1);
circle(tmp, center, radius, Scalar(255, 255, 255), -1);


bitwise_and(tmp, tmp1, tmp1);

最后一步是找到我们的树的轮廓,并把它画在原图上。

findContours(tmp1, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
max_area = 0;
j = 0;
for(k = 0; k < contours.size(); k++)
{
tmp_area = contourArea(contours[k]);
if(tmp_area > max_area)
{
max_area = tmp_area;
j = k;
}
}


approxPolyDP(contours[j], contours[j], 30, true);
convexHull(contours[j], contours[j]);


drawContours(original, contours, j, Scalar(0, 0, 255), 3);

很抱歉,目前我的网络连接不好,无法上传图片。我以后再试着做。

圣诞快乐。

编辑:

以下是最终输出的一些图片:

一些老式的图像处理方法 这个想法是基于假设图像描绘了在通常较暗和较光滑的背景上点亮的树木(或在某些情况下的前景)。被点亮的树木区域更“有活力”,具有更高的强度
具体过程如下:

  1. 转换为灰度
  2. 应用LoG过滤来获得最“活跃”的区域
  3. 应用亮度阈值来获得最亮的区域
  4. 结合前两个得到一个初步的蒙版
  5. 应用形态扩张来扩大区域并连接相邻组件
  6. 根据候选区域的面积大小剔除较小的候选区域

你得到的是一个二进制掩码和每个图像的包围框。

下面是使用这种简单技术的结果: enter image description here

代码在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
我用Matlab R2007a编写代码。我用k-means粗略地提取了圣诞树。我 将只显示一张图像的中间结果,并显示所有六张图像的最终结果

首先,我将RGB空间映射到Lab空间上,这样可以增强红色b通道的对比度:

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)。我之所以这样格式化是因为圣诞节 图中的树都有红灯,有时是绿色,有时是蓝色 还有照明。< / p >
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局部二进制模式,使用中心像素作为阈值,和 通过计算平均像素强度值的差值得到对比度 高于阈值,平均值低于阈值
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博士的机器学习课程。我拿了 在他的编程作业中,我自己写了代码)
[centroids, idx] = runkMeans(X, initial_centroids, max_iters);
mask=reshape(idx,img_size(1),img_size(2));


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [centroids, idx] = runkMeans(X, initial_centroids, ...
max_iters, plot_progress)
[m n] = size(X);
K = size(initial_centroids, 1);
centroids = initial_centroids;
previous_centroids = centroids;
idx = zeros(m, 1);


for i=1:max_iters
% For each example in X, assign it to the closest centroid
idx = findClosestCentroids(X, centroids);


% Given the memberships, compute new centroids
centroids = computeCentroids(X, idx, K);


end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function idx = findClosestCentroids(X, centroids)
K = size(centroids, 1);
idx = zeros(size(X,1), 1);
for xi = 1:size(X,1)
x = X(xi, :);
% Find closest centroid for x.
best = Inf;
for mui = 1:K
mu = centroids(mui, :);
d = dot(x - mu, x - mu);
if d < best
best = d;
idx(xi) = mui;
end
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function centroids = computeCentroids(X, idx, K)
[m n] = size(X);
centroids = zeros(K, n);
for mui = 1:K
centroids(mui, :) = sum(X(idx == mui, :)) / sum(idx == mui);
end

由于程序在我的计算机中运行非常慢,我只运行了3次迭代。通常停止 准则是(i)迭代时间至少10次,或(ii)质心不再发生变化。来 我的测试,增加迭代可能会区分背景(天空和树,天空和 建筑,…)更准确,但没有显示出急剧变化的圣诞树 提取。还要注意,k-means也不受随机质心初始化的影响,因此建议多次运行程序进行比较。< / p >

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

一些__abc0表明mean-shift可能比k-means更健壮,而许多__abc0表明mean-shift可能比k-means更健壮 基于图切的算法在复杂的边界上也很有竞争力 分割。我自己写了一个均值漂移算法,它似乎能更好地提取区域 没有足够的光线。但是mean-shift有点过度分割了,还有一些策略 合并是必要的。在我的电脑里,它甚至比k-means要慢得多 放弃它。我热切期待看到其他人在这里提交优秀的成绩 使用上面提到的那些现代算法。< / p > 然而,我始终认为特征选择是图像分割的关键组成部分。与 一个适当的特征选择,可以最大化的边缘之间的对象和背景,许多 分割算法肯定会起作用。不同的算法可以改善结果 从1到10,但特征选择可以使它从0到1

圣诞快乐!

...另一个老式的解决方案-纯粹的基于HSV处理:

  1. 将图像转换为HSV色彩空间
  2. 根据HSV中的启发式创建掩码(见下文)
  3. 将形态扩张应用于掩模以连接断开的区域
  4. 丢弃小块区域和水平块(记住树是垂直块)
  5. 计算包围框

一个词关于启发式在HSV处理:

  1. 所有带有色调(H)在210 - 320度之间的东西都被丢弃为蓝洋红色,它应该在背景或不相关的区域
  2. 所有带有数值(V)低于40%的内容也会因为太暗而被丢弃

当然,人们可以尝试许多其他可能性来微调这种方法……

这里是MATLAB代码来做的技巧(警告:代码远远没有被优化!!我使用了不推荐用于MATLAB编程的技术,只是为了能够跟踪过程中的任何东西——这可以大大优化):

% clear everything
clear;
pack;
close all;
close all hidden;
drawnow;
clc;


% initialization
ims=dir('./*.jpg');
num=length(ims);


imgs={};
hsvs={};
masks={};
dilated_images={};
measurements={};
boxs={};


for i=1:num,
% load original image
imgs{end+1} = imread(ims(i).name);
flt_x_size = round(size(imgs{i},2)*0.005);
flt_y_size = round(size(imgs{i},1)*0.005);
flt = fspecial( 'average', max( flt_y_size, flt_x_size));
imgs{i} = imfilter( imgs{i}, flt, 'same');
% convert to HSV colorspace
hsvs{end+1} = rgb2hsv(imgs{i});
% apply a hard thresholding and binary operation to construct the mask
masks{end+1} = medfilt2( ~(hsvs{i}(:,:,1)>(210/360) & hsvs{i}(:,:,1)<(320/360))&hsvs{i}(:,:,3)>0.4);
% apply morphological dilation to connect distonnected components
strel_size = round(0.03*max(size(imgs{i})));        % structuring element for morphological dilation
dilated_images{end+1} = imdilate( masks{i}, strel('disk',strel_size));
% do some measurements to eliminate small objects
measurements{i} = regionprops( dilated_images{i},'Perimeter','Area','BoundingBox');
for m=1:length(measurements{i})
if (measurements{i}(m).Area < 0.02*numel( dilated_images{i})) || (measurements{i}(m).BoundingBox(3)>1.2*measurements{i}(m).BoundingBox(4))
dilated_images{i}( round(measurements{i}(m).BoundingBox(2):measurements{i}(m).BoundingBox(4)+measurements{i}(m).BoundingBox(2)),...
round(measurements{i}(m).BoundingBox(1):measurements{i}(m).BoundingBox(3)+measurements{i}(m).BoundingBox(1))) = 0;
end
end
dilated_images{i} = dilated_images{i}(1:size(imgs{i},1),1:size(imgs{i},2));
% compute the bounding box
[y,x] = find( dilated_images{i});
if isempty( y)
boxs{end+1}=[];
else
boxs{end+1} = [ min(x) min(y) max(x)-min(x)+1 max(y)-min(y)+1];
end


end


%%% additional code to display things
for i=1:num,
figure;
subplot(121);
colormap gray;
imshow( imgs{i});
if ~isempty(boxs{i})
hold on;
rr = rectangle( 'position', boxs{i});
set( rr, 'EdgeColor', 'r');
hold off;
end
subplot(122);
imshow( imgs{i}.*uint8(repmat(dilated_images{i},[1 1 3])));
end

结果:

在结果中,我显示了蒙面图像和包围框。 enter image description here

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

这里我以某种方式结合了我的另外两个建议,取得更好的结果。事实上,我看不出这些结果还能有什么更好的(特别是当你看到该方法产生的掩码图像时)。

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

  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

结果

results

高分辨率结果仍然< >强可用!< / >强
更多的实验图片可以在这里找到。 < / >强

我有一个方法,我认为是有趣的,有点不同于其他。与其他方法相比,我的方法的主要区别在于如何执行图像分割步骤——我使用了Python的scikit-learn中的DBSCAN聚类算法;它被优化用于寻找一些无定形的形状,这些形状可能不一定有一个清晰的形心。

在顶层,我的方法相当简单,可以分解为大约3个步骤。首先,我应用一个阈值(或者实际上是两个独立且不同的阈值的逻辑“或”)。与许多其他答案一样,我假设圣诞树将是场景中较亮的物体之一,所以第一个阈值只是一个简单的单色亮度测试;在0-255范围内,任何值高于220的像素(其中黑色为0,白色为255)都保存为二进制黑白图像。第二个阈值试图寻找红色和黄色的光,这在六幅图像的左上方和右下方的树木中特别突出,并在大多数照片中普遍存在的蓝绿色背景中脱颖而出。我将rgb图像转换为hsv空间,并要求色调在0.0-1.0范围内小于0.2(大致对应于黄色和绿色之间的边界)或大于0.95(对应于紫色和红色之间的边界),另外我要求明亮,饱和的颜色:饱和度和值都必须高于0.7。将两个阈值程序的结果逻辑地“或”-ed在一起,得到的黑白二值图像矩阵如下图所示:

圣诞树,在HSV和单色亮度阈值后

您可以清楚地看到,每张图像都有一个大的像素集群,大致对应于每棵树的位置,加上一些图像还有一些其他的小集群,对应于一些建筑物窗户上的灯光,或地平线上的背景场景。下一步是让计算机识别出这些是独立的集群,并用集群成员ID号正确地标记每个像素。

对于这个任务,我选择了DBSCAN。相对于其他聚类算法,DBSCAN通常的行为有一个非常好的可视化比较,可用在这里。正如我前面所说,它可以很好地处理非晶态形状。DBSCAN的输出,每个集群用不同的颜色绘制,如下所示:

DBSCAN clustering output .

在查看这个结果时,有几件事需要注意。首先,DBSCAN要求用户设置一个“接近度”参数以规范其行为,该参数有效地控制了点对之间的分离程度,以便算法声明一个新的单独的集群,而不是将一个测试点聚集到已经存在的集群上。我将这个值设置为每个图像对角线大小的0.04倍。由于图像的大小从VGA到HD 1080不等,因此这种相对比例的清晰度非常关键。

另一点值得注意的是,在scikit-learn中实现的DBSCAN算法有内存限制,这对于本示例中的一些较大的图像来说是相当具有挑战性的。因此,对于一些较大的图像,我实际上不得不“decimate”(即只保留每3或4个像素,并删除其他像素)每个集群,以保持在这个限制范围内。由于这种剔除过程的结果,在一些较大的图像上很难看到剩余的单个稀疏像素。因此,仅出于显示的目的,上面图像中彩色编码的像素已被有效地略微“膨胀”,以便更好地突出。这纯粹是为了叙事的美感;尽管在我的代码中有提到这种膨胀的注释,但请放心,它与任何实际重要的计算无关。

一旦识别并标记了集群,第三步也是最后一步就很容易了:我只需要在每个图像中选择最大的集群(在这种情况下,我选择根据成员像素的总数来衡量“大小”,尽管也可以使用某种类型的度量标准来衡量物理范围),然后计算该集群的凸包。凸包就变成了树的边界。通过该方法计算的6个凸包如下图中红色部分所示:

圣诞树与他们计算的边界

源代码是为Python 2.7.6编写的,它依赖于numpyscipymatplotlibscikit-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()

使用一种与我所看到的完全不同的方法,我创建了一个脚本,通过他们的灯来检测圣诞树。结果总是一个对称的三角形,如果需要的话,还会有数值,比如树的角度(“肥度”)。

这个算法最大的威胁显然是树旁边(大量)或树前面的灯(在进一步优化之前是更大的问题)。 编辑(补充):它不能做的:找出是否有一棵圣诞树,在一张图片中找到多棵圣诞树,正确地检测出拉斯维加斯中心的圣诞树,检测出严重弯曲、颠倒或被砍倒的圣诞树……;) < / p >

不同的阶段是:

  • 计算每个像素的亮度(R+G+B)
  • 将每个像素上的所有8个相邻像素的值相加
  • 根据这个值排列所有像素(最亮的第一)-我知道,不是很微妙…
  • 从这些中选择N个,从上面开始,跳过那些太接近的
  • 计算这些前N的(给我们树的近似中心)
  • 从中间位置开始向上扩大搜索光束,从选定的最亮的光中寻找最上面的光(人们倾向于将至少一个光放在最上面)
  • 从这里开始,想象线条向左和向右呈60度向下(圣诞树不应该那么粗)
  • 减小60度,直到20%最亮的光在这个三角形之外
  • 在三角形的最底部找到光,给你树的较低水平边界
  • 完成

标记说明:

  • 大红色十字在树的中心:最亮的N个灯的中间
  • 虚线从那里往上:“搜索光束”,寻找树的顶部
  • 小十字:树顶
  • 小红叉:所有最亮的灯光
  • 红三角:呃!

源代码:

<?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;
}




?>
< p >图片: Upper left Lower center Lower left Upper right Upper center 右下

额外奖励:来自维基百科的德国Weihnachtsbaum Römerberg http://commons.wikimedia.org/wiki/File:Weihnachtsbaum_R%C3%B6merberg.jpg < / p >

我的解决步骤:

  1. 获取R通道(从RGB) -我们在这个通道上所做的所有操作:

  2. 创建感兴趣的区域(ROI)

    • 最小值149的阈值R通道(右上图像)

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

    • 李< / ul > < / >
    • 检测计算roi中的eges。树有很多边(右中图)

      • 扩张的结果

      • 侵蚀半径更大(左下图)

      • 李< / ul > < / >
      • 选择最大的(按区域)对象-它是结果区域

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

      • 包围框(右下图-绿色框)

enter image description here

第一个结果-最简单,但不是开源软件-“自适应视觉工作室+自适应视觉库”: 这不是开源的,但是很快就能建立原型:

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

< p >下一步。我们需要开源解决方案。将AVL过滤器更改为OpenCV过滤器: 这里我做了一些小改动,例如边缘检测使用cvCanny过滤器,为了尊重roi,我将区域图像与边缘图像相乘,为了选择最大的元素,我使用findContours + contourArea,但想法是一样的

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

OpenCV solution

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

好,现在我们使用开源过滤器,但它仍然不是完全开源的。 最后一步-移植到c++代码。我在2.4.4版本中使用OpenCV

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

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

我在opencv中使用python。

我的算法是这样的:

  1. 首先,它从图像中取出红色通道
  2. 对红色通道应用阈值(最小值200)
  3. 然后应用形态梯度,然后做一个“关闭”(扩张,然后侵蚀)
  4. 然后它找到平面上的轮廓然后选择最长的轮廓。

The result: .

代码:

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

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