基于SIFT+Kmeans+LDA的图片分类器的实现

PS: 很久没做CV的事情了,这是很早以前刚入门时候的一篇,以后再有CV相关工作会发布在新的个人站点:http://my.phirobot.com/blog/category/cv.html CV分类下。


 

 posted @ 2012-04-24 20:36 from [FreedomShe]

题记:2012年4月1日回到家,南大计算机研究僧复试以后,等待着的就是独坐家中无聊的潇洒。不知哪日,无意中和未来的同学潘潘聊到了图像处理,聊到了她的论文《基于LDA的行人检测》,出于有一年半工作经验的IT男人的本能,就一起开始学习研究这篇“论文”了。众所周知,老师给学生设置论文题目的,起初都是很模糊的——自己没有思考清楚实践上的可行性和具体思路,仅从理论了解上就给学生设置一些“难以实现”的论文任务。几经修改和商讨,最后的论文实际上就是“基于SIFT+Kmeans+LDA的图片分类器的实现”了。至此,代码已经编写完毕,图片分类的效果还算满意。

——copyright:由于是一起学习研究的结果,相关所有内容潘潘童鞋可以以第一作者身份使用!

1. 实现思路
2. 软件环境
3. Step1——SIFT应用
4. Step2——Kmeans应用
5. Step3——数词频的实现
6. Step4——LDA应用
7. 参考


一、实现思路

  分类器的功能是:输入一组图片,给定需要分类的类别数lda_k(>1);输出lda_k个文件夹,每个文件夹内的图片为一类图片。

  第一步是SIFT特征提取:输入图片,输出图片的特征点集,即feature列表,每个feature代表一个图片的某个局部特征,每个feature的数据结构由一个128维浮点数组表示。至此,可以将一幅图片转换成一个feature集。

  第二步是Kmeans聚类:输入是所有图片的feature集的综合,给定参数km_k代表需要聚类的类别数;输出是km_k个feature,在LDA的视角看来就是“单词表”,用“单词表”中的一个“单词”(类中的质心feature)代表kmeans聚类里面一类的所有feature。

  第三步是统计词频:(对每个图片)输入是图片的feature集和“单词表”,分别计算该图片feature集中每个feature对应的“单词”,并统计每个“单词”在该feature集中出现的次数即词频;输出是词频统计数据。

  最后一步是LDA训练潜在主题:输入是所有图片文件的词频统计数据,以及给定的需要训练出来的主题类别数lda_k;LDA输出参数较多,其中最有用的就是文档-主题条件概率矩阵(theta矩阵),即举证中每个元素表示P(主题k|文档m)——在文档m中,主题是k的概率——通过该概率即可判断当前文档最可能的主题,实现了将所有文档分类为lda_k个主题。

  总之,理论上LDA研究的实体是一组文档,每个文档由若干单词组成,通过无监督学习,能够发现lda_k个主题,并且确定theta矩阵——文档确定的情况下生成主题k的概率,以及phi矩阵——主题确定的情况下生成单词v的概率。分类器通过SIFT算法将图片转换为若干feature,即将图片看成是“文档”feature看成是“单词”。而仅通过SIFT处理后的feature并不能直接单做“单词”作为LDA的输入,因为几乎每个feature都不一样,还需要Kmeans算法对所有图片的feature集的总和做一次聚类,得到km_k个类别的中心feature,即生成km_k个“单词”的“单词表”,并以此中心feature代替一个类别内的所有其他feature,从而将一个图片“文档”中的所有feature均在“单词表”中能够找到代表它的“单词”,这样图片就真正转换为了LDA能够处理的“文档”。

[Go Top]


二、软件环境

  VS2010,MFC,C++。
  安装并配置Opencv,参见VS2010+Opencv-2.4.0的配置攻略。
  下载并集成SIFT源码,参见在VS2010中应用SIFT(C)源码。
  下载并集成LDA源码,参见在VS2010中应用LDA(C)源码。

  Kmeans为Opencv自带函数,无需应用其他源码。建立好自己的工程,集成算法源码后,工程文件夹大致结构应为下图所示:

基于SIFT+Kmeans+LDA的图片分类器的实现_第1张图片

  设计好自己的例程界面,并关联好响应函数和成员变量,本例程界面如下:

基于SIFT+Kmeans+LDA的图片分类器的实现_第2张图片

[Go Top]


三、Step1——SIFT应用

  在该步骤内,程序依据“图片源目录”给出的图片目录路径,扫描目录内的所有图片文件,对每个执行如下操作:

...
n = _sift_features(img, &features, SIFT_INTVLS/*3*/, SIFT_SIGMA/*1.6*/, SIFT_CONTR_THR/*0.04*/,
    SIFT_CURV_THR/*10*/, SIFT_IMG_DBL/*1*/, SIFT_DESCR_WIDTH/*4*/, SIFT_DESCR_HIST_BINS/*8*/); //SIFTfeature提取
...
export_features(out_file_name, features, n); //将features导出为文件
...
if(勾选了“保存SIFT特征图”)
{
  draw_features(img, features, n); //在img图片上标记出features
  cvSaveImage(out_img_name, img, NULL); //将标记后的图片保存
}
...

  其中最主要的三个函数就是_sift_features(…), export_features(…), draw_features(…)均为sift源码所提供。(注:feature有两种类型——OXFD和LOWE,本程序只涉及LOWE类型,所有OXFD相关格式均自动忽略。)

  _sift_features(…)函数第一个参数img为传入图片的IplImage指针格式,为Opencv所定义的图片数据结构;features后面的参数均为SIFT算法的输入参数,具体含义见作者的源码注释。

需要注意和理解的是features这个参数,其指向的为一个结构体feature的数组,feature结构为:

/**
Structure to represent an affine invariant image feature.  The fields
x, y, a, b, c represent the affine region around the feature:
a(x-u)(x-u) + 2b(x-u)(y-v) + c(y-v)(y-v) = 1
*/
struct feature
{
    double x;                      /**< x coord */
    double y;                      /**< y coord */
    double a;                      /**< Oxford-type affine region parameter */
    double b;                      /**< Oxford-type affine region parameter */
    double c;                      /**< Oxford-type affine region parameter */
    double scl;                    /**< scale of a Lowe-style feature */
    double ori;                    /**< orientation of a Lowe-style feature */
    int d;                         /**< descriptor length */
    double descr[FEATURE_MAX_D];   /**< descriptor */
    int type;                      /**< feature type, OXFD or LOWE */
    int category;                  /**< all-purpose feature category */
    struct feature* fwd_match;     /**< matching feature from forward image */
    struct feature* bck_match;     /**< matching feature from backmward image */
    struct feature* mdl_match;     /**< matching feature from model */
    CvPoint2D64f img_pt;           /**< location in image */
    CvPoint2D64f mdl_pt;           /**< location in model */
    void* feature_data;            /**< user-definable data */
};

  其中x,y表示feature在图片中的坐标,scl, ori表示在图中标记特征的强度和方向,descr是最重要的特征信息即128维的特征向量(FEATURE_MAX_D==128)。实际上所保存的特征文件里面也只保存了feature的这些信息。features就是feature的一个数组,包含了SIFT算法所提取的图片的所有feature,返回值n表示features数组中有多少个feature元素。

  export_features(…)就是将上一步中的features保存为文件,文件格式如下:

441 128
178.616459 111.621902 34.241822 1.292619
 0 0 0 0 0 0 0 0 12 0 0 0 1 3 0 8 140 4 0 0
 0 0 0 30 43 3 0 0 0 0 0 2 0 0 0 0 2 9 0 0
 94 0 0 0 8 72 6 37 172 15 0 0 0 2 1 119 122 5 0 0
 0 0 0 12 0 0 0 0 0 2 0 0 57 5 0 0 2 73 9 14
 172 133 0 0 0 11 3 14 128 45 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 4 1 2 2 4 13 0 1 172 152 2 4 4 6 0 2
 145 116 0 0 0 0 0 0
