opencv机器学习 Haar特征 LBP特征 adaboost集成学习 级联分类器 支持向量机SVM 主成分分析PCA 人工神经网络(ANN) 笑脸检测 SVM分类 笑脸 检测

github地址

一、Haar特征 级联分类器 (CascadeClassifier)

AdaBoost强分类器串接
级联分类器是将若干个分类器进行连接,从而构成一种多项式级的强分类器。
从弱分类器到强分类器的级联(AdaBoost 集成学习  改变训练集)

级联分类器使用前要先进行训练,怎么训练?


用目标的特征值去训练,对于人脸来说,通常使用Haar特征进行训练。
其他还有 LBP特征


【1】提出积分图(Integral image) 加速特征提取。在该论文中作者使用的是Haar-like特征,
       然后使用积分图能够非常迅速的计算不同尺度上的Haar-like特征。
【2】使用AdaBoost作为  特征选择的方法选择少量的特征在使用AdaBoost构造强分类器。
【3】以级联的方式,从简单到 复杂 逐步 串联 强分类器,形成 级联分类器。

级联分类器。该分类器由若干个简单的AdaBoost强分类器串接得来。
假设AdaBoost分类器要实现99%的正确率,1%的误检率需要200维特征,
而实现具有99.9%正确率和50%的误检率的AdaBoost分类器仅需要10维特征,
那么通过级联,假设10级级联,最终得到的正确率和误检率分别为:
(99.9%)^10 = 99%
(0.5)^10   = 0.1

检测体系:是以现实中很大一副图片作为输入,然后对图片中进行多区域,多尺度的检测,

所谓多区域,是要对图片划分多块,对每个块进行检测,

由于训练的时候一般图片都是20*20左右的小图片,

所以对于大的人脸,还需要进行多尺度的检测。

多尺度检测一般有两种策略:

    一种    是不改变搜索窗口的大小,而不断缩放图片,

        这种方法需要对每个缩放后的图片进行区域特征值的运算,效率不高,

  另一种方法 改变搜索窗口,

       是不断初始化搜索窗口size为训练时的图片大小,不断扩大搜索窗口进行搜索。

在区域放大的过程中会出现同一个人脸被多次检测,这需要进行区域的合并。
无论哪一种搜索方法,都会为输入图片输出大量的子窗口图像,
这些子窗口图像经过筛选式级联分类器会不断地被每个节点筛选,抛弃或通过。


级联分类器的策略是,将若干个强分类器由简单到复杂排列,
希望经过训练使每个强分类器都有较高检测率,而误识率可以放低。

AdaBoost训练出来的强分类器一般具有较小的误识率,但检测率并不很高,
一般情况下,高检测率会导致高误识率,这是强分类阈值的划分导致的,
要提高强分类器的检测率既要降低阈值,要降低强分类器的误识率就要提高阈值,
这是个矛盾的事情。据参考论文的实验结果,
增加分类器个数可以在提高强分类器检测率的同时降低误识率,

所以级联分类器在训练时要考虑如下平衡,

一是弱分类器的个数和计算时间的平衡,

二是强分类器检测率和误识率之间的平衡。

#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

//函数声明
void detectAndDisplay( Mat frame );

//全局变量
string face_cascade_name = "../../common/data/cascade/haarcascades/haarcascade_frontalface_alt.xml";
string eyes_cascade_name = "../../common/data/cascade/haarcascades/haarcascade_eye_tree_eyeglasses.xml";
// 级联分类器 类
CascadeClassifier face_cascade;
CascadeClassifier eyes_cascade;

string window_name = "Capture - Face detection";
//RNG rng(12345);

// @主函数
int main( int argc, const char** argv )
{
	//CvCapture* capture;
	VideoCapture capture;
	Mat frame;

//==========【1】 加载级联分类器文件模型==============
	// 加载级联分类器
	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( -1 );
        if ( ! capture.isOpened() ) { printf("--(!)Error opening video capture\n"); return -1; }
	while( capture.read(frame) )
	{
//=========【3】对当前帧使用分类器进行检测============
		if( !frame.empty() )
		{ detectAndDisplay( frame ); }
		else
		{ printf(" --(!) No captured frame -- Break!"); break; }

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

// @函数 detectAndDisplay 
void detectAndDisplay( Mat frame )
{
	std::vector<Rect> faces;//检测到的人脸 矩形区域 左下点坐标 长和宽
	Mat frame_gray;

	cvtColor( frame, frame_gray, CV_BGR2GRAY );//转换成灰度图
	equalizeHist( frame_gray, frame_gray );//直方图均衡画

	//-- 多尺寸检测人脸
	face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30) );
	// image:当然是输入图像了,要求是8位无符号图像,即灰度图
	//objects:输出向量容器(保存检测到的物体矩阵)
	//scaleFactor:每张图像缩小的尽寸比例 1.1 即每次搜索窗口扩大10%
	//minNeighbors:每个候选矩阵应包含的像素领域
	//flags:表示此参数模型是否更新标志位; 0|CV_HAAR_SCALE_IMAGE   0|CASCADE_FIND_BIGGEST_OBJECT  CASCADE_DO_ROUGH_SEARCH
	//minSize :表示最小的目标检测尺寸;
	//maxSize:表示最大的目标检测尺寸;
	//
	for( int i = 0; i < faces.size(); i++ )//画出椭圆区域
	{//矩形中心点
	   Point center( faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5 );
	   ellipse( frame, center, Size( faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, Scalar( 255, 0, 255 ), 4, 8, 0 );

	   Mat faceROI = frame_gray( faces[i] );//对应区域的像素图片
	   std::vector<Rect> eyes;//眼睛

	   //-- 在每张人脸上检测双眼
	   eyes_cascade.detectMultiScale( faceROI, eyes, 1.1, 2, 
					0 
					//|CASCADE_FIND_BIGGEST_OBJECT
					//|CASCADE_DO_ROUGH_SEARCH
					|CV_HAAR_SCALE_IMAGE, Size(30, 30) );

	   for( int j = 0; j < eyes.size(); j++ )
	   {    // 中心点
		Point center( faces[i].x + eyes[j].x + eyes[j].width*0.5,
		              faces[i].y + eyes[j].y + eyes[j].height*0.5 );
		int radius = cvRound( (eyes[j].width + eyes[i].height)*0.25 );
		circle( frame, center, radius, Scalar( 255, 0, 0 ), 4, 8, 0 );// 画圆
	}
	}
	//-- 显示结果图像 显示人脸椭圆区域  人眼睛
	imshow( window_name, frame );
}


二、LBP特征级联分类器

 与Haar特征相比,LBP特征是整数特征,因此训练和检测过程都会比Haar特征快几倍。
LBP和Haar特征用于检测的准确率,是依赖训练过程中的训练数据的质量和训练参数。
训练一个与基于Haar特征同样准确度的LBP的分类器是可能的。

级联分类器的策略是,将若干个( AdaBoost训练出来的强分类器 ) 由简单到复杂排列,

 希望经过训练使每个强分类器都有较高检测率,而误识率可以放低。

#include "opencv2/objdetect.hpp"
#include "opencv2/videoio.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"

#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

//函数声明
void detectAndDisplay( Mat frame );

//全局变量
String face_cascade_name = "../../common/data/cascade/lbpcascades/lbpcascade_frontalface.xml";
String eyes_cascade_name = "../../common/data/cascade/haarcascades/haarcascade_eye_tree_eyeglasses.xml";
CascadeClassifier face_cascade;//这里可是试试 指针方式  会快一些
// CascadeClassifier* face_cascade_prt;
CascadeClassifier eyes_cascade;
String window_name = "Capture - Face detection";

// @主函数
int main( void )
{
	VideoCapture capture;
	Mat frame;

//==========【1】 加载级联分类器文件模型==============
	// 加载级联分类器
        // face_cascade_prt = new CascadeClassifier();//指针方式
        //  face_cascade_prt->load( face_cascade_name )   
	if( !face_cascade.load( face_cascade_name ) ){ printf("--(!)Error loading face cascade\n"); return -1; };
	if( !eyes_cascade.load( eyes_cascade_name ) ){ printf("--(!)Error loading eyes cascade\n"); return -1; };

//==========【2】打开内置摄像头视频流==============
	capture.open( -1 );
	if ( ! capture.isOpened() ) { printf("--(!)Error opening video capture\n"); return -1; }

	while ( capture.read(frame) )
	{
	if( frame.empty() )
	{
	    printf(" --(!) No captured frame -- Break!");
	    break;
	}

//=========【3】对当前帧使用分类器进行检测============
	detectAndDisplay( frame );

	//-- bail out if escape was pressed
	int c = waitKey(10);
	if( (char)c == 27 ) { break; }
	}
	return 0;
}

// @函数 detectAndDisplay 
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, 2, 0, Size(80, 80) );
	// image:当然是输入图像了,要求是8位无符号图像,即灰度图
	//objects:输出向量容器(保存检测到的物体矩阵)
	//scaleFactor:每张图像缩小的尽寸比例 1.1 即每次搜索窗口扩大10%
	//minNeighbors:每个候选矩阵应包含的像素领域
	//flags:表示此参数模型是否更新标志位; 0|CV_HAAR_SCALE_IMAGE   0|CASCADE_FIND_BIGGEST_OBJECT  CASCADE_DO_ROUGH_SEARCH
	//minSize :表示最小的目标检测尺寸;
	//maxSize:表示最大的目标检测尺寸;
	for( size_t i = 0; i < faces.size(); i++ )
	{
	Mat faceROI = frame_gray( faces[i] );
	std::vector<Rect> eyes;

	//-- In each face, detect eyes
	eyes_cascade.detectMultiScale( faceROI, eyes, 1.1, 2, 0 |CASCADE_SCALE_IMAGE, Size(30, 30) );
	if( eyes.size() == 2)
	{
	    // 画出椭圆区域
	    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, 0 ), 2, 8, 0 );
	    //-- 在每张人脸上检测双眼
	    for( size_t j = 0; j < eyes.size(); j++ )
	    { //-- Draw the eyes // 中心点
		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, 255 ), 3, 8, 0 );// 画圆
	    }
	}

	}
