Leafsnap实现(2)-服务端实现

在论文分析中已经讲了几个主要的步骤,接下来我们就具体地来实现:

1.RGB2SV

首先我们需要把RGB色彩空间下的图案转化为在HSV色彩空间下,并且忽略H空间,只用SV空间。也就是说把H空间的所有值都设为0.关键代码如下:

1
2
3
4
5
6

cvCvtColor(image,change,CV_RGB2HSV);  //将RGB色系转换为HSV色系

cvSplit(change,H,S,V,0);//分离多通道

cvMerge(Zero,S,V,0,two);

2.使用EM算法来对图像进行二值化

首先我们通过OpenCV的官方文档来了解EM算法,主要看具体实现。

文章说了训练函数是有三种类型的,根据文章说明,我们选择第二种方式 trainE ,也就是先从E-Step开始,需要提供均值、权重和协方差矩阵。

下面来看看第二种类型的函数声明:

C++: bool EM::trainE(InputArray samples, InputArray means0, InputArray covs0=noArray(), InputArray weights0=noArray(), OutputArray logLikelihoods=noArray(), OutputArray labels=noArray(), OutputArray probs=noArray())

参数介绍:

samples :单信道矩阵,每行是一个sample。必须是CV_64F类型

means0:初始均值

covs0: 初始协方差

weights0: 初始权重

logLikelihoods:可选的输出选项,包含了一个输出矩阵,里面是每个sample相似度算法的值

labels:对于每一个sample,都有一个分类标记

probs:后验概率矩阵

在做这一步的时候碰到了一个问题:如何提取多通道图像中某个通道的值?

这篇文章告诉了我解决方案。还有一个需要注意的地方就是如果是三维的类型要使用Vec3b

在写代码的时候,多次碰到一个问题:IplImage*类型到Mat类型的转化。

这里还有一些历史渊源。一开始的OpenCV版本操作图像使用的是IplImage*的类型,后来有了Mat之后,OpenCV推荐使用Mat类型,因为不用手动申请和释放内存,就很方便了。

同时碰到有些openCV函数有前缀cv,有些没有前缀cv,举个例子:

cvDrawContours,第一个参数就要接受IplImage*类型

而 drawContours,第一个参数需要接受Mat类型。

前面一个应该是老版本,而后面一个是新版本。由于自己写代码的时候没有特别注意,使用的都是老版本的函数,都不接受Mat类型,每次用都把Mat转到IplImage。有一种本末倒置的感觉。下次写代码需要注意这个问题。

3.去除false positive region

这里的方法就是提取轮廓,首先提取当前图像的所有外轮廓,然后选取面积最大的那个外轮廓,忽略其他所有轮廓。关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

cvFindContours(dsw, storage, &first_contour, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

//外轮廓也可能有多个,所以我们需要选取出最主要的,也就是最大的外轮廓
//这一步就是cut false positive,虽然没有在图上体现出来,但是我们只要获得了最大轮廓就相当于做了这一步
//因为后面的步骤只用到了轮廓这个特征点集
CvSeq *max_contour;//该变量保存了最大的那个轮廓
double max_area = 0;
double area = 0;
int cnt = 0;

for(; first_contour != 0; first_contour = first_contour->h_next)
{
area = cvContourArea(first_contour);
if(area > max_area)
{
max_area = area;
max_contour = first_contour;
}
cnt++;
}
max_contour->h_next = 0;//为了防止把后面的轮廓也画上去

这里注意轮廓点的类型转换,由于我使用的还是老版本,找了半天才找到这个方法

4.顶帽变换去茎

顶帽算法不难,但是本身这个方法不是特别有效能够去茎,而且顶帽算法有个kernel参数,需要设置区域大小。这里的参数也需要调试。首先来看一下这篇文章介绍,明白顶帽变换的作用,图像做差之后就是可能是茎的一些区域了。关键代码如下:

1
2
3
4
5
6
7
8
9

#define morph_size  10

Mat origin(img_origin);
//设定内核
Mat element = getStructuringElement(
MORPH_CROSS, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );

/// 运行指定形态学操作
morphologyEx(origin, img_tophat, MORPH_TOPHAT, element);

之后还要选取可能是茎的区域从图像中除去。这一步目前还没有很好的方法。

 

5.计算HoCS特征

两种方法,面积法和周长法。这一步就是有点烦,就是两个for循环从左到右,从上到下每个点判断。但是要注意边界条件。目前只实现了面积法,周长法有空也实现一下。这一步看具体代码。

 

6.树叶比较

计算出HoCS特征之后,把数据库中的叶子图片特征都提取出来并保存起来。每次新来一张图片,先提取这张图片的HoCS特征,然后跟所有数据库中的特征比较一遍,找出最像的前N个特征,这些特征所属的叶子种类就是最后的返回结果。

 

最后给出原文献和我目前的进度源码:

原文献:

http://pan.baidu.com/s/1nYnMI

源码(包括前面的分类):

https://github.com/miibotree/Leafsnap