220.732993 432.886679 20.895674 -1.740551
 6 0 0 1 168 133 17 5 60 0 0 1 37 20 24 25 3 0 0 0
 14 26 17 3 0 0 0 0 0 0 0 0 7 0 0 0 168 144 2 1
 123 0 0 0 80 38 2 15 13 0 0 0 12 44 4 4 0 0 0 0
 0 0 0 0 2 0 0 1 168 67 0 0 121 13 0 2 144 16 0 3
 22 2 0 4 30 7 0 0 0 0 0 0 1 0 0 0 0 0 0 0
 168 81 0 0 87 9 0 1 125 38 2 5 24 2 0 3 20 16 2 2
 0 0 0 0 1 3 0 0
……

  其中第一行的第一个int表示文件中feature的个数,第二个int表示feature的维数。接下来每段132个浮点数(后128个近似为整数)为一个feautre,前4个分别为x, y, scr, ori,后128个为表征feature的128维向量。

  draw_features(…)就是将feature信息示例性地标注在图上了,图片如下:

基于SIFT+Kmeans+LDA的图片分类器的实现_第3张图片

  cvSaveImage(…)是Opencv的函数,将加过标注的图片保存为文件。

  这一步实际上要做的重点就是输入为图片,输出为SIFT算法后为每张图片生成的feature文件集:

基于SIFT+Kmeans+LDA的图片分类器的实现_第4张图片

  这样每张图片就相当于一个“文档”,而feature就是“文档”中的“预备单词”。

[Go Top]


四、Step2——Kmeans应用

  Step1里面的feature只是“预备单词”,在成为单词之前还要通过Step2生成“单词表”和Step3将“文档”中的“预备单词”找到“单词表”中最相近的“单词”替换之(并不是真正操作上的替换,只是当成“单词表”中的“单词”统计出来而已)。

  在Step2中,关键操作如下:

...
CvMat *samples=cvCreateMat(featureNum, dims, CV_32FC1); //包含所有图片的所有feature信息的矩阵,featureNum个feature,每个feature为dims(128)维向量,每一维的元素类型为32位浮点数
CvMat *clusters=cvCreateMat(featureNum, 1, CV_32SC1); //每个feature所在“质心”的指针(实际上本例程中没有用到该信息)
CvMat *centers=cvCreateMat(k, dims, CV_32FC1); //“质心”信息的数组,k个“质心”每个质心都是dims(128)维向量,每一维的元素类型为32位浮点数
cvSetZero(clusters); //将矩阵初始化为0
cvSetZero(centers); //将矩阵初始化为0
while(file.ReadString(strLine)) 
{
    ...
    n = import_features(CIni::CStrToChar(fileName), FEATURE_LOWE, &features); //导入feature文件,n为导入的feature个数
    ...
    //将feature文件内所有feature信息存入samples矩阵结构内
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < dims; j++)
        {
            samples->data.fl[temp++] = features[i].descr[j];
        }
    }
}
cvKMeans2(samples, k, clusters,cvTermCriteria(CV_TERMCRIT_EPS,10,1.0), 3, (CvRNG *)0, KMEANS_USE_INITIAL_LABELS, centers); //Kmeans聚类
...
cvSave(CIni::CStrToChar(ini.getWordListFilePath()), centers); //保存单词表
...

  其中关键函数当然是import_features(...)和cvKMeans2(...),前者是sift源码里的方法,用来导入feature文件使之成为内存数据结构,后者是Opencv里的kmeans算法之一(cvKMeans2(...)内部调用了kmeans(...))。

  在说明他们之前首先要了解Opencv内部通用数据结构——用来存储矩阵类型的CvMat,其声明如下:

typedef struct CvMat
{
    int type;
    int step;

    /* for internal use only */
    int* refcount;
    int hdr_refcount;

    union
    {
        uchar* ptr;
        short* s;
        int* i;
        float* fl;
        double* db;
    } data;

#ifdef __cplusplus
    union
    {
        int rows;
        int height;
    };

    union
    {
        int cols;
        int width;
    };
#else
    int rows;
    int cols;
#endif
}

  其中最重要的就是用来存储元素信息的一维数组data,data为一个联合体,包含了各种通用类型。data虽然是一维结构,但通过rows, cols标识,可以将data看成二维的矩阵结构,type就标识了元素类型,而step就标识了一行的步长(占用字节数)。
  cvCreateMat(int rows, int cols, int type);包含了三个参数,rows和cols分别表示data二维矩阵的行数和列数,type表示元素的类型。元素类型通过宏来定义,CV_32FC1中的32表示元素的位数,F表示是浮点数,Cn表示通道数,C1表示1通道,C3表示3通道(只有在类似RGB的三元表示一个像素点颜色时才用到多通道,而通道实际上就是说一个元素应该由几列来表示)。
  有了上面的知识,就知道了samples矩阵是featureNum行,128列的矩阵,而矩阵元素类型为32位浮点数。featureNum的值为所有图片所有feature的总和,也就是说把所有的feature文件里面的feature信息都读入到samples矩阵内用来作为Kmeans的输入。clusters矩阵是featureNum行1列的矩阵,实际上就是featureNum个int元素的数组,每个int元素都相当于一个下标,标识对应feature(一行)属于centers中的哪个类(“质心”),而本程序里面没有使用到clusters提供的信息,featuresNum个feature分别对应于哪个“质心”是在Step3算词频里面实现的。centers就是保存“单词表”信息的矩阵了,k行128列的32位浮点矩阵,即k个feature,每个feature都是一类feature的“质心”——该类所有feature中最中间那个(通过欧式距离来计算的)feature。

  关键操作来了,import_features(...)就是将feature文件读入内存,使之成为feature的内存数据结构。
  int import_features(char* filename, int type, struct feature** feat)第一个参数就是feature文件的路径,第二个参数是feature的类型,本文是FEATURE_LOWE类型,第三个参数就是传出参数feature数组了,返回值为读入内存的feature个数。在接下来的操作中就需要把features里面的数据全部拷贝到samples矩阵结构内,这样所有文件的feature信息都拷贝到samples内后就完成了kmeans传入数据的工作。

  cvKMeans2(...)是Opencv在kmeans(...)之上封装的一个函数,参数含义可以参照Hongquan的博客中的《OpenCV中kmean算法的实现》,本文需要说明的几个参数是:第一个参数samples——传入所有图片feature信息的总和;第二个参数k即kmeans的聚类个数(本程序通过界面的类别数设置);第七个参数flags表示生成随机数的方式,可能每次运行程序对相同的输入输出的单词表都不同,那么就跟这个参数相关;第八个参数centers,这就是我们需要的输出了,即“单词表”信息,每一行就是一个feature也就是一个“单词”。

  这样,读入Step1中输出的所有feature文件,然后综合起来并输入参数k(聚类类别数),然后就可以生成一张包含k个“单词”的“单词表”了,用Opencv的cvSave(...)函数把这张“单词表”保存为yml文件,就是本步骤的主要输出了。输出“单词表”文件wordList.yml格式类似:

%YAML:1.0
wordList: !!opencv-matrix
   rows: 80
   cols: 128
   dt: f
   data: [ 1.64593754e+001, 2.11062511e+001, 30.,
       3.48312492e+001, 2.39375000e+001, 1.28156252e+001,
       1.25531254e+001, 1.16812506e+001, 4.92906265e+001,
       3.61625023e+001, 2.94968758e+001, 23., 1.70375004e+001,
       1.12718754e+001, 1.71968746e+001, 3.13437500e+001,
       3.27593765e+001, 2.25156250e+001, 2.02625008e+001,
       2.26406250e+001, 2.73750000e+001, 1.90531254e+001,
       2.41375008e+001, 2.85156250e+001, 1.92875004e+001,
       ............................................. ]

[Go Top]


五、Step3——数词频的实现

  前面已经说过,Step2是生成“单词表”,而Step3就是通过计算欧式距离来确定原始feature文档中每个feature对应“单词表”中的哪个“单词”,然后统计出来,生成对应的统计文件——词频文件wordF.data。主要操作如下:

