实战OpenCV之人脸识别

基础入门

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

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

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

人脸检测:从图像中定位人脸的位置,确定人脸的边界框。常用的方法包括:基于特征的传统方法(比如: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,基本可以判定为同一个人。

相关推荐
刘什么洋啊Zz1 小时前
MacOS下使用Ollama本地构建DeepSeek并使用本地Dify构建AI应用
人工智能·macos·ai·ollama·deepseek
奔跑草-2 小时前
【拥抱AI】GPT Researcher 源码试跑成功的心得与总结
人工智能·gpt·ai搜索·deep research·深度检索
禁默3 小时前
【第四届网络安全、人工智能与数字经济国际学术会议(CSAIDE 2025】网络安全,人工智能,数字经济的研究
人工智能·安全·web安全·数字经济·学术论文
AnnyYoung4 小时前
华为云deepseek大模型平台:deepseek满血版
人工智能·ai·华为云
INDEMIND5 小时前
INDEMIND:AI视觉赋能服务机器人,“零”碰撞避障技术实现全天候安全
人工智能·视觉导航·服务机器人·商用机器人
慕容木木5 小时前
【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体的替代品,可本地部署+知识库,注册即可有750w的token使用
人工智能·火山引擎·deepseek·deepseek r1
南 阳5 小时前
百度搜索全面接入DeepSeek-R1满血版:AI与搜索的全新融合
人工智能·chatgpt
企鹅侠客6 小时前
开源免费文档翻译工具 可支持pdf、word、excel、ppt
人工智能·pdf·word·excel·自动翻译
冰淇淋百宝箱6 小时前
AI 安全时代:SDL与大模型结合的“王炸组合”——技术落地与实战指南
人工智能·安全
Elastic 中国社区官方博客7 小时前
Elasticsearch Open Inference API 增加了对 Jina AI 嵌入和 Rerank 模型的支持
大数据·人工智能·elasticsearch·搜索引擎·ai·全文检索·jina