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能夠處理的“文檔”。
二、軟件環境
VS2010,MFC,C++。
安裝並配置Opencv,參見VS2010+Opencv-2.4.0的配置攻略。
下載並集成SIFT源碼,參見在VS2010中應用SIFT(C)源碼。
下載並集成LDA源碼,參見在VS2010中應用LDA(C)源碼。
Kmeans為Opencv自帶函數,無需應用其他源碼。建立好自己的工程,集成算法源碼后,工程文件夾大致結構應為下圖所示:
設計好自己的例程界面,並關聯好響應函數和成員變量,本例程界面如下:
三、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信息示例性地標注在圖上了,圖片如下:
cvSaveImage(…)是Opencv的函數,將加過標注的圖片保存為文件。
這一步實際上要做的重點就是輸入為圖片,輸出為SIFT算法后為每張圖片生成的feature文件集:
這樣每張圖片就相當於一個“文檔”,而feature就是“文檔”中的“預備單詞”。
四、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,
............................................. ]
五、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運算的輸入。
六、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; m<cps->num_docs; m++) { ... for (int l=0; l<cps->docs[m].length; l++) { for (int c=0; c<cps->docs[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矩陣的例子如下表:
|
|
||||||||||||||||||||||||||||
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過程,通過下面這幅圖是不難了解算法過程的:
綜上,知道了每個圖片文檔對應哪個主題的概率最大后,就可以根據主題個數新建lda_k個文件夾,然后把分類到對應主題的圖片拷貝過去,從而實現了對圖片的LDA分類。分類效果如圖:
七、參考
【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
源碼下載:http://www.pudn.com/downloads464/sourcecode/graph/texture_mapping/detail1949366.html