//====显示结果图像 显示人脸椭圆区域  人眼睛=====
	imshow( window_name, frame );
}



三、支持向量机(SVM) 分类器 Support Vector Machines

支持向量机 (SVM) 是一个类分类器,正式的定义是一个能够将不同类样本
在样本空间分隔的超平面(n-1 demension)。
 换句话说,给定一些标记(label)好的训练样本 (监督式学习),
SVM算法输出一个最优化的分隔超平面。

假设给定一些分属于两类的2维点,这些点可以通过直线分割, 我们要找到一条最优的分割线.

|
|
|              +
|            +    +
|               +
|  *               +
|                        +
|   *   *           +
|   *                 +
|  *      *             +
|   *    *
——————————————————————————————————>

w * 超平面 = 0  ,w 为超平面(分割面)的垂线段向量
x+ 为正类    x- 为负类
w * x+ + b >= 1   向量点乘  在河另一边的为 正类
w * x- + b <= -1
 
w * x+0 + b =  1        ,    y = +1  河边样本
w * x-0 + b = -1         ,   y = -1

河宽
(x+0 - x-0) * w/||w|| =
 
x+0 * w/||w||  - x-0 * w/||w|| =

(1-b) /||w|| - (-1-b)/||w||  =

2 * /||w||   maxmize
=======>

min 1/2 * ||w||^2

这是一个拉格朗日优化问题

L = 1/2 * ||w||^2 - SUM(a * yi *( w* xi +b) - 1)

dL/dw  = ||w||  - sum(a * yi * xi)   令为  0    计算 w
 
dL/db  =  SUM(a * yi)                令为  to 0     

====>
||w||  = sum(a * yi * xi)
SUM(a * yi)  = 0
=====>

L =  -1/2  SUM(SUM(ai*aj * yi*yj * xi*xi))


#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include "opencv2/imgcodecs.hpp"
#include <opencv2/highgui.hpp>
#include <opencv2/ml.hpp>
using namespace cv;
using namespace cv::ml;
int main(int, char**)
{
    // 图像大小
    int width = 512, height = 512;
    Mat image = Mat::zeros(height, width, CV_8UC3);

    // 建立训练样本 training data
    int labels[4] = {1, -1, -1, -1};// 由分属于两个类别的2维点组成, 其中一类包含一个样本点,另一类包含三个点。
    float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
    Mat trainingDataMat(4, 2, CV_32FC1, trainingData);
    Mat labelsMat(4, 1, CV_32SC1, labels);

    // Train the SVM
    Ptr<SVM> svm = SVM::create();
    svm->setType(SVM::C_SVC);// SVM类型.  C_SVC 该类型可以用于n-类分类问题 (n \geq 2)
    svm->setKernel(SVM::LINEAR);// SVM 核类型.  将训练样本映射到更有利于可线性分割的样本集
    // 算法终止条件.   最大迭代次数和容许误差
    svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
    // 训练支持向量
    svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);

    // SVM区域分割
    Vec3b green(0,255,0), blue (255,0,0);
    for (int i = 0; i < image.rows; ++i)
        for (int j = 0; j < image.cols; ++j)
        {
            Mat sampleMat = (Mat_<float>(1,2) << j,i);// generate data
            float response = svm->predict(sampleMat);// 分类类别
            if (response == 1)// 绿色表示标记为1的点,蓝色表示标记为-1的点。
                image.at<Vec3b>(i,j)  = green;
            else if (response == -1)
                image.at<Vec3b>(i,j)  = blue;
        }

    // Show the training data
    int thickness = -1;
    int lineType = 8;
    circle( image, Point(501,  10), 5, Scalar(  0,   0,   0), thickness, lineType );
    circle( image, Point(255,  10), 5, Scalar(255, 255, 255), thickness, lineType );
    circle( image, Point(501, 255), 5, Scalar(255, 255, 255), thickness, lineType );
    circle( image, Point( 10, 501), 5, Scalar(255, 255, 255), thickness, lineType );

    // 支持向量
    thickness = 2;
    lineType  = 8;
    Mat sv = svm->getUncompressedSupportVectors();
    for (int i = 0; i < sv.rows; ++i)
    {
        const float* v = sv.ptr<float>(i);
        circle( image,  Point( (int) v[0], (int) v[1]),   6,  Scalar(128, 128, 128), thickness, lineType);
    }

    imwrite("result.png", image);        // save the image
    imshow("SVM Simple Example", image); // show it to the user
    waitKey(0);
}

3.2 支持向量机对线性不可分数据的处理 考虑 错分类样本离同类区域的距离

为什么需要将支持向量机优化问题扩展到线性不可分的情形?
在多数计算机视觉运用中,我们需要的不仅仅是一个简单的SVM线性分类器,
我们需要更加强大的工具来解决 训练数据无法用一个超平面分割 的情形。

我们以人脸识别来做一个例子,训练数据包含一组人脸图像和一组非人脸图像(除了人脸之外的任何物体)。
这些训练数据超级复杂,以至于为每个样本找到一个合适的表达 (特征向量) 以让它们能够线性分割是非常困难的。


最优化问题的扩展

还记得我们用支持向量机来找到一个最优超平面。 既然现在训练数据线性不可分,
我们必须承认这个最优超平面会将一些样本划分到错误的类别中。 在这种情形下的优化问题,
需要将 错分类(misclassification) 当作一个变量来考虑。
新的模型需要包含原来线性可分情形下的最优化条件,即最大间隔(margin),
以及在线性不可分时分类错误最小化。


比如,我们可以最小化一个函数,该函数定义为在原来模型的基础上再加上一个常量乘以样本被错误分类的次数:
L = 1/2 * ||w||^2 - SUM(a * yi *( w* xi +b) - 1)  +  c * ( 样本被错误分类的次数)

它没有考虑错分类的样本距离同类样本所属区域的大小。 因此一个更好的方法是考虑 错分类样本离同类区域的距离:

L = 1/2 * ||w||^2 - SUM(a * yi *( w* xi +b) - 1)  +  c * ( 错分类样本离同类区域的距离)


   C比较大时分类错误率较小,但是间隔也较小。 在这种情形下,
错分类对模型函数产生较大的影响,既然优化的目的是为了最小化这个模型函数,
那么错分类的情形必然会受到抑制。

   C比较小时间隔较大,但是分类错误率也较大。 在这种情形下,
模型函数中错分类之和这一项对优化过程的影响变小,
优化过程将更加关注于寻找到一个能产生较大间隔的超平面。


#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include "opencv2/imgcodecs.hpp"
#include <opencv2/highgui.hpp>
#include <opencv2/ml.hpp>

#define NTRAINING_SAMPLES   100 // 每类训练样本数量  

#define FRAC_LINEAR_SEP     0.9f// Fraction of samples which compose the linear separable part

using namespace cv;
using namespace cv::ml;
using namespace std;

