第一步,準備資料。
原始資料采用FDDB人臉檢測測評資料集,FDDB是全世界最具權威的人臉檢測評測平台之一,包含2845張圖檔,共有5171個人臉作為測試集。測試集範圍包括:不同姿勢、不同分辨率、旋轉和遮擋等圖檔,同時包括灰階圖和彩色圖,标準的人臉标注區域為橢圓形。
當然,為了簡單起見,我們不直接使用這個資料集。我的做法是,自己做一些正樣本和負樣本資料集。正樣本,即人臉,從FDDB資料集中選擇一些圖檔,然後手動裁剪出人臉部分的圖檔,大概裁剪了100張。部分人臉如下圖:

負樣本,即非人臉,采用程式,每隔一定的間隔滑動裁剪。當然,可能會裁剪出一些人臉,這些需要手動除去。其中,裁剪圖像的大小有幾種,分别是64*64,100*100,128*128,144*144,160*160,總共截取了大概500張圖檔。部分圖像結果如下:
訓練資料集,從上述的正樣本中拿出90張,負樣本中拿出400張,作為訓練集。将剩下的正負樣本作為測試集。
第二步,提取特征。此處特征采用HOG特征。
具體步驟如下:
1)讀取訓練的圖檔
2)将訓練圖檔縮放至64*64大小
3)将圖像轉換為灰階圖像
4)對灰階圖像求出HOG特征。
得到的HOG為1*144大小的Mat類型的資料。由于下一步SVM訓練中,送入SVM的訓練資料為Mat類型的矩陣,其中每一行表示一個訓練資料,故SVM訓練資料Mat大小為n*144,n表示訓練集數量。是以,需要先建立一個n*144的Mat類型資料,然後将每個圖像的HOG特征複制到剛才産生的Mat資料,每個圖像的HOG特征為剛才産生的Mat資料的一行。
第三步,訓練SVM。opencv3自帶SVM,隻需要簡單幾步設定即可。
函數如下:
1)建立SVM
cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::create();
2)設定SVM類型
svm->setType(cv::ml::SVM::Types::C_SVC);
3)設定核函數為線性核函數
svm->setKernel(cv::ml::SVM::KernelTypes::LINEAR);
4)設定疊代終止條件
svm->setTermCriteria(cv::TermCriteria(cv::TermCriteria::MAX_ITER, 100, 1e-6));
5)開始訓練
svm->train(sampleFeatureMat, cv::ml::SampleTypes::ROW_SAMPLE, sampleLabelMat);
6)儲存訓練後的SVM參數
svm->save("svm_save.xml");
注:一些參數說明
svm_type –指定SVM的類型,下面是可能的取值:
CvSVM::C_SVC C類支援向量分類機。 n類分組 (n \geq 2),允許用異常值懲罰因子C進行不完全分類。
CvSVM::NU_SVC \nu類支援向量分類機。n類似然不完全分類的分類器。參數為 \nu 取代C(其值在區間【0,1】中,nu越大,決策邊界越平滑)。
CvSVM::ONE_CLASS 單分類器,所有的訓練資料提取自同一個類裡,然後SVM建立了一個分界線以分割該類在特征空間中所占區域和其它類在特征空間中所占區域。
CvSVM::EPS_SVR \epsilon類支援向量回歸機。訓練集中的特征向量和拟合出來的超平面的距離需要小于p。異常值懲罰因子C被采用。
CvSVM::NU_SVR \nu類支援向量回歸機。 \nu 代替了 p。
kernel_type –SVM的核心類型,下面是可能的取值:
CvSVM::LINEAR 線性核心。沒有任何向映射至高維空間,線性區分(或回歸)在原始特征空間中被完成,這是最快的選擇。K(x_i, x_j) = x_i^T x_j.
CvSVM::POLY 多項式核心: K(x_i, x_j) = (\gamma x_i^T x_j + coef0)^{degree},\gamma > 0.
CvSVM::RBF 基于徑向的函數,對于大多數情況都是一個較好的選擇: K(x_i, x_j) = e^{-\gamma ||x_i - x_j||^2},\gamma > 0.
CvSVM::SIGMOID Sigmoid函數核心:K(x_i, x_j) = \tanh(\gamma x_i^T x_j +coef0).
degree –核心函數(POLY)的參數degree。
gamma –核心函數(POLY/ RBF/ SIGMOID)的參數\gamma。
coef0 –核心函數(POLY/ SIGMOID)的參數coef0。
Cvalue – SVM類型(C_SVC/ EPS_SVR/ NU_SVR)的參數C。
nu – SVM類型(NU_SVC/ ONE_CLASS/ NU_SVR)的參數 \nu。
p – SVM類型(EPS_SVR)的參數 \epsilon。
class_weights – C_SVC中的可選權重,賦給指定的類,乘以C以後變成 class\_weights_i * C。是以這些權重影響不同類别的錯誤分類懲罰項。權重越大,某一類别的誤分類資料的懲罰項就越大。
term_crit – SVM的疊代訓練過程的中止條件,解決部分受限制二次最優問題。您可以指定的公差和/或最大疊代次數。
第四步,測試。将新的圖檔送入SVM中,讓SVM預測結果。
總體程式如下:
(測試環境:Win7 64位+ Visual Studio 2015 + Opencv310)
#include<iostream>
#include <fstream>
#include<opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/ml.hpp"
using namespace std;
using namespace cv;
//Parameters
#define N_BINS 16 //Number of bins
#define N_DIVS 3 //Number of cells = N_DIVS*N_DIVS
#define N_PHOG N_DIVS*N_DIVS*N_BINS
#define BIN_RANGE (2*CV_PI)/N_BINS
//Haar Cascade Path
//Input: Grayscale image
//Output: HOG features
Mat hog(const Mat &Img);
#define PosSamNO 90 //正樣本個數
#define NegSamNO 400 //負樣本個數
#define HardExampleNO 0
#define TRAIN true //是否進行訓練,true表示重新訓練,false表示讀取xml檔案中的SVM模型
int main()
{
// initial SVM
cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::create();
int DescriptorDim;//HOG描述子的維數,由圖檔大小、檢測視窗大小、塊大小、細胞單元中直方圖bin個數決定
Mat sampleFeatureMat;//所有訓練樣本的特征向量組成的矩陣,行數等于所有樣本的個數,列數等于HOG描述子維數
Mat sampleLabelMat;//訓練樣本的類别向量,行數等于所有樣本的個數,列數等于1;1表示有人,-1表示無人
if (TRAIN)
{
//依次讀取正樣本圖檔,生成HOG特征
for (int i = 1; i <= PosSamNO; i++)
{
char pic_name[64];
sprintf(pic_name, "E:\\data\\face\\pos\\%04d.jpg", i);
// cout << pic_name << endl;
Mat src = imread(pic_name);//讀取圖檔
resize(src, src, Size(64, 64));//将圖檔大小縮放為64*64
Mat img_gray;
cvtColor(src, img_gray, CV_BGR2GRAY);//将彩色圖檔轉換為灰階圖
Mat feature = hog(img_gray);//提取HOG特征
if (1 == i)
{
DescriptorDim = feature.cols;//feature.size();//HOG描述子的維數
//初始化所有訓練樣本的特征向量組成的矩陣,行數等于所有樣本的個數,列數等于HOG描述子維數sampleFeatureMat
sampleFeatureMat = Mat::zeros(PosSamNO + NegSamNO + HardExampleNO, DescriptorDim, CV_32FC1);
//初始化訓練樣本的類别向量,行數等于所有樣本的個數,列數等于1;1表示有人,0表示無人
sampleLabelMat = Mat::zeros(PosSamNO + NegSamNO + HardExampleNO, 1, CV_32SC1);
}
for (int j = 0; j < DescriptorDim; j++)
sampleFeatureMat.at<float>(i - 1, j) = feature.at<float>(0, j);//第i個樣本的特征向量中的第j個元素
sampleLabelMat.at<int>(i - 1, 0) = 1;//正樣本類别為1,是人臉
}
//依次讀取負樣本圖檔,生成HOG特征
for (int i = 1; i <= NegSamNO; i++)
{
char pic_name[64];
sprintf(pic_name, "E:\\data\\face\\neg2\\%04d.jpg", i);
// cout << pic_name << endl;
Mat src = imread(pic_name);//讀取圖檔
resize(src, src, Size(64, 64));
Mat img_gray;
cvtColor(src, img_gray, CV_BGR2GRAY);
Mat feature = hog(img_gray);
for (int j = 0; j < DescriptorDim; j++)
sampleFeatureMat.at<float>(PosSamNO + i - 1, j) = feature.at<float>(0, j);//第i個樣本的特征向量中的第j個元素
sampleLabelMat.at<int>(PosSamNO + i - 1, 0) = -1;//負樣本類别為1,非人臉
}
輸出樣本的HOG特征向量矩陣到檔案
//ofstream fout("SampleFeatureMat.txt");
//for (int i = 0; i < PosSamNO + NegSamNO; i++)
//{
// fout << i << endl;
// for (int j = 0; j < DescriptorDim; j++)
// fout << sampleFeatureMat.at<float>(i, j) << " ";
// fout << endl;
//}
//訓練SVM分類器
//cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::create();
svm->setType(cv::ml::SVM::Types::C_SVC);//設定SVM類型
svm->setKernel(cv::ml::SVM::KernelTypes::LINEAR);//設定核函數
svm->setTermCriteria(cv::TermCriteria(cv::TermCriteria::MAX_ITER, 100, 1e-6));
// train operation
svm->train(sampleFeatureMat, cv::ml::SampleTypes::ROW_SAMPLE, sampleLabelMat);
svm->save("svm_save.xml");
}
else//若TRAIN為false,從XML檔案讀取訓練好的分類器
{
String filename = "svm_save.xml";
svm = cv::ml::StatModel::load<cv::ml::SVM>(filename);
//svm->load(filename);
}
//下面開始預測
for (int i = 1; i < 22; i++)
{
char pic_name[64];
sprintf(pic_name, "E:\\data\\face\\test\\%04d.jpg", i);
cout << pic_name << ":";
Mat src = imread(pic_name);//讀取圖檔
resize(src, src, Size(64, 64));
Mat img_gray;
cvtColor(src, img_gray, CV_BGR2GRAY);
Mat feature = hog(img_gray);
float respose = svm->predict(feature);
if (respose == 1)
cout << "人臉" << endl;
else if (respose == -1)
cout << "非人臉" << endl;
}
getchar();
return 0;
}
Mat hog(const Mat &Img)
{
Mat Hog;
Hog = Mat::zeros(1, N_PHOG, CV_32FC1);
Mat Ix, Iy;
//Find orientation gradients in x and y directions
Sobel(Img, Ix, CV_16S, 1, 0, 3);
Sobel(Img, Iy, CV_16S, 0, 1, 3);
int cellx = Img.cols / N_DIVS;
int celly = Img.rows / N_DIVS;
int img_area = Img.rows * Img.cols;
for (int m = 0; m < N_DIVS; m++)
{
for (int n = 0; n < N_DIVS; n++)
{
for (int i = 0; i<cellx; i++)
{
for (int j = 0; j<celly; j++)
{
float px, py, grad, norm_grad, angle, nth_bin;
//px = Ix.at(m*cellx+i, n*celly+j);
//py = Iy.at(m*cellx+i, n*celly+j);
px = static_cast<float>(Ix.at<int16_t>((m*cellx) + i, (n*celly) + j));
py = static_cast<float>(Iy.at<int16_t>((m*cellx) + i, (n*celly) + j));
grad = static_cast<float>(std::sqrt(1.0*px*px + py*py));
norm_grad = grad / img_area;
//Orientation
angle = std::atan2(py, px);
//convert to 0 to 360 (0 to 2*pi)
if (angle < 0)
angle += 2 * CV_PI;
//find appropriate bin for angle
nth_bin = angle / BIN_RANGE;
//add magnitude of the edges in the hog matrix
Hog.at<float>(0, (m*N_DIVS + n)*N_BINS + static_cast<int>(angle)) += norm_grad;
}
}
}
}
//Normalization
for (int i = 0; i< N_DIVS*N_DIVS; i++)
{
float max = 0;
int j;
for (j = 0; j<N_BINS; j++)
{
if (Hog.at<float>(0, i*N_BINS + j) > max)
max = Hog.at<float>(0, i*N_BINS + j);
}
for (j = 0; j<N_BINS; j++)
Hog.at<float>(0, i*N_BINS + j) /= max;
}
return Hog;
}
測試集如下:
預測結果如下:
結果均正确。
說明:這隻是一個簡單的例子,要想真正很好地實作人臉檢測,還需要很多改進。