在图像实际应用过程中,在很多情况下我们需要截取下人脸的某些位置,且保证被截取位置大小方向固定,以眼睛为例,如果要保证截取的眼睛大小固定则需要保持两眼中心的连线水平且距离固定。在视频流的情况下,随着人头部的晃动和头部距离摄像头的远近,眼睛的方向、大小时刻在发生变化,此时便需要进行人脸几何校正(根据头部的晃动角度旋转图像并缩放图像),保证在图像中人的眼睛的大小及方向是确定的,从而能够截取到人脸准确的位置。
本文所做的人脸几何校正是基于opencv和dlib的,opencv和dlib的配置可参考以下两个博客:
Opencv的安装与配置:博客链接
Dlib的安装与配置:博客链接
首先我们需要使用dlib进行人脸的68个关键点的定位,(关键点的定位可以参考我的这篇博客博客链接)根据关键点为位置,进行图像旋转等操作
将点36和点45的中心点做为旋转点
记点36到45的距离为:r=sqrt( (x_45 - x_36)2+(y_45 - y_36)2 )
图中L的长度为:L=x_45 - x_36
图中h的高度为:h=y_45 - y_36
根据几何原理知:sin
a
a
a=r/h
所以有:
a
a
a=arcsin( r / h)
将点39和点42的中心点作为旋转点,记为:P (x_rotating , y_rotating)
则有:
x_rotating=(x_39 + x_42) / 2;
y_rotating=(y_39 + y_42) / 2;
几何校正效果图如下:
几何校正源码如下:
源码中以截取两个眼睛的图像为例,在经过旋转和缩放后,可以截取人脸任意位置的图像且大小固定,不受人脸角度和人脸距离相机距离的影响
#include <dlib\opencv.h>
#include <opencv2\opencv.hpp>
#include <dlib\image_processing\frontal_face_detector.h>
#include <dlib\image_processing\render_face_detections.h>
#include <dlib\image_processing.h>
#include <dlib\gui_widgets.h>
#include <iostream>
#include <vector>
#include <cmath>
#include<time.h>
#include<math.h>
#define DISTANCE 90 //点36到点42的固定距离
using namespace std;
using namespace dlib;
using namespace cv;
//opencv实现图片的任意角度旋转
//逆时针旋转图像degree角度(原尺寸)
Mat rotateImage(Mat src, int angle, int x_rotating, int y_rotating)
{
//旋转中心为图像中心
Mat srcImage = src;
Mat rotate_dstImage;
Mat rot_mat(2, 3, CV_32FC1);
Point center = Point(x_rotating, y_rotating);
double scale = 0.6;
//计算旋转矩阵
rot_mat = getRotationMatrix2D(center, angle, 1);
//旋转图像
warpAffine(srcImage, rotate_dstImage, rot_mat, srcImage.size());
return rotate_dstImage;
}
int main() {
Mat right_roi; //声明一个固定大小的图像,用于存储左眼图像
Mat left_roi; //声明一个固定大小的图像,用于存储右眼图像
Mat left_eye_copy; //保存左眼图像
Mat right_eye_copy; //保存右眼图像
try {
VideoCapture cap(0);
if (!cap.isOpened()) {
printf("Unable to connect a camera!");
return 1;
}
frontal_face_detector detector = get_frontal_face_detector();
shape_predictor pos_modle;
deserialize("D:/shape_predictor_68_face_landmarks/shape_predictor_68_face_landmarks.dat") >> pos_modle;
while (waitKey(30) != 27) {
Mat temp;
Mat cv_temp; //后边的操作将temp转化为dilb的类型,opencv不能处理,声明一个与temp一样的cv_temp
Mat rotate_temp; //旋转后的图像
Mat scaling_temp; //缩放后的图像
cap >> temp;
cv_temp = temp.clone(); //保存opencv类型的图像
//将图像转化为dlib中的BGR的形式
cv_image<bgr_pixel> cimg(temp);
std::vector<dlib::rectangle> faces = detector(cimg);
std::vector<full_object_detection> shapes;
unsigned int faceNumber = faces.size(); //获取容器中向量的个数即人脸的个数
for (unsigned int i = 0; i < faceNumber; i++) {
shapes.push_back(pos_modle(cimg, faces[i]));
}
if (!shapes.empty()) {
int faceNumber = shapes.size();
for (int j = 0; j < faceNumber; j++)
{
for (int i = 0; i < 68; i++)
{
//用来画特征值的点
cv::circle(temp, cvPoint(shapes[j].part(i).x(), shapes[j].part(i).y()), 1, cv::Scalar(0, 0, 255), -1);
//参数说明 图像 圆心 线条宽度 颜色 线的类型
//显示数字
//cv::putText(temp, to_string(i), cvPoint(shapes[0].part(i).x(), shapes[0].part(i).y()), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0, 0, 255));
}
}
//左眼关键点
int x_36 = shapes[0].part(36).x();
int y_36 = shapes[0].part(36).y();
int x_37 = shapes[0].part(37).x();
int y_37 = shapes[0].part(37).y();
int x_38 = shapes[0].part(38).x();
int y_38 = shapes[0].part(38).y();
int x_39 = shapes[0].part(39).x();
int y_39 = shapes[0].part(39).y();
int x_40 = shapes[0].part(40).x();
int y_40 = shapes[0].part(40).y();
int x_41 = shapes[0].part(41).x();
int y_41 = shapes[0].part(41).y();
int left_length = x_38 - x_37;
//右眼关键点
int x_42 = shapes[0].part(42).x();
int y_42 = shapes[0].part(42).y();
int x_43 = shapes[0].part(43).x();
int y_43 = shapes[0].part(43).y();
int x_44 = shapes[0].part(44).x();
int y_44 = shapes[0].part(44).y();
int x_45 = shapes[0].part(45).x();
int y_45 = shapes[0].part(45).y();
int x_46 = shapes[0].part(46).x();
int y_46 = shapes[0].part(46).y();
int x_47 = shapes[0].part(47).x();
int y_47 = shapes[0].part(47).y();
int right_length = x_44 - x_43;
//人脸几何校正(保持双眼连线水平)
//旋转中心点为两眼角点(36和45)的中心位置
float x_rotating = (x_39 + x_42) / 2; //旋转中心点横坐标
float y_rotating = (y_39 + y_42) / 2; //旋转中心点纵坐标
//计算旋转角度alpha
float r = (x_45 - x_36) * (x_45 - x_36) + (y_45 - y_36) * (y_45 - y_36);
r = sqrt(r);
float l = x_45 - x_36;
float h = y_45 - y_36;
double alpha;
if (h < 0) { //人脸像左倾斜
//h = 0 - h;
double r_h = h / r;
alpha = (double)((asin(r_h) * 180) / CV_PI); //alpha=arcsin(r/h)
alpha = 360 + alpha;
}
else { //人脸向右倾斜
double r_h = h / r;
alpha = (double)((asin(r_h) * 180) / CV_PI); //alpha=arcsin(r/h)
}
//cout << "r=" << r << endl;
//cout << "l=" << l << endl;
//cout << "h=" << h << endl;
//cout << "alpha=" << alpha << endl;
//旋转图像
cvtColor(cv_temp, cv_temp, COLOR_BGR2GRAY);
rotate_temp = rotateImage(cv_temp, alpha, x_rotating, y_rotating);
//重新定位点36,37,42,43变换后的位置
//36,37,42,43点的X坐标没有改变
int hight_36_37 = y_36 - y_37;
int hight_42_43 = y_42 - y_43;
double length_36_rotating = (x_rotating - x_36) * (x_rotating - x_36) + (y_rotating - y_36) * (y_rotating - y_36);
length_36_rotating = sqrt(length_36_rotating);
double length_42_rotating = (x_rotating - x_42) * (x_rotating - x_42) + (y_rotating - y_42) * (y_rotating - y_42);
length_42_rotating = sqrt(length_42_rotating);
//36,42的纵坐标与旋转点的纵坐标一样
y_36 = y_rotating;
y_42 = y_rotating;
y_37 = y_36 - hight_36_37;
y_43 = y_42 - hight_42_43;
//重新定位横坐标
x_36 = (int)x_rotating - (int)length_36_rotating;
x_42 = (int)x_rotating + (int)length_42_rotating;
//imshow("rotate_temp", rotate_temp);
//固定下点36和点42的距离
//等比例缩放图像
int distance = x_42 - x_36;
double scaling; //缩放比例
scaling = distance / (double)DISTANCE; //DISTANCE宏定义为90,
//cout << "scaling:" << scaling << endl;
if (scaling <= 1) {
scaling = 1 + (1 - scaling);
}
else {
scaling = 1 - (scaling - 1);
}
//根据缩放的比例缩放图像
resize(rotate_temp, scaling_temp, Size((int)(rotate_temp.cols * scaling), (int)(rotate_temp.rows * scaling)), 0, 0, INTER_LINEAR);
imshow("scaling_temp", scaling_temp);
cout << "distance:" << distance << endl;
//再次定位点36,37,42,43,rotating变换后的位置,用于截取眼睛图像
x_rotating = x_rotating * scaling;
y_rotating = y_rotating * scaling;
x_36 = x_rotating - (length_36_rotating * scaling);
y_36 = y_rotating;
y_37 = y_36 - (hight_36_37 * scaling);
x_42 = x_rotating + (length_42_rotating * scaling);
y_42 = y_rotating;
y_43 = y_42 - (hight_42_43 * scaling);
int distance_36_42 = x_42 - x_36;
//cout << "distance_36_42:" << distance_36_42 << endl;
//复制左眼的部分的图像
left_roi = scaling_temp(Rect(x_36 - 10, y_37 - 5, 50, 20));
left_eye_copy = Mat(Size(50, 20), left_roi.type());
left_eye_copy = Scalar(255, 255, 255); //给图像填充颜色
left_eye_copy = left_roi.clone();
imshow("left_eye_copy", left_eye_copy);
//复制右眼的部分的图像
right_roi = scaling_temp(Rect(x_42 - 5, y_43 - 5, 50, 20));
right_eye_copy = Mat(Size(50, 20), right_roi.type());
right_eye_copy = Scalar(255, 255, 255); //给图像填充颜色
right_eye_copy = right_roi.clone();
imshow("right_eye_copy", right_eye_copy);
cv::imshow("Dlib标记", temp);
}
}
}
catch (serialization_error& e) {
cout << "You need dlib‘s default face landmarking file to run this example." << endl;
cout << endl << e.what() << endl;
}
catch (exception& e) {
cout << e.what() << endl;
}
}