static void help()
{
    cout<< "\n--------------------------------------------------------------------------" << endl
        << "This program shows Support Vector Machines for Non-Linearly Separable Data. " << endl
        << "Usage:"                                                               << endl
        << "./non_linear_svms" << endl
        << "--------------------------------------------------------------------------"   << endl
        << endl;
}
int main()
{
    help();

    // 图像大小
    const int WIDTH = 512, HEIGHT = 512;
    Mat I = Mat::zeros(HEIGHT, WIDTH, CV_8UC3);

//======【1】建立训练样本 =======================================
    Mat trainData(2*NTRAINING_SAMPLES, 2, CV_32FC1);//训练数据
    Mat labels   (2*NTRAINING_SAMPLES, 1, CV_32SC1);//标签
    RNG rng(100); //随机数
    // 线性规律 数据量
    int nLinearSamples = (int) (FRAC_LINEAR_SEP * NTRAINING_SAMPLES);

    //=== 线性规律 数据 class 1=========
    Mat trainClass = trainData.rowRange(0, nLinearSamples);
    // The x coordinate of the points is in [0, 0.4)
    Mat c = trainClass.colRange(0, 1);
    rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(0.4 * WIDTH));
    // The y coordinate of the points is in [0, 1)
    c = trainClass.colRange(1,2);
    rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(HEIGHT));

//===== 非 线性规律 数据  class 2==========
    trainClass = trainData.rowRange(2*NTRAINING_SAMPLES-nLinearSamples, 2*NTRAINING_SAMPLES);
    // The x coordinate of the points is in [0.6, 1]
    c = trainClass.colRange(0 , 1);
    rng.fill(c, RNG::UNIFORM, Scalar(0.6*WIDTH), Scalar(WIDTH));
    // The y coordinate of the points is in [0, 1)
    c = trainClass.colRange(1,2);
    rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(HEIGHT));

//=====非 线性规律 数据 ===========
    // Generate random points for the classes 1 and 2
    trainClass = trainData.rowRange(  nLinearSamples, 2*NTRAINING_SAMPLES-nLinearSamples);
    // The x coordinate of the points is in [0.4, 0.6)
    c = trainClass.colRange(0,1);
    rng.fill(c, RNG::UNIFORM, Scalar(0.4*WIDTH), Scalar(0.6*WIDTH));
    // The y coordinate of the points is in [0, 1)
    c = trainClass.colRange(1,2);
    rng.fill(c, RNG::UNIFORM, Scalar(1), Scalar(HEIGHT));

//=========Set up the labels for the classes ===============
    labels.rowRange(                0,   NTRAINING_SAMPLES).setTo(1);  // Class 1
    labels.rowRange(NTRAINING_SAMPLES, 2*NTRAINING_SAMPLES).setTo(2);  // Class 2


//============= 2. Set up the support vector machines parameters ===============
    //=======3. Train the svm =======
    cout << "Starting training process" << endl;
    Ptr<SVM> svm = SVM::create();
    svm->setType(SVM::C_SVC);// SVM类型.  C_SVC 该类型可以用于n-类分类问题 (n \geq 2)
    svm->setC(0.1);//  错分类样本离同类区域的距离 的权重
    svm->setKernel(SVM::LINEAR);// SVM 核类型.  将训练样本映射到更有利于可线性分割的样本集
    // 算法终止条件.   最大迭代次数和容许误差
    svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, (int)1e7, 1e-6));
    svm->train(trainData, ROW_SAMPLE, labels);
    cout << "Finished training process" << endl;

//================ 4. Show the decision regions ==================
    Vec3b green(0,100,0), blue (100,0,0);
    for (int i = 0; i < I.rows; ++i)
        for (int j = 0; j < I.cols; ++j)
        {
            Mat sampleMat = (Mat_<float>(1,2) << i, j);
            float response = svm->predict(sampleMat);
            if      (response == 1)    I.at<Vec3b>(j, i)  = green;
            else if (response == 2)    I.at<Vec3b>(j, i)  = blue;
        }
//================5. Show the training data ====================
    int thick = -1;
    int lineType = 8;
    float px, py;
 // Class 1===============
    for (int i = 0; i < NTRAINING_SAMPLES; ++i)
    {
        px = trainData.at<float>(i,0);
        py = trainData.at<float>(i,1);
        circle(I, Point( (int) px,  (int) py ), 3, Scalar(0, 255, 0), thick, lineType);
    }
  // Class 2======================
    for (int i = NTRAINING_SAMPLES; i <2*NTRAINING_SAMPLES; ++i)
    {
        px = trainData.at<float>(i,0);
        py = trainData.at<float>(i,1);
        circle(I, Point( (int) px, (int) py ), 3, Scalar(255, 0, 0), thick, lineType);
    }

//============== 6. Show support vectors =====================
    thick = 2;
    lineType  = 8;
    Mat sv = svm->getSupportVectors();
    for (int i = 0; i < sv.rows; ++i)
    {
        const float* v = sv.ptr<float>(i);
        circle( I,  Point( (int) v[0], (int) v[1]), 6, Scalar(128, 128, 128), thick, lineType);
    }
    imwrite("result.png", I);                      // save the Image
    imshow("SVM for Non-Linear Training Data", I); // show it to the user
    waitKey(0);
}


四、主成分分析  数据降维   协方差矩阵 的特征值和特征向量

PCA(Principal Component Analysis)主成分分析
主要用于数据降维

样本  协方差矩阵 的特征值和特征向量  取前四个特征值所对应的特征向量
特征矩阵 投影矩阵


对于一组样本的feature组成的多维向量,多维向量里的某些元素本身没有区分
我们的目的是找那些变化大的元素,即方差大的那些维,
而去除掉那些变化不大的维,从而使feature留下的都是最能代表此元素的“精品”,
而且计算量也变小了。

对于一个k维的feature来说,相当于它的每一维feature与
其他维都是正交的(相当于在多维坐标系中,坐标轴都是垂直的),
那么我们可以变化这些维的坐标系,从而使这个feature在某些维上方差大,
而在某些维上方差很


求得一个k维特征的投影矩阵,这个投影矩阵可以将feature从高维降到低维。
投影矩阵也可以叫做变换矩阵。新的低维特征必须每个维都正交,特征向量都是正交的

通过求样本矩阵的协方差矩阵,然后求出协方差矩阵的特征向量,这些特征向量就可以构成这个投影矩阵了。
特征向量的选择取决于协方差矩阵的特征值的大

对于一个训练集,100个样本,feature是10维,那么它可以建立一个100*10的矩阵,作为样本。
求这个样本的协方差矩阵,得到一个10*10的协方差矩阵,然后求出这个协方差矩阵的特征值和特征向量,
应该有10个特征值和特征向量,我们根据特征值的大小,取前四个特征值所对应的特征向量,
构成一个10*4的矩阵,这个矩阵就是我们要求的特征矩阵,100*10的样本矩阵乘以这个10*4的特征矩阵,
就得到了一个100*4的新的降维之后的样本矩阵,每个样本的维数下


#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <iostream>

using namespace std;
using namespace cv;

// Function declarations
void drawAxis(Mat&, Point, Point, Scalar, const float);
double getOrientation(const vector<Point> &, Mat&);

/**
 * @function drawAxis
 */
void drawAxis(Mat& img, Point p, Point q, Scalar colour, const float scale = 0.2)
{
//! [visualization1]
    double angle;
    double hypotenuse;
    angle = atan2( (double) p.y - q.y, (double) p.x - q.x ); // angle in radians
    hypotenuse = sqrt( (double) (p.y - q.y) * (p.y - q.y) + (p.x - q.x) * (p.x - q.x));
//    double degrees = angle * 180 / CV_PI; // convert radians to degrees (0-180 range)
//    cout << "Degrees: " << abs(degrees - 180) << endl; // angle in 0-360 degrees range

    // Here we lengthen the arrow by a factor of scale
    q.x = (int) (p.x - scale * hypotenuse * cos(angle));
    q.y = (int) (p.y - scale * hypotenuse * sin(angle));
    line(img, p, q, colour, 1, LINE_AA);

    // create the arrow hooks
    p.x = (int) (q.x + 9 * cos(angle + CV_PI / 4));
    p.y = (int) (q.y + 9 * sin(angle + CV_PI / 4));
    line(img, p, q, colour, 1, LINE_AA);

    p.x = (int) (q.x + 9 * cos(angle - CV_PI / 4));
    p.y = (int) (q.y + 9 * sin(angle - CV_PI / 4));
    line(img, p, q, colour, 1, LINE_AA);
//! [visualization1]
}

/**
 * @function getOrientation
 */
