1. 程式人生 > >使用Opencv的opencv_traincascade來訓練屬於自己的分類器,檢測自己想檢測的東西,666,從使用到放棄。

使用Opencv的opencv_traincascade來訓練屬於自己的分類器,檢測自己想檢測的東西,666,從使用到放棄。

提供一個人臉檢測的訓練工程,其裡面包括原始的訓練樣本、製作好的訓練樣本、訓練指令等,感覺其樣本分類特別麻煩其下載地址為:

1 、opencv裡的分類器大概介紹:

  OpenCV中有兩個程式可以訓練級聯分類器: opencv_haartraining and opencv_traincascade``。 ``opencv_traincascade 是一個新程式,使用OpenCV 2.x API 以C++ 編寫。這二者主要的區別是 opencv_traincascade 支援 Haar [Viola2001] 和 LBP [Liao2007] (Local Binary Patterns) 兩種特徵,現在已經發展到可以支援hog特徵,並易於增加其他的特徵。與Haar特徵相比,LBP特徵是整數特徵,因此訓練和檢測過程都會比Haar特徵快幾倍。LBP和Haar特徵用於檢測的準確率,是依賴訓練過程中的訓練資料的質量和訓練引數。訓練一個與基於Haar特徵同樣準確度的LBP的分類器是可能的。

2、分類器的樣本製作:

  其樣本點的製作主要是指正樣本的製作,因為其負樣本製作很簡單,只要把其圖片的相對路徑生成到檔案裡即可用於訓練,其如下:

使用這個指令生成的路徑是絕對路徑,其生成後需要把絕對路徑給替換掉即可。使用的指令為:

dir /b/s/p/w *.jpg > positives.txt

注意:負樣本的圖片大小可以不用進行歸一化成統一大小,因為其在訓練的時候可以進行指定大小,訓練時會進行影象resize操作。

   接著就是正樣本的生成,其是比較重要的。其中正樣本的樣本採集有兩種方法:

A、只通過一張正樣本圖片,結合負樣本進行其他正樣本的生成。這種方法比較適合剛性的物體,即正樣本目標不會變形,所以要對其進行樣本採集比較困難,例如交通LOGO牌,無法對其進行變形採用。這種情況可以通過影象手段來對其進行一些影象預處理操作,從而產生樣本。其使用的指令是:

其要使用 【-img】引數來指明單張正樣本,其中的【num】是指通過這一張正樣本要生成的樣本數量,其是通過負樣本結合來生成的,負樣本主要是給旋轉等操作後的正樣本提供背景。他會在生成樣本的同時直接生成vec檔案。

 注意:其中更好的生成樣本的工具程式碼是:opencv-haar-classifier-training   其使用指令碼來呼叫opencv_createsamples.exe進行樣本的生成,而且也可以順路生成vec檔案,真的超方便。

B、通過人為採集所有訓練樣本。其中正樣本的txt檔案格式如下,其中檔案路徑後面的格式是在指令生成後自己手動替換得到的:

其指令如下:

  此時提供的是包含所有的正樣本路徑的txt檔案,其生成可以參考上面的方法。此時這裡設定的x、y、z三軸旋角等一些資料增城的引數是不會被採用的,其只有上面A單張樣本是才使用這些引數。其中的【-num】的只要小於或等於正樣本的指,否則會報錯。

  注意:這是把所有目標都裁剪下來的處理方式,而且需要進行樣本歸一化。還有一種就是使用工具在整張圖片上進行正樣本標註,其可以使用ObjectMarker工具。具體過程參考部落格:基於級聯分類器的多目標檢測

3、級聯分類器的訓練:

 但樣本都準備好後,其訓練指令為:

opencv_traincascade -data classifier -vec pos.vec -bg negatives.txt   -numStages 20 -minHitRate 0.999 -maxFalseAlarmRate 0.5 -numPos 14000   -numNeg 10000 -w 20 -h 20 -mode ALL -precalcValBufSize 1024   -precalcIdxBufSize 1024 -featureType LBP
pause

通用引數:

-data <cascade_dir_name>
目錄名,如不存在訓練程式會建立它,用於存放訓練好的分類器。
-vec <vec_file_name>
包含正樣本的vec檔名(由 opencv_createsamples 程式生成)。
-bg <background_file_name>
背景描述檔案,也就是包含負樣本檔名的那個描述檔案。
-numPos <number_of_positive_samples>
每級分類器訓練時所用的正樣本數目。其指設定為正樣本數量的85%(這是一個保守值)。具體的也要根據級聯器的層數來決定的。因為每個stages都是會增加圖片數量來進行分類。

