《Master Opencv...讀書筆記》非剛性人臉跟蹤 IV (終)

一、我們目前爲止擁有什麼

爲了有一個連續完整的認識,在介紹最後一節前,先梳理下至今我們訓練了哪些數據特徵,並且訓練它們的目的是什麼。

 

1.      ft_data:利用手工標註工具,獲取最原始的樣本訓練數據,包括以下內容:

  •  圖像名稱集合imnames:表明在哪幅圖像上標註特徵點;
  • 二維座標集合points:手工標準點,後續更高級別特徵均圍繞這些特徵點展開;
  • 對稱座標索引集合symmetry:標註樣本圖像的鏡像圖像上的特徵點,擴大樣本庫;
  •  連接索引集合connections:描述手工標註的人臉特徵點之間的幾何約束及相對位置;

博文地址:http://blog.csdn.net/jinshengtao/article/details/42614091

涉及主要技術:如何利用Opencv序列化存儲“類”的結構數據

 

2.      shape_model:由於人臉的高度結構化特徵對局部形變產生了極大的約束,因此我們需要提取一種特徵來描述手工標註點集與人臉器官在幾何空間上的對應關係。這種關係包括全局形變(人臉平移、縮放、旋轉)和局部形變(描述不同人、不同表情之間臉部形狀的不同)。這種特徵的訓練結果包含如下內容:

  • 參數向量p:在手工標註的特徵點集被投影到人臉子空間(之前被稱作聯合分佈空間)前,需要設置被投影點集在子空間的縮放比例、旋轉角度、還有投影的範圍;
  • 子空間標準基V:描述人臉模型的聯合投影矩陣,包括k個局部形變參數(表情模型)和4個全局形變參數,用於將圖像標註點投影到人臉特徵子空間;
  • 參數變化向量e:手工標註樣本點投影到人臉特徵子空間後得到座標集合的標準差,由於投影本身會導致人臉特徵的失真,所以用該標準差作爲閾值,修正失真clamp函數;
  • 連接矩陣C:描述之前標註的連接關係矩陣,沿用ft_data中的連接索引集合,並未做其他操作;

     我們訓練該特徵,就是爲了得到樣本圖像的標註點投影到人臉特徵子空間的投影矩陣。另外,該投影矩陣內的k個局部形變參數代表了k個表情,一次投影將產生k組子空間座標。

博文地址:http://blog.csdn.net/jinshengtao/article/details/43376049

涉及主要技術:奇異值分解、Procrustes Analysis、求施密特正交矩陣

 

3.      patch_model:團塊特徵模版,即人臉每個部位的特徵圖像。團塊特徵的訓練結果包含如下內容:

  • 參考形狀矩陣reference:通過人工指定參數向量p,在人臉子空間產生k種投影的座標集合。由於圖像的全局幾何約束,爲了提取更好的團塊模型,我們需要求人工標註點到該矩陣reference的仿射變化矩陣(calc_simil函數完成),從而對樣本圖像也進行相應的仿射變化
  • 團塊矩陣P:它是一種歸一化的圖像,代表當前特徵點附近的圖像特徵

      在人臉跟蹤時,需要對人臉不同部位各自的描述信息,以便於對每個特徵點周圍的圖像進行模版匹配,達到人臉精細化跟蹤的目的。

博文地址:http://blog.csdn.net/jinshengtao/article/details/43974311

涉及主要技術:隨機梯度下降法、最小二乘法

 

二、打算怎麼去跟蹤,完整的跟蹤方案

1.      手工標註數據,獲取原始訓練樣本(多人,多表情)

2.      訓練形狀模型(提取這些表情模型,幾何依賴關係保證後面的跟蹤“像人臉”)

3.      訓練團塊模型(提取每個表情所包含的團塊特徵,人臉跟蹤全靠這個模版匹配了)

4.      初始化人臉檢測器(怎麼在第一幀或跟蹤失敗時,開始/繼續人臉檢測)

5.      根據上一幀的人臉特徵點,結合形狀和團塊信息,估計當前幀的人臉特徵點集(考慮空間高斯噪聲,此噪聲是跟蹤錯誤導致,不是圖像噪聲)

 

