基础入门
随着计算机视觉技术和深度学习的发展,人脸识别已经成为一项广泛应用的技术,涵盖了从安全监控、身份验证、智能家居到大型公共安全项目等多个领域。
人脸识别技术通常包括以下几个主要步骤。
图像采集:通过摄像头或其他图像采集设备,捕获包含人脸的图像或视频帧。
人脸检测:从图像中定位人脸的位置,确定人脸的边界框。常用的方法包括:基于特征的传统方法(比如:Haar特征)、基于深度学习的方法(比如:YOLO、SSD等)。
特征提取:从检测到的人脸区域中提取有用的特征向量。这些特征可以是基于几何形状的特征(比如:眼睛、鼻子、嘴巴之间的相对位置)、颜色特征、或深度学习模型提取的高维特征向量。
特征匹配:将提取到的特征向量与数据库中的特征向量进行比较,以识别或验证个人的身份。常见的方法包括:欧氏距离、余弦相似度等。
在OpenCV 4.X版本中,新引入了FaceDetectorYN和FaceRecognizerSF两个类,以提供更高效且准确的面部检测和识别能力。
FaceDetectorYN
FaceDetectorYN是一个用于面部检测的面向对象接口,它基于YOLOv3架构,并且专门针对人脸检测进行了优化。FaceDetectorYN提供了高效且准确的面部检测能力,适用于实时应用和大规模人脸数据库等场景。FaceDetectorYN可加载预训练的面部检测模型,并提供了一系列方法来检测图像中的人脸。
FaceDetectorYN::create静态函数用于创建人脸检测的模型实例,其接口原型如下。
cpp
static Ptr<FaceDetectorYN> create(const String& model,
const String& config, Size inputSize,
double scoreThreshold = 0.9, double nmsThreshold = 0.3,
int topK = 5000);
各个参数的含义如下。
model:模型文件的路径。
config:配置文件的路径。如果模型文件支持直接加载(比如:ONNX格式),则此参数可以为空字符串。
inputSize:模型接收的输入图像尺寸,通常为"(宽度, 高度)"的形式。
scoreThreshold:检测得分阈值,默认为0.9。只有得分高于此阈值的检测框,才会被认为是有效的。
nmsThreshold:非极大值抑制(NMS)的阈值,默认为0.3,用于去除重叠的检测框。
topK:最多保留的检测结果数量,默认为5000。
detect函数用于人脸检测的推理,以获取检测框和关键点信息,其接口原型如下。
cpp
int detect(InputArray image, OutputArray faces);
各个参数的含义如下。
image:输入的图像。
faces:输出的人脸检测结果,通常是一个Mat,其形状为[num_faces, 15]。每个人脸包含15个元素,元素的含义如下。
(1)元素0-1:人脸框左上角的x、y位置。
(2)元素2-3:人脸框的宽度和高度。
(3)元素4-5:右眼的x、y位置。
(4)元素6-7:左眼的x、y位置。
(5)元素8-9:鼻子的x、y位置。
(6)元素10-11:右嘴角的x、y位置。
(7)元素12-13:左嘴角的x、y位置。
(8)元素14:人脸检测的得分。
FaceRecognizerSF
FaceRecognizerSF主要用于从图像中提取人脸特征向量,这些特征向量可以用于后续的人脸识别任务。其主要功能包括下面三点。
1、特征提取:从输入的人脸图像中提取出一个固定长度的特征向量,这个特征向量能够代表该人脸的主要特征。
2、特征对比:比较两个人脸特征向量之间的相似度,从而判断两个人脸是否属于同一人。
3、人脸对齐:对输入的人脸图像进行对齐处理,使得提取出的特征更具有一致性。
FaceRecognizerSF的主要接口和方法如下。
FaceRecognizerSF::create静态函数用于创建人脸识别的模型实例,其接口原型如下。
cpp
static Ptr<FaceRecognizerSF> create(const String& model, const String& config);
各个参数的含义如下。
model:模型文件的路径。
config:配置文件的路径。如果模型文件支持直接加载(比如:ONNX格式),则此参数可以为空字符串。
alignCrop函数用于对齐并裁剪输入的人脸图像,使其符合模型要求的标准姿势,其接口原型如下。
cpp
void alignCrop(InputArray src_img, InputArray face_box, OutputArray aligned_img) const;
各个参数的含义如下。
src_img:输入的图像。
face_box:人脸框的位置信息,通常是一个cv::Rect或类似的结构,表示人脸的位置。
aligned_img:输出的对齐并裁剪后的人脸图像。
feature函数用于从对齐后的人脸图像中提取特征向量,其接口原型如下。
cpp
void feature(InputArray aligned_img, OutputArray face_feature) const;
各个参数的含义如下。
aligned_img:输入的对齐并裁剪后的人脸图像。
face_feature:输出的特征向量,可用于后续的比对。
match函数用于计算两个特征向量之间的相似度,以判断它们是否属于同一个人,其接口原型如下。
cpp
double match(InputArray face_feature1, InputArray face_feature2,
int dis_type = FaceRecognizerSF::FR_COSINE) const;
各个参数的含义如下。
face_feature1:第一个特征向量。
face_feature2:第二个特征向量。
dis_type:相似度计算的方式,默认为FR_COSINE(余弦相似度),也可以选择FR_NORM_L2(L2 范数)。
实战解析
下面的实战代码演示了如何使用FaceDetectorYN和FaceRecognizerSF来进行人脸检测、对齐、特征提取以及相似度匹配。
首先,我们从两个不同的图像文件中加载图像,并检查是否成功加载。接着,我们创建了两个人脸处理模块的实例:一个用于人脸检测,另一个用于特征识别。
为了使人脸检测更高效,我们调整了图像的尺寸到320 x 320。然后,使用人脸检测器在调整后的图像上查找人脸,并将检测到的人脸信息存储在faces1和faces2中。如果检测到了人脸,程序会使用人脸识别器来对齐并裁剪出人脸区域。随后,进一步提取每个人脸的特征向量,并将这些特征向量克隆到新的Mat对象中。
注意:必须将特征向量克隆一份,否则match会一直返回1。这是因为,feature返回的特征向量是内部共享数据的,内部data指向的是同一个指针。
一旦有了两个人脸的特征向量,我们就会计算它们之间的相似度,并输出这个值。最后,我们使用DrawFaces函数在检测到人脸的图像上绘制人脸框及特征点,并在其中一个图像上叠加显示相似度值。
cpp
#include <opencv2/opencv.hpp>
using namespace cv;
#include <iostream>
using namespace std;
static void DrawFaces(Mat& input, Mat& faces, int thickness = 2)
{
for (int i = 0; i < faces.rows; i++)
{
// 打印人脸信息
cout << "Face " << i
<< ", top-left coordinates: (" << faces.at<float>(i, 0) <<
", " << faces.at<float>(i, 1) << "), " << "box width: " <<
faces.at<float>(i, 2) << ", box height: " <<
faces.at<float>(i, 3) << ", " << "score: " <<
format("%.2f", faces.at<float>(i, 14)) << endl;
// 画边框
rectangle(input, Rect2i(int(faces.at<float>(i, 0)), int(faces.at<float>(i, 1)),
int(faces.at<float>(i, 2)), int(faces.at<float>(i, 3))), Scalar(0, 255, 0), thickness);
// 画特征点
circle(input, Point2i(int(faces.at<float>(i, 4)), int(faces.at<float>(i, 5))), 2,
Scalar(255, 0, 0), thickness);
circle(input, Point2i(int(faces.at<float>(i, 6)), int(faces.at<float>(i, 7))), 2,
Scalar(0, 0, 255), thickness);
circle(input, Point2i(int(faces.at<float>(i, 8)), int(faces.at<float>(i, 9))), 2,
Scalar(0, 255, 0), thickness);
circle(input, Point2i(int(faces.at<float>(i, 10)), int(faces.at<float>(i, 11))), 2,
Scalar(255, 0, 255), thickness);
circle(input, Point2i(int(faces.at<float>(i, 12)), int(faces.at<float>(i, 13))), 2,
Scalar(0, 255, 255), thickness);
}
}
int main(int argc, char** argv)
{
Mat image1 = imread("person_tong1.jpg");
Mat image2 = imread("person_tong2.jpg");
if (image1.empty() || image2.empty())
{
cout << "Can not open or find the image" << endl;
return -1;
}
// 创建FaceDetectorYN实例
Ptr<FaceDetectorYN> detector = FaceDetectorYN::create(
"face_detection_yunet_2023mar_int8.onnx", "",
Size(320, 320), 0.5);
// 创建FaceRecognizerSF实例
Ptr<FaceRecognizerSF> recognizer = FaceRecognizerSF::create(
"face_recognition_sface_2021dec_int8.onnx", "");
// 调整图像尺寸
Mat resized_image1;
resize(image1, resized_image1, Size(320, 320));
Mat resized_image2;
resize(image2, resized_image2, Size(320, 320));
// 检测人脸
Mat faces1;
detector->detect(resized_image1, faces1);
Mat faces2;
detector->detect(resized_image2, faces2);
// 对齐并裁剪人脸
Mat aligned_face1, aligned_face2;
if (!faces1.empty())
{
recognizer->alignCrop(resized_image1, faces1.row(0), aligned_face1);
}
if (!faces2.empty())
{
recognizer->alignCrop(resized_image2, faces2.row(0), aligned_face2);
}
// 提取特征向量
Mat embedding1;
if (!aligned_face1.empty())
{
recognizer->feature(aligned_face1, embedding1);
embedding1 = embedding1.clone();
}
Mat embedding2;
if (!aligned_face2.empty())
{
recognizer->feature(aligned_face2, embedding2);
embedding2 = embedding2.clone();
}
// 计算两张图像中人脸特征向量的相似度
double similarity = 0.0;
if (!embedding1.empty() && !embedding2.empty())
{
similarity = recognizer->match(embedding1, embedding2);
cout << "Similarity: " << similarity << endl;
}
else
{
cout << "No face detected in one or both images" << endl;
return -1;
}
// 在图像上绘制人脸框
if (!faces1.empty())
{
DrawFaces(resized_image1, faces1);
}
if (!faces2.empty())
{
DrawFaces(resized_image2, faces2);
char pszText[128] = { 0 };
sprintf(pszText, "Similarity: %.2f", similarity);
putText(resized_image2, pszText, Point(20, 50), FONT_HERSHEY_SIMPLEX,
1.0, Scalar(0, 0, 255), 2, 8);
}
// 显示图像
namedWindow("Face 1");
imshow("Face 1", resized_image1);
namedWindow("Face 2");
imshow("Face 2", resized_image2);
waitKey(0);
destroyAllWindows();
return 0;
}
执行上面的代码,运行效果可参考下图。可以看到,两张图片中人脸的相似度达到0.61,基本可以判定为同一个人。