double getOrientation(const vector<Point> &pts, Mat &img)
{
//! [pca]
    //Construct a buffer used by the pca analysis
    int sz = static_cast<int>(pts.size());
    Mat data_pts = Mat(sz, 2, CV_64FC1);
    for (int i = 0; i < data_pts.rows; ++i)
    {
        data_pts.at<double>(i, 0) = pts[i].x;
        data_pts.at<double>(i, 1) = pts[i].y;
    }

    //Perform PCA analysis
    PCA pca_analysis(data_pts, Mat(), PCA::DATA_AS_ROW);

    //Store the center of the object
    Point cntr = Point(static_cast<int>(pca_analysis.mean.at<double>(0, 0)),
                      static_cast<int>(pca_analysis.mean.at<double>(0, 1)));

    //Store the eigenvalues and eigenvectors
    vector<Point2d> eigen_vecs(2);
    vector<double> eigen_val(2);
    for (int i = 0; i < 2; ++i)
    {
        eigen_vecs[i] = Point2d(pca_analysis.eigenvectors.at<double>(i, 0),
                                pca_analysis.eigenvectors.at<double>(i, 1));

        eigen_val[i] = pca_analysis.eigenvalues.at<double>(i);
    }

//! [pca]
//! [visualization]
    // Draw the principal components
    circle(img, cntr, 3, Scalar(255, 0, 255), 2);
    Point p1 = cntr + 0.02 * Point(static_cast<int>(eigen_vecs[0].x * eigen_val[0]), static_cast<int>(eigen_vecs[0].y * eigen_val[0]));
    Point p2 = cntr - 0.02 * Point(static_cast<int>(eigen_vecs[1].x * eigen_val[1]), static_cast<int>(eigen_vecs[1].y * eigen_val[1]));
    drawAxis(img, cntr, p1, Scalar(0, 255, 0), 1);
    drawAxis(img, cntr, p2, Scalar(255, 255, 0), 5);

    double angle = atan2(eigen_vecs[0].y, eigen_vecs[0].x); // orientation in radians
//! [visualization]

    return angle;
}

/**
 * @function main
 */
int main(int argc, char** argv)
{
//! [pre-process]
    // Load image
    CommandLineParser parser(argc, argv, "{@input | ../../common/data/pca_test1.jpg | input image}");
    parser.about( "This program demonstrates how to use OpenCV PCA to extract the orienation of an object.\n" );
    parser.printMessage();

    Mat src = imread(parser.get<String>("@input"));

    // Check if image is loaded successfully
    if(src.empty())
    {
        cout << "Problem loading image!!!" << endl;
        return EXIT_FAILURE;
    }

    imshow("src", src);

    // Convert image to grayscale
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);

    // Convert image to binary
    Mat bw;
    threshold(gray, bw, 50, 255, THRESH_BINARY | THRESH_OTSU);
//! [pre-process]

//! [contours]
    // Find all the contours in the thresholded image
    vector<vector<Point> > contours;
    findContours(bw, contours, RETR_LIST, CHAIN_APPROX_NONE);

    for (size_t i = 0; i < contours.size(); ++i)
    {
        // Calculate the area of each contour
        double area = contourArea(contours[i]);
        // Ignore contours that are too small or too large
        if (area < 1e2 || 1e5 < area) continue;

        // Draw each contour only for visualisation purposes
        drawContours(src, contours, static_cast<int>(i), Scalar(0, 0, 255), 2, LINE_8);
        // Find the orientation of each shape
        getOrientation(contours[i], src);
    }
//! [contours]

    imshow("output", src);

    waitKey(0);
    return 0;
}

五、人工神经网络(ANN) 简称神经网络(NN)

人工神经网络(ANN) 简称神经网络(NN),
能模拟生物神经系统对物体所作出的交互反应,
是由具有适应性的简单单元(称为神经元)组成的广泛并行互连网络。
 神经元
 y = w  * x + b   线性变换   (旋转 平移 伸缩 升/降维)
 z = a(y)         非线性变换 激活函数

常用 Sigmoid 函数作激活函数

y = sigmod(x)  = 1/(1+exp(-x))  映射到0 ~ 1之间
 
 OpenCV 中使用的激活函数是另一种形式,

f(x) = b *  (1 - exp(-c*x)) / (1 + exp(-c*x))

当 α = β = 1 时
f(x) =(1 - exp(-x)) / (1 + exp(-x))
该函数把可能在较大范围内变化的输入值,“挤压” 到 (-1, 1) 的输出范围内

// 设置激活函数,目前只支持 ANN_MLP::SIGMOID_SYM
virtual void cv::ml::ANN_MLP::setActivationFunction(int type, double param1 = 0, double param2 = 0);

神经网络
2.1  感知机 (perceptron)
  感知机由两层神经元组成,输入层接收外界输入信号,而输出层则是一个 M-P 神经元。
  实际上,感知机可视为一个最简单的“神经网络”,用它可很容易的实现逻辑与、或、非等简单运算。

2.2 层级结构
  常见的神经网络,可分为三层:输入层、隐含层、输出层。
  输入层接收外界输入,隐层和输出层负责对信号进行加工,输出层输出最终的结果。

2.3  层数设置
      OpenCV 中,设置神经网络层数和神经元个数的函数为 setLayerSizes(InputArray _layer_sizes),
    // (a) 3层,输入层神经元个数为 4,隐层的为 6,输出层的为 4
    Mat layers_size = (Mat_<int>(1,3) << 4,6,4);

    // (b) 4层,输入层神经元个数为 4,第一个隐层的为 6,第二个隐层的为 5,输出层的为 4
    Mat layers_size = (Mat_<int>(1,4) << 4,6,5,4);

1)  创建
    static Ptr<ANN_MLP> cv::ml::ANN_MLP::create();  // 创建空模型

2) 设置参数

// 设置神经网络的层数和神经元数量
virtual void cv::ml::ANN_MLP::setLayerSizes(InputArray _layer_sizes);

// 设置激活函数,目前只支持 ANN_MLP::SIGMOID_SYM
virtual void cv::ml::ANN_MLP::setActivationFunction(int type, double param1 = 0, double param2 = 0);

// 设置训练方法,默认为 ANN_MLP::RPROP,较常用的是 ANN_MLP::BACKPROP
// 若设为 ANN_MLP::BACKPROP,则 param1 对应 setBackpropWeightScale()中的参数,
// param2 对应 setBackpropMomentumScale() 中的参数
virtual void cv::ml::ANN_MLP::setTrainMethod(int method, double param1 = 0, double param2 = 0);
virtual void cv::ml::ANN_MLP::setBackpropWeightScale(double val); // 默认值为 0.1
virtual void cv::ml::ANN_MLP::setBackpropMomentumScale(double val); // 默认值为 0.1
 
// 设置迭代终止准则,默认为 TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01)
virtual void cv::ml::ANN_MLP::setTermCriteria(TermCriteria val);

3)  训练

// samples - 训练样本; layout - 训练样本为 “行样本” ROW_SAMPLE 或 “列样本” COL_SAMPLE; response - 对应样本数据的分类结果
virtual bool cv::ml::StatModel::train(InputArray samples,int layout,InputArray responses);  

4)  预测

// samples,输入的样本书数据;results,输出矩阵,默认不输出;flags,标识,默认为 0
virtual float cv::ml::StatModel::predict(InputArray samples, OutputArray results=noArray(),int flags=0) const;      

5) 保存训练好的神经网络参数
    bool trained = ann->train(tData);  
    if (trained) {
          ann->save("ann_param");
     }

6) 载入训练好的神经网络
 方法1 Ptr<ml::ANN_MLP> ann = ml::ANN_MLP::load("ann_param");

neuralNetwork = Algorithm::load<ANN_MLP>(fileName);

#include<opencv2/opencv.hpp>  
#include <iostream>    
#include <string>    
  