三、如何初始化第一幀及檢測人臉

   由於人臉在相鄰幀之間的動作變化較小,所以到目前爲止,我們假設每幀圖像中人臉的特徵都分佈在當前估計點周圍的合理範圍內。但是我們仍面臨着一個嚴重的問題,到底怎樣初始化第一幀得到其中的人臉特徵模型

   對於第一幀,這裏我們採用比較直觀的方式,使用opencv內置的級聯檢測器來尋找人臉的大致區域,用外接矩形來描述。按照數據驅動的思路,通過學習訓練使我們的系統能夠學習人臉外界矩形與人臉跟蹤特徵之間的幾何關係detector_offset向量,然後利用該向量對人臉參考形狀矩陣reference進行仿射變換,獲得外界矩形區域內的人臉特徵點。

接下來,我們首先介紹訓練的步驟,然後展示我們的訓練結果。

訓練過程:

1.      載入樣本標註點ft_data及形狀模型數據shape_model

2.      設置參數向量p,構造人臉子空間座標點集合作爲人臉特徵的參考點集

3.      調用trian函數,學習外界矩形與人臉特徵點之間的幾何關係detector_offset


train函數入參:

  data:ft_data對象實例,包含了手工標註信息

  fname:級聯分類器名稱(比如:haarcascade_frontalface_alt.xml)

  ref:參考形狀矩陣,在人臉子空間的k種投影點集

  mirror:鏡像樣本圖像標記

  visi:訓練過程可視化標記

  frac:有效特徵點比率閾值

 

    這裏train函數的目標是獲取detector_offset向量,該向量的作用是將之前訓練得到的形狀模型以合理的方式鑲嵌到人臉上。detector_offset向量通過外接矩形的width和該區域內手工標註點集合pt的重心計算得到。具體過程如下:

(1)    加載級聯分類器、形狀參考矩陣

(2)    對手工標註的每一幅圖片,使用級聯分類器搜索人臉區域

(3)    判斷人臉的外接矩形內是否包含足夠多的標註點(防止錯誤學習)

(4)    如果包含足夠的標註點,則按照如下公式計算

重心:

                 

     計算每一幅圖像的offset,構成平面座標集合(X,Y)及縮放比例集合Z:






對X、Y、Z集合分別按升序排序,去各自的中值作爲最終的detector_offset(Xm,Ym,Zm

具體實現代碼:

//====================================================================
void
face_detector::
train(ft_data &data,
      const string fname,
      const Mat &ref,
      const bool mirror,
      const bool visi,
      const float frac,
      const float scaleFactor,
      const int minNeighbours,
      const Size minSize)
{
  //載入級聯分類器
  detector.load(fname.c_str()); 
  detector_fname = fname; 
  reference = ref.clone();
  vector<float> xoffset(0),yoffset(0),zoffset(0);
  for(int i = 0; i < data.n_images(); i++)
  {
    //獲取每一張訓練圖片
    Mat im = data.get_image(i,0); 
	if(im.empty())
	   continue;
	//獲取訓練圖片對應的標註點
    vector<Point2f> p = data.get_points(i,false); 
	int n = p.size();
    Mat pt = Mat(p).reshape(1,2*n);
    vector<Rect> faces;
	Mat eqIm; 
	//直方圖均衡化
	equalizeHist(im,eqIm);
	//人臉檢測
    detector.detectMultiScale(eqIm,faces,scaleFactor,minNeighbours,0
                  |CV_HAAR_FIND_BIGGEST_OBJECT
                  |CV_HAAR_SCALE_IMAGE,minSize);
    if(faces.size() >= 1)
	{
      if(visi)
	  {
	    //框出人臉區域
		Mat I; cvtColor(im,I,CV_GRAY2RGB);
		for(int i = 0; i < n; i++)circle(I,p[i],1,CV_RGB(0,255,0),2,CV_AA);
		rectangle(I,faces[0].tl(),faces[0].br(),CV_RGB(255,0,0),3);
		imshow("face detector training",I); waitKey(10); 
      }
      //check if enough points are in detected rectangle
      if(this->enough_bounded_points(pt,faces[0],frac))
	  {
		Point2f center = this->center_of_mass(pt); 
		float w = faces[0].width;
		xoffset.push_back((center.x - (faces[0].x+0.5*faces[0].width ))/w);
		yoffset.push_back((center.y - (faces[0].y+0.5*faces[0].height))/w);
		zoffset.push_back(this->calc_scale(pt)/w);
      }
    }
    if(mirror)
	{
      im = data.get_image(i,1); if(im.empty())continue;
      p = data.get_points(i,true);
      pt = Mat(p).reshape(1,2*n);
      equalizeHist(im,eqIm);
      detector.detectMultiScale(eqIm,faces,scaleFactor,minNeighbours,0
                  |CV_HAAR_FIND_BIGGEST_OBJECT
                |CV_HAAR_SCALE_IMAGE,minSize);
      if(faces.size() >= 1){
		if(visi)
		{
		  Mat I; cvtColor(im,I,CV_GRAY2RGB);
		  for(int i = 0; i < n; i++)circle(I,p[i],1,CV_RGB(0,255,0),2,CV_AA);
		  rectangle(I,faces[0].tl(),faces[0].br(),CV_RGB(255,0,0),3);
		  imshow("face detector training",I); waitKey(10);
		}
		//check if enough points are in detected rectangle
		if(this->enough_bounded_points(pt,faces[0],frac))
		{
		  Point2f center = this->center_of_mass(pt); float w = faces[0].width;
		  xoffset.push_back((center.x - (faces[0].x+0.5*faces[0].width ))/w);
		  yoffset.push_back((center.y - (faces[0].y+0.5*faces[0].height))/w);
		  zoffset.push_back(this->calc_scale(pt)/w);
		}
      }
    }
  }
  //choose median value,選取集合中值
  Mat X = Mat(xoffset),Xsort,Y = Mat(yoffset),Ysort,Z = Mat(zoffset),Zsort;
  cv::sort(X,Xsort,CV_SORT_EVERY_COLUMN|CV_SORT_ASCENDING); int nx = Xsort.rows;
  cv::sort(Y,Ysort,CV_SORT_EVERY_COLUMN|CV_SORT_ASCENDING); int ny = Ysort.rows;
  cv::sort(Z,Zsort,CV_SORT_EVERY_COLUMN|CV_SORT_ASCENDING); int nz = Zsort.rows;
  detector_offset = Vec3f(Xsort.fl(nx/2),Ysort.fl(ny/2),Zsort.fl(nz/2)); 
  return;
}

      訓練過程如下,一共9幅樣本圖,每幅圖首先利用opencv級聯分離檢索人臉位置,然後在樣本圖像上標出標註點。接下來判斷矩形內標註點的數量是否合理,最後計算我們的偏移量detector_offset。下圖是我們的樣本圖像、標註點、人臉位置示意圖:




      經過上述訓練後,我們將序列化存儲:級聯分類器名稱、偏移向量、形狀參考點集。接下來,在展示訓練結果之前,先介紹下detect函數,如何將我們訓練的矩形與特徵點的偏移關係套用到測試圖像上。

detect函數,輸入一副人臉測試圖片,根據訓練結果,在該人臉圖像上標準人臉特徵點。具體操作過程:

(1)    彩色圖像轉化成灰度圖像,並進行直方圖均衡化

(2)    利用Opencv的級聯分類器檢測人臉位置

(3)    根據人臉外界矩形,結合detector_offset和參考形狀模型,重新計算標註點的座標

這步公式比較簡單不列了,但作者上邊和這裏設計的意圖需要自己體會下,關鍵代碼:

//predict face placement
  Rect R = faces[0]; //人臉外界矩形
  Vec3f scale = detector_offset*R.width;
  int n = reference.rows/2; 
vector<Point2f> p(n);
  for(int i = 0; i < n; i++){
    //scale[2]代表縮放比例
    p[i].x = scale[2]*reference.fl(2*i  ) + R.x + 0.5 * R.width  + scale[0];
    p[i].y = scale[2]*reference.fl(2*i+1) + R.y + 0.5 * R.height + scale[1];
  }
  return p;

     展示訓練的結果如下,任老頭子拿出來曬曬,好肥:


       從上圖來看,我們的訓練使得人臉特徵點基本標註都正確了。本節內容說白了就是訓練一個外界矩形與標註點的幾何關係,然後讓之後的測試圖像人臉特徵點得到合理標注。這裏需要細細體會的是,老外幾何關係detector_offset的數學設計!!現在講了第一幀圖像的處理方法,接下來在第四節介紹後續幀,人臉檢測與特徵點標註如何實現。

四、人臉跟蹤實現步驟及代碼

      本文的人臉跟蹤問題,就是尋找一種高效、健壯的方法,將多個獨立的人臉特徵通過之前訓練的幾何依賴關係聯合起來,實現精確跟蹤每幅圖像中人臉特徵的幾何位置。也許有人會問,在已經用Opencv級聯分類器檢測人臉了,再考慮幾何依賴關係是否有意義。下面兩幅圖像,分別是帶與不帶幾何依賴的人臉跟蹤效果圖。


      上面對比的結果清晰地展示了人臉特徵之間空間內部依賴關係的優勢(這裏因爲pathc_model已經訓練了很多人臉特徵,由於每幀變化不大,僅僅依靠這些特徵點就能產生no dependency的結果了,只是如果有了空間內部依賴關係,每個點的跟蹤效果會更好)。

      圖像對比非常明顯,造成這種差異的原因:僅按照檢測到人臉特徵的位置進行跟蹤會導致過度噪聲。因爲每個人臉特徵跟蹤時,採用模版匹配法,即便在正確的位置,該區域圖像在人臉模版上的反饋,也有可能不是最佳的。無論是圖像噪聲、光照變化、還是表情變化,解決人臉特徵”模版匹配式跟蹤”侷限性的唯一方法,就是藉助每個人臉特徵之間的幾何關係。

     那麼這個幾何依賴關係到底在人臉跟蹤時怎麼做呢?我們將人臉特徵提取的結果投影到形狀模型的線性子空間(shape model),也就是最小化原始點集到其在人臉子空間最接近合理形狀分佈的投影點集的距離。(就是說,把通過模版匹配檢測到的原始點集A投影到人臉子空間產生新的點集B,再按照某種約束規則,通過對A迭代變化,使得A’到B的距離最小)。

     這麼做的好處:在用融合了幾何關係的人臉特徵模版匹配法跟蹤人臉時,即便空間噪聲滿足高斯近乎於高斯分佈,其檢測效果也“最像“人臉的形狀。

上一節我們學習瞭如何初始化第一幀中的人臉特徵點模型,接下來我們要搞明白如何使用上一幀或第一幀的人臉特徵點來估計當前幀的人臉特徵點,達到跟蹤的目的。我們先來認識以下3個類:

fps_timer類:計算程序運算的速度XX幀/秒,在face_tracker類中track函數調用。

face_tracker_params類:完成face_tracker中基本參數的初始化、序列化存儲,包括搜索區域集合,最大迭代次數,級聯分類器參數等等。

face_tracker類:人臉跟蹤的核心模塊,也是本次介紹的重點,包含train和track兩個部分。

 

    人臉跟蹤的訓練過程face_tracker::train,其實就是簡單的把以往的訓練數據重新打包序列化保存,包括shape_model、patch_model、face_detector。

int main(int argc,char** argv)
{
	//create face tracker model
	face_tracker tracker;
	tracker.smodel = load_ft<shape_model>("shape.xml");
	tracker.pmodel = load_ft<patch_models>("patch.xml");
	tracker.detector = load_ft<face_detector>("detector.xml");

	//save face tracker
	save_ft<face_tracker>("tracker.xml",tracker); 
return 0;
}

    人臉跟蹤的track函數,擁有兩種功能。當tracking標誌位爲fasle時,程序屬於構建模型(detectmode)階段,爲第一幀或下一幀圖像初始化的人臉特徵,所用的技術就是上一節所講的;當tracking標誌位爲true時,則根據上一幀人臉特徵點的位置估計下一幀的人臉特徵,這個操作主要由fit函數完成。

int
face_tracker::
track(const Mat &im,const face_tracker_params &p)
{
  //convert image to greyscale
  Mat gray; 
  if(im.channels()==1)
     gray = im;
  else
     cvtColor(im,gray,CV_RGB2GRAY);

  //initialise,爲第一幀或下一幀初始化人臉特徵
  if(!tracking)
    points = detector.detect(gray,p.scaleFactor,p.minNeighbours,p.minSize);
  if((int)points.size() != smodel.npts())
    return 0;

  //fit,通過迭代縮小的搜索範圍,估計當前幀中的人臉特徵點
  for(int level = 0; level < int(p.ssize.size()); level++)
    points = this->fit(gray,points,p.ssize[level],p.robust,p.itol,p.ftol);

  //set tracking flag and increment timer
  tracking = true; 
  timer.increment(); 
  return 1;
}

     face_tracker::fit函數的主要功能:給定一幀圖像及上一幀人臉特徵點集,在當前圖像上搜索該點集附近的人臉特徵,併產生新的人臉特徵點集。

fit函數入參:

image:當前幀灰度圖像

init:上一幀人臉特徵點集(幾何位置)

ssize:搜索區域大小

robust:標誌位,決定是否採用robustmodel fitting流程,應對人臉特徵的孤立點

itol:robust modelfitting迭代上限

ftol:迭代收斂判斷閾值

 

返回值:

pts:在給定的搜索區域大小後,當前幀中人臉特徵位置點集

//==========================================================================
vector<Point2f>
face_tracker::
fit(const Mat &image,
    const vector<Point2f> &init,
    const Size ssize,
    const bool robust,
    const int itol,
    const float ftol)
{
  int n = smodel.npts();//number of points in shape model
  assert((int(init.size())==n) && (pmodel.n_patches()==n));
  smodel.calc_params(init); vector<Point2f> pts = smodel.calc_shape();

  //find facial features in image around current estimates
  vector<Point2f> peaks = pmodel.calc_peaks(image,pts,ssize);

  //optimise
  if(!robust){
    smodel.calc_params(peaks); //compute shape model parameters        
    pts = smodel.calc_shape(); //update shape
  }else{
    Mat weight(n,1,CV_32F),weight_sort(n,1,CV_32F);
    vector<Point2f> pts_old = pts;
    for(int iter = 0; iter < itol; iter++){
      //compute robust weight
      for(int i = 0; i < n; i++)weight.fl(i) = norm(pts[i] - peaks[i]);
      cv::sort(weight,weight_sort,CV_SORT_EVERY_COLUMN|CV_SORT_ASCENDING);
      double var = 1.4826*weight_sort.fl(n/2); if(var < 0.1)var = 0.1;
      pow(weight,2,weight); weight *= -0.5/(var*var); cv::exp(weight,weight); 

      //compute shape model parameters    
      smodel.calc_params(peaks,weight);
      
      //update shape
      pts = smodel.calc_shape();
      
      //check for convergence
      float v = 0; for(int i = 0; i < n; i++)v += norm(pts[i]-pts_old[i]);
      if(v < ftol)break; else pts_old = pts;
    }
  }return pts;
}

上面代碼中,有兩個函數:

    shape_mode::calc_param,爲了得到合理的人臉子空間投影,我們需要求參數向量p,該函數通過人臉子空間投影座標集合pts與人臉特徵空間標準基V,計算得到參數向量

    patch_models::calc_peaks,根據人臉子空間點集在當前圖像內搜索人臉特徵,併產生新的人臉特徵位置估計

 

    calc_param函數:計算參數向量p時可分爲兩種投影:simpleprojection和scale projection

如果權值爲空,則採用簡單投影,由於V是標準正交基:


否則對每個被投影點設置尺度權值,採用尺度投影,利用opencv提供的奇異值分解法求解非齊次線性系統:

(1)    遍歷每個原始點集,取出聯合分佈矩陣V2n*k(人臉模型)對應的矩形區域寬k,高2,起始點(0,2i),存入矩陣v2*k

(2)    Opecnv函數solve求解非齊次線性方程,求p


 其中,,w爲每個點的權重,p是需要求解的向量。(至於權值,老外在後面robust model fitting流程有用到。由於H和g是對每種表情模式作累加的,所以我猜想權值w的作用是控制每個人臉特徵點對每種表情模式的影響,即只要哪些點就能顯著表達對應表情)

最後,無論哪種投影,都經過clamp函數處理,根據c_factor個標準差e約束調整參數向量p,防止人臉投影失真。

//===============================================================
void 
shape_model::
calc_params(const vector<Point2f> &pts,const Mat weight,const float c_factor)
{
  int n = pts.size(); assert(V.rows == 2*n);
  Mat s = Mat(pts).reshape(1,2*n); //point set to vector format
  if(weight.empty())p = V.t()*s;   //simple projection
  else{                            //scaled projection
    if(weight.rows != n){cout << "Invalid weighting matrix" << endl; abort();}
    int K = V.cols; Mat H = Mat::zeros(K,K,CV_32F),g = Mat::zeros(K,1,CV_32F);
    for(int i = 0; i < n; i++){
      Mat v = V(Rect(0,2*i,K,2)); float w = weight.fl(i);
      H += w*v.t()*v; g += w*v.t()*Mat(pts[i]);
    }
    solve(H,g,p,DECOMP_SVD);
  }this->clamp(c_factor);          //clamp resulting parameters
}

[另外,calc_param與calc_shape,如果排除權值,那麼是一對互逆的操作]

 

calc_peaks函數:

     通過上面的函數我們得到設置投影範圍的參數向量p,然後再調用calc_shape函數就可以得到人臉子空間中的特徵點pts。現在我們要在每個人臉子空間特徵點附近搜索包含人臉特徵的區域,最終爲下一幀生成新的特徵估計。這也是爲什麼,我們只需在第一幀或者跟蹤失敗時,才需要調用Opencv級聯分類器重新定位人臉的原因。

calc_peaks函數入參:

image:當前包含人臉的灰度圖像

pts:前一幀估計的人臉特徵點集在人臉子空間投影座標集合

ssize:搜索區域窗口大小

返回值:

  當前幀人臉特徵估計的點集


//========================================================================
vector<Point2f> 
patch_models::
calc_peaks(const Mat &im,
       const vector<Point2f> &points,
       const Size ssize)
{
  int n = points.size(); assert(n == int(patches.size()));
  Mat pt = Mat(points).reshape(1,2*n);
  Mat S = this->calc_simil(pt);// 計算當前點集到人臉參考模型的變化矩陣
  Mat Si = this->inv_simil(S); //對矩陣S求逆
  vector<Point2f> pts = this->apply_simil(Si,points);
  for(int i = 0; i < n; i++){
    Size wsize = ssize + patches[i].patch_size(); Mat A(2,3,CV_32F);     
    A.fl(0,0) = S.fl(0,0); A.fl(0,1) = S.fl(0,1);
    A.fl(1,0) = S.fl(1,0); A.fl(1,1) = S.fl(1,1);
    A.fl(0,2) = pt.fl(2*i  ) - 
      (A.fl(0,0) * (wsize.width-1)/2 + A.fl(0,1)*(wsize.height-1)/2);
    A.fl(1,2) = pt.fl(2*i+1) - 
      (A.fl(1,0) * (wsize.width-1)/2 + A.fl(1,1)*(wsize.height-1)/2);
    Mat I; warpAffine(im,I,A,wsize,INTER_LINEAR+WARP_INVERSE_MAP);
    Mat R = patches[i].calc_response(I,false);
    Point maxLoc; minMaxLoc(R,0,0,0,&maxLoc);
    pts[i] = Point2f(pts[i].x + maxLoc.x - 0.5*ssize.width,
             pts[i].y + maxLoc.y - 0.5*ssize.height);
  }return this->apply_simil(S,pts);
}

上面代碼片段中,介紹以下函數:

(1)    apply_simil函數,對點集points按照Si進行仿射變化(將人臉特徵子空間中的座標經過仿射變換轉成圖像空間中的座標,或者反過來)

(2)    calc_response函數,在灰度圖像上搜索人臉特徵(團塊圖像)的匹配位置,核心技術是Opencv API:matchTemplate模版匹配函數(圖像I,模版T,匹配結果,算法標記),這裏採用的算法是CV_TM_CCOEFF_NORMED,標準相關匹配宏。具體做法就是在原始圖像上滑動模版窗口,在一次移動一個像素,最後在每個像素點上的匹配度量值R(x):



w,h爲模版T的寬和高

(3)    minMaxLoc函數(數組 ,最小值,最大值,最小值座標,最大值座標):尋找矩陣中的最大最小值的位置,這裏在矩陣R中尋找最大值,即最佳匹配位置。不需要關注的,API內直接填0即可。

 

calc_peak總體說來,利用上一幀座標構造人臉特徵的搜索區域,藉助之前訓練得到的團塊模型,在搜索區域內進行模版匹配,找到最優匹配點,作爲新一幀人臉特徵點的座標。完整操作過程如下:

(1)    計算前一幀人臉特徵座標到人臉參考模型座標的仿射變換 S2*3

(2)    計算上述仿射變化的逆矩陣 Si 2*3

(3)    通過逆矩陣Si將人臉特徵子空間中的座標還原成圖像幀中的座標

(4)    遍歷所有點,在原始圖像中搜索每個人臉特徵模版圖像匹配的座標點

(5)    利用(4)中的座標,修正人臉特徵估計點位置

(6)    利用仿射變化矩陣,再次將圖像中的座標投影到人臉特徵子空間中,作爲下一幀的人臉特徵座標估計

 

     在對每幀圖像進行人臉跟蹤時,track函數都會通過fit函數迭代產生多個人臉子空間座標集合,並且每次迭代的時候,搜索區域都在減小。在迭代過程中,可能會產生很多孤立的特徵點(孤立點,我認爲是模版匹配時得到人臉特徵錯誤估計點,因爲本文沒有一種機制保證R(X)的反饋一定包含人臉特徵)。爲了得到更精確的人臉跟蹤效果,如果存在孤立點時,仍採用簡單投影simple projection,會嚴重影響跟蹤效果。因此,老外在計算投影參數calc_param時引入了權重,搞了一套robust model fitting流程,特意去除孤立點。

 

權值的計算:

pts,上一幀人臉特徵子空間估計點集;

peaks,當前幀人臉特徵子空間投影點集(模版匹配);


     以上公式,本節一開始有提到,想要表達:前後兩幀估計點集之差服從高斯分佈,即空間噪聲滿足高斯近乎於高斯分佈。咱有了權值後,就可以計算帶權值的參數向量,然後更新投影。循環退出的條件,循環次數達到最大,或者前後兩次重新計算的投影之間的距離滿足閾值ftol。

 

還有兩個問題:

a.      爲什麼用一個逐漸縮小的搜索窗口,多次模版匹配人臉特徵?

由於採用模版匹配法,通過多次在尺度不斷減小的窗口中尋找人臉特徵,可以使得估計點更好的表達人臉特徵所在的位置(畢竟是俺像素遍歷,在R(X)中挑最大值,多挑幾次總是不錯的,越挑越細)

 

b.      如何理解通常情況下,隨着搜索範圍的減小,孤立點會被自動排除(robust=false)?

因此在模版匹配時,挑選R(x)的最大值作爲當前特徵點,即使由於噪聲啥的導致了錯誤,在下次搜索時,搜索窗口變小了,該錯誤點附近圖像的反饋R(x)一定會變小,所以可以被剔除。這裏假設模版匹配總能找到收斂的位置,如果實在離譜了,那麼請按”d”鍵,重新初始化人臉檢測器把。

 

接下來,完整展示跟蹤結果:

#include "ft.hpp"
#include "face_tracker.hpp"
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#define fl at<float>
//===========================================================================
void
draw_string(Mat img,                       //image to draw on
			const string text)             //text to draw
{
	Size size = getTextSize(text,FONT_HERSHEY_COMPLEX,0.6f,1,NULL);
	putText(img,text,Point(0,size.height),FONT_HERSHEY_COMPLEX,0.6f,
		Scalar::all(0),1,CV_AA);
	putText(img,text,Point(1,size.height+1),FONT_HERSHEY_COMPLEX,0.6f,
		Scalar::all(255),1,CV_AA);
}

//========================================================================
int main(int argc,char** argv)
{
	//load detector model
	Mat im;
	face_tracker tracker = load_ft<face_tracker>("tracker.xml");

	//create tracker parameters
	face_tracker_params p; 
	p.robust = false;
	p.ssize.resize(3);
	p.ssize[0] = Size(21,21);
	p.ssize[1] = Size(11,11);
	p.ssize[2] = Size(5,5);

	//open video stream
	VideoCapture cam; 
	namedWindow("face tracker");

	cam.open("test.avi");
	if(!cam.isOpened()){
		cout << "Failed opening video file." << endl
			<< usage << endl; return 0;
	}
	//detect until user quits

	while(cam.get(CV_CAP_PROP_POS_AVI_RATIO) < 0.999999){		 
		cam >> im; 
		if(tracker.track(im,p))
			tracker.draw(im);
		draw_string(im,"d - redetection");
		tracker.timer.display_fps(im,Point(1,im.rows-1));
		imshow("face tracker",im);
		int c = waitKey(10);
		if(c == 'q')
			break;
		else if(c == 'd')
			tracker.reset();
	}
	destroyWindow("face tracker"); 
	cam.release(); 
	return 0;
}









     老外也比較實在,當人臉跟蹤失敗的時候,你只要按d,重新復位人臉跟蹤器即可。。。缺點,優點也很明顯,只能一個次跟蹤一個人,對於側臉效果很差

 

五、寫了這麼多,我們到底還能做什麼(心得與拓展)

    分了4個批次終於寫完了,我想應該表達了老外80%-90%的意思。注意我的訓練圖片太少,並且受限於opencv級聯分類器的選擇(人臉檢測宏CV_HAAR_FIND_BIGGEST_OBJECT),所以對於側臉的跟蹤效果不好。

後面的網友可以使用老外的提供的強大數據庫,並挑選opencv合理的人臉分類器,應該可以滿足側臉跟蹤的要求。


所有代碼下載地址:

 http://download.csdn.net/detail/jinshengtao/8555713


表情識別:

有人問到這個事情,我是這麼想的:

模仿PCA人臉檢測算法:http://blog.csdn.net/jinshengtao/article/details/18599165

在那個文章裏,我把每個人臉圖像轉化成一維向量,多個圖像這麼轉換後就得到訓練集,還記那個36000*20的矩陣嘛?然後利用PCA算法,提取主成份,構造平均臉什麼的。最後,將測試集圖像也投影到平均臉的空間裏,計算二者的距離,挑距離最小的作爲最終匹配。

這裏也可以這麼模仿。現在可以比較精確的跟蹤每個人臉的特徵點了,我們把每幅圖像對應的人臉特徵點集也搞成一維的,然後挑選多個表情充分獨立的圖像所對應的點集構成訓練集,一樣採用PCA提取主成份,搞個投影空間。然後表情識別,無非就是算距離,給標籤罷了。

 

至於,行人動作識別,這個有難度,需要查文獻。畢竟行人的動作幅度可比人臉表情幅度大多了,非常容易跟丟。

 

最後附上一個叫“大嘴說圖”的網友的連接,他羅列了“人臉器官精確定位/人臉特徵點的跟蹤”的主流算法及代碼文檔資源(ASM,活動形狀模型),有興趣的朋友可以拓展下。

 

http://blog.sina.com.cn/s/blog_ebbe6d790102vmez.html



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章