思路:思路很简单,前面有一篇讲了如何利用3000fps检测人脸特征点,把特征点检测出来之后,就有了人脸的大概轮廓。如下图。
上图只是显示了部分特征点,从0-16个特征点可以知道大概的脸型。
但是还有上半部分没有特征点,无法确定头发部分。怎么办呢?通过查看文献,其中这篇论文《Face Image Quality Assessment Based on Learning to Rank》提到了一个方法把人脸抠出来,然后对齐。
如下图
图中(a)是一般的框人脸方法,图(b)是通过人脸的四个眼角,两个嘴角特征点画出一个圆,然后图(c)人眼睛的四个眼角确定矩形框的倾斜角度,矩形的长和宽是4r和2.4r时效果最好,对齐之后的效果图中(efg)。
借鉴这种画圆求半径的思想,那我能不能利用这个半径再往上取一个矩形框呢?
于是动手试了一下,效果如下图。
效果还可以啊,换一张图试一下。
测试了很多张图片效果都还行。于是就才用这种方法了。
下面是核心代码,比较多测试时用的注释哈,将就看一下,使用起来也方便。后续我会将代码放在github上,有兴趣可以去看看。
void Detection( std::vector<cv::String> files) {
LBFRegressor regressor;
extern string cascadeName;
CascadeClassifier cascade;
double scale = 1.0;
int i = 0;
regressor.Load(modelPath + "LBF.model");
// -- 1. Load the cascades
if (!cascade.load(cascadeName)) {
cerr << "ERROR: Could not load classifier cascade" << endl;
}
const static Scalar colors[] = { CV_RGB(0,0,255),
CV_RGB(0,128,255),
CV_RGB(0,255,255),
CV_RGB(0,255,0),
CV_RGB(255,128,0),
CV_RGB(255,255,0),
CV_RGB(255,0,0),
CV_RGB(255,0,255) };
int count = files.size();
for (int j = 0; j < count; j++)
{
Mat gray;
double t = 0;
vector<Rect> faces;
//vector<double> polygonX;//多边形轮廓点集合
//vector<double> polygonY;
vector<Point> polygon;
Mat img = imread(files[j]);
cvtColor(img, gray, CV_BGR2GRAY);
// --Detection
t = (double)cvGetTickCount();
//cascade.detectMultiScale(gray, faces,
// 1.1, 2, 0
// //|CV_HAAR_FIND_BIGGEST_OBJECT
// //|CV_HAAR_DO_ROUGH_SEARCH
// | CV_HAAR_SCALE_IMAGE
// ,
// Size(30, 30));
cascade.detectMultiScale(gray, faces, 1.3, 1, 0, Size(30, 30));
t = (double)cvGetTickCount() - t;
printf("detection time = %g ms\n", t / ((double)cvGetTickFrequency()*1000.));
// --Alignment
t = (double)cvGetTickCount();
//检测到超过一个人脸,则只计算一个人脸的特征,因为送入的图片本身就只包含一张人脸,检测出现两个
//人脸,则检测出错,所以加facesize,保证只检测大的人脸特征点。不然特征点会多于18个,绘制的roi中间
//会被挖空。
//for (vector<Rect>::const_iterator r = faces.begin(); r != faces.end(); r++, i++) {
for (vector<Rect>::const_iterator r = faces.begin(); r != faces.end(); r= faces.end(), i++) {
Point center;
Scalar color = colors[i % 8];
BoundingBox boundingbox;
boundingbox.start_x = r->x*scale;
boundingbox.start_y = r->y*scale;
boundingbox.width = (r->width - 1)*scale;
boundingbox.height = (r->height - 1)*scale;
boundingbox.centroid_x = boundingbox.start_x + boundingbox.width / 2.0;
boundingbox.centroid_y = boundingbox.start_y + boundingbox.height / 2.0;
t = (double)cvGetTickCount();
Mat_<double> current_shape = regressor.Predict(gray, boundingbox, 1);
t = (double)cvGetTickCount() - t;
printf("alignment time = %g ms\n", t / ((double)cvGetTickFrequency()*1000.));
// // draw bounding box
// rectangle(img, cvPoint(boundingbox.start_x,boundingbox.start_y),
// cvPoint(boundingbox.start_x+boundingbox.width,boundingbox.start_y+boundingbox.height),Scalar(0,255,0), 1, 8, 0);
// draw result :: red
double deta = current_shape(0, 0) - current_shape(15, 0);
double rec_point1_y = current_shape(0, 1) + deta / 2;//第0个特征点与第15个特征点的差值的一般作为矩形的宽
double rec_point1_x = current_shape(0, 0);
//如果超过则赋值为0,到图片边界
if (rec_point1_x < 0) { rec_point1_x = 0.0; }
if (rec_point1_y < 0) { rec_point1_y = 0.0; }
Point rec_point1 = Point(rec_point1_x, rec_point1_y);//对角线两个点
Point rec_point2 = Point(current_shape(16, 0), current_shape(16, 1));
Point rec_point3 = Point(rec_point1_x, current_shape(16, 1));
Point rec_point4 = Point(current_shape(16, 0), rec_point1_y);
polygon.push_back(rec_point1);
polygon.push_back(rec_point3);
//cout << deta << endl;
//cout << rec_point1 << endl;
//cout << rec_point2<< endl;
//rectangle(img, rec_point1, rec_point2, Scalar(255, 255, 0), 2, 8, 0);
for (int i = 0; i <16 /*global_params.landmark_num*/; i++) {
//circle(img, Point2d(current_shape(i, 0), current_shape(i, 1)), 3, Scalar(255, 255, 255), -1, 8, 0);
//cout << current_shape(i, 0) << endl;
//cout << current_shape(i, 1) << endl;
//cout << global_params.landmark_num << endl;
Point pp = Point (current_shape(i, 0), current_shape(i, 1));
Point pp2 = Point (current_shape(i+1, 0), current_shape(i+1, 1));
//putText(img, std::to_string(i), pp, FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 0, 255),1, 4);//在图片上写文字
//cout << std::to_string(i)<<pp << endl;
//cout << pp << endl;
//line(img, pp,pp2, Scalar(255, 255, 0),2);
//imshow("底板", img);
//polygonX.push_back(current_shape(i, 0));//添加多边形轮廓点
//polygonY.push_back(current_shape(i, 1));//添加多边形轮廓点
polygon.push_back(pp);
//cout << polygonX[i] << endl;
//waitKey(300);
}
//double deta = current_shape(0, 0) - current_shape(15, 0);
//double rec_point1_y = current_shape(0, 1) + deta / 2;//第0个特征点与第15个特征点的差值的一般作为矩形的宽
//double rec_point1_x = current_shape(0, 0);
如果超过则赋值为0,到图片边界
//if (rec_point1_x < 0) { rec_point1_x = 0.0; }
//if (rec_point1_y < 0) { rec_point1_y = 0.0; }
//Point rec_point1 = Point(rec_point1_x, rec_point1_y);//对角线两个点
//Point rec_point2 = Point(current_shape(16, 0), current_shape(16, 1));
//Point rec_point3 = Point(rec_point1_x, current_shape(16, 1));
//Point rec_point4 = Point(current_shape(16, 0), rec_point1_y);
cout << deta << endl;
cout << rec_point1 << endl;
cout << rec_point2<< endl;
//rectangle(img, rec_point1,rec_point2,Scalar(255,255,0), 2, 8, 0);
//line(img, Point(current_shape(36,0),current_shape(36,1)), Point(current_shape(45,0),current_shape(45,1)), Scalar(255, 255, 0), 2);
//cv::imshow("result", img);
//waitKey(300);
polygon.push_back(rec_point2);
polygon.push_back(rec_point4);
//cout << files[j] << endl;
string depthFace_dir, RGBFace_dir;
string roi_depthFace_dir, roi_RGBFace_dir;//抠出的人脸轮廓
//depthFace_dir = files[j];//RGB图片绝对路径
RGBFace_dir = files[j];//RGB图片绝对路径
//depthFace_dir.replace()
depthFace_dir = string_replace(RGBFace_dir, "RGBFace", "DepthFace");
depthFace_dir = string_replace(depthFace_dir, "jpg", "png");
Mat Depth_image = imread(depthFace_dir, CV_LOAD_IMAGE_UNCHANGED);//读取CV_16UC1类型的数据
//cout << depthFace_dir << endl;
if (!Depth_image.empty()) {
Mat roi_depthFace = contour_roi(Depth_image, polygon);
//新建一个roi_depthFace_dir文件夹,用于保存处理后的深度图片
roi_depthFace_dir = string_replace(depthFace_dir, "DepthFace", "roi_DepthFace");
//cout << roi_depthFace_dir << endl;
imwrite(roi_depthFace_dir, roi_depthFace);
Mat roi_RGBFace = contour_roi(img, polygon);
//新建一个roi_RGBFace_dir文件夹,用于保存处理后的RGB图片
//保存这个图是为了方便删除抠出的人脸是无效的,可以直接删除
roi_RGBFace_dir = string_replace(RGBFace_dir, "RGBFace", "roi_RGBFace");
//cout << roi_RGBFace_dir << endl;
imwrite(roi_RGBFace_dir, roi_RGBFace);
}
else {
cout << depthFace_dir << endl;
ofstream out;
out.open("E:/学习/深度学习/活体检测/数据集/Captrue_Data/dataset/real/read_image_emtpy.txt",ios::out);
out << depthFace_dir << "\n";
continue;
}
}
//cv::imshow("result", img);
//waitKey(10);
//Mat image = imread("E:/201842516518997.png", CV_LOAD_IMAGE_UNCHANGED);//CV_16UC1类型的数据
//读取16位的深度图
//waitKey(100);
//waitKey(100);
}
}