文章目录

  • (一)Seetaface2 基本概念
    • 1 图像
      • 1.1 结构定义
      • 1.2 使用示例
    • 2. 人脸检测和关键点定位
      • 2.1 人脸检测器
      • 2.2 人脸关键点定位器
    • 3. 人脸特征提取和对比
      • 3.1 人脸特征提取
  • (二)代码实现
  • (三)效果
  • (四)写在后面

(一)Seetaface2 基本概念

1 图像

1.1 结构定义

作为图像处理的C++库,图像存储是一个基本数据结构。
接口中图像对象为SeetaImageData。

    struct SeetaImageData{int width; // 图像宽度int height; // 图像高度int channels; // 图像通道unsigned char *data; // 图像数据};

这里要说明的是data的存储格式,其存储的是连续存放的试用8位无符号整数表示的像素值,存储为[height, width, channels]顺序。彩色图像时三通道以BGR通道排列。
如下图所示,就是展示了高4宽3的彩色图像内存格式。

Image Layout

该存储在内存中是连续存储。因此,上图中表示的图像data的存储空间为433=36bytes。

提示: BGR是OpenCV默认的图像格式。在大家很多时候看到就是直接将cv::Mat的data直接赋值给SeetaImageData的data。

data的数据类型为uint8,数值范围为[0, 255],表示了对应的灰度值由最暗到最亮。在一些用浮点数 float表示灰度值的平台上,该范围映射到为[0, 1]。

这里详细描述格式是为了对接不同应用场景下的图像库的图像表述格式,这里注意的两点,1. 数据类型是uint8表示的[0, 255]范围;2. 颜色通道是BGR格式,而很多图像库常见的是RGB或者RGBA(A为alpha不透明度)。

提示:颜色通道会影响识别算法的精度,在不同库的转换时需要小心注意。

这种纯C接口不利于一些资源管理的情况。SeetaFace 提供给了对应SeetaImageData的封装seeta::ImageData和seeta::cv::ImageData。

1.2 使用示例

以下就是使用OpenCV加载图像后转换为SeetaImageData的操作。

cv::Mat cvimage = cv::imread("1.jpg", cv::IMREAD_COLOR);SeetaImageData simage;simage.width = cvimage.cols;simage.height = cvimage.rows;simage.channels = cvimage.channels();simage.data = cvimage.data;

注意:simage.data拿到的是“借来的”临时数据,在试用的期间,必须保证cvimage的对象不被释放,或者形式的更新。
注意:原始图像可能是空的,为了输入的合法性,需要通过cvimage.empty()方法,判断图像是否为空。

这里通过cv::imread并且第二个参数设置为cv::IMREAD_COLOR,获取到的cv::Mat的图像数据是连续存储的,这个是使用SeetaImageData必须的。如果不确定是否是连续存储的对象,可以调用下述代码段进行转换。

if (!cvimage.isContinuous()) cvimage = cvimage.clone();

当然,根据cv::Mat和SeetaImageData,对象的转换可以逆向。

cv::Mat another_cvimage = cv::Mat(simage.height, simage.width, CV_8UC(simage.channels), simage.data);

seeta::ImageData和seeta::cv::ImageData也是基于这种基本的类型定义进行的封装,并加进了对象声明周期的管理。

这里展示了一种封装:

    namespace seeta{namespace cv{// using namespace ::cv;class ImageData : public SeetaImageData {public:ImageData( const ::cv::Mat &mat ): cv_mat( mat.clone() ) {this->width = cv_mat.cols;this->height = cv_mat.rows;this->channels = cv_mat.channels();this->data = cv_mat.data;}private:::cv::Mat cv_mat;};}}

这样SeetaImageData使用代码就可以简化为:

   seeta::cv::ImageData = cv::imread("1.jpg");

因为seeta::cv::ImageData继承了SeetaImageData,因此在需要传入const SeetaImageData &类型的地方,可以直接传入seeta::cv::ImageData对象。

2. 人脸检测和关键点定位

终于经过繁杂的基础特性说明之后,迎来了两个重要的识别器模块。人脸检测和关键点定位。

人脸检测, seeta::FaceDetector 就是输入待检测的图片,输出检测到的每个人脸位置,用矩形表示。
关键点定位,seeta::FaceLandmarker就是输入待检测的图片,和待检测的人脸位置,输出N个关键点的坐标(图片内)。

两个模块分别负责找出可以处理的人脸位置,检测出关键点用于标定人脸的状态,方便后续的人脸对齐后进行对应识别分析。

2.1 人脸检测器

人脸检测器的效果如图所示:
FaceDetector

这里给出人脸检测器的主要接口:

    namespace seeta {class FaceDetector {FaceDetector(const SeetaModelSetting &setting);SeetaFaceInfoArray detect(const SeetaImageData &image) const;std::vector<SeetaFaceInfo> detect_v2(const SeetaImageData &image) const;void set(Property property, double value);double get(Property property) const;}}

构造一个检测器的函数参考如下:

    #include <seeta/FaceDetector.h>seeta::FaceDetector *new_fd() {seeta::ModelSetting setting;setting.append("face_detector.csta");return new seeta::FaceDetector(setting);}

有了检测器,我们就可以对图片检测人脸,检测图片中所有人脸并打印坐标的函数参考如下:

    #include <seeta/FaceDetector.h>void detect(seeta::FaceDetector *fd, const SeetaImageData &image) {std::vector<SeetaFaceInfo> faces = fd->detect_v2(image);for (auto &face : faces) {SeetaRect rect = face.pos;std::cout << "[" << rect.x << ", " << rect.y << ", "<< rect.width << ", " << rect.height << "]: "<< face.score << std::endl;}}

这里要说明的是,一般检测返回的所有人脸是按照置信度排序的,当应用需要获取最大的人脸时,可以对检测结果进行一个部分排序获取出最大的人脸,如下代码排序完成后,faces[0]就是最大人脸的位置。

    std::partial_sort(faces.begin(), faces.begin() + 1, faces.end(), [](SeetaFaceInfo a, SeetaFaceInfo b) {return a.pos.width > b.pos.width;});

人脸检测器可以设置一些参数,通过set方法。可以设置的属性有:

    seeta::FaceDetector::PROPERTY_MIN_FACE_SIZE 最小人脸seeta::FaceDetector::PROPERTY_THRESHOLD 检测器阈值seeta::FaceDetector::PROPERTY_MAX_IMAGE_WIDTH 可检测的图像最大宽度seeta::FaceDetector::PROPERTY_MAX_IMAGE_HEIGHT 可检测的图像最大高度

最小人脸是人脸检测器常用的一个概念,默认值为20,单位像素。它表示了在一个输入图片上可以检测到的最小人脸尺度,注意这个尺度并非严格的像素值,例如设置最小人脸80,检测到了宽度为75的人脸是正常的,这个值是给出检测能力的下限。

最小人脸和检测器性能息息相关。主要方面是速度,使用建议上,我们建议在应用范围内,这个值设定的越大越好。SeetaFace采用的是BindingBox Regresion的方式训练的检测器。如果最小人脸参数设置为80的话,从检测能力上,可以将原图缩小的原来的1/4,这样从计算复杂度上,能够比最小人脸设置为20时,提速到16倍。

检测器阈值默认值是0.9,合理范围为[0, 1]。这个值一般不进行调整,除了用来处理一些极端情况。这个值设置的越小,漏检的概率越小,同时误检的概率会提高;

可检测的图像最大宽度和可检测的图像最大高度是相关的设置,默认值都是2000。最大高度和宽度,是算法实际检测的高度。检测器是支持动态输入的,但是输入图像越大,计算所使用的内存越大、计算时间越长。如果不加以限制,一个超高分辨率的图片会轻易的把内存撑爆。这里的限制就是,当输入图片的宽或者高超过限度之后,会自动将图片缩小到限制的分辨率之内。

我们当然希望,一个算法在各种场景下都能够很好的运行,但是自然规律远远不是一个几兆的文件就是能够完全解释的。应用上总会需要取舍,也就是trade-off。

2.2 人脸关键点定位器

关键点定位器的效果如图所示:
FaceLandmarker

关键定定位输入的是原始图片和人脸检测结果,给出指定人脸上的关键点的依次坐标。

这里检测到的5点坐标循序依次为,左眼中心、右眼中心、鼻尖、左嘴角和右嘴角。
注意这里的左右是基于图片内容的左右,并不是图片中人的左右,即左眼中心就是图片中左边的眼睛的中心。

同样的方式,我们也可以构造关键点定位器:

    #include <seeta/FaceLandmarker.h>seeta::FaceLandmarker *new_fl() {seeta::ModelSetting setting;setting.append("face_landmarker_pts5.csta");return new seeta::FaceLandmarker(setting);}

根据人脸检测关键点,并将坐标打印出来的代码如下:

    #include <seeta/FaceLandmarker.h>void mark(seeta::FaceLandmarker *fl, const SeetaImageData &image, const SeetaRect &face) {std::vector<SeetaPointF> points = fl->mark(image, face);for (auto &point : points) {std::cout << "[" << point.x << ", " << point.y << "]" << std::endl;}}

当然开放版也会有多点的模型放出来,限于篇幅不再对点的位置做过多的文字描述。
例如face_landmarker_pts68.csta就是68个关键点检测的模型。其坐标位置可以通过逐个打印出来进行区分。

这里需要强调说明一下,这里的关键点是指人脸上的关键位置的坐标,在一些表述中也将关键点称之为特征点,但是这个和人脸识别中提取的特征概念没有任何相关性。并不存在结论,关键点定位越多,人脸识别精度越高。

一般的关键点定位和其他的基于人脸的分析是基于5点定位的。而且算法流程确定下来之后,只能使用5点定位。5点定位是后续算法的先验,并不能直接替换。从经验上来说,5点定位已经足够处理人脸识别或其他相关分析的精度需求,单纯增加关键点个数,只是增加方法的复杂度,并不对最终结果产生直接影响。

参考:seeta/FaceDetector.h seeta/FaceLandmarker.h

3. 人脸特征提取和对比

这两个重要的功能都是seeta::FaceRecognizer模块提供的基本功能。特征提取方式和对比是对应的。

这是人脸识别的一个基本概念,就是将待识别的人脸经过处理变成二进制数据的特征,然后基于特征表示的人脸进行相似度计算,最终与相似度阈值对比,一般超过阈值就认为特征表示的人脸是同一个人。

这里SeetaFace的特征都是float数组,特征对比方式是向量內积。

3.1 人脸特征提取

首先可以构造人脸识别器以备用:

    #include <seeta/FaceRecognizer.h>seeta::FaceRecognizer *new_fr() {seeta::ModelSetting setting;setting.append("face_recognizer.csta");return new seeta::FaceRecognizer(setting);}

特征提取过程可以分为两个步骤:1. 根据人脸5个关键点裁剪出人脸区域;2. 将人脸区域输入特征提取网络提取特征。
这两个步骤可以分开调用,也可以独立调用。

两个步骤分别对应seeta::FaceRecognizer的CropFaceV2和ExtractCroppedFace。也可以用Extract方法一次完成两个步骤的工作。

这里列举使用Extract进行特征提取的函数:

    #include <seeta/FaceRecognizer.h>#include <memory>std::shared_ptr<float> extract(seeta::FaceRecognizer *fr,const SeetaImageData &image,const std::vector<SeetaPointF> &points) {std::shared_ptr<float> features(new float[fr->GetExtractFeatureSize()],std::default_delete<float[]>());fr->Extract(image, points.data(), features.get());return features;}

同样可以给出相似度计算的函数:

    #include <seeta/FaceRecognizer.h>#include <memory>float compare(seeta::FaceRecognizer *fr,const std::shared_ptr<float> &feat1,const std::shared_ptr<float> &feat2) {return fr->CalculateSimilarity(feat1.get(), feat2.get());}

注意:这里points的关键点个数必须是SeetaFace提取的5点关键点。

特征长度是不同模型可能不同的,要使用GetExtractFeatureSize方法获取当前使用模型提取的特征长度。

相似度的范围是[0, 1],但是需要注意的是,如果是直接用內积计算的话,因为特征中存在复数,所以计算出的相似度可能为负数。识别器内部会将负数映射到0。

在一些特殊的情况下,需要将特征提取分开两步进行,比如前端裁剪处理图片,服务器进行特征提取和对比。下面给出分步骤的特征提取方式:

    #include <seeta/FaceRecognizer.h>#include <memory>std::shared_ptr<float> extract_v2(seeta::FaceRecognizer *fr,const SeetaImageData &image,const std::vector<SeetaPointF> &points) {std::shared_ptr<float> features(new float[fr->GetExtractFeatureSize()],std::default_delete<float[]>());seeta::ImageData face = fr->CropFaceV2(image, points.data());fr->ExtractCroppedFace(face, features.get());return features;}

函数中间临时申请的face和features的对象大小,在识别器加载后就已经固定了,所以这部分的内存对象是可以复用的。

特别指出,如果只是对一个图像中最大人脸做特征提取的函数可以实现为:

    std::shared_ptr<float> extract(seeta::FaceDetector *fd,seeta::FaceLandmarker *fl,seeta::FaceRecognizer *fr,const SeetaImageData &image) {auto faces = fd->detect_v2(image);if (faces.empty()) return nullptr;std::partial_sort(faces.begin(), faces.begin() + 1, faces.end(),[](SeetaFaceInfo a, SeetaFaceInfo b) {return a.pos.width > b.pos.width;});auto points = fl->mark(image, faces[0].pos);return extract(fr, image, points);}

(二)代码实现

实现81特征点检测

/** @Descripttion: * @version: * @Author: Yueyang* @email: 1700695611@qq.com* @Date: 2020-11-28 01:14:29* @LastEditors: Yueyang* @LastEditTime: 2020-11-28 11:35:18*/
#include <seeta/FaceDetector.h>
#include <seeta/FaceLandmarker.h>
#include <seeta/FaceRecognizer.h>
#include <seeta/Struct.h>
#include <array>
#include <map>
#include <iostream>#ifdef __cplusplus
extern "C" {#endif
#include "cv.h"
#include "li_image.h"
#include "li_painter.h"
#ifdef __cplusplus
}
#endifint test_image(seeta::FaceDetector &FD, seeta::FaceLandmarker &FL)
{for(int i=1;i<=4;i++){std::string image_path = std::to_string(i)+".bmp";std::cout << "Loading image: " << image_path << std::endl;Li_Image* img=Li_Load_Image((BYTE*)image_path.c_str(),BMP_888);Li_Image* square=Li_ReShape(img,500,500);Li_Image* img2=Li_Rotate(square);Li_Image* img1=Li_Convert_Image(img2,LI_BMP_888_2_LI_BMP_8);SeetaImageData simage;simage.width=img1->width;simage.height=img1->height;simage.data=(unsigned char*)img1->data;simage.channels=1;auto faces = FD.detect(simage);std::cout<<faces.size<<std::endl;for (int i = 0; i < faces.size; ++i){auto &face = faces.data[i];auto points = FL.mark(simage, face.pos);Li_Line(img2,0xFF0000,face.pos.x,face.pos.y,face.pos.x,face.pos.y+face.pos.height);Li_Line(img2,0xFF0000,face.pos.x,face.pos.y,face.pos.x+face.pos.width,face.pos.y);Li_Line(img2,0xFF0000,face.pos.x+face.pos.width,face.pos.y,face.pos.x+face.pos.width,face.pos.y+face.pos.height);Li_Line(img2,0xFF0000,face.pos.x,face.pos.y+face.pos.height,face.pos.x+face.pos.width,face.pos.y+face.pos.height);for (auto &point : points){Li_Circle(img2,0xFF0000,point.x,point.y,1);}}Li_Image*res= Li_Rotate(img2);auto output_path = image_path + ".pts81.bmp";Li_Save_Image((BYTE*)output_path.c_str(),res);std::cerr << "Saving result into: " << output_path << std::endl;}return EXIT_SUCCESS;
}int main()
{seeta::ModelSetting::Device device = seeta::ModelSetting::CPU;int id = 0;seeta::ModelSetting FD_model( "./model/fd_2_00.dat", device, id );seeta::ModelSetting FL_model( "./model/pd_2_00_pts81.dat", device, id );seeta::FaceDetector FD(FD_model);seeta::FaceLandmarker FL(FL_model);FD.set(seeta::FaceDetector::PROPERTY_VIDEO_STABLE, 1);return test_image(FD, FL);}

(三)效果


(四)写在后面

代码中有错误的地方还望指出。我已经将项目同步到了github,我会实时更新这个代码仓库。
项目github地址:
LiteCV

【数字图像处理】基于SeetaFace2的人脸检测相关推荐

  1. 基于opencv的人脸检测与识别(python)(1)

    基于opencv的人脸检测与识别(python语言)(1) 人脸检测和识别技术就目前而言,已经相对成熟,各类算法层出不穷,这都归功于各位奋斗在一线的大佬的努力(站在巨人的肩膀上的感觉就是爽).本文是参 ...

  2. 基于PCA的人脸检测(Matlab版代码)

    花了几天,终于把matlab版的人脸检测运行成功了,虽然正确率不是很高,看着各种论文上的人脸检测正确率都出奇的高,我是不怎么相信的,有的论文连基于平均脸的人脸检测正确率都能达到98%,汗啊--  也许 ...

  3. java r$_基于javacv的人脸检测Demo

    [实例简介] 基于javacv的人脸检测Demo,参考文章:http://blog.csdn.net/viviwen123/article/details/6386302#reply [实例截图] [ ...

  4. OpenCV与图像处理学习十七——OpenCV人脸检测(含代码)

    OpenCV与图像处理学习十七--OpenCV人脸检测(含代码) 一.人脸识别概要 1.1 人脸检测 1.2 人脸对齐(Face Alignment) 1.3 人脸特征提取(Face Feature ...

  5. 【零基础跑项目】20代码教你基于opencv的人脸检测

    20代码教你基于opencv的人脸检测

  6. OpenCV数字图像处理基于C++:灰度变换

    OpenCV数字图像处理基于C++:灰度变换 1.1 灰度变换概念 在图像预处理中,图像的灰度变换是图像增强的重要手段,灰度变换可以使图像对比度扩展,图像清晰,特征明显,灰度变换主要利用点运算来修正像 ...

  7. 基于opencv实现人脸检测

    基于opencv实现人脸检测 opencv简述 opencv是一个开源的计算机视觉库,它有着C++,Python,Java等接口,支持Windows,Linux,Mac OS,IOS 和 Androi ...

  8. 基于YOLO的人脸检测和人脸计数(课程设计)

    基于YOLO的人脸检测和人脸计数(课程设计) 训练测试代码.数据集.测试视频下载地址:代码.数据集下载地址 支持YOLOV3和YOLOV3-TINY 环境要求: * Python 3.7 * PyTo ...

  9. Matlab 基于肤色的人脸检测定位

    %-----------------------基于肤色的人脸检测定位----------------------------- clc ; clear ; close all ; %-------- ...

  10. 基于opencv的人脸检测(图片、视频、摄像头)

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一.检测图片中的人脸 二.检测视频与摄像头中的人脸 总结 前言 人脸检测识别一直是个热门的研究问题,同时也是opencv中 ...

最新文章

  1. nagios 使用MSN 发报警消息
  2. Windows Server 2008 Server Core - 小脚印,大安全
  3. html支持了3种类型的按钮即,第3章HTML5.ppt
  4. 剑破冰山—Oracle开发艺术 前言
  5. Objective-C ,ios,iphone开发基础:NSDictionary(字典) 和 NSMutableDictionary
  6. bootstrap 源码中部分不了解的css属性
  7. 云炬60s看世界20211117
  8. linux 系统管理(二) 磁盘分区
  9. 蓝凌ekp开发_新华教育集团战略升级,携手蓝凌量身定制数字化办公平台
  10. C#网络编程(异步传输字符串) - Part.3[转自JimmyZhang博客]
  11. pku 2976 Dropping tests 01分数规划
  12. Lwm2m的server分析
  13. String常用方法汇总
  14. DSP DTK6437、seed6437 通过指定的定标数据生成梯形波(带串口通信)
  15. 华为 2020暑期实习 面试回忆
  16. 招聘数据采集+Hive数据分析+数据可视化
  17. 【MIUI刷机】旧机降级记录
  18. C51汇编语言寻址方式,80C51单片机指令系统的7种寻址方式
  19. 梦幻古龙服务器 文档,梦幻古龙GM命令大全较完整
  20. 试题 历届试题 对局匹配

热门文章

  1. BI项目失败?看看是不是缺少了这几项闭环!
  2. Java后端通过图片URL获取图片并保存
  3. tomcat部署,js中文乱码
  4. 计算机原理(3)-内存工作原理
  5. 计算机基础知识题二,计算机基础知识题库(五)
  6. roboware的使用
  7. SpringBoot接口接收json参数
  8. 共享茶室小程序都具备哪些功能?
  9. 前端开发工具Axure——Axure原型图查看
  10. CAS方式实现单点登录