非刚性人脸跟踪 —— 实用工具
面向?qū)ο笤O計
與人臉檢測和人臉識別一樣,人臉跟蹤也由兩部分組成:數(shù)據(jù)和算法。算法通過預先儲存(即離線)的數(shù)據(jù)來訓練模型,然后對新來的(即在線)數(shù)據(jù)執(zhí)行某類操作。因此,采用面向?qū)ο笤O計是不錯的選擇。
在 opencv 2.x?版本中,可方便引入 XML/YAML?文件存儲類型,對算法來講,會大大簡化組織離線數(shù)據(jù)任務。下面通過一個假象類來展示這個功能
- 自定義類 foo 1 // foo.h 2 /* 3 在下面的代碼中,定義了一個序列化函數(shù),可對 I/O 函數(shù) read 和 write 實現(xiàn)序列化。 4 FileStorage 類支持兩種能被序列化的數(shù)據(jù)結(jié)構(gòu)類型。 5 為了簡單起見,本章所有類將采用映射,其中每個用于存儲的變量都會創(chuàng)建一個 FileNode::MAP 類型的 FileNode 對象。 6 這需要分配給變量中的每個元素唯一鍵。為了保持一致性,將變量名作為標簽 7 */ 8 9 #include <opencv2/opencv.hpp> 10 #include <iostream> 11 using namespace cv; 12 using namespace std; 13 14 class foo { 15 public: 16 int a, b; 17 void write(FileStorage &fs) const { // 序列化存儲自定義數(shù)據(jù)類型 18 assert(fs.isOpened()); 19 fs << "{" << "a" << a << "b" << b << "}"; // 創(chuàng)建 FileNode::MAP 類型的對象 20 } 21 void read(const FileNode& node) { // 讀取數(shù)據(jù) 22 assert(node.type() == FileNode::MAP); 23 node["a"] >> a; node["b"] >> b; 24 } 25 };
- 為了使 FileStorage 類的序列化能正常工作,還需要定義write, read函數(shù) 1 template<class T>
2 void
3 write(FileStorage& fs,
4 const string&,
5 const T& x)
6 {
7 x.write(fs);
8 }
9 //==============================================================================
10 template<class T>
11 void
12 read(const FileNode& node,
13 T& x,
14 const T& d)
15 {
16 if(node.empty())x = d; else x.read(node);
17 }
?
?
- 為了讓保存和加載采用了序列化的用戶自定義類變得容易,采用模塊化函數(shù)定義了load_ft,save_ft函數(shù) 1 template <class T> 2 T load_ft(const char* fname){ 3 T x; FileStorage f(fname,FileStorage::READ); 4 f["ft object"] >> x; f.release(); return x; // 定義與對象關聯(lián)的標簽都為 ft object 5 } 6 //============================================================================== 7 template<class T> 8 void save_ft(const char* fname,const T& x){ 9 FileStorage f(fname,FileStorage::WRITE); 10 f << "ft object" << x; f.release(); 11 }
- 將以上定義在 ft.hpp 中 1 /* 2 ft.hpp 3 用于加載、保存對象數(shù)據(jù) 4 */ 5 6 #ifndef _FT_FT_HPP_ 7 #define _FT_FT_HPP_ 8 #include <opencv2/opencv.hpp> 9 //============================================================================== 10 // 為了讓保存和加載采用了序列化的用戶自定義類變得容易,采用模塊化函數(shù)定義了load_ft,save_ft函數(shù) 11 template <class T> 12 T load_ft(const char* fname){ 13 T x; FileStorage f(fname,FileStorage::READ); 14 f["ft object"] >> x; f.release(); return x; // 定義與對象關聯(lián)的標簽都為 ft object 15 } 16 //============================================================================== 17 template<class T> 18 void save_ft(const char* fname,const T& x){ 19 FileStorage f(fname,FileStorage::WRITE); 20 f << "ft object" << x; f.release(); 21 } 22 //============================================================================== 23 // 為了使 FileStorage 類的序列化能正常工作,還需要定義write, read函數(shù) 24 template<class T> 25 void 26 write(FileStorage& fs, 27 const string&, 28 const T& x) 29 { 30 x.write(fs); 31 } 32 //============================================================================== 33 template<class T> 34 void 35 read(const FileNode& node, 36 T& x, 37 const T& d) 38 { 39 if(node.empty())x = d; else x.read(node); 40 } 41 //============================================================================== 42 #endif ft.hpp
- 主函數(shù),有一個問題,儲存到 xml 文件總是報錯,而?yaml?文件可以正常存取 1 /* 2 main.cpp 3 測試 opencv 文件儲存 4 */ 5 6 #include "opencv_hotshots/ft/ft.hpp" 7 #include "foo.h" 8 9 int main() { 10 foo A; // 初始化自定義對象 A 11 A.a = 1; A.b = 2; 12 save_ft<foo>("foo.yaml", A); // 將自定義對象存到 foo.yaml 13 foo B = load_ft<foo>("foo.yaml"); // 讀取對象 14 cout << B.a << "," << B.b << endl; 15 16 system("pause"); 17 return 0; 18 }
- 程序運行結(jié)果
? ? ? ? ??? ? ? ? ? ? ? ? ??
?
?
?
數(shù)據(jù)收集:圖像和視頻標注
現(xiàn)代人臉跟蹤技術幾乎完全是數(shù)據(jù)驅(qū)動,即用來檢測圖像中面部特征位置的算法依靠面部特征的外觀模型和幾何依賴性,該依賴性來自樣本集中人臉間的相對位置。樣本集越大,算法就更具有魯棒性,因為人臉所表現(xiàn)出的變化范圍就更清楚。因此,構(gòu)建人臉跟蹤算法的第一步是創(chuàng)建用于進行圖像/視頻的標注工具,用戶可用此工具來指定在每個樣本圖中想要的面部特征位置。
訓練數(shù)據(jù)類型
訓練人臉跟蹤算法的數(shù)據(jù)一般由以下四部分構(gòu)成:
-
- 圖像:這部分是包含整個人臉圖像(圖像或視頻幀)的集合
- 標注:這部分采用手工方法標注每幅圖像中被跟蹤的面部特征的相對位置
- 對稱性索引:這部分對定義了雙邊對稱特征的面部特征點都保留了一個編號,以便用來鏡像訓練圖像,可有效地讓訓練集大小增加一倍
- 連通性索引:這部分是一組標注的索引對,它們定義了面部特征的語義解釋。連通性對可視化跟蹤結(jié)果很有用
這四個組件的可視化情形顯示在下圖中,從左到右依次是原始圖像、臉部特征標注、顏色編碼的雙邊對稱點、鏡像圖像與相應標注、面部特征的連通性。
? ? ??
?
為了方便管理這種數(shù)據(jù),需實現(xiàn)具有讀寫功能的類。本章將使用在 ft_data.hpp?頭文件中定義的 ft_data?類,它是按人臉跟蹤數(shù)據(jù)的特性專門設計的。所有元素都定義成類的公有成員變量,如下所示
1 class ft_data{ //人臉跟蹤數(shù)據(jù) 2 public: 3 vector<int> symmetry; // 人臉特征點的索引,維數(shù)與用戶定義的特征點數(shù)一樣 4 vector<Vec2i> connections; // 定義一對連通的面部特征 5 vector<string> imnames; // 存儲每個圖像文件名 6 vector<vector<Point2f> > points; // 存儲特征點的位置 7 ... 8 }?
?
ft_data?類實現(xiàn)了許多訪問數(shù)據(jù)的有用方法。為了訪問數(shù)據(jù)集的圖像,可用 get_image?函數(shù)加載圖像。使用該函數(shù)需指定加載圖像的索引 idx ,以及是否將圖像以 y?軸做鏡像。該函數(shù)實現(xiàn)如下:
1 Mat 2 ft_data:: 3 get_image(const int idx, // 圖像索引 4 const int flag) // 0=gray,1=gray+flip,2=rgb,3=rgb+flip 5 { 6 if((idx < 0) || (idx >= (int)imnames.size()))return Mat(); 7 Mat img,im; 8 if(flag < 2)img = imread(imnames[idx],0); // gray 9 else img = imread(imnames[idx],1); // rgb 10 if(flag % 2 != 0)flip(img,im,1); // 以 y 軸做鏡像 11 else im = img; 12 return im; 13 }?
?
為了通過指定的索引來得到相應圖像的一個點集,可使用 get_points?函數(shù)通過鏡像索引來得到一個基于浮點的坐標向量
1 vector<Point2f> 2 ft_data:: 3 get_points(const int idx, // 相應圖像的索引 4 const bool flipped) // 是否以 y 軸做鏡像 5 { 6 if((idx < 0) || (idx >= (int)imnames.size()))return vector<Point2f>(); 7 vector<Point2f> p = points[idx]; 8 if(flipped){ // 以 y 軸做鏡像 9 Mat im = this->get_image(idx,0); // im 用來獲取圖像的寬度 10 int n = p.size(); vector<Point2f> q(n); 11 for(int i = 0; i < n; i++){ // 沿豎直方向翻轉(zhuǎn) 12 q[i].x = im.cols-1-p[symmetry[i]].x; 13 q[i].y = p[symmetry[i]].y; 14 }return q; 15 }else return p; 16 }?
?
ft_data?類還實現(xiàn)了一個函數(shù) rm_incomplete_samples,該函數(shù)刪除集合中沒有進行相應標注的樣本,具體實現(xiàn)如下:
1 void 2 ft_data:: 3 rm_incomplete_samples() // 刪除集合中沒有進行相應標注的樣本 4 { 5 int n = points[0].size(),N = points.size(); 6 // 找出標注數(shù)最多的樣本,作為標準樣本 7 for(int i = 1; i < N; i++)n = max(n,int(points[i].size())); 8 for(int i = 0; i < int(points.size()); i++){ 9 if(int(points[i].size()) != n){ // 樣本標注點的數(shù)量小于標準樣本標注點數(shù),從樣本中刪除 10 points.erase(points.begin()+i); imnames.erase(imnames.begin()+i); i--; 11 }else{ 12 int j = 0; 13 for(; j < n; j++){ 14 // 若點的(x,y)存在小于0,則可認為它在相應的圖像中不存在 15 if((points[i][j].x <= 0) || (points[i][j].y <= 0))break; 16 } 17 if(j < n){ // 從樣本中刪除 18 points.erase(points.begin()+i); imnames.erase(imnames.begin()+i); i--; 19 } 20 } 21 } 22 }?
?
ft_data?類還實現(xiàn)了函數(shù)?read?和?write?的序列化,這樣就可以方便地存儲和加載該類。
1 void 2 ft_data:: 3 write(FileStorage &fs) const 4 { 5 assert(fs.isOpened()); 6 fs << "{"; 7 fs << "n_connections" << (int)connections.size(); // 面部特征的語義解釋 8 for(int i = 0; i < int(connections.size()); i++){ 9 char str[256]; const char* ss; 10 sprintf(str,"connections %d 0",i); ss = str; fs << ss << connections[i][0]; 11 sprintf(str,"connections %d 1",i); ss = str; fs << ss << connections[i][1]; 12 } 13 fs << "n_symmetry" << (int)symmetry.size(); // 特征點的索引 14 for(int i = 0; i < int(symmetry.size()); i++){ 15 char str[256]; const char* ss; 16 sprintf(str,"symmetry %d",i); ss = str; fs << ss << symmetry[i]; 17 } 18 fs << "n_images" << (int)imnames.size(); // 圖像絕對路徑 19 for(int i = 0; i < int(imnames.size()); i++){ 20 char str[256]; const char* ss; 21 sprintf(str,"image %d",i); ss = str; fs << ss << imnames[i]; 22 } 23 int n = points[0].size(),N = points.size(); // 描述人臉特征點的結(jié)構(gòu) 24 Mat X(2*n,N,CV_32F); X = -1; 25 for(int i = 0; i < N; i++){ 26 if(int(points[i].size()) == n){ 27 for(int j = 0; j < n; j++){ 28 X.at<float>(2*j ,i) = points[i][j].x; 29 X.at<float>(2*j+1,i) = points[i][j].y; 30 } 31 } 32 } 33 fs << "shapes" << X << "}"; 34 } 35 //============================================================================== 36 void 37 ft_data:: 38 read(const FileNode& node) 39 { 40 assert(node.type() == FileNode::MAP); 41 int n; node["n_connections"] >> n; connections.resize(n); 42 for(int i = 0; i < n; i++){ 43 char str[256]; const char* ss; 44 sprintf(str,"connections %d 0",i); ss = str; node[ss] >> connections[i][0]; 45 sprintf(str,"connections %d 1",i); ss = str; node[ss] >> connections[i][1]; 46 } 47 node["n_symmetry"] >> n; symmetry.resize(n); 48 for(int i = 0; i < n; i++){ 49 char str[256]; const char* ss; 50 sprintf(str,"symmetry %d",i); ss = str; node[ss] >> symmetry[i]; 51 } 52 node["n_images"] >> n; imnames.resize(n); 53 for(int i = 0; i < n; i++){ 54 char str[256]; const char* ss; 55 sprintf(str,"image %d",i); ss = str; node[ss] >> imnames[i]; 56 } 57 Mat X; node["shapes"] >> X; int N = X.cols; n = X.rows/2; 58 points.resize(N); 59 for(int i = 0; i < N; i++){ 60 points[i].clear(); 61 for(int j = 0; j < n; j++){ 62 Point2f p(X.at<float>(2*j,i),X.at<float>(2*j+1,i)); 63 if((p.x >= 0) && (p.y >= 0))points[i].push_back(p); 64 } 65 } 66 } read write?
?
?
為對數(shù)據(jù)集進行可視化操作, ft_data?實現(xiàn)了許多用于繪圖的函數(shù)。
1 void 2 ft_data:: 3 draw_points(Mat &im, 4 const int idx, 5 const bool flipped, 6 const Scalar color, 7 const vector<int> &pts) 8 { 9 if((idx < 0) || (idx >= (int)imnames.size()))return; 10 int n = points[idx].size(); 11 if(pts.size() == 0){ 12 for(int i = 0; i < n; i++){ 13 if(!flipped)circle(im,points[idx][i],1,color,2,CV_AA); 14 else{ 15 Point2f p(im.cols - 1 - points[idx][symmetry[i]].x, 16 points[idx][symmetry[i]].y); 17 circle(im,p,1,color,2,CV_AA); 18 } 19 } 20 }else{ 21 int m = pts.size(); 22 for(int j = 0; j < m; j++){ 23 int i = pts[j]; if((i < 0) || (i >= n))continue; 24 if(!flipped)circle(im,points[idx][i],1,color,2,CV_AA); 25 else{ 26 Point2f p(im.cols - 1 - points[idx][symmetry[i]].x, 27 points[idx][symmetry[i]].y); 28 circle(im,p,1,color,2,CV_AA); 29 } 30 } 31 } 32 } 33 //============================================================================== 34 void 35 ft_data:: 36 draw_sym(Mat &im, 37 const int idx, 38 const bool flipped, 39 const vector<int> &pts) 40 { 41 if((idx < 0) || (idx >= (int)imnames.size()))return; 42 int n = points[idx].size(); 43 RNG rn; vector<Scalar> colors(n); 44 for(int i = 0; i < n; i++)colors[i] = Scalar::all(0.0); 45 for(int i = 0; i < n; i++){ 46 if(colors[i] == Scalar::all(0.0)){ 47 colors[i] = Scalar(rn.uniform(0,255),rn.uniform(0,255),rn.uniform(0,255)); 48 colors[symmetry[i]] = colors[i]; 49 } 50 } 51 vector<Point2f> p = this->get_points(idx,flipped); 52 if(pts.size() == 0){ 53 for(int i = 0; i < n; i++){circle(im,p[i],1,colors[i],2,CV_AA);} 54 }else{ 55 int m = pts.size(); 56 for(int j = 0; j < m; j++){ 57 int i = pts[j]; if((i < 0) || (i >= n))continue; 58 circle(im,p[i],1,colors[i],2,CV_AA); 59 } 60 } 61 } 62 //============================================================================== 63 void 64 ft_data:: 65 draw_connect(Mat &im, 66 const int idx, 67 const bool flipped, 68 const Scalar color, 69 const vector<int> &con) 70 { 71 if((idx < 0) || (idx >= (int)imnames.size()))return; 72 int n = connections.size(); 73 if(con.size() == 0){ 74 for(int i = 0; i < n; i++){ 75 int j = connections[i][0],k = connections[i][1]; 76 if(!flipped)line(im,points[idx][j],points[idx][k],color,1); 77 else{ 78 Point2f p(im.cols - 1 - points[idx][symmetry[j]].x, 79 points[idx][symmetry[j]].y); 80 Point2f q(im.cols - 1 - points[idx][symmetry[k]].x, 81 points[idx][symmetry[k]].y); 82 line(im,p,q,color,1); 83 } 84 } 85 }else{ 86 int m = con.size(); 87 for(int j = 0; j < m; j++){ 88 int i = con[j]; if((i < 0) || (i >= n))continue; 89 int k = connections[i][0],l = connections[i][1]; 90 if(!flipped)line(im,points[idx][k],points[idx][l],color,1); 91 else{ 92 Point2f p(im.cols - 1 - points[idx][symmetry[k]].x, 93 points[idx][symmetry[k]].y); 94 Point2f q(im.cols - 1 - points[idx][symmetry[l]].x, 95 points[idx][symmetry[l]].y); 96 line(im,p,q,color,1); 97 } 98 } 99 } 100 } 繪圖函數(shù)?
?
?
2.?標注工具
? 為了使生成的標注能被本章中的代碼使用,可在?annotate.cpp?文件中找到一個基本的標注工具。該工具將一個視屏流作為輸入,這個視頻流可以來自文件或相機、使用該工具的過程有如下五個步驟:
- 捕獲圖像:第一步是將圖像流顯示在屏幕上,用戶按下 S?鍵就可選擇圖像進行標注。
- 主要代碼如下: 1 //選擇圖像進行標注
2 annotation.set_capture_instructions(); // 顯示幫助信息
3 while (cam.get(CV_CAP_PROP_POS_AVI_RATIO) < 0.999999){ // 循環(huán)遍歷每一幀
4 Mat im, img; cam >> im;
5 annotation.image = im.clone();
6 annotation.draw_instructions();
7 imshow(annotation.wname, annotation.image); // 顯示當前幀
8 int c = waitKey(0); // 等待按鍵,q 退出,s 選擇圖像進行標注,其它任意鍵 下一幀
9 if (c == 'q')break;
10 else if (c == 's'){
11 int idx = annotation.data.imnames.size(); char str[1024];
12 if (idx < 10)sprintf(str, "00%d.png", idx);
13 else if (idx < 100)sprintf(str, "0%d.png",idx);
14 else sprintf(str, "%d.png", idx); // 文件名格式 三位整數(shù).png
15 imwrite(str, im); // 保存該幀圖像
16 annotation.data.imnames.push_back(str);
17 cam >> im; // 顯示下一幀
18 imshow(annotation.wname, im);
19 }
20 }
21 if (annotation.data.imnames.size() == 0)return 0;
22 annotation.data.points.resize(annotation.data.imnames.size());
?
- 運行效果:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???? ? ? ?
- 主要代碼如下: 1 //選擇圖像進行標注
2 annotation.set_capture_instructions(); // 顯示幫助信息
3 while (cam.get(CV_CAP_PROP_POS_AVI_RATIO) < 0.999999){ // 循環(huán)遍歷每一幀
4 Mat im, img; cam >> im;
5 annotation.image = im.clone();
6 annotation.draw_instructions();
7 imshow(annotation.wname, annotation.image); // 顯示當前幀
8 int c = waitKey(0); // 等待按鍵,q 退出,s 選擇圖像進行標注,其它任意鍵 下一幀
9 if (c == 'q')break;
10 else if (c == 's'){
11 int idx = annotation.data.imnames.size(); char str[1024];
12 if (idx < 10)sprintf(str, "00%d.png", idx);
13 else if (idx < 100)sprintf(str, "0%d.png",idx);
14 else sprintf(str, "%d.png", idx); // 文件名格式 三位整數(shù).png
15 imwrite(str, im); // 保存該幀圖像
16 annotation.data.imnames.push_back(str);
17 cam >> im; // 顯示下一幀
18 imshow(annotation.wname, im);
19 }
20 }
21 if (annotation.data.imnames.size() == 0)return 0;
22 annotation.data.points.resize(annotation.data.imnames.size());
- 標注第一幅圖:第二步首先將上一步中第一幅圖呈現(xiàn)給用戶,然后用戶會在這幅圖中選擇需要跟蹤的面部特征位置。
- 主要代碼如下: 1 // 標注第一幅圖像 2 setMouseCallback(annotation.wname, pp_MouseCallback, 0); 3 annotation.set_pick_points_instructions(); // 顯示幫助信息 4 annotation.set_current_image(0); // 選擇第一幅圖像 5 annotation.draw_instructions(); 6 annotation.idx = 0; 7 while (1){ // 在鍵入 q 之前,鼠標單擊標注特征點 8 annotation.draw_points(); 9 imshow(annotation.wname, annotation.image); 10 if (waitKey(0) == 'q')break; 11 } 12 if (annotation.data.points[0].size() == 0)return 0; 13 annotation.replicate_annotations(0); // 保存特征點位置信息
- 運行效果(為檢驗代碼,只選取三個特征點):
? ? ? ? ??
?
- 標注連通性:在這一步中,用戶需選擇將兩組點連接起來,以建立人臉模型的連通性結(jié)構(gòu)
- 主要代碼如下: 1 //標注連通性
2 setMouseCallback(annotation.wname, pc_MouseCallback, 0);
3 annotation.set_connectivity_instructions(); // 幫助信息
4 annotation.set_current_image(0);
5 annotation.draw_instructions();
6 annotation.idx = 0;
7 while (1){ // 在鍵入 q 之前,鼠標單擊一組點建立連接
8 annotation.draw_connections();
9 imshow(annotation.wname, annotation.image); if (waitKey(0) == 'q')break;
10 }
11 save_ft(fname.c_str(), annotation.data);
?
- 運行效果如下:
- 主要代碼如下: 1 //標注連通性
2 setMouseCallback(annotation.wname, pc_MouseCallback, 0);
3 annotation.set_connectivity_instructions(); // 幫助信息
4 annotation.set_current_image(0);
5 annotation.draw_instructions();
6 annotation.idx = 0;
7 while (1){ // 在鍵入 q 之前,鼠標單擊一組點建立連接
8 annotation.draw_connections();
9 imshow(annotation.wname, annotation.image); if (waitKey(0) == 'q')break;
10 }
11 save_ft(fname.c_str(), annotation.data);
? ? ?
- ?標注對稱性:這一步仍然使用上一步的圖像,用戶需選出左右對稱的點。
- 主要代碼如下: 1 //標注對稱性
2 setMouseCallback(annotation.wname, ps_MouseCallback, 0);
3 annotation.initialise_symmetry(0);
4 annotation.set_symmetry_instructions();
5 annotation.set_current_image(0);
6 annotation.draw_instructions();
7 annotation.idx = 0; annotation.pidx = -1;
8 while (1){ // 在鍵入 q 之前,鼠標單擊特征點標注對稱性
9 annotation.draw_symmetry();
10 imshow(annotation.wname, annotation.image); if (waitKey(0) == 'q')break;
11 }
12 save_ft(fname.c_str(), annotation.data);
?
- ?運行效果如下:
- 主要代碼如下: 1 //標注對稱性
2 setMouseCallback(annotation.wname, ps_MouseCallback, 0);
3 annotation.initialise_symmetry(0);
4 annotation.set_symmetry_instructions();
5 annotation.set_current_image(0);
6 annotation.draw_instructions();
7 annotation.idx = 0; annotation.pidx = -1;
8 while (1){ // 在鍵入 q 之前,鼠標單擊特征點標注對稱性
9 annotation.draw_symmetry();
10 imshow(annotation.wname, annotation.image); if (waitKey(0) == 'q')break;
11 }
12 save_ft(fname.c_str(), annotation.data);
? ? ? ?
- ?標注剩下的圖像:重復第 2 步至第 4 步,移動特征點使特征點對應特征位置
- 主要代碼如下: 1 //標注剩下的圖像
2 if (type != 2){
3 setMouseCallback(annotation.wname, mv_MouseCallback, 0);
4 annotation.set_move_points_instructions(); // 幫助信息
5 annotation.idx = 1; annotation.pidx = -1;
6 while (1){
7 annotation.set_current_image(annotation.idx);
8 annotation.draw_instructions();
9 annotation.set_clean_image(); // 背景圖
10 annotation.draw_connections(); // 連線
11 imshow(annotation.wname, annotation.image);
12 int c = waitKey(0); // q 退出,p 下一幅圖像,o 上一幅圖像
13 if (c == 'q')break;
14 else if (c == 'p'){ annotation.idx++; annotation.pidx = -1; }
15 else if (c == 'o'){ annotation.idx--; annotation.pidx = -1; }
16 if (annotation.idx < 0)annotation.idx = 0;
17 if (annotation.idx >= int(annotation.data.imnames.size()))
18 annotation.idx = annotation.data.imnames.size() - 1;
19 }
20 }
21 save_ft(fname.c_str(), annotation.data);
?
- 運行效果如下:
- 主要代碼如下: 1 //標注剩下的圖像
2 if (type != 2){
3 setMouseCallback(annotation.wname, mv_MouseCallback, 0);
4 annotation.set_move_points_instructions(); // 幫助信息
5 annotation.idx = 1; annotation.pidx = -1;
6 while (1){
7 annotation.set_current_image(annotation.idx);
8 annotation.draw_instructions();
9 annotation.set_clean_image(); // 背景圖
10 annotation.draw_connections(); // 連線
11 imshow(annotation.wname, annotation.image);
12 int c = waitKey(0); // q 退出,p 下一幅圖像,o 上一幅圖像
13 if (c == 'q')break;
14 else if (c == 'p'){ annotation.idx++; annotation.pidx = -1; }
15 else if (c == 'o'){ annotation.idx--; annotation.pidx = -1; }
16 if (annotation.idx < 0)annotation.idx = 0;
17 if (annotation.idx >= int(annotation.data.imnames.size()))
18 annotation.idx = annotation.data.imnames.size() - 1;
19 }
20 }
21 save_ft(fname.c_str(), annotation.data);
? ? ? ??
?
該工具將標注數(shù)據(jù)儲存到 ann.yaml?中,如下:
? ? ? ? ? ? ? ? ? ??
? 3.?準備標注數(shù)據(jù)( MUCT 數(shù)據(jù)集)
為了讓本章的標注工作變得輕松一些,可利用公開的 MUCT?數(shù)據(jù)集。這個數(shù)據(jù)集由 3755?張人臉圖像構(gòu)成,每張人臉有76個點作為標記。數(shù)據(jù)集的圖像是在不同光照條件和頭部姿勢下拍攝的人,他們來自不同年齡和種族。
該數(shù)據(jù)集只包含了標注點,需要自定義連通性和對稱性。標注連通性和對稱性之后效果如下圖左,標注數(shù)據(jù)儲存在?annotations.yaml?中,如下圖右:
? ? ?? ? ??
visualize_annotations.cpp?實現(xiàn)對數(shù)據(jù)集可視化操作,關鍵代碼如下:
1 cout << "n images: " << data.imnames.size() << endl 2 << "n points: " << data.symmetry.size() << endl 3 << "n connections: " << data.connections.size() << endl; 4 // 可視化標注數(shù)據(jù) 5 namedWindow("Annotations"); 6 int index = 0; bool flipped = false; 7 while(1){ 8 Mat image; 9 if(flipped)image = data.get_image(index,3); 10 else image = data.get_image(index,2); // 背景圖片 11 data.draw_connect(image,index,flipped); // 連通 12 data.draw_sym(image,index,flipped); // 對稱 13 imshow("Annotations",image); 14 int c = waitKey(0); // q 退出,p 下一張,o 上一張,f 翻轉(zhuǎn) 15 if(c == 'q')break; 16 else if(c == 'p')index++; 17 else if(c == 'o')index--; 18 else if(c == 'f')flipped = !flipped; 19 if(index < 0)index = 0; 20 else if(index >= int(data.imnames.size()))index = data.imnames.size()-1; 21 } 可視化數(shù)據(jù)運行效果如下:
? ? ? ?? ??? ? ?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/coderJiebao/p/MasterOpencv02.html
總結(jié)
以上是生活随笔為你收集整理的非刚性人脸跟踪 —— 实用工具的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BASIC-1_蓝桥杯_闰年判断
- 下一篇: android上使用蓝牙设备进行语音输入