...
FileStorage readfs(CIni::CStrToChar(ini.getWordListFilePath()), FileStorage::READ); //以只读的形式打开yml。
Mat wordList; //单词表矩阵
readfs[CIni::CStrToChar(CIni::removeSuffix(CIni::getFileNameFromPath(ini.getWordListFilePath())))] >> wordList; //读取单词表
...
while(“对于每个feature文档”) 
{
  ...
  int n = import_features(CIni::CStrToChar(fileName), FEATURE_LOWE, &features);
  ...
  for(int i = 0; i < n; i++)
  {
    ...
    distMin = normL2Sqr_(pa, pb, dims); //计算欧式距离
    ...
    for(int j = 1; j < wordNum; j++)
    {
      ...
      dist = normL2Sqr_(pa, pb, dims); //计算欧式距离
      ...
    }
  }
}
...

  显而易见,前三个操作就是利用Opencv的函数,将Step2中生成的“单词表”读入矩阵结构(Mat)的变量wordList中。而对于每个feature文档,通过sift源码的import_features(...)函数读入以feature结构体为元素的数组features中。接下来对于每个feature计算它与wordList中哪个“单词”最接近,最后统计出该文档中包含的单词及其个数。

  normL2Sqr_(pa, pb, dims)是Opencv中计算欧式距离的函数,其中pa指向一个feature,而pb指向单词表中的一个“单词”(“单词”也是feature),dims表示待计算数据的维数,在这里就是128了。

  经过上面的操作,统计好每个图片文档中每个“单词”出现的次数后,统一保存为一个词频文档,即将所有图片文档的词频信息保存在一个文件wordF.data内,其文件结构为:

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

  其中每一行代表一个文档(图片文档),%d:%d的结构表示单词ID:单词个数,第一个数字表示后面的元素项个数。这些数据就是Step3统计词频的输出了,也是Step4LDA运算的输入。

[Go Top]


六、Step4——LDA应用

  作者对LDA的实现并不是像sift和kmeans那样由一个函数通过参数传入传出来给出,而是作者的main函数的一个实现过程。本例程LDA的主要过程为:

...
int topic_num = lda_k; //LDA分类数
struct corpus *cps;
struct est_param param;
...
cps = read_corpus(data); //读取训练集
init_param(cps,¶m,topic_num); //初始化参数
//迭代计算
while (1)
{
  //对每个文档使用sampling方法计算
  for (int m=0; mnum_docs; m++)
  {
    ...
    for (int l=0; ldocs[m].length; l++)
    {
      for (int c=0; cdocs[m].words[l].count; c++)
      {
        param.z[m][word_index] = sampling(m,word_index,cps->docs[m].words[l].id,topic_num,cps,¶m,alpha,beta,p,s_talpha,vbeta); //sampling计算
        ...
      }
    }
  }
  if ((iter_time >= burn_in_num) && (iter_time % SAMPLE_LAG == 0))
  {
    calcu_param(¶m, cps,topic_num,alpha,beta); //计算参数
    ...
  }
  //迭代结束条件
  if (sample_time ==sample_num)
  {
    break;
  }
}
average_param(¶m, cps,topic_num,alpha,beta,sample_num); //计算theta,phi平均值
save_model(cps,¶m,model_name,alpha,beta,topic_num,sample_num); //保存结果数据
...

  对于没有接触过LDA的人要看懂函数过程还是很困难的,但对于有语言功底的程序员,如果只是应用LDA过程,只需要了解几个主要的LDA概念就行了。(其他部分请参考作者代码注释)

1、LDA的输入。LDA的输入除了Step3生成的词频统计信息外,还需要一些参数。这些参数一般设置为通用参数就行了,本例程只有lda_k为界面输入,表示LDA训练的主题个数。
  read_corpus(data)方法中,data为字符串,表示输入数据的路径,该方法将文件读取为struct corpus的格式。corpus结构体表示的是一个文档集,其结构可以从声明中看出:

struct word
{
    int id;
    int count;
};

struct document
{
    int id; //文档id
    int num_term; //文档包含的单词个数(count的总和)
    int length; //文档包含的单词类别个数(id:count结构的个数)
    struct word* words;
};

struct corpus
{
    struct document* docs;
    int num_docs; //文档个数
    int num_terms; //单词表中单词总数(实际上是所有单词中最大id+1)
};

  对比Step3中生成的词频文件,不难看懂上面这些结构体的意义。

