使用检测网络检测出人脸之后,在下一步比对或者训练之前,要对人脸进行对齐,我觉得对齐这个词不够精确,我称之为摆正。通过调研,我使用的方法是使用两只眼睛的坐标作为摆正的标准(当然,这需要检测网路能够输出眼睛坐标),摆正之后要对人脸进行重新裁剪,这需要利用眼睛、鼻子和嘴角的信息。归纳起来就是:
1、得出两只眼睛形成的直线的夹角,按照该夹角的反方向进行图像的旋转;旋转之后关键的一步是根据旋转公式,获得旋转之后的特征点的坐标
2、摆正之后,根据眼睛、鼻子、嘴巴纵向距离,估测要裁剪的人脸的高度;
3、确定高度之后,设定高宽比(如1:1,或者1.16:1)等等,确定要裁出的人脸的宽度;
4、根据鼻子和两只眼睛横向的距离比,计算左右两边的具体坐标
std::vector points;//points(vector)元素是人脸两只眼睛所在的点,应该是为了拟合直线以进行人脸旋转
cv::Point eye0 = cv::Point(res[max_index].points_x[0], res[max_index].points_y[0]);
cv::Point eye1 = cv::Point(res[max_index].points_x[1], res[max_index].points_y[1]);
cv::Point mouth_0 = cv::Point(res[max_index].points_x[3], res[max_index].points_y[3]);
cv::Point mouth_1 = cv::Point(res[max_index].points_x[4], res[max_index].points_y[4]);
cv::Point nose = cv::Point(res[max_index].points_x[2], res[max_index].points_y[2]);
cv::line(image, eye0, eye1, (255, 0, 255), 3);
cv::line(image, mouth_0, mouth_1, (0, 0, 255), 3);
cv::imshow("1", image);
points.push_back(eye0);
points.push_back(eye1);
cv::Vec4f line;//vec4f类型的line是拟合的结果,前两个元素表示方向,后两个元素表示直线上一点。
cv::fitLine(points,
line,
CV_DIST_HUBER,
0,
0.01,
0.01);
double cos_theta = line[0];
double sin_theta = line[1];
double x0 = line[2], y0 = line[3];
double phi = atan2(sin_theta, cos_theta) + 3.1416 / 2.0;
double rho = y0 * cos_theta - x0 * sin_theta;
/*std::cout << "phi = " << phi / 3.1416 * 180 << std::endl;
std::cout << "rho = " << rho << std::endl;*/
//DrawLine(image, phi, rho, cv::Scalar(255));
double k = sin_theta / cos_theta;
double b = y0 - k * x0;
double x = 0;
double y = k * x + b;
/*std::cout << k << std::endl;
std::cout << b << std::endl;*/
cv::Mat input_img = image.clone();
cv::Mat temp_img;
// 将图像按照相应的角度旋转
float angle = phi / 3.1416 * 180 - 90; //角度
float radian = (float)(angle / 180.0 * CV_PI); //弧度
// 填充图像使其符合旋转要求
int uniSize = (int)(max(input_img.cols, input_img.rows)* 1.414); //以宽、高较大者的根号2倍,作为啥呢?
int dx = (int)(uniSize - input_img.cols) / 2;//原来是作为扩充宽度
int dy = (int)(uniSize - input_img.rows) / 2;
copyMakeBorder(input_img, temp_img, dy, dy, dx, dx, cv::BORDER_CONSTANT);//以周边恒定值0为扩充值进行扩充
//copyMakeBorder(temp_mat, temp_mat, dy, dy, dx, dx, cv::BORDER_CONSTANT);
// 旋转中心
//cv::Point2f center((float)(temp_img.cols / 2), (float)(temp_img.rows / 2));//看旋转中心的值就知道是以图片中作为旋转中心的
cv::Point2f center((float)(image.cols / 2), (float)(image.rows / 2));
cv::Mat affine_matrix = getRotationMatrix2D(center, angle, 1.0);
// 旋转
warpAffine(temp_img, temp_img, affine_matrix, temp_img.size());
//warpAffine(temp_mat, temp_mat, affine_matrix, temp_img.size());
warpAffine(temp_mat, temp_mat, affine_matrix, image.size());
radian = -radian;
/*float eye0_new_x = (eye0.x - center.x)*cos(radian) - (eye0.y - center.y)*sin(radian) + center.x; //以center为坐标原点
float eye0_new_y = (eye0.x - center.x)*sin(radian) + (eye0.y - center.y)*cos(radian) + center.y;
float eye1_new_x = (eye1.x - center.x)*cos(radian) - (eye1.y - center.y)*sin(radian) + center.x;
float eye1_new_y = (eye1.x - center.x)*sin(radian) + (eye1.y - center.y)*cos(radian) + center.y;
cv::Point2f eye0_new(eye0_new_x, eye0_new_y);
cv::Point2f eye1_new(eye1_new_x, eye1_new_y);*/
cv::Point2f eye0_new = get_new_point(eye0, center, radian);
cv::Point2f eye1_new = get_new_point(eye1, center, radian);
cv::Point2f mouth0_new = get_new_point(mouth_0, center, radian);
cv::Point2f mouth1_new = get_new_point(mouth_1, center, radian);
cv::Point2f nose_new = get_new_point(nose, center, radian);
cout << eye0_new.x << " "<< eye1_new.x << " " << nose_new.x << endl;
//cv::line(temp_mat, eye0_new, eye1_new, (255, 255, 255), 2);
//cv::line(temp_mat, eye0_new, nose_new, (0, 0, 255), 2);
//cv::line(temp_mat, mouth0_new, mouth1_new, (255, 255, 255), 5);
cv::imshow("test", temp_mat);
float delta_W = eye1_new.x - eye0_new.x;
float delta_H = (mouth0_new.y + mouth1_new.y) / 2 - eye0_new.y; //眼睛和嘴唇之间高度差
cout << "两眼间距:" << delta_W << endl;
cout << "眼睛、嘴角高度差:" << delta_H << endl;
//确定要截出的顶端和底端
float y_top = eye0_new.y - delta_H / 2.14;
float y_bottom = (mouth0_new.y + mouth1_new.y) / 2 + delta_H / 3.33;
cout << y_bottom - y_top << endl;
float width = (float)(y_bottom - y_top);
float both_sides = width - delta_W;
float h_distance_eye0_nose = abs(nose.x - eye0_new.x);
float h_distance_eye1_nose = abs(eye1_new.x - nose.x);
float h_ratio_0 = h_distance_eye0_nose /( h_distance_eye0_nose + h_distance_eye1_nose);
float x_left = 0.0;
float x_right = 0.0;
if (h_ratio_0 >= 0.5)
{
x_left = min(eye0_new.x, nose.x) - both_sides*h_ratio_0;
x_right = width + x_left;
}
else
{
x_right = max(nose.x, eye1_new.x) + both_sides*(1-h_ratio_0);
x_left = x_right - width;
}
rect = cv::Rect(cv::Point(x_left, y_top), cv::Point(x_right, y_bottom));
if (rect.x < 0) { rect.x = 0; }
if (rect.y < 0) { rect.y = 0; }
if (rect.x > temp_mat.cols){ rect.x = temp_mat.cols; }
if (rect.y > temp_mat.rows){ rect.y = temp_mat.rows; }
if (rect.x + rect.width > temp_mat.cols){ rect.width = temp_mat.cols - rect.x; }
if (rect.y + rect.height > temp_mat.rows){ rect.height = temp_mat.rows - rect.y; }
cv::Mat face_cut(temp_mat, rect);