-numNeg <number_of_negative_samples>
每級分類器訓練時所用的負樣本數目,可以大於 -bg 指定的圖片數目。
-numStages <number_of_stages>
訓練的分類器的級數。

-precalcValBufSize <precalculated_vals_buffer_size_in_Mb>
快取大小,用於儲存預先計算的特徵值(feature values),單位為MB。
-precalcIdxBufSize <precalculated_idxs_buffer_size_in_Mb>
快取大小,用於儲存預先計算的特徵索引(feature indices),單位為MB。記憶體越大,訓練時間越短。
-baseFormatSave
這個引數僅在使用Haar特徵時有效。如果指定這個引數,那麼級聯分類器將以老的格式儲存。
級聯引數:
-stageType <BOOST(default)>
級別(stage)引數。目前只支援將BOOST分類器作為級別的型別。
-featureType<{HAAR(default), LBP}>
特徵的型別: HAAR - 類Haar特徵; LBP - 區域性紋理模式特徵。
-w <sampleWidth>
-h <sampleHeight>
訓練樣本的尺寸(單位為畫素)。必須跟訓練樣本建立(使用 opencv_createsamples 程式建立)時的尺寸保持一致。
Boosted分類器引數:
-bt <{DAB, RAB, LB, GAB(default)}>
Boosted分類器的型別: DAB - Discrete AdaBoost, RAB - Real AdaBoost, LB - LogitBoost, GAB - Gentle AdaBoost。
-minHitRate <min_hit_rate>
分類器的每一級希望得到的最小檢測率。總的檢測率大約為 min_hit_rate^number_of_stages。總檢測率即為整個級聯器的檢測召回率,
-maxFalseAlarmRate <max_false_alarm_rate>
分類器的每一級希望得到的最大誤檢率。總的誤檢率大約為 max_false_alarm_rate^number_of_stages. 為整個級聯器的誤檢率

-weightTrimRate <weight_trim_rate>
Specifies whether trimming should be used and its weight. 一個還不錯的數值是0.95。
-maxDepth <max_depth_of_weak_tree>
弱分類器樹最大的深度。一個還不錯的數值是1,是二叉樹(stumps)。
-maxWeakCount <max_weak_tree_count>
每一級中的弱分類器的最大數目。The boosted classifier (stage) will have so many weak trees (<=maxWeakCount), as needed to achieve the given -maxFalseAlarmRate.
類Haar特徵引數:
-mode <BASIC (default) | CORE | ALL>
選擇訓練過程中使用的Haar特徵的型別。 BASIC 只使用右上特徵, ALL 使用所有右上特徵和45度旋轉特徵。

此時訓練的結果圖:

   注意:由於是20stages,所以訓練時間要三四個鍾,其中採用LBP特徵會比Haar特徵快十幾倍的訓練速度。還有如果想把檢測準確度提高很高,則需要大量的樣本和好的影象質量。

4、中斷後,如果不行在繼續訓練,如何通過先前的中間檔案來生產分類器檔案cascade.xml?

  方法:此時需要修改訓練指令的【-numStages】將其設定成已有的states的層數,則其則會生成上面的檢測檔案xml。其如下:

至此已經訓練完成,並得到cascade.xml檢測檔案,接下來就是如何使用這個檔案用於人臉檢測,其方法是使用opencv3書裡提供的人臉檢測例子,只要替換相應的xml檔案即可,如下:

//---------------------------------【標頭檔案、名稱空間包含部分】----------------------------
//		描述:包含程式所使用的標頭檔案和名稱空間
//-------------------------------------------------------------------------------------------------
#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;




void detectAndDisplay( Mat frame );

//--------------------------------【全域性變數宣告】----------------------------------------------
//		描述:宣告全域性變數
//-------------------------------------------------------------------------------------------------
//注意,需要把"haarcascade_frontalface_alt.xml"和"haarcascade_eye_tree_eyeglasses.xml"這兩個檔案複製到工程路徑下
String face_cascade_name = "cascade.xml";
String eyes_cascade_name = "haarcascade_eye_tree_eyeglasses.xml";
CascadeClassifier face_cascade;
CascadeClassifier eyes_cascade;
string window_name = "Capture - Face detection";
RNG rng(12345);


//-----------------------------------【main( )函式】--------------------------------------------
//		描述:控制檯應用程式的入口函式,我們的程式從這裡開始
//-------------------------------------------------------------------------------------------------
int main( void )
{
  VideoCapture capture;
  Mat frame;


  //-- 1. 載入級聯(cascades)
  if( !face_cascade.load( face_cascade_name ) ){ printf("--(!)Error loading\n"); return -1; };
  if( !eyes_cascade.load( eyes_cascade_name ) ){ printf("--(!)Error loading\n"); return -1; };

  //-- 2. 讀取視訊
  capture.open(0);
  if( capture.isOpened() )
  {
    for(;;)
    {
      capture >> frame;

      if( !frame.empty() )
       { detectAndDisplay( frame ); }
      else
       { printf(" --(!) No captured frame -- Break!"); break; }

      int c = waitKey(10);
      if( (char)c == 'c' ) { break; }

    }
  }
  return 0;
}