using namespace std;  
using namespace cv;  
using namespace ml;  
int main()  
{  
    // 标签
    float labels[10][2]={ { 0.9,0.1 },
			  { 0.1,0.9 },
			  { 0.9,0.1 },
			  { 0.1,0.9 },
			  { 0.9,0.1 },
			  { 0.9,0.1 },
			  { 0.1,0.9 },
			  { 0.1,0.9 },
			  { 0.9,0.1 },
			  { 0.9,0.1 } };  
    //这里对于样本标记为0.1和0.9而非0和1,主要是考虑到sigmoid函数的输出为一般为0和1之间的数,
    // 只有在输入趋近于-∞和+∞才逐渐趋近于0和1,而不可能达到。  
    Mat labelsMat(10, 2, CV_32FC1, labels);  
    // 训练集
    float trainingData[10][2] = { { 11,12   },
				  { 111,112 },
				  { 21,22   },
			 	  { 211,212 },
				  { 51,32   },
				  { 71,42   },
				  { 441,412 },
				  { 311,312 },
				  { 41,62   },
				  { 81,52   } };  
    Mat trainingDataMat(10, 2, CV_32FC1, trainingData);  

    // BP 神经网络 模型  创建和参数设置
    Ptr<ANN_MLP> ann = ANN_MLP::create();
    //5层:输入层,3层隐藏层和输出层,每层均为两个神经元perceptron    
    Mat layerSizes = (Mat_<int>(1, 5) << 2, 2, 2, 2, 2); 
    ann->setLayerSizes(layerSizes);//  
    // 设置激活函数,目前只支持 ANN_MLP::SIGMOID_SYM 
    ann->setActivationFunction(ANN_MLP::SIGMOID_SYM,1.0,1.0);//
    // 设置迭代结束条件 
    //  ann->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 300, FLT_EPSILON));  
    // 设置训练方法   反向传播  动量 下山发
    ann->setTrainMethod(ANN_MLP::BACKPROP,0.1,0.9);  
    // 训练数据
    Ptr<TrainData> tData = TrainData::create(trainingDataMat, ROW_SAMPLE, labelsMat);
    // 执行训练  
    bool trained = ann->train(tData);  
    // 保存训练好的神经网络参数
    if (trained) {
          ann->save("ann_param");
     }

    // 载入训练好的神经网络
    //    Ptr<ml::ANN_MLP> ann = ml::ANN_MLP::load("ann_param");

    //  512 x 512 零矩阵   
    int width = 512, height = 512;  
    Mat image = Mat::zeros(height, width, CV_8UC3);  

    Vec3b green(0, 255, 0), blue(255, 0, 0);  
    for (int i = 0; i < image.rows; ++i)  
    {  
        for (int j = 0; j < image.cols; ++j)  
        {  
            Mat sampleMat = (Mat_<float>(1, 2) << i, j);//生成数
            Mat responseMat;//预测结果  我前面定义的 输出为两维
            ann->predict(sampleMat, responseMat);  
            float* p = responseMat.ptr<float>(0);  
            if (p[0] > p[1])  
            {  
                image.at<Vec3b>(j, i) = green;// 正样本 绿色
            }  
            else  
            {  
                image.at<Vec3b>(j, i) = blue; // 负样本 蓝色
            }  
        }  
    }  
    // 画出训练样本数据  
    int thickness = -1;  
    int lineType = 8;
    int r = 8;  //半径
    circle(image, Point(111, 112), r, Scalar(0, 0, 0), thickness, lineType);  
    circle(image, Point(211, 212), r, Scalar(0, 0, 0), thickness, lineType);  
    circle(image, Point(441, 412), r, Scalar(0, 0, 0), thickness, lineType);  
    circle(image, Point(311, 312), r, Scalar(0, 0, 0), thickness, lineType);  
    circle(image, Point(11, 12), r, Scalar(255, 255, 255), thickness, lineType);  
    circle(image, Point(21, 22), r, Scalar(255, 255, 255), thickness, lineType);  
    circle(image, Point(51, 32), r, Scalar(255, 255, 255), thickness, lineType);  
    circle(image, Point(71, 42), r, Scalar(255, 255, 255), thickness, lineType);  
    circle(image, Point(41, 62), r, Scalar(255, 255, 255), thickness, lineType);  
    circle(image, Point(81, 52), r, Scalar(255, 255, 255), thickness, lineType);  
  
    imwrite("result.png", image);       //  保存训练的结果  
  
    imshow("BP Simple Example", image); //    
    waitKey(0);  
  
    return 0;  
}  

六、利用级联分类器检测人脸  人工神经网络来区别是否为笑脸

使用 级联分类器 检测出人脸
缩放到 统一大小  放入
人工神经网络内 训练
笑脸 / 非笑脸
保存分类器

OpenCV 中使用的激活函数是另一种形式,
f(x) = b *  (1 - exp(-c*x)) / (1 + exp(-c*x))
当 α = β = 1 时

f(x) =(1 - exp(-x)) / (1 + exp(-x))

#include "network.h"

CascadeClassifier* faceCascade;  //人脸检测 级联分类器 指针
Ptr<ANN_MLP> neuralNetwork;      //人工神经网络

//初始化一个用于训练的神经网络
//参数:无
//返回:0 - 正常,-1 - 异常
int networkInit(void)
{
	/*加载人脸检测分类器*/
	faceCascade = new CascadeClassifier();
	if(!faceCascade->load("../../common/data/cascade/haarcascades/haarcascade_frontalface_alt.xml"))
	{
		printf("Can't load face cascade.\n");
		return -1;
	}

	/*设置神经网络参数*/
    neuralNetwork = ANN_MLP::create();// 创建
    Mat layerSize = (Mat_<int>(1,3)<<2500,50,2); //网络层数及神经元数  
// 50*50 pix像素的图片 展开成一列 2500 像素输入 50个隐藏神经元 2个输出神经元
    neuralNetwork->setLayerSizes(layerSize);// 设置网络尺寸
    neuralNetwork->setActivationFunction(ANN_MLP::SIGMOID_SYM,1.0,1.0);//激活函数 sigmod函数 
    neuralNetwork->setTrainMethod(ANN_MLP::BACKPROP,0.0001,0.001); //训练方法  反向传播 动量 下山发
    neuralNetwork->setTermCriteria(TermCriteria(TermCriteria::COUNT+TermCriteria::EPS,30000,0.0001)); //迭代终止条件

	return 0;
}

//初始化一个用于分类的神经网络
//参数:训练好的 神经网络数据文件名
//返回:0 - 正常,-1 - 异常
int networkInit(const char *fileName)
{
   // 加载人脸检测 分类器
    faceCascade = new CascadeClassifier();
// ../../common/data/cascade/lbpcascades/lbpcascade_frontalface.xml  // lbp 特征
    if(!faceCascade->load("../../common/data/cascade/haarcascades/haarcascade_frontalface_alt.xml"))
    {
        printf("Can't load face cascade.\n");
        return -1;
    }
	/*从指定文件中加载神经网络数据*/
    printf("Loading network form %s\n",fileName);
    neuralNetwork = Algorithm::load<ANN_MLP>(fileName);
// Ptr<ml::ANN_MLP> ann = ml::ANN_MLP::load("ann_param");
    return 0;
}

//训练神经网络并保存到指定的文件
//参数:正样本容器,负样本容器,保存神经网络数据的文件名
//返回:0 - 正常,-1 - 异常
int networkTrain(std::vector<Mat>* posFaceVector,std::vector<Mat>* negFaceVector, const char *fileName)
{
//初始化训练数据矩阵和类标矩阵====================
	//训练数据矩阵每行为一个样本,行数=正样本数+负样本数,列数=样本维数=样本宽度x样本高度=输入层神经元个数
	//类标矩阵每行为一个样本的类标,行数同上,列数=2=输出层神经元个数,(1,0)代表正样本,(0,1)代表负样本
    Mat trainDataMat(posFaceVector->size() + negFaceVector->size(), 2500, CV_32FC1);// 数据量 × 2500维度
    Mat labelMat = Mat::zeros(posFaceVector->size() + negFaceVector->size(),2,CV_32FC1);//标签 数据量 × 2维度

// 生成训练数据矩阵和类标矩阵================
    for(unsigned int i = 0;i < posFaceVector->size();i++)
    {
      // 进来的图像为矩阵 reshap 到 一列 在复制到 trainDataMat的一行 并且 归一化到 0~1 之间  防止计算溢出
        posFaceVector->at(i).reshape(0,1).convertTo(trainDataMat.row(i),CV_32FC1,0.003921569,0);  //0.003921569=1/255
        labelMat.at<float>(i,0) = 1;//类标签 样本
    }

    for(unsigned int i = 0;i < negFaceVector->size();i++)
    {
        negFaceVector->at(i).reshape(0,1).convertTo(trainDataMat.row(i + posFaceVector->size()),CV_32FC1,0.003921569,0);
        labelMat.at<float>(i + posFaceVector->size(), 1) = 1;
    }

// 训练网络=================
    neuralNetwork->train(trainDataMat,ROW_SAMPLE,labelMat);

// 保存网络数据=================
    neuralNetwork->save(fileName);

    return 0;
}

#include <stdio.h>
#include <vector>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/time.h>

#include <opencv2/opencv.hpp>

#include "network.h"

using namespace std;
using namespace cv;

void printHelpMessage(void);      //打印帮助信息
int  preview(void);               //预览摄像头图像
int  trainNetwork(void);          //训练神经网络
int  smileFaceDetect(void);       //在摄像头图像中执行笑脸检测

char devPath[256] = "/dev/video0";                       //摄像头设备文件名
char posPath[256] = "../trainset/positive/";             //正训练集路径
char negPath[256] = "../trainset/negtive/";              //负训练集路径
char networkFilename[256] = "../networks/default.xml";   //保存神经网络数据的文件名

bool preview_flag = false;  //预览标志
bool train_flag = false;    //训练标志
bool detect_flag = false;   //笑脸检测标志

std::vector<Mat> posFaces;  //存放正样本的 Mat 容器
std::vector<Mat> negFaces;  //存放负样本的 Mat 容器