2、LDA的输出。LDA的输出包含很多数据,由save_model(...)函数输出为文件:


  lda.other文件保存参数alpha, beta, topic_num, sample_num的值。lda.topic_assgin文件保存z矩阵,z[m][n]==k表示文档m中的单词n所对应的主题为k。lda.theta文件保存theta矩阵,theta[m][k]表示在文档为m时,生成主题k的概率,即条件概率p(主题k|文档m)。lda.phi文件保存phi矩阵,phi[k][v]表示在主题为k时,生成单词v的概率,即条件概率p(单词v|主题k)。

  其中需要关注的只有theta和phi两个矩阵,而本例程只用到了theta矩阵的信息。theta矩阵和phi矩阵的例子如下表:

P(Topic_k|Doc_m)

Topic1

Topic2

Doc1

0.45

0.55

Doc2

0.1

0.9

Doc3

0.6

0.4

                

P(Word_v|Topic_k)

Word1

Word2

Word3

Word4

Topic1

0.45

0.15

0.2

0.2

Topic2

0.1

0.5

0.3

0.1

theta矩阵   phi矩阵

  其中的Topic都是由LDA通过无监督学习得到的潜在主题,只需要用户告诉LDA主题数目就行了。从上面的表格中不难看出,在theta矩阵中,我们知道了每个文档生成每个主题的概率,通过比较概率大小就可以确定文档所对应的主题了。

3、LDA算法及其运算所需结构体struct est_param。

struct est_param
{
    int **z; //z[m][n] stands for topic assigned to nth word in mth document
    double **theta; // theta[m][k] stands for the topic mixture proportion for document m
    double **phi; // phi[k][v] stands for the probability of vth word in vocabulary is assigned to topic k 
    // count statistics
    int **nd; //nd[m][k] stands for the number of words assigned to kth topic in mth document
    int **nw; //nw[k][t] stands for the number of kth topic assigned to tth term
    int *nd_sum; //nd_sum[m] total number of word in mth document
    int *nw_sum; //nw_sum[k] total number of terms assigned to kth topic
};

  该结构体内theta和phi二维数组是我们熟悉的输出,z数组也是前面提到的输出之一,后面的四个变量是LDA运算中间过程的必备临时变量。LDA算法,如果了解了相关的变量名和LDA过程,通过下面这幅图是不难了解算法过程的:

基于SIFT+Kmeans+LDA的图片分类器的实现_第5张图片

  综上,知道了每个图片文档对应哪个主题的概率最大后,就可以根据主题个数新建lda_k个文件夹,然后把分类到对应主题的图片拷贝过去,从而实现了对图片的LDA分类。分类效果如图:

基于SIFT+Kmeans+LDA的图片分类器的实现_第6张图片

基于SIFT+Kmeans+LDA的图片分类器的实现_第7张图片

[Go Top]


七、参考

【Opencv】
Opencv下载:http://sourceforge.net/projects/opencvlibrary/files/
Opencv教程:http://www.opencv.org.cn/index.php

【SIFT】
SIFT源码下载:http://blogs.oregonstate.edu/hess/code/sift/ 
SIFT源码使用方法:http://www.open-open.com/lib/view/1325332699514

 【Kmeans】Kmeans为Opencv自带函数。
OpenCV中kmean算法的实现:http://blog.hongquan.me/?p=8

【LDA】
LDA源码下载:http://code.google.com/p/lsa-lda/ 
gibbs的LDA实现:http://sourceforge.net/projects/gibbslda/

 【Bag-of-words模型】
SIFT算法的应用--目标识别之Bag-of-words模型:http://blog.csdn.net/v_JULY_v/article/details/6555899

[Go Top]


源码下载:http://www.pudn.com/downloads464/sourcecode/graph/texture_mapping/detail1949366.html

转载于:https://www.cnblogs.com/freedomshe/archive/2012/04/24/sift_kmeans_lda_img_classification.html

你可能感兴趣的:(基于SIFT+Kmeans+LDA的图片分类器的实现)