1. 简介
本文介绍的内容是判断给定图像中人物的年龄和性别等信息。大致思路是首先使用人脸检测器定位人脸位置,然后分别使用年龄检测模型和性别检测模型得到最终的检测结果。
2. Face Detector
该项目的关键是首先准确定位人脸的位置,上图是所使用的人脸检测模型。
3. Age Detector
4. Gender Detector
5. 代码实现
#include<tuple>
#include<vector>
#include<iostream>
#include<opencv2/dnn.hpp>
#include<opencv2/imgproc.hpp>
#include<opencv2/highgui.hpp>
using namespace cv;
using namespace std;
using namespace cv::dnn;
tuple<Mat, vector<vector<int>>> getFaceBox(Net net, Mat& frame, double conf_threshold) {
Mat frameOpenCVDNN = frame.clone();
// 输入图像高、宽
int frameHeight = frameOpenCVDNN.rows;
int frameWidth = frameOpenCVDNN.cols;
// 缩放系数
double inScaleFactor = 1.0;
// 归一化后的图像大小
Size size = Size(300, 300);
// 归一化所使用的均值
Scalar meanVal = Scalar{ 104, 117, 123 };
// 获取输入图像并转换格式
cv::Mat inputBlob;
// 各参数含义(输入图像, 缩放系数, 输出图形尺寸, 均值, 是否交换RB两通道, 是否裁剪图像)
inputBlob = cv::dnn::blobFromImage(frameOpenCVDNN, inScaleFactor, size, meanVal, true, false);
// 模型输入,根据关键字"data"
net.setInput(inputBlob, "data");
// 模型输出,根据关键字"detection_out"
cv::Mat detection = net.forward("detection_out");
// 存放输出结果,并使用detection初始化
cv::Mat detectionMat(detection.size[2], detection.size[3], CV_32F, detection.ptr<float>());
// 存放检测框
vector<vector<int>> bboxes;
// 遍历检测结果
for (int i = 0; i < detectionMat.rows; ++i) {
// 置信度
float confidence = detectionMat.at<float>(i, 2);
if (confidence > conf_threshold) {
// 获取边界框
int x1 = static_cast<int>(detectionMat.at<float>(i, 3) * frameWidth);
int y1 = static_cast<int>(detectionMat.at<float>(i, 4) * frameHeight);
int x2 = static_cast<int>(detectionMat.at<float>(i, 5) * frameWidth);
int y2 = static_cast<int>(detectionMat.at<float>(i, 6) * frameHeight);
vector<int> box = { x1, y1, x2, y2 };
bboxes.push_back(box);
// 绘制用于标识人脸位置的矩形
cv::rectangle(frameOpenCVDNN, cv::Point(x1, y1), cv::Point(x2, y2), cv::Scalar(0, 255, 0), 2, 4);
}
}
return make_tuple(frameOpenCVDNN, bboxes);
}
int main(int argc, char** argv) {
// 各模型地址
string faceProto = "assets/opencv_face_detector.pbtxt";
string faceModel = "assets/opencv_face_detector_uint8.pb";
string ageProto = "assets/age_deploy.prototxt";
string ageModel = "assets/age_net.caffemodel";
string genderProto = "assets/gender_deploy.prototxt";
string genderModel = "assets/gender_net.caffemodel";
Scalar MODEL_MEAN_VALUES = Scalar(78.4263377603, 87.7689143744, 114.895847746);
// 年龄
vector<string> ageList = { "(0-2)", "(4-6)", "(8-12)", "(15-20)",
"(25-32)", "(38-43)", "(48-53)", "(60-100)" };
// 性别
vector<string> genderList = { "Male", "Female" };
// 模型加载,readNet支持Caffe(.caffemodel), TensorFlow(.pb), Torch(.t7), DarkNet(.weights), DLDT(.bin), ONNX(.onnx)等模型
// 不支持常用的PyTorch模型,需要首先将其转换为ONNX等格式
Net faceNet = readNet(faceModel, faceProto);
Net ageNet = readNet(ageModel, ageProto);
Net genderNet = readNet(genderModel, genderProto);
// 读取图像
Mat image = imread("assets/face.jpg");
// 人脸检测,并获取结果
Mat img;
vector<vector<int>> bboxes;
tie(img, bboxes) = getFaceBox(faceNet, image, 0.7);
// 如果没有检测到目标
if (bboxes.size() == 0) {
cout << "No face detected!" << endl;
return -1;
}
// 处理结果
int padding = 20;
for (auto it = begin(bboxes); it != end(bboxes); ++it) {
// 由(x1,y1,x2,y2)转换成(x,y,w,h),并增加填充
Rect rec(it->at(0) - padding, it->at(1) - padding,
it->at(2) - it->at(0) + 2 * padding, it->at(3) - it->at(1) + 2 * padding);
// 设置RoI
Mat face = img(rec);
// 处理输入图像
Mat blob;
blob = blobFromImage(face, 1, Size(227, 227), MODEL_MEAN_VALUES, false);
// 设置模型输入
genderNet.setInput(blob);
// 预测年龄
vector<float> genderPreds = genderNet.forward();
// 得到结果对应的索引,使用std::distance返回结果要么为0要么为1
int max_index_gender = std::distance(genderPreds.begin(), max_element(genderPreds.begin(), genderPreds.end()));
// 性别
string gender = genderList[max_index_gender];
cout << "Gender: " << gender << endl;
// 获得年龄预测输出
ageNet.setInput(blob);
vector<float> agePreds = ageNet.forward();
// 得到结果对应的索引,同上
int max_indice_age = std::distance(agePreds.begin(), max_element(agePreds.begin(), agePreds.end()));
// 年龄
string age = ageList[max_indice_age];
cout << "Age: " << age << endl;
// 显示内容
string label = gender + ", " + age;
// 显示结果
cv::putText(img, label, Point(it->at(0), it->at(1) - 15), cv::FONT_HERSHEY_SIMPLEX, 0.9, Scalar(0, 255, 255), 2, cv::LINE_AA);
imshow("Image", img);
imwrite("assets/out.jpg", img);
}
return 0;
}
结果展示:
参考
- https://github.com/spmallick/learnopencv/tree/master/AgeGender.