//主函数
int main(int argc, char *argv[])
{
	/*命令行参数解析*/
	int opt;

	if(argc == 1) //没有参数
		printHelpMessage();

    while((opt = getopt(argc,argv,"hpted:T:F:N:")) != -1)//获取下一个命令行参数
	{
		switch(opt)
		{
			case 'h':printHelpMessage();    break; //打印帮助信息
			case 'd':strcpy(devPath,optarg);break; //指定摄像头设备
			case 'p':preview_flag = true;   break; //预览摄像头图像
			case 't':train_flag = true;     break; //训练神经网络
                        case 'e':detect_flag = true;    break; //从摄像头捕获视频中检测笑脸
			case 'T':strcpy(posPath,optarg);break; //指定正训练集路径
			case 'F':strcpy(negPath,optarg);break; //指定负训练集路径
               case 'N':strcpy(networkFilename,optarg); break; //指定神经网络文件名
			default:printf("Invalid argument %c!\n",opt);return -1;//非法参数
		}
	}

	// 如果路径字符串末位无'\',则添加之
	if(posPath[strlen(posPath)-1] != '/')
		strcat(posPath,"/");
	if(negPath[strlen(negPath)-1] != '/')
		strcat(negPath,"/");

	/*根据标志决定程序的功能*/
	if(train_flag == true)
		trainNetwork();//训练神经网络
	else if(preview_flag == true)
		preview();     //预览摄像头图像
    else if(detect_flag == true)
        smileFaceDetect();     //从摄像头捕获视频中检测笑脸

    usleep(300000);
    return 0;
}

//将摄像头设备名转换为设备索引
//参数:设备名字符串 /dev/video0 ~ /dev/video100
//返回:设备索引,范围0-9999
int getDeviceIndex(const char *devicePath)
{
	// 检查输入字符串是否为Linux下的V4L2设备名
	if(strncmp(devicePath,"/dev/video",10))
	{
		printf("Invalid device path!\n");
		return -1;
	}

	char devIndex[5];// 保存设备号
	unsigned int i;

	/*截取"/dev/video"之后的子串*/
	for(i = 10;i < strlen(devicePath);i++)
		devIndex[i-10] = devicePath[i];
	devIndex[i-10] = '\0';
	/*将字符串转换为整形数并返回*/
	return atoi(devIndex);
}

//训练一个神经网络,并将网络数据保存到文件
//参数:无
//返回:0 - 正常,-1 - 异常
int trainNetwork(void)
{
	networkInit();           //初始化网络
	posFaces.reserve(1000);  //为容器预留1000个样本的内存
	negFaces.reserve(1000);

// 遍历正样本
	DIR *posDir = opendir(posPath);//打开 正训练集路径
	if(posDir == NULL)
	{
		printf("Can't open directory %s\n",posPath);
		perror(posPath);
		return -1;
	}
	struct dirent *posDirent;
	while((posDirent = readdir(posDir)) != NULL)//读取下一个文件入口
	{
		/*跳过两个特殊文件*/
		if(strcmp(posDirent->d_name,".") == 0 || strcmp(posDirent->d_name,"..") == 0)
			continue;
		/*获得当前文件的完整文件名*/
		char posFileName[256];
		strcpy(posFileName, posPath);
		strcat(posFileName, posDirent->d_name);

		//处理当前文件 
		Mat temp;
		int result;
		result = detectFaceFormImg(posFileName, &temp);  //对当前文件做人脸检测
		if(result == 1)
		posFaces.push_back(temp);                        //若检测成功,则将人脸添加到正样本容器中
	}
        printf("Got %d positive samples.\n",(int)posFaces.size());

// 遍历负样本 
	DIR *negDir = opendir(negPath);//打开负训练集路径
	if(negDir == NULL)
	{
		printf("Can't open directory %s\n",negPath);
		perror(negPath);
		return -1;
	}
	struct dirent *negDirent;
	while((negDirent = readdir(negDir)) != NULL)     //读取下一个文件入口
	{
		/*跳过两个特殊文件*/
		if(strcmp(negDirent->d_name,".") == 0 || strcmp(negDirent->d_name,"..") == 0)
			continue;

		/*获得当前文件的完整文件名*/
		char negFileName[256];
		strcpy(negFileName,negPath);
		strcat(negFileName,negDirent->d_name);

		/*处理当前文件*/
		Mat temp;
		int result;
		result = detectFaceFormImg(negFileName,&temp);//对当前文件做人脸检测
		if(result == 1)
		negFaces.push_back(temp);                     //若检测成功,则将人脸添加到负样本容器
	}
        printf("Got %d negtive samples.\n",(int)negFaces.size());

	struct timeval before, after;//时间

	/*训练神经网络,并保存到文件*/
	printf("begin network training...\n");
	gettimeofday(&before,NULL);
	networkTrain(&posFaces, &negFaces, networkFilename);
	gettimeofday(&after,NULL);
	long int usec = (after.tv_sec - before.tv_sec)*1000000 + (after.tv_usec-before.tv_usec);
	printf("training completed,use %lds %ldms\n",usec/1000000,(usec%1000000)/1000);
	return 0;
}

//在摄像头捕获视频中检测笑脸
//参数:无
//返回:0 - 正常,-1 - 异常
int smileFaceDetect(void)
{
	networkInit(networkFilename);//初始化检测神经网络
	VideoCapture capture;
	capture.open(getDeviceIndex(devPath));//打开摄像头捕获
	if(!capture.isOpened())
	{
	printf("Can not open device!\n");
	return -1;
	}
        //设置捕获图像分辨率  320*240
	capture.set(CAP_PROP_FRAME_WIDTH, 640);     
	capture.set(CAP_PROP_FRAME_HEIGHT,480);

	Mat frame;
	while(waitKey(10) != 'q') //循环直到按下键盘上的Q键
	{
		capture>>frame;                 //捕获一帧图像
		Rect  face_dec;
		if(networkClassify(&frame, face_dec) == 1)//如果当前帧中检测到笑脸,则绘制一个醒目的红色矩形框
		{
		    // rectangle(frame,Point(0,0),Point(319,239),Scalar(0,0,255),10,4,0);

	   	   Point center( face_dec.x + face_dec.width*0.5, face_dec.y + face_dec.height*0.5 );
	   	   ellipse( frame, center, Size(face_dec.width*0.5, face_dec.height*0.5), 0, 0, 360, Scalar( 0, 0, 255 ), 10, 4, 0 );
		}
		imshow("smile face detecting",frame);  //显示当前帧
	}

	capture.release();

	return 0;
}

//预览摄像头捕获图像
//参数:无
//返回:0 - 正常,-1 - 异常
int preview(void)
{
	VideoCapture capture;
	capture.open(getDeviceIndex(devPath));    //打开摄像头捕获
	if(!capture.isOpened())
	{
		printf("Can not open device!\n");
		return -1;
	}

	capture.set(CAP_PROP_FRAME_WIDTH,640);   //设置捕获图像分辨率
	capture.set(CAP_PROP_FRAME_HEIGHT,480);

	Mat frame;
	
	while(waitKey(10) != 'q')                //循环直到按下键盘上的Q键
	{
		capture>>frame;                      //捕获一帧图像
		imshow("real-time video",frame);     //显示当前帧
	}

	capture.release();

	return 0;
}

//打印帮助信息
//参数:无
//返回:无
void printHelpMessage(void)
{
	printf("A BP artificial neural network demo	for smile detection.\n");
	printf("Usage:\n");
	printf("\tsmileDetection [options] ...\n\n");
	printf("Options:\n");
	printf("\t-h\tprint this help message\n");
	printf("\t-p\tpreview the real-time video\n");
	printf("\t-d\tspecify the camera device, default /dev/video0\n");
	printf("\t-t\ttrain a network only\n");
    printf("\t-e\tdetect smile face form camera capture stream\n");
	printf("\t-T\tspecify the positive sample location, default ../trainset/positive/\n");
	printf("\t-F\tspecify the negtive sample location, default ../trainset/negtive/\n");
	printf("\t-N\tspecify the network file, default ../networks/default.xml\n");
}


七、SVM分类 笑脸 检测

/*
使用 级联分类器 检测出人脸
缩放到 统一大小  放入
SVM 训练
笑脸 / 非笑脸
保存分类器

OpenCV 中使用的激活函数是另一种形式,
f(x) = b *  (1 - exp(-c*x)) / (1 + exp(-c*x))
当 α = β = 1 时
f(x) =(1 - exp(-x)) / (1 + exp(-x))

*/
#include "svm_class.h"

