基础入门

        随着计算机视觉技术和深度学习的发展,人脸识别已经成为一项广泛应用的技术,涵盖了从安全监控、身份验证、智能家居到大型公共安全项目等多个领域。

        人脸识别技术通常包括以下几个主要步骤。

        图像采集:通过摄像头或其他图像采集设备,捕获包含人脸的图像或视频帧。

        人脸检测:从图像中定位人脸的位置,确定人脸的边界框。常用的方法包括:基于特征的传统方法(比如:Haar特征)、基于深度学习的方法(比如:YOLO、SSD等)。

        特征提取:从检测到的人脸区域中提取有用的特征向量。这些特征可以是基于几何形状的特征(比如:眼睛、鼻子、嘴巴之间的相对位置)、颜色特征、或深度学习模型提取的高维特征向量。

        特征匹配:将提取到的特征向量与数据库中的特征向量进行比较,以识别或验证个人的身份。常见的方法包括:欧氏距离、余弦相似度等。

        在OpenCV 4.X版本中,新引入了FaceDetectorYN和FaceRecognizerSF两个类,以提供更高效且准确的面部检测和识别能力。

FaceDetectorYN

        FaceDetectorYN是一个用于面部检测的面向对象接口,它基于YOLOv3架构,并且专门针对人脸检测进行了优化。FaceDetectorYN提供了高效且准确的面部检测能力,适用于实时应用和大规模人脸数据库等场景。FaceDetectorYN可加载预训练的面部检测模型,并提供了一系列方法来检测图像中的人脸。

        FaceDetectorYN::create静态函数用于创建人脸检测的模型实例,其接口原型如下。

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函数用于人脸检测的推理,以获取检测框和关键点信息,其接口原型如下。

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静态函数用于创建人脸识别的模型实例,其接口原型如下。

static Ptr<FaceRecognizerSF> create(const String& model, const String& config);

        各个参数的含义如下。

        model:模型文件的路径。

        config:配置文件的路径。如果模型文件支持直接加载(比如:ONNX格式),则此参数可以为空字符串。

        alignCrop函数用于对齐并裁剪输入的人脸图像,使其符合模型要求的标准姿势,其接口原型如下。

void alignCrop(InputArray src_img, InputArray face_box, OutputArray aligned_img) const;

        各个参数的含义如下。

        src_img:输入的图像。

        face_box:人脸框的位置信息,通常是一个cv::Rect或类似的结构,表示人脸的位置。

        aligned_img:输出的对齐并裁剪后的人脸图像。

        feature函数用于从对齐后的人脸图像中提取特征向量,其接口原型如下。

void feature(InputArray aligned_img, OutputArray face_feature) const;

        各个参数的含义如下。

        aligned_img:输入的对齐并裁剪后的人脸图像。

        face_feature:输出的特征向量,可用于后续的比对。

        match函数用于计算两个特征向量之间的相似度,以判断它们是否属于同一个人,其接口原型如下。

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函数在检测到人脸的图像上绘制人脸框及特征点,并在其中一个图像上叠加显示相似度值。

#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,基本可以判定为同一个人。

实战OpenCV之人脸识别-LMLPHP

11-19 17:39