针对上一篇“深度摄像头-活体识别”的改进版
大致思路:
1.RGB人脸检测
2.同步人脸位置到深度图矩形框
3.裁剪矩形框,提取LBP特征
4.训练SVM模型。
5.集成模型到demo程序
详细说明:
1.RGB人脸检测使用的是seetaface,速度和准确率比opencv自带的检测器好。返回一个faces的数组,包括人脸个数和位置信息。这里可以根据要求选择做不做最大化人脸选取,选取的过程非常简单,这里不做赘述。其实我们主要就是获取人脸位置信息(x,y表示人脸框的左上角位置,w表示框宽,h表示框高)
2.因为摄像头出产做过对齐,所以这里可以省去对齐的操作。RGB和深度都是640*480画面,所以直接同步就行了。需要注意的是深度图的原始数据是16位,所以在转成mat格式的时候最好也使用16位,不丢失细节。cv::Mat u16depth(depthHeight,depthWidth,CV_16UC1,(void)*m_depthFrame.getData());
3.裁剪深度人脸框位置cv::Mat depth_face = u16depth(Rect(roi.x,roi.y,roi.width,roi.height)).clone;然后主要就是对depth_face做文章了。
(1).人脸框距离检测,因为摄像头硬件的原因,在距离较远的时候,分布到人脸上的深度信息很少。所以我人为设定在一米的距离里做活体检测。
int font = cv::FONT_HERSHEY_COMPLEX;//字体格式
double nosemean = cv::mean(depth_face)[0];
if (nosemean > 1000)
{
cv::rectangle(depth,roi,cv::Scalar(0,0,255),2);//在深度图像中画出人脸
cv::putText(depth,"far",cvPoint(roi.x,roi.y),font,1.5,cv::Scalar(0,0,255),2);
}
else
{
int numOfblank = 0;//记录颜色位黑色的像素点
float rate;//黑色像素点占的比例
int mindepth = MAX_DEPTH;
int maxdepth = 0;
int maxrow = 0;
int minrow = 0;
for (int i = 0; i < depth_face.rows; i++)
{
for (int j = 0; j < depth_face.cols; j++)
{
int depth = depth_face.at(i,j);
if (depth == 0)
{
numOfblank++;
}
}
}
rate = (float)numOfblank / (float)(depth_face.rows * depth_face.cols);
if (rate > 0.4)//黑色像素点占的比例超过40%时,不进入识别模块
{
cv::rectangle(depth,roi,cv::Scalar(0,255,0),2);//在深度图像中画出人脸
cv::putText(depth,"close",cvPoint(roi.x,roi.y),font,1.5,cv::Scalar(0,255,0),2);
}
else
{
Size dsize = Size(128,128);//resize到128*128,识别模型训练的训练数据都是128*128尺寸
cv::Mat image2 = Mat(dsize,CV_16UC1);
cv::resize(depth_face,image2,dsize);
cv::rectangle(depth,roi,cv::Scalar(255,0,0),2);//在深度图像中画出人脸
LBP lbp;
Mat feature_lbp = Mat(image2.rows,image.cols,CV_8UC1,Scalar(0));
lbp.elbp(image2,feature_lbp,1,8)//LBP提取特征,后续训练模型的时候会详细介绍
const int chanels[1] = {0};
//直方图的每一个维度的柱条的数目(就是将灰度级分组)
int histSize[] = {256};
float midRanges[] = {0,255};//定义一个变量来储存单个维度数值的取值范围
const float *ranges[] = {midRanges};//确定每一个维度的取值范围,就是横坐标的总数
cv::Mat dstHist;
cv::calcHist(&feature_lbp,1,channels,Mat(),dstHist,1,hisSize,ranges,true,false);
cv::Mat dstHist_new;
transpose(hstHist,dstHist_new);
normalize(dstHist_new,dstHist_new,0,dstHist.rows,NORM_MINMAX,-1,Mat());//归一化结果存在dstHist
float response = svm->predict(dstHist_new,noArray(),cv::ml::StatModel::Flags::RAW_OUTPUT);//计算出样本距离超平面的距离
float result = response * 1000000;//乘1000000是为了方便观看
if(result >= -10)
{
cv::rectangle(depth,roi,cv::Scalar(0,255,0),2);//在深度图像中画出人脸
cv::putText(depth,"real",cvPoint(roi.x,roi.y),font,1.5,cv::Scalar(0,255,0),2);
}
else
{
cv::rectangle(depth,roi,cv::Scalar(0,255,0),2);//在深度图像中画出人脸
cv::putText(depth,"unreal",cvPoint(roi.x,roi.y),font,1.5,cv::Scalar(0,255,0),2);
}
}
这里说几点:
试过直接用16位深度数据做识别,也试过用LBP特征图来做识别,但是训练出来的效果都不理想。最后使用的是LBP图的直方图。
LBP提取特征的方法将在下一篇模型训练里做说明
集成到demo程序里,活体识别准确率还可以,但是泛化效果欠佳
后续方向:
使用深度学习来替代SVM,现在已经基本实现,效果比svm好很多,但是速度没有svm快,采用的是MobileNet网络,直接对原始16位mat数据进行训练识别。
rgb人脸检测依赖光线问题的解决办法是,直接训练出深度人脸检测器,目前正在进行中