void detectAndDisplay( Mat frame )
{
   std::vector<Rect> faces;
   Mat frame_gray;

   cvtColor( frame, frame_gray, COLOR_BGR2GRAY );
   equalizeHist( frame_gray, frame_gray );
   //-- 人臉檢測
   face_cascade.detectMultiScale( frame_gray, faces, 1.1, 3, 0| cv::CASCADE_SCALE_IMAGE, Size(20, 20) );

   for( size_t i = 0; i < faces.size(); i++ )
    {
      Point center( faces[i].x + faces[i].width/2, faces[i].y + faces[i].height/2 );
       
      ellipse( frame, center, Size( faces[i].width/2, faces[i].height/2), 0, 0, 360, Scalar( 255, 0, 255 ), 2, 8, 0 );

      Mat faceROI = frame_gray( faces[i] );
      std::vector<Rect> eyes;

      //-- 在臉中檢測眼睛
      eyes_cascade.detectMultiScale( faceROI, eyes, 1.1, 2, 0 |CV_HAAR_SCALE_IMAGE, Size(30, 30) );

      for( size_t j = 0; j < eyes.size(); j++ )
       {
         Point eye_center( faces[i].x + eyes[j].x + eyes[j].width/2, faces[i].y + eyes[j].y + eyes[j].height/2 );
         int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
         circle( frame, eye_center, radius, Scalar( 255, 0, 0 ), 3, 8, 0 );
       }
    }
   //-- 顯示最終效果圖
   imshow( window_name, frame );
}
"cascade.xml";
String eyes_cascade_name = "haarcascade_eye_tree_eyeglasses.xml";
CascadeClassifier face_cascade;
CascadeClassifier eyes_cascade;
string window_name = "Capture - Face detection";
RNG rng(12345);


//-----------------------------------【main( )函式】--------------------------------------------
//		描述:控制檯應用程式的入口函式,我們的程式從這裡開始
//-------------------------------------------------------------------------------------------------
int main( void )
{
  VideoCapture capture;
  Mat frame;


  //-- 1. 載入級聯(cascades)
  if( !face_cascade.load( face_cascade_name ) ){ printf("--(!)Error loading\n"); return -1; };
  if( !eyes_cascade.load( eyes_cascade_name ) ){ printf("--(!)Error loading\n"); return -1; };

  //-- 2. 讀取視訊
  capture.open(0);
  if( capture.isOpened() )
  {
    for(;;)
    {
      capture >> frame;

      if( !frame.empty() )
       { detectAndDisplay( frame ); }
      else
       { printf(" --(!) No captured frame -- Break!"); break; }

      int c = waitKey(10);
      if( (char)c == 'c' ) { break; }

    }
  }
  return 0;
}


void detectAndDisplay( Mat frame )
{
   std::vector<Rect> faces;
   Mat frame_gray;

   cvtColor( frame, frame_gray, COLOR_BGR2GRAY );
   equalizeHist( frame_gray, frame_gray );
   //-- 人臉檢測
   face_cascade.detectMultiScale( frame_gray, faces, 1.1, 3, 0| cv::CASCADE_SCALE_IMAGE, Size(20, 20) );

   for( size_t i = 0; i < faces.size(); i++ )
    {
      Point center( faces[i].x + faces[i].width/2, faces[i].y + faces[i].height/2 );
       
      ellipse( frame, center, Size( faces[i].width/2, faces[i].height/2), 0, 0, 360, Scalar( 255, 0, 255 ), 2, 8, 0 );

      Mat faceROI = frame_gray( faces[i] );
      std::vector<Rect> eyes;

      //-- 在臉中檢測眼睛
      eyes_cascade.detectMultiScale( faceROI, eyes, 1.1, 2, 0 |CV_HAAR_SCALE_IMAGE, Size(30, 30) );

      for( size_t j = 0; j < eyes.size(); j++ )
       {
         Point eye_center( faces[i].x + eyes[j].x + eyes[j].width/2, faces[i].y + eyes[j].y + eyes[j].height/2 );
         int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
         circle( frame, eye_center, radius, Scalar( 255, 0, 0 ), 3, 8, 0 );
       }
    }
   //-- 顯示最終效果圖
   imshow( window_name, frame );
}