CascadeClassifier* faceCascade;  //人脸检测 级联分类器 指针
// Ptr<ANN_MLP> neuralNetwork;      //人工神经网络
// Ptr<SVM> svm = SVM::create();// SVM分类器
Ptr<SVM> svm_ptr;

//初始化一个用于训练的SVM
//参数:无
//返回:0 - 正常,-1 - 异常
int SVMInit(void)
{
	/*加载人脸检测分类器*/
	faceCascade = new CascadeClassifier();
	if(!faceCascade->load("../../common/data/cascade/haarcascades/haarcascade_frontalface_alt.xml"))
	{
		printf("Can't load face cascade.\n");
		return -1;
	}

	// SVM分类器参数
    svm_ptr = SVM::create();// 创建
    // 50*50 pix像素的图片 展开成一列 2500 像素输入 
    svm_ptr->setType(SVM::C_SVC);   //  SVM类型.  C_SVC 该类型可以用于n-类分类问题 (n \geq 2)
    svm_ptr->setC(0.5);             //  错分类样本离同类区域的距离 的权重
    svm_ptr->setKernel(SVM::LINEAR);   //  
    // 算法终止条件.   最大迭代次数和容许误差
    svm_ptr->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, (int)1e7, 1e-6)); 
	return 0;
}

//初始化一个用于分类的 SVM
//参数:训练好的 SVM分类器
//返回:0 - 正常,-1 - 异常
int SVMInit(const char *fileName)
{
	// 加载人脸检测 分类器
    faceCascade = new CascadeClassifier();
// ../../common/data/cascade/lbpcascades/lbpcascade_frontalface.xml  // lbp 特征
    if(!faceCascade->load("../../common/data/cascade/haarcascades/haarcascade_frontalface_alt.xml"))
    {
        printf("Can't load face cascade.\n");
        return -1;
    }
	/*从指定文件中加载神经网络数据*/
    printf("Loading SVM form %s\n",fileName);
    svm_ptr = Algorithm::load<SVM>(fileName);
// Ptr<ml::ANN_MLP> ann = ml::ANN_MLP::load("ann_param");
    return 0;
}

//训练 SVM 并保存到指定的文件
//参数:正样本容器,负样本容器,保存 SVM 数据的文件名
//返回:0 - 正常,-1 - 异常
int SVMTrain(std::vector<Mat>* posFaceVector,std::vector<Mat>* negFaceVector, const char *fileName)
{
//初始化训练数据矩阵和类标矩阵====================
	//训练数据矩阵每行为一个样本,行数=正样本数+负样本数,列数=样本维数=样本宽度x样本高度
	//类标矩阵每行为一个样本的类标,行数同上,列数=1 +1代表正样本 -1代表负样本
    Mat trainDataMat(posFaceVector->size() + negFaceVector->size(), 2500, CV_32FC1);// 数据量 × 2500维度
// SVM 标签不能为 float or double   CV_32FC1 -----> CV_32S
    Mat labelMat = Mat::zeros(posFaceVector->size() + negFaceVector->size(),1,CV_32S);//标签 数据量 × 1维度

// 生成训练数据矩阵和类标矩阵================
    for(unsigned int i = 0;i < posFaceVector->size();i++)
    {
      // 进来的图像为矩阵 reshap 到 一列 在复制到 trainDataMat的一行 并且 归一化到 0~1 之间  防止计算溢出
        posFaceVector->at(i).reshape(0,1).convertTo(trainDataMat.row(i),CV_32FC1,0.003921569,0);  //0.003921569=1/255
        labelMat.at<float>(i) = 1;//类标签 样本  1代表正样本  
    }

    for(unsigned int i = 0;i < negFaceVector->size();i++)
    {
        negFaceVector->at(i).reshape(0,1).convertTo(trainDataMat.row(i + posFaceVector->size()),CV_32FC1,0.003921569,0);
        labelMat.at<float>(i + posFaceVector->size()) = -1;// 代表负样本
    }

// 训练网络=================
    svm_ptr->train(trainDataMat,ROW_SAMPLE,labelMat);

// 保存网络数据=================
    svm_ptr->save(fileName);

    return 0;
}

//用训练好的神经网络进行分类
//参数:待分类的样本
//返回:1 - 正样本(笑脸),0 - 负样本(非笑脸),-1 - 未检测到人脸
int SVMClassify(Mat *image, Rect& face_dec)
{
	// 人脸检测
    Mat gray,roi,classifyDataMat;
    std::vector<Rect> faces;// 级联分类器 检测到的人脸矩阵 检测到的人脸 矩形区域 左下点坐标 长和宽
    cvtColor(*image,gray,CV_BGR2GRAY);   //将样本转换为灰度图
    //equalizeHist(gray,gray);             //直方图均衡化

    faceCascade->detectMultiScale(
            gray,
            faces,
            1.1,// 每张图像缩小的尽寸比例 1.1 即每次搜索窗口扩大10%
            4,// 每个候选矩阵应包含的像素领域
            CASCADE_SCALE_IMAGE|CASCADE_FIND_BIGGEST_OBJECT|CASCADE_DO_ROUGH_SEARCH,
            Size(50,50)); //在样本中检测人脸,只检测最大的人脸  至少50*50 像素的 表示最小的目标检测尺寸

    if(faces.size() < 1)                 //样本中检测不到人脸
        return -1;
    face_dec = faces[0];// 人脸
    roi = gray(faces[0]);// 取灰度图像的 第一个人脸
// Point center( faces[0].x + faces[0].width*0.5, faces[0].y + faces[0].height*0.5 );//中心点
    resize(roi,roi,Size(50,50),0,0,CV_INTER_LINEAR);  //将检测到的人脸区域缩放到50x50像素

// 对检测到的人脸进行分类,即判断是笑脸还是非笑脸 
    roi = Mat_<float>(roi);
    roi.reshape(0,1).convertTo(classifyDataMat,CV_32FC1,0.003921569,0);//转换为待分类数据  50×50---> 1× 2500 归一化到 0~1 
    float response = svm_ptr->predict(classifyDataMat);
    cout<< response << endl;  //打印分类结果 -1   1
    if(response >= 1)
        return 1;  //正类 1
    else
        return 0;  //否则为负样本(非笑脸)
}

//从指定图像中检测人脸
//参数:图像文件名,存放人脸区域的矩阵
//返回:1 - 成功检测到人脸,0 - 未检测到人脸,-1 - 异常
int detectFaceFormImg(const char *imgFilename, Mat *faceRoi)
{
    Mat img,gray,roi;
	
    std::vector<Rect> faces;// 级联分类器 检测到的人脸矩阵 检测到的人脸 矩形区域 左下点坐标 长和宽
	/*读取图像文件*/
	img = imread(imgFilename);
	if(img.empty())
	{
		printf("Can't read file %s\n",imgFilename);
		return -1;
	}

	cvtColor(img,gray,CV_BGR2GRAY);  //将图像转换为灰度图
	//equalizeHist(gray,gray);//直方图均衡化

	faceCascade->detectMultiScale(
			gray,
                        faces,
			1.1,// 每张图像缩小的尽寸比例 1.1 即每次搜索窗口扩大10%
			6,// 每个候选矩阵应包含的像素领域
			CASCADE_SCALE_IMAGE|CASCADE_FIND_BIGGEST_OBJECT|CASCADE_DO_ROUGH_SEARCH,
			Size(40,40));//在图像中检测人脸 表示最小的目标检测尺寸

    if(faces.size() < 1)             //未检测到人脸
	{
		printf("Can't find face in %s, skip it.\n",imgFilename); 
		return 0;
	}

    roi = gray(faces[0]);                           //人脸区域ROI
    resize(roi,roi,Size(50,50),0,0,CV_INTER_LINEAR);//将人脸区域ROI缩放到50x50像素

    *faceRoi = roi.clone();                         //将人脸区域ROI拷贝到输出矩阵

    return 1;
}
/*

使用 级联分类器 检测出人脸
缩放到 统一大小  放入
SVM 训练
笑脸 / 非笑脸
保存分类器

OpenCV 中使用的激活函数是另一种形式,
f(x) = b *  (1 - exp(-c*x)) / (1 + exp(-c*x))
当 α = β = 1 时
f(x) =(1 - exp(-x)) / (1 + exp(-x))

使用 ./smile_dec -e   检测笑脸
./smile_dec -t        训练笑脸分类器

 

*/
#include <stdio.h>
#include <vector>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/time.h>

#include <opencv2/opencv.hpp>

#include "svm_class.h"

using namespace std;
using namespace cv;

void printHelpMessage(void);      //打印帮助信息
int  preview(void);               //预览摄像头图像
int  trainSVM(void);              //SVM
int  smileFaceDetect(void);       //在摄像头图像中执行笑脸检测

char devPath[256] = "/dev/video0";                       //摄像头设备文件名
char posPath[256] = "../trainset/positive/";             //正训练集路径
char negPath[256] = "../trainset/negtive/";              //负训练集路径
char SVMFilename[256] = "../networks/SVMdefault.xml";    //保存神经网络数据的文件名

bool preview_flag = false;  //预览标志
bool train_flag   = false;  //训练标志
bool detect_flag  = false;  //笑脸检测标志

std::vector<Mat> posFaces;  //存放正样本的 Mat 容器
std::vector<Mat> negFaces;  //存放负样本的 Mat 容器

//主函数
int main(int argc, char *argv[])
{
	/*命令行参数解析*/
	int opt;

	if(argc == 1) //没有参数
		printHelpMessage();

    while((opt = getopt(argc,argv,"hpted:T:F:N:")) != -1)//获取下一个命令行参数
	{
		switch(opt)
		{
			case 'h':printHelpMessage();    break; //打印帮助信息
			case 'd':strcpy(devPath,optarg);break; //指定摄像头设备
			case 'p':preview_flag = true;   break; //预览摄像头图像
			case 't':train_flag = true;     break; //训练神经网络
                        case 'e':detect_flag = true;    break; //从摄像头捕获视频中检测笑脸
			case 'T':strcpy(posPath,optarg);break; //指定正训练集路径
			case 'F':strcpy(negPath,optarg);break; //指定负训练集路径
                   case 'N':strcpy(SVMFilename,optarg); break; //指定神经网络文件名
			default:printf("Invalid argument %c!\n",opt);return -1;//非法参数
		}
	}

	/*如果路径字符串末位非'\',则添加之*/
	if(posPath[strlen(posPath)-1] != '/')
		strcat(posPath,"/");
	if(negPath[strlen(negPath)-1] != '/')
		strcat(negPath,"/");

	/*根据标志决定程序的功能*/
	if(train_flag == true)
		trainSVM();       //训练SVM
	else if(preview_flag == true)
		preview();        //预览摄像头图像
        else if(detect_flag == true)
        	smileFaceDetect();//从摄像头捕获视频中检测笑脸

    usleep(300000);
    return 0;
}

//将摄像头设备名转换为设备索引
//参数:设备名字符串 /dev/video0 ~ /dev/video100
//返回:设备索引,范围0-9999
int getDeviceIndex(const char *devicePath)
{
	/*检查输入字符串是否为Linux下的V4L2设备名*/
	if(strncmp(devicePath,"/dev/video",10))
	{
		printf("Invalid device path!\n");
		return -1;
	}

	char devIndex[5];
	unsigned int i;

	/*截取"/dev/video"之后的子串*/
	for(i = 10;i < strlen(devicePath);i++)
		devIndex[i-10] = devicePath[i];
	devIndex[i-10] = '\0';
	/*将字符串转换为整形数并返回*/
	return atoi(devIndex);
}

//训练一个神经网络,并将网络数据保存到文件
//参数:无
//返回:0 - 正常,-1 - 异常
int trainSVM(void)
{
	SVMInit();               //初始化网络
	posFaces.reserve(1000);  //为容器预留1000个样本的内存
	negFaces.reserve(1000);

// 遍历正样本
	DIR *posDir = opendir(posPath);//打开 正训练集路径
	if(posDir == NULL)
	{
		printf("Can't open directory %s\n",posPath);
		perror(posPath);
		return -1;
	}
	struct dirent *posDirent;
	while((posDirent = readdir(posDir)) != NULL)//读取下一个文件入口
	{
		/*跳过两个特殊文件*/
		if(strcmp(posDirent->d_name,".") == 0 || strcmp(posDirent->d_name,"..") == 0)
			continue;
		/*获得当前文件的完整文件名*/
		char posFileName[256];
		strcpy(posFileName, posPath);
		strcat(posFileName, posDirent->d_name);

		//处理当前文件 
		Mat temp;
		int result;
		result = detectFaceFormImg(posFileName, &temp);  //对当前文件做人脸检测
		if(result == 1)
		posFaces.push_back(temp);                        //若检测成功,则将人脸添加到正样本容器中
	}
        printf("Got %d positive samples.\n",(int)posFaces.size());

	// 遍历负样本 
	DIR *negDir = opendir(negPath);//打开负训练集路径
	if(negDir == NULL)
	{
		printf("Can't open directory %s\n",negPath);
		perror(negPath);
		return -1;
	}
	struct dirent *negDirent;
	while((negDirent = readdir(negDir)) != NULL)     //读取下一个文件入口
	{
		/*跳过两个特殊文件*/
		if(strcmp(negDirent->d_name,".") == 0 || strcmp(negDirent->d_name,"..") == 0)
			continue;

		/*获得当前文件的完整文件名*/
		char negFileName[256];
		strcpy(negFileName,negPath);
		strcat(negFileName,negDirent->d_name);

		/*处理当前文件*/
		Mat temp;
		int result;
		result = detectFaceFormImg(negFileName,&temp);//对当前文件做人脸检测
		if(result == 1)
		negFaces.push_back(temp);                     //若检测成功,则将人脸添加到负样本容器
	}
        printf("Got %d negtive samples.\n",(int)negFaces.size());

	struct timeval before, after;//时间

	/*训练神经网络,并保存到文件*/
	printf("begin network training...\n");
	gettimeofday(&before,NULL);
	SVMTrain(&posFaces, &negFaces, SVMFilename);
	gettimeofday(&after,NULL);
	long int usec = (after.tv_sec - before.tv_sec)*1000000 + (after.tv_usec-before.tv_usec);
	printf("training completed,use %lds %ldms\n",usec/1000000,(usec%1000000)/1000);
	return 0;
}

//在摄像头捕获视频中检测笑脸
//参数:无
//返回:0 - 正常,-1 - 异常
int smileFaceDetect(void)
{
	SVMInit(SVMFilename);//初始化检测神经网络
	VideoCapture capture;
	capture.open(getDeviceIndex(devPath));//打开摄像头捕获
	if(!capture.isOpened())
	{
	printf("Can not open device!\n");
	return -1;
	}
        //设置捕获图像分辨率  320*240
	capture.set(CAP_PROP_FRAME_WIDTH, 640);     
	capture.set(CAP_PROP_FRAME_HEIGHT,480);

	Mat frame;
	while(waitKey(10) != 'q') //循环直到按下键盘上的Q键
	{
		capture>>frame;                 //捕获一帧图像
		Rect  face_dec;
		if(SVMClassify(&frame, face_dec) == 1)//如果当前帧中检测到笑脸,则绘制一个醒目的红色矩形框
		{
		    // rectangle(frame,Point(0,0),Point(319,239),Scalar(0,0,255),10,4,0);

	   	   Point center( face_dec.x + face_dec.width*0.5, face_dec.y + face_dec.height*0.5 );
	   	   ellipse( frame, center, Size(face_dec.width*0.5, face_dec.height*0.5), 0, 0, 360, Scalar( 0, 0, 255 ), 10, 4, 0 );
		}
		imshow("smile face detecting",frame);  //显示当前帧
	}

	capture.release();

	return 0;
}

//预览摄像头捕获图像
//参数:无
//返回:0 - 正常,-1 - 异常
int preview(void)
{
	VideoCapture capture;
	capture.open(getDeviceIndex(devPath));    //打开摄像头捕获
	if(!capture.isOpened())
	{
		printf("Can not open device!\n");
		return -1;
	}

	capture.set(CAP_PROP_FRAME_WIDTH,640);   //设置捕获图像分辨率
	capture.set(CAP_PROP_FRAME_HEIGHT,480);

	Mat frame;
	
	while(waitKey(10) != 'q')                //循环直到按下键盘上的Q键
	{
		capture>>frame;                      //捕获一帧图像
		imshow("real-time video",frame);     //显示当前帧
	}

	capture.release();

	return 0;
}

//打印帮助信息
//参数:无
//返回:无
void printHelpMessage(void)
{
	printf("A BP artificial neural network demo	for smile detection.\n");
	printf("Usage:\n");
	printf("\tsmileDetection [options] ...\n\n");
	printf("Options:\n");
	printf("\t-h\tprint this help message\n");
	printf("\t-p\tpreview the real-time video\n");
	printf("\t-d\tspecify the camera device, default /dev/video0\n");
	printf("\t-t\ttrain a network only\n");
    printf("\t-e\tdetect smile face form camera capture stream\n");
	printf("\t-T\tspecify the positive sample location, default ../trainset/positive/\n");
	printf("\t-F\tspecify the negtive sample location, default ../trainset/negtive/\n");
	printf("\t-N\tspecify the network file, default ../networks/default.xml\n");
}


猜你喜欢

转载自blog.csdn.net/xiaoxiaowenqiang/article/details/79815770