• [技术干货] 智慧园区集成资产说明——添加人脸图片接口说明
    功能描述该接口用于向VCM子系统的指定人脸库中添加人脸照片。该照片中应包含指定人员的人脸图片。注意事项同一人员在人脸库的人脸图片不能超过五张。一个人脸库最大支持100万人脸。基本信息接口名称添加人脸图片v1.1接口路径/io-adapter/south/face-recognition/v1.1/face-picture/addHTTP方法POST请求参数请求参数如表1所示。表1 请求消息参数说明参数名称类型必选(M)/可选(O)位置参数含义X-HW-IDStringMHeaderROMA授权的应用ID。X-HW-APPKEYStringOHeaderROMA授权的应用的密钥。说明:如果调用侧在ROMA平台设置了白名单,该参数可不填,如果没有,该参数必填。channelStringMHeader选择的子系统渠道。由上层应用携带,供ROMA平台做路由选择。厂家无需实现。repositoryCodeStringMBody人脸库标识。“增加人脸库”接口返回的人脸库标识。imageUrlStringOBody人脸图片的URL路径与imageBase64二者选一。长度范围:不超过255个字符。imageBase64StringOBody人脸图片内容的BASE64编码的字符串。与imageUrl二者选一。Base64编码后大小不超过5M。图片为JPG/JPEG/BMP/PNG格式。imageIdStringOBody人脸图片的唯一ID,与当前图像绑定。若用户没提供,系统会生成一个。该ID长度范围为1~36位,可以包含字母、数字、中划线或者下划线,不包含其他的特殊字符。VCM不支持personIdStringMBody人脸图片所包含的人员实例的ID。一张人脸照片建议仅包含一个人员的正脸。如果照片中有多个人脸,则表示此人员实例的脸是照片中尺寸最大的那个脸。长度范围:0-9A-Fa-f,长度24个字符extensionobjectOBody扩展对象。用于携带厂家定义的特有字段。表2 extension参数说明参数名称类型必选(M)/可选(O)位置参数含义nameStringOBody人员姓名,支持中文、英文、数字和空格,首位不为特殊字符,长度[1,255]。genderStringOBody人员性别:0:男1:女-1:未知credentialTypeStringOBody证件类型:0:身份证1:护照2:学生证3:军官证4:驾照5:其他credentialNumberStringOBody证件号,支持英文、数字、(),长度[1,255]。请求样例{ "repositoryCode": "5d11c13a066b980884971778", "imageBase64": "/9j/4...", "personId": "5cf8b755066b980884970701" }响应参数响应参数如表3所示。表3 响应消息参数说明参数名称类型参数含义resultoutputs接口响应参数。成功时返回下方结果,失败或者数据为空时,不返回result内部的内容结构,仅返回null。resCodeString返回码。具体请参考“人员匹配错误码”。resMsgString返回消息。originalResInfooriginalResInfo原始响应消息。表4 outputs参数说明参数名称类型参数含义facesFaceStructure[]照片中包含的人脸结构。如果照片中包含多张人脸,则返回一个列表。表5 FaceStructure参数说明参数名称类型参数含义personIdString人脸图片所包含的人员实例的ID固定为24个字符。faceIdString人脸ID。支持1~36个字符。表6 originalResInfo参数说明参数名称类型参数含义originalResCodeString原始返回码。originalResMsgString原始返回消息。响应样例{ "result": { "faces": [ { "faceId": "5d1325f6066b980c1aa4ee6b", "personId": "5cf8b755066b980854970701" } ] }, "resCode": "0", "resMsg": "Success.", "originalResInfo": { "originalResCode": "0", "originalResMsg": "success" } }
  • [问题求助] 第三方登录可否增加人脸识别登录配置
    在云速建站后台管理的第三方帐号登录中,能不能增加人脸识别登录配置?以解决会员实名认证的问题,
  • [其他] ModelArts奉上的青春云毕业礼 ——用AI为毕业生们“拍”一张专属的毕业照
    去年同醉,酴花下,健笔赋新词。今年君去,酴欲破,谁与醉为期。六月初夏,又是一年毕业季,喜悦总是与感伤并存:我们为迈入新的人生阶段而喜悦,也为与朝夕相处之人分别而不舍。一张毕业照,寄托着多少人的思念。对于很多人来说,有一张身穿学士服的毕业照,学生生活才算完整,它给三年、四年甚至更长时间的高校生活画上了一个圆满的句号。有创意的毕业照也常常能吸引不少人的注意,几乎每年,创意毕业照都能登上全网热搜。可是今年的毕业季却成了许多人的遗憾,出于疫情防控的需要,很多学校取消了毕业典礼,甚至封闭校园,不允许学生返校。对于毕业生来说,寒假时轻轻松松的"下个月见",却可能成为对彼此说的最后一句话。很多人说,他们可能失去了唯一一次穿学士服、拍毕业照的机会。为了弥补这样的遗憾,不少人"大显神通":有的人用PS给自己"抠"了一张毕业照,有的同学精通绘画,给自己和朋友们画了一张Q版毕业照,还有的同学把前两者结合,将自己的脸拼到了画中。但总觉得差了点什么,可能大家更想要一张"真实"的毕业照吧...于是今天,我将为大家分享,如何用AI简单而优雅地实现“云毕业照的拍摄”。首先看看效果:上传一个人的证件照,就可以很轻松地实现身穿学士服的毕业照的合成。当然,青春云毕业怎会只有一个人,当上传多张照片时,就可以实现一个多人合照的生成。如果说觉得背景有一些单调,那也没有关系!系统同样支持更换背景~大家可以在下面的网站中尝试生成自己的毕业照~http://119.3.249.156:32123/那么有小伙伴肯定会问了~这样一个毕业照生成器的工作原理是什么样的呢?接下来就跟大家分享我的设计思路和核心代码~1.图片上传在这个网站中,前端通过一个form标签,获取包括照片、性别、学科、学位等信息,传递到后端以进行毕业照的生成。2.特征提取当获得了上传的照片,就可以提取其人脸特征,以实现进一步的换脸操作。这在里我们使用的是python中的dlib库,它拥有一个人脸特征提取器,可以通过人脸上的68个特征点实现对人脸特征的采集。完成人脸特征点的提取后,我们就可以根据特征点构建一个人脸掩模,黑色的部分代表需要换脸的部分,白色的部分代表脸部以外(即不需要换脸的部分)。3.换脸整个换脸的操作是非常简单的,只要定位到学士服模板中人脸的位置,然后根据掩模做一个柏松融合,就能得到换脸之后的学士服照片了。大家可能会问,什么是柏松融合?其实很简单,举个栗子~如上图所示,这里的掩模是一个左半部分白色,右半部分黑色的矩形,这代表着将用图中橘子的右半部分替换苹果的右半部分,得到的便是如图右下角所示的一个“半苹果半橘子”的图片,其过渡部分非常自然,几乎看不出有拼接的痕迹。而实现这样合成效果的方法就是柏松融合。而我们将其用到了人脸上。4.多人合成单人的毕业照显得不那么完整,我们可以通过创建一个大的画布,将通过上述步骤生成的单人毕业照依次拼接在上面,从而实现多人毕业照的合成。5.添加背景背景添加方法和换脸类似,只是这次抠的是整个人的身体,再将纯蓝的背景更换为喜欢的照片,就能实现毕业照背景的添加。至此,整个AI毕业照的生成就彻底完成啦~当然,还有一些细节问题需要注意。比如,在进行多人毕业照合成时,涉及到一个对上传照片中每一张人脸位置的检测,目前主流的方法往往是使用神经网络进行检测,编写难度、硬件要求都比较高,因此我选择在ModelArts上直接实现,它提供了一个自动学习功能,可以一键训练人脸检测模型并在线部署。打开华为云ModelArts控制台,直接点击【自动学习】,就进入了配置界面:在选定数据集输出和输出位置之后,点击创建项目,进入到【数据标注】界面中。在数据标注界面,可以很方便地上传数据、并对通过鼠标框选的方式进行人脸数据的标注,完成数据集的标注后,点击【开始训练】按钮。对训练验证集比例、计算规格等进行配置,最后点击确定,自动人脸检测模型就会开始训练,整个训练过程会持续大约10分钟。 当训练完成时,会自动部署在线预测服务,通过调用在线服务,就可以实现人脸检测功能。 当然,在线服务不仅仅只能在网页中调用,可以通过在python代码中调用在线服务,从而实现与程序的完美结合,从而减少本地机器的负担、提高识别准确率。到这里,整个项目的分享就结束啦,欢迎大家下载附件中的源代码~一起体验“云毕业”叭!(注意哟~源代码中的在线服务service_id需要自己按照上面步骤部署~)【转载】华为云社区 author: Srius  发表于 2020/06/12 16:44:47
  • [问题求助] Atlas 200 dk 用树莓派摄像头进行人脸识别的案例
    【功能模块】Atlas 200 DK外接树莓派摄像头采集人脸后,进行人脸识别的案例,请问哪里有,在gitee/samples里没有找到,麻烦您可以发下具体的链接么,不胜感激【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [问题求助] 【Atlas 200 DK】【目标识别】如何将multi_channels_rtsp中的人脸识别换成自己的识别物?
    如何将multi_channels_rtsp中的人脸识别换成自己的识别物(cup,pencil,telephone等)?具体是在哪里改
  • [MindX SDK] 实时人脸检测案例分享
    # MindX SDK -- 人脸检测参考设计案例 ## 1 案例概述 ### 1.1 概要描述 在本系统中,目的是基于MindX SDK,在华为云昇腾平台上,开发端到端**实时人脸检测**的参考设计,实现**对三路视频中的人脸以及人脸关键点进行检测**的功能,达到性能要求 ### 1.2 模型介绍 本项目用到了一个模型: 用于人脸检测的Yunet模型 Yunet模型相关文件可以在此处下载:https://mindx.sdk.obs.cn-north-4.myhuaweicloud.com/mindxsdk-referenceapps%20/contrib/yunet/yunet.onnx ### 1.3 实现流程 1、基础环境: MindX SDK : 版本2.0.4 获取方式:https://www.hiascend.com/software/Mindx-sdk ubuntu : 版本:18.04 获取方式:请上ubuntu官网获取 Ascend-CANN-toolkit: 版本:5.0.4 获取方式:https://www.hiascend.com/software/cann/commercial 2、模型转换:利用atc工具完成模型转化:yunet.onxx --> yunet.om 3、业务流程编排与配置 4、开发检测后处理插件YunetPostProcess 5、python主程序代码开发 技术流程图如下: ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/13/1657676880325366760.png) ### 1.4 代码地址 > 本项目的代码地址为:https://gitee.com/ascend/mindxsdk-referenceapps/tree/master/contrib/yunet ## 2 软件方案介绍 ### 2.1 项目方案架构介绍 表2.1 系统方案中各模块功能: | 序号 | 子系统 | 功能描述 | | ---- | -------------- | ------------------------------------------------------------ | | 1 | 视频输入流 | 接收外部调用接口的输入视频路径,对视频进行拉流,并将拉去的裸流存储到缓冲区(buffer)中,并发送到下游插件。 | | 2 | 视频解码 | 用于视频解码,当前只支持H264格式。 | | 3 | 数据分发 | 对单个输入数据进行2次分发。 | | 4 | 数据缓存 | 输出时为后续处理过程创建一个线程,用于将输入数据与输出数据解耦,并创建缓存队列,存储尚未输入到下流插件的数据。 | | 5 | 图像处理 | 对解码后的YUV格式的图像进行放缩。 | | 6 | 模型推理插件 | 目标检测。 | | 7 | 模型后处理插件 | 对模型输出的张量进行后处理,得到物体类型数据。 | | 8 | 目标框转绘插件 | 物体类型转化为OSD实例 | | 9 | OSD可视化插件 | 实现对视频流的每一帧图像进行绘制。 | | 10 | 视频编码插件 | 用于将OSD可视化插件输出的图片进行视频编码,输出视频。 | ### 2.2 代码目录结构与说明 本工程名称为Yunet实时人脸检测,工程目录如下图所示: ```` ├── build.sh ├── config │ ├── face_yunet.cfg # yunet配置文件 │ └── Yunet.aippconfig # 模型转换aipp配置文件 ├── kpmain.py # 关键点信息输出代码 ├── main.py # 单路视频输出代码 ├── test.py # 三路后处理性能测试代码 ├── models │ └── Yunet.onnx ├── pipeline │ ├── InferTest.pipeline # 三路后处理性能测试pipeline │ ├── PluginTest.pipeline # 原方案插件性能测试pipeline │ ├── KPYunet.pipeline # 关键点信息输出pipeline │ └── Yunet.pipeline # 单路视频输出pipeline ├── plugin │ ├── build.sh │ ├── CMakeLists.txt │ ├── YunetPostProcess.cpp # 人脸检测框后处理代码 │ └── YunetPostProcess.h ├── plugin2 │ ├── build.sh │ ├── CMakeLists.txt │ ├── KPYunetPostProcess.cpp # 人脸关键点后处理代码 │ ├── KPYunetPostProcess.h ├── plugin3 │ ├── build.sh │ ├── CMakeLists.txt │ ├── TotalYunetPostProcess.cpp # 人脸检测框与关键点后处理代码(以供可视化) │ └── TotalYunetPostProcess.h ├── README.md └── run.sh ```` ## 3.1 环境依赖说明 环境依赖软件和版本如下表: | 软件名称 | 版本 | | :-----------: | :---------: | | ubantu | 18.04.1 LTS | | MindX SDK | 2.0.4 | | Python | 3.9.2 | | CANN | 5.0.4 | | numpy | 1.22.3 | | opencv-python | 4.5.5 | | 软件名称 | 版本 | 说明 | 使用教程 | | -------- | ----- | ------------------------------ | ------------------------------------------------------------ | | live555 | 1.09 | 实现视频转rstp进行推流 | [链接](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99/Live555%E7%A6%BB%E7%BA%BF%E8%A7%86%E9%A2%91%E8%BD%ACRTSP%E8%AF%B4%E6%98%8E%E6%96%87%E6%A1%A3.md) | | ffmpeg | 4.2.1 | 实现mp4格式视频转为264格式视频 | [链接](https://gitee.com/ascend/mindxsdk-referenceapps/blob/master/docs/%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99/pc%E7%AB%AFffmpeg%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B.md#https://ffmpeg.org/download.html) | 本项目适用于单人及多人正脸视频。对于人脸侧面视频,可以将人脸位置正确标出,但关键点信息标注准确率较低。本项目可以适用于仰卧人脸,但不适用于侧卧人脸。 特别地,在无人脸的情况下,我们在视频左上角设置了红色提示点。当左上角像素出现红色时,说明此场景没有检测出人脸。(下面给出该特殊点检测框的数据信息) ```` "MxpiObject":[{"classVec":[{"classId":3,"className":"","confidence":0,"headerVec":[]}],"x0":0,"x1":0,"y0":0,"y1":0}] ```` 另外,本项目要求输入视频为1920*1080 25fps视频,不支持25帧率以上视频。 ### 3.2 环境搭建 在编译运行项目前,需要设置环境变量: ```bash . /usr/local/Ascend/ascend-toolkit/set_env.sh . ${SDK安装路径}/mxVision/set_env.sh export install_path=/usr/local/Ascend/ascend-toolkit/latest export PATH=${install_path}/atc/ccec_compiler/bin:${install_path}/atc/bin:$PATH export PYTHONPATH=${install_path}/atc/python/site-packages:${install_path}/atc/python/site-packages/auto_tune.egg/auto_tune:${install_path}/atc/python/site-packages/schedule_search.egg export LD_LIBRARY_PATH=${install_path}/atc/lib64:$LD_LIBRARY_PATH export ASCEND_OPP_PATH=${install_path}/opp ``` 注:其中SDK安装路径${MX_SDK_HOME}替换为用户的SDK安装路径。 ### 3.3 模型转换 本项目中使用的模型是yunet模型,onnx模型可以直接[下载](https://mindx.sdk.obs.cn-north-4.myhuaweicloud.com/mindxsdk-referenceapps%20/contrib/yunet/yunet.onnx)。下载后使用模型转换工具ATC将onnx模型转换为om模型,模型转换工具相关介绍参考[链接](https://support.huaweicloud.com/tg-cannApplicationDev330/atlasatc_16_0005.html) 模型转换步骤如下: 设置ATC env:当前目录下运行 ```` export install_path=/usr/local/Ascend/ascend-toolkit/latest export PATH=/usr/local/python3/bin:${install_path}/atc/ccec_compiler/bin:${install_path}/atc/bin:$PATH export PYTHONPATH=${install_path}/atc/python/site-packages:${install_path}/atc/python/site-packages/auto_tune.egg/auto_tune:${install_path}/atc/python/site-packages/schedule_search.egg:$PYTHONPATH export LD_LIBRARY_PATH=${install_path}/atc/lib64:$LD_LIBRARY_PATH export ASCEND_OPP_PATH=${install_path}/opp ```` cd到models文件夹,运行 ```` atc --framework=5 --model=yunet.onnx --output=yunet --input_format=NCHW --input_shape="input:1,3,120,160" --log=debug --soc_version=Ascend310 --insert_op_conf=../config/Yunet.aippconfig ```` 执行该命令后会在指定输出.om模型路径生成项目指定模型文件`yunet.om`。若模型转换成功则输出: ``` ATC start working now, please wait for a moment. ATC run success, welcome to the next use. ``` aipp文件配置如下: ``` aipp_op { related_input_rank : 0 src_image_size_w : 160 src_image_size_h : 120 crop : false aipp_mode: static input_format : YUV420SP_U8 csc_switch : true rbuv_swap_switch : false matrix_r0c0 : 256 matrix_r0c1 : 454 matrix_r0c2 : 0 matrix_r1c0 : 256 matrix_r1c1 : -88 matrix_r1c2 : -183 matrix_r2c0 : 256 matrix_r2c1 : 0 matrix_r2c2 : 359 input_bias_0 : 0 input_bias_1 : 128 input_bias_2 : 128 mean_chn_0 : 0 mean_chn_1 : 0 mean_chn_2 : 0 min_chn_0 : 0.0 min_chn_1 : 0.0 min_chn_2 : 0.0 var_reci_chn_0 : 1.0 var_reci_chn_1 : 1.0 var_reci_chn_2 : 1.0 } ``` ## 4 实时人脸检测流程开发实现 ### 4.1 pipeline编排 ``` mxpi_rtspsrc mxpi_videodecoder # 视频解码 mxpi_imageresize # 图像缩放 tee # 解码视频分发 mxpi_tensorinfer # 模型推理 mxpi_objectpostprocessor # 模型后处理 mxpi_object2osdinstances # osd实例化 mxpi_channelimagesstitcher # 图片合并 mxpi_channelosdcoordsconverter mxpi_opencvosd mxpi_videoencoder appsink ``` ### 4.2 实时人脸检测后处理库开发 参考分类识别模型后处理库开发。 > 参考链接:https://gitee.com/ascend/docs-openmind/blob/master/guide/mindx/sdk/tutorials/quick_start/4-2%E6%A8%A1%E5%9E%8B%E5%90%8E%E5%A4%84%E7%90%86%E5%BA%93(%E5%86%85%E7%BD%AE%E7%B1%BB%E5%9E%8B)%E5%BC%80%E5%8F%91%E8%B0%83%E8%AF%95%E6%8C%87%E5%AF%BC.md ### 4.3 主程序开发 1、初始化流管理。 2、加载视频,进行推理。 3、获取pipeline各插件输出结果,解析输出结果。 4、根据识别结果在视频中标出人脸信息 5、测试性能,获取输出视频帧率 6、销毁流 ## 5 编译与运行 `main.py`:用来生成端对端单路推理的可视化视频,以提供单路推理结果可视化的应用样例 `kpmain.py`:用来生成单路关键点后处理的数据结果(用来确保关键点类型后处理的实现成功,关键点效果看main.py的可视化结果) (`kpmain.py`在此项目中不是必须的,当前没有keypoint类型osd支持下,仅给出单路pipeline输出数据信息供参考) `test.py`:用来输出端对端三路推理的后处理结果,以检测三路推理性能是否达标 需要注意的是,本项目后处理插件支持三路视频推理的后处理,但由于mxVision-2.0.4暂不支持三路后处理输出,所以此处采取单路视频可视化和三路推理性能检测两部分,分别提供可视化应用与性能检测的功能。 1.编译后处理插件 `cd`到`plugin`目录,`mkdir`新建文件夹`build` `cd`到`build`,运行 ```` cmake .. make -j make install ```` 如果权限问题,`cd`到MindSDK安装路径的`lib/modelpostprocessors`目录,将`libyunetpostprocess.so`的权限更改为`640`。 对于`plugin2`、`plugin3`目录也同样处理。 2.`config/face_yunet.cfg` 确认权限`640`。 3.运行`main.py`程序 `cd`到根目录,运行 ```` bash run.sh ```` 最后会得到`result.264`即为输出结果 ## 6 常见问题 ### 权限问题 **问题描述:** ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/13/1657676735740192074.png) **解决方案:** cd到run包的./lib/modelpostprocessors目录,运行 ```` chmod 640 libyunetpostprocess.so ```` 对于plugin2、plugin3目录也同样处理。 ### 负荷问题 若视频解码器负荷过高则会出现以下问题: ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/13/1657676706976134205.png) ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/13/1657676761121560284.png) 导致此问题的可能原因为:视频帧率过高、视频尺寸过大或解码器正在同时解码过多其他视频 解决方案:确保三路视频都为1920*1080 25fps并且减少其它任务的运行 ## 7 参考链接 > Yunet模型,参考链接:https://github.com/ShiqiYu/libfacedetection
  • [技术干货] 基于STM32单片机设计的红外测温仪(带人脸检测)
    # 基于STM32单片机设计的红外测温仪(带人脸检测) 由于医学发展的需要,在很多情况下,一般的温度计己经满足不了快速而又准确的测温要求,例如:车站、地铁、机场等人口密度较大的地方进行人体温度测量。 当前设计的这款红外测温仪由测温硬件+上位机软件组合而成,主要用在地铁、车站入口等地方,可以准确识别人脸进行测温,如果有人温度超标会进行语音提示并且保存当前人脸照片。 ## 1、 硬件选型与设计思路 ### (1). 设备端 主控单片机采用STM32F103C8T6,人体测温功能采用非接触式红外测温模块。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657088443080712875.png) ### (2). 上位机设计思路 上位机采用Qt5设计,Qt5是一套基于C++语言的跨平台软件库,性能非常强大,目前桌面端很多主流的软件都是采用QT开发。比如: 金山办公旗下的-WPS,字节跳动旗下的-剪映,暴雪娱乐公司旗下-多款游戏登录器等等。Qt在车联网领域用的也非常多,比如,哈佛,特斯拉,比亚迪等等很多车的中控屏整个系统都是采用Qt设计。 在测温项目里,上位机与STM32之间采用串口协议进行通信,上位机可以打开笔记本电脑默认的摄像头,进行人脸检测;当检测到人脸时,控制STM32测量当前人体的实时温度实时,再将温度传递到上位机显示;当温度正常时,上位机上显示绿色的提示字样“温度正常”,并有语音播报,语音播报的声音使用笔记本自带的声卡发出。如果温度过高,上位机显示红色提示字样“温度异常,请重新测量”,并有语音播报提示。温度过高时,会自动将当前人脸拍照留存,照片存放在当前软件目录下的“face”目录里,文件的命名规则是“38.8_2022-01-05-22-12-34.jpg”,其中38.8表示温度值,后面是日期(年月日时分秒)。 ### (3) 上位机运行效果 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657088458540921923.png) ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20227/6/1657088467253561881.png) 上位机需要连接STM32设备之后才可以获取温度数据,点击软件上的打开摄像头按钮,开启摄像头,让检测到人脸时,下面会显示当前测量的温度。如果没有连接STM32设备,那么默认会显示一个正常的固定温度值。界面上右边红色的字,表示当前处理一帧图像的耗时时间,电脑性能越好,检测速度越快。 ### (4) 拿到可执行文件之后如何运行? 先解压压缩包,进入“测温仪上位机-可执行文件”目录,将“haarcascade_frontalface_alt2.xml”拷贝到C盘根目录。 ![img](https://bbs-img.huaweicloud.com/blogs/img/16e6b1.png) ![img](https://bbs-img.huaweicloud.com/blogs/img/17a28c.png) 然后双击“FaceTemperatureCheck.exe”运行程序。 ![img](https://bbs-img.huaweicloud.com/blogs/img/18c1fe.png) 未连接设备,也可以打开摄像头检测人脸,只不过温度值是一个固定的正常温度值范围。 ## 二、上位机设计 ## 2.1 安装编译环境 如果需要自己编译运行源代码,需要先安装Qt5开发环境。 下载地址: https://download.qt.io/official_releases/qt/5.12/5.12.0/ ![img](https://bbs-img.huaweicloud.com/blogs/img/197e29.png) 下载之后,先将电脑网络断掉(不然会提示要求输入QT的账号),然后双击安装包进行安装。 安装可以只需要选择一个MinGW 32位编译器就够用了,详情看下面截图,选择“MinGW 7.3.0 32-bit”后,就点击下一步,然后等待安装完成即可。 ![img](https://bbs-img.huaweicloud.com/blogs/img/1b27e0.png) ## 2.2 软件代码整体效果 如果需要完整的工程,可以在这里去下载:https://download.csdn.net/download/xiaolong1126626497/85892490 ![img](https://bbs-img.huaweicloud.com/blogs/img/1d3671.png) 打开工程后(工程文件的后缀是xxx.pro),点击左下角的绿色三角形按钮就可以编译运行程序。 ![img](https://bbs-img.huaweicloud.com/blogs/img/206514.png) ## 2.3 UI设计界面 ![img](https://bbs-img.huaweicloud.com/blogs/img/239444.png) ## 2.4 人脸检测核心代码 ```cpp //人脸检测代码 bool ImageHandle::opencv_face(QImage qImage) { bool check_flag=0; QTime time; time.start(); static CvMemStorage* storage = nullptr; static CvHaarClassifierCascade* cascade = nullptr; //模型文件路径 QString face_model_file = QCoreApplication::applicationDirPath()+"/"+FACE_MODEL_FILE; //加载分类器:正面脸检测 cascade = (CvHaarClassifierCascade*)cvLoad(face_model_file.toUtf8().data(), 0, 0, 0 ); if(!cascade) { qDebug()"分类器加载错误.\n"; return check_flag; } //创建内存空间 storage = cvCreateMemStorage(0); //加载需要检测的图片 IplImage* img = QImageToIplImage(&qImage); if(img ==nullptr ) { qDebug()"图片加载错误.\n"; return check_flag; } double scale=1.2; //创建图像首地址,并分配存储空间 IplImage* gray = cvCreateImage(cvSize(img->width,img->height),8,1); //创建图像首地址,并分配存储空间 IplImage* small_img=cvCreateImage(cvSize(cvRound(img->width/scale),cvRound(img->height/scale)),8,1); cvCvtColor(img,gray, CV_BGR2GRAY); cvResize(gray, small_img, CV_INTER_LINEAR); cvEqualizeHist(small_img,small_img); //直方图均衡 /* * 指定相应的人脸特征检测分类器,就可以检测出图片中所有的人脸,并将检测到的人脸通过矩形的方式返回。 * 总共有8个参数,函数说明: 参数1:表示输入图像,尽量使用灰度图以加快检测速度。 参数2:表示Haar特征分类器,可以用cvLoad()函数来从磁盘中加载xml文件作为Haar特征分类器。 参数3:用来存储检测到的候选目标的内存缓存区域。 参数4:表示在前后两次相继的扫描中,搜索窗口的比例系数。默认为1.1即每次搜索窗口依次扩大10% 参数5:表示构成检测目标的相邻矩形的最小个数(默认为3个)。如果组成检测目标的小矩形的个数和小于 min_neighbors - 1 都会被排除。如果min_neighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框,这种设定值一般用在用户自定义对检测结果的组合程序上。 参数6:要么使用默认值,要么使用CV_HAAR_DO_CANNY_PRUNING,如果设置为CV_HAAR_DO_CANNY_PRUNING,那么函数将会使用Canny边缘检测来排除边缘过多或过少的区域,因此这些区域通常不会是人脸所在区域。 参数7:表示检测窗口的最小值,一般设置为默认即可。 参数8:表示检测窗口的最大值,一般设置为默认即可。 函数返回值:函数将返回CvSeq对象,该对象包含一系列CvRect表示检测到的人脸矩形。 */ CvSeq* objects = cvHaarDetectObjects(small_img, cascade, storage, 1.1, 3, 0/*CV_HAAR_DO_CANNY_PRUNING*/, cvSize(50,50)/*大小决定了检测时消耗的时间多少*/); qDebug()"人脸数量:"total; //遍历找到对象和周围画盒 QPainter painter(&qImage);//构建 QPainter 绘图对象 QPen pen; pen.setColor(Qt::blue); //画笔颜色 pen.setWidth(5); //画笔宽度 painter.setPen(pen); //设置画笔 CvRect *max=nullptr; for(int i=0;i(objects->total);++i) { //得到人脸的坐标位置和宽度高度信息 CvRect* r=(CvRect*)cvGetSeqElem(objects,i); if(max==nullptr)max=r; else { if(r->width > max->width || r->height > max->height) { max=r; } } } //如果找到最大的目标脸 if(max!=nullptr) { check_flag=true; //将人脸区域绘制矩形圈起来 painter.drawRect(max->x*scale,max->y*scale,max->width*scale,max->height*scale); } cvReleaseImage(&gray); //释放图片内存 cvReleaseImage(&small_img); //释放图片内存 cvReleaseHaarClassifierCascade(&cascade); //释放内存-->分类器 cvReleaseMemStorage(&objects->storage); //释放内存-->检测出图片中所有的人脸 //释放图片 cvReleaseImage(&img); qint32 time_ms=0; time_ms=time.elapsed(); //耗时时间 emit ss_log_text(QString("%1").arg(time_ms)); //保存结果 m_image=qImage.copy(); return check_flag; } ``` ## 2.5 配置文件(修改参数-很重要) ![img](https://bbs-img.huaweicloud.com/blogs/img/2fe364.png) 参数说明: 如果电脑上有多个摄像头,可以修改配置文件里的摄像头编号,具体的数量在程序启动时会自动查询,通过打印代码输出到终端。 如果自己第一次编译运行源码,运行之后, (1)需要将软件源码目录下的“haarcascade_frontalface_alt2.xml” 文件拷贝到C盘根目录,或者其他非中文目录下,具体路径可以在配置文件里修改,默认就是C盘根目录。 (2)需要将软件源码目录下的“OpenCV-MinGW-Build-OpenCV-3.4.7\x86\mingw\bin”目录里所有文件拷贝到,生成的程序执行文件同级目录下。 这样才能保证程序可以正常运行。 报警温度的阀值范围,也可以自行更改,在配置文件里有说明。 ## 2.6 语音提示文件与背景图 语音提示文件,背景图是通过资源文件加载的。 源文件存放在,源代码的“FaceTemperatureCheck\res”目录下。 ![img](https://bbs-img.huaweicloud.com/blogs/img/32b8d5.png) 自己也可以自行替换,重新编译程序即可生效。 ## 2.7 语音播报与图像显示处理代码 ```cpp //图像处理的结果 void Widget::slot_HandleImage(bool flag,QImage image) { bool temp_state=0; //检测到人脸 if(flag) { //判断温度是否正常 if(current_tempMIN_TEMP) { temp_state=true; //显示温度正常 ui->label_temp->setStyleSheet("color: rgb(0, 255, 127);font: 20pt \"Arial\";"); ui->label_temp->setText(QString("%1℃").arg(current_temp)); } else //语音播报,温度异常 { temp_state=false; //显示温度异常 ui->label_temp->setStyleSheet("color: rgb(255, 0, 0);font: 20pt \"Arial\";"); ui->label_temp->setText(QString("%1℃").arg(current_temp)); } //获取当前ms时间 long long currentTime = QDateTime::currentDateTime().toMSecsSinceEpoch(); //判断时间间隔是否到达 if(currentTime-old_currentTime>AUDIO_RATE_MS) { //更改当前时间 old_currentTime=currentTime; //温度正常 if(temp_state) { //语音播报,温度正常 QSound::play(":/res/ok.wav"); } //温度异常 else { //语音播报,温度异常 QSound::play(":/res/error.wav"); //拍照留存 QString dir_str = QCoreApplication::applicationDirPath()+"/face"; //检查目录是否存在,若不存在则新建 QDir dir; if (!dir.exists(dir_str)) { bool res = dir.mkpath(dir_str); qDebug() "新建目录状态:" res; } //目录存在就保存图片 QDir dir2; if (dir2.exists(dir_str)) { //拼接名称 QDateTime dateTime(QDateTime::currentDateTime()); //时间效果: 2020-03-05 16:25::04 周一 QString qStr=QString("%1/%2_").arg(dir_str).arg(current_temp); qStr+=dateTime.toString("yyyy-MM-dd-hh-mm-ss-ddd"); image.save(qStr+".jpg"); } } } } else //不显示温度 { ui->label_temp->setStyleSheet("color: rgb(0, 255, 127);font: 20pt \"Arial\";"); ui->label_temp->setText("----"); } //处理图像的结果画面 ui->widget_player->slotGetOneFrame(image); } ``` ## 2.8 STM32的温度接收处理代码 ```cpp //刷新串口端口 void Widget::on_pushButton_flush_uart_clicked() { QList UartInfoList=QSerialPortInfo::availablePorts(); //获取可用串口端口信息 ui->comboBox_ComSelect->clear(); if(UartInfoList.count()>0) { for(int i=0;i/如果当前串口 COM 口忙就返回真,否则返回假 { QString info=UartInfoList.at(i).portName(); info+="(占用)"; ui->comboBox_ComSelect->addItem(info); //添加新的条目选项 } else { ui->comboBox_ComSelect->addItem(UartInfoList.at(i).portName()); //添加新的条目选项 } } } else { ui->comboBox_ComSelect->addItem("无可用COM口"); //添加新的条目选项 } } //连接测温设备 void Widget::on_pushButton_OpenUart_clicked() { if(ui->pushButton_OpenUart->text()=="连接测温设备") //打开串口 { ui->pushButton_OpenUart->setText("断开连接"); /*配置串口的信息*/ UART_Config->setPortName(ui->comboBox_ComSelect->currentText()); //COM的名称 if(!(UART_Config->open(QIODevice::ReadWrite))) //打开的属性权限 { QMessageBox::warning(this, tr("状态提示"), tr("设备连接失败!\n请选择正确的COM口"), QMessageBox::Ok); ui->pushButton_OpenUart->setText("连接测温设备"); return; } } else //关闭串口 { ui->pushButton_OpenUart->setText("连接测温设备"); /*关闭串口-*/ UART_Config->clear(QSerialPort::AllDirections); UART_Config->close(); } } //读信号 void Widget::ReadUasrtData() { /*返回可读的字节数*/ if(UART_Config->bytesAvailable()=0) { return; } /*定义字节数组*/ QByteArray rx_data; /*读取串口缓冲区所有的数据*/ rx_data=UART_Config->readAll(); //转换温度 current_temp=rx_data.toDouble(); } ```
  • [问题求助] 能不能将华为云的人脸识别功能做成插件
    像华为云客服,华为云OBS等都以插件形式方便用户,可以不可以将华为云的人脸识别功能和文字识别功能做成插件,比如会员实名认证,刷脸登录都会用到。
  • [问题求助] 如何在微信支付或支付宝支付中增加指纹支付
    在后台支付设置里能不能增加支付方式种类,除了扫码方式外,可不可以增加人脸支付和指纹支付?还有就是在第三方登录方式中除了QQ,微博,微信三种外可不可以增加人脸识别和指纹登录?
  • [优化更新] 如何在微信支付或者支付宝支付中增加指纹支付
    在后台支付设置里能不能增加支付方式种类,除了扫码方式外,可不可以增加人脸支付和指纹支付?还有就是在第三方登录方式中除了QQ,微博,微信三种外可不可以增加人脸识别和指纹登录?
  • [问题求助] 人脸识别编译警告,求助!!!!
    又找不到包的警告,求助!!!
  • [MindX SDK] CenterFace动态分辨率模型推理案例分享
    # MindX SDK+CenterFace动态分辨率模型推理 ## 1 介绍 [MindX SDK](https://gitee.com/link?target=https%3A%2F%2Fwww.hiascend.com%2Fsoftware%2Fmindx-sdk) 是华为推出的软件开发套件(SDK),提供极简易用、高性能的API和工具,助力昇腾AI处理器赋能各种应用场景。对于一个训练好的模型,在Mind SDK上仅需在stream流的pipeline配置文件中简单配置几行,便可使用内置的插件实现模型的前处理和后处理,完成常见的推理任务。本文将介绍如何在MindX SDK框架上实现动态分辨率模型的推理。(代码地址:https://gitee.com/ascend/mindxsdk-referenceapps/tree/master/contrib/CenterFace) **动态分辨率:**指模型转化为om格式在MindX SDK上进行推理时,通常模型输入的图片大小一般固定为ATC工具转化时所设定的值。如果所有尺寸的图片都缩放至同一大小,对于尺寸与模型输入差距过大的图片,模型推理的效果难以符合预期。因此在使用ATC工具转化模型时,开启动态分辨率的功能,配置多个档位以适配常见的图片尺寸,模型推理时根据图片大小选用合适的档位。 本案例推理采用[CenterFace](https://github.com/Star-Clouds/CenterFace)(一个轻量化的人脸检测模型,同时实现了人脸检测+关键点检测),考虑到API中已有的后处理插件基类,以及输出结果的特殊性,开发了两个后处理插件,其余均使用框架自带插件。 本文将涵盖以下内容:*模型推理流程*、*动态分辨率模型转换*、*模型后处理开发介绍*。 ## 2 开发工具准备 > MindX SDK模型开发须使用本地IDE工具连接昇腾服务器,使用本教程前,请确保以下环境正确安装。 - 在昇腾机器上安装MindX SDK,MindX SDK软件包安装[参考链接](https://support.huawei.com/enterprise/zh/doc/EDOC1100209684?section=j00h) - Cmake - 本地C++开发环境搭建,以下演示使用[CLion](https://www.jetbrains.com/clion/download/download-thanks.html?platform=windows) - 远程SSH工具,如MobaXterm,xshell,final shell(以下演示使用MobaXterm) ## 3 开发环境配置 请参考官方技术文档:[IDE开发环境搭建](https://gitee.com/ascend/docs-openmind/blob/master/guide/mindx/sdk/tutorials/quick_start/1-2IDE开发环境搭建.md#/ascend/docs-openmind/blob/master/guide/mindx/sdk/tutorials/quick_start/Cmake介绍.md) ## 4 开发流程介绍 在MinX SDK上实现模型推理,通常包含以下几个步骤: - 准备好用于推理的模型。 - 使用ATC工具转换为om模型(转换过程根据模型的要求配置aipp做前处理)。 - 根据业务需求,编排steam流,编写主函数。 - 根据需要开发插件或后处理插件。 ## 5 开发 ### 5.1 准备模型 从CenterFace官方仓下载onnx模型([github下载链接](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2FStar-Clouds%2FCenterFace%2Fblob%2Fmaster%2Fmodels%2Fonnx%2Fcenterface.onnx)),无法访问github请使用[备用链接](https://mindx.sdk.obs.cn-north-4.myhuaweicloud.com/mindxsdk-referenceapps%20/contrib/CenterFaceWithDynamicResolution/centerface_offical.onnx)。 如果你的模型是其它格式,同样可以通过以下工具转化为MindX SDK使用的模型格式。 ### 5.2 动态分辨率模型转换 转换模型之前需要在昇腾服务器上安装和配置好ATC模型转换工具(模型转换工具相关介绍参考[链接](https://gitee.com/link?target=https%3A%2F%2Fsupport.huaweicloud.com%2Ftg-cannApplicationDev330%2Fatlasatc_16_0005.html))。如果服务器上已经具备ATC工具,但未配置环境变量,可参考如下配置: ```bash export install_path=/usr/local/Ascend/ascend-toolkit/latest export PATH=/usr/local/python3/bin:${install_path}/atc/ccec_compiler/bin:${install_path}/atc/bin:$PATH export PYTHONPATH=${install_path}/atc/python/site-packages:${install_path}/atc/python/site-packages/auto_tune.egg/auto_tune:${install_path}/atc/python/site-packages/schedule_search.egg export LD_LIBRARY_PATH=${install_path}/atc/lib64:$LD_LIBRARY_PATH export ASCEND_OPP_PATH=${install_path}/opp ``` 接着获取模型输入节点的名称,下载Netron,安装后使用Netron打开onnx模型 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654843579757448142.png) 可以看到模型的输入节点名称为:`input.1` 使用SSH工具附带的ftp功能把下载好的onnx模型上传至昇腾服务器,cd进入模型存放的目录,执行如下命令: ```bash atc --model=centerface_offical.onnx --output=centerface_offical --dynamic_image_size="768,1024;800,800;1024,768;864,1120;1120,864;960,1216;1216,960;1056,1312;1312,1056;1152,1408;1408,1152;1248,1504;1504,1248;1344,1600;1600,1344;1440,1696;1696,1440;1536,1792;1792,1536;1632,1888;1888,1632;1728,1984;1984,1728;1824,2080;2080,1824" --soc_version=Ascend310 --input_shape="input.1:1,3,-1,-1" --input_format=NCHW --framework=5 --insert_op_conf=centerface_aipp.cfg ``` 以上命令转成具有多档位的动态分辨率模型,转成单档位可使用如下命令: ```bash atc --model=centerface_offical.onnx --output=centerface_offical --soc_version=Ascend310 --input_shape="input.1:1,3,800,800" --input_format=NCHW --framework=5 --insert_op_conf=centerface_aipp.cfg ``` 命令参数的解释如下: - --model 待转换模型存放的路径 - --output 模型输出路径及输出模型命名 - --soc_version 芯片版本 - --input_shape 模型输入节点名称和tensor形状,当配置动态分辨率属性时,图片宽高应设为-1 - --input_format 输入图片的数据格式 - --dynamic_image_size 动态分辨率属性,配置后模型可接受不同分辨率的输入 - --framework 原模型使用的框架 - --insert_op_conf aipp配置文件 > aipp配置文件为可选,作用在于对模型输入数据进行前处理,是根据模型的输入要求进行具体配置的。 > > 此处因MindX SDK图片解码器仅支持JPG图像,并且解码成YUV420sp,而CenterFace模型的输入格式为RGB888,因此配置了色域转换的相关属性。具体配置文件如下 ```yaml aipp_op{ aipp_mode: static crop: false input_format : YUV420SP_U8 #非动态分辨率请设置具体的宽高 src_image_size_h : 0 src_image_size_w : 0 csc_switch : true rbuv_swap_switch : false matrix_r0c0 : 256 matrix_r0c1 : 0 matrix_r0c2 : 359 matrix_r1c0 : 256 matrix_r1c1 : -88 matrix_r1c2 : -183 matrix_r2c0 : 256 matrix_r2c1 : 454 matrix_r2c2 : 0 input_bias_0 : 0 input_bias_1 : 128 input_bias_2 : 128 mean_chn_0 : 0 mean_chn_1 : 0 mean_chn_2 : 0 min_chn_0 : 0 min_chn_1 : 0 min_chn_2 : 0 var_reci_chn_0: 1.0 var_reci_chn_1: 1.0 var_reci_chn_2: 1.0 } ``` 开启动态分辨率属性后,经过ATC工具转化的模型,已具备接受多个分辨率输入的功能,通过修改pipeline中缩放插件的属性,即可改变模型输入的大小。 ### 5.3 项目创建与pipeline流程编排 #### 新建CLION项目 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654843536158771876.png) #### CmakeLists文件 ```cmake cmake_minimum_required(VERSION 3.10) project(CenterFace) add_compile_options(-fPIC -fstack-protector-all -g -Wl,-z,relro,-z,now,-z -pie -Wall) add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0 -Dgoogle=mindxsdk_private) set(CMAKE_BUILD_TYPE Debug) set(MX_SDK_HOME $ENV{MX_SDK_HOME}) #使用本地IDE运行请配置MX_SDK_HOME的绝对路径 if (NOT DEFINED ENV{MX_SDK_HOME}) string(REGEX REPLACE "(.*)/(.*)/(.*)/(.*)" "\\1" MX_SDK_HOME ${CMAKE_CURRENT_SOURCE_DIR}) message(STATUS "set default MX_SDK_HOME: ${MX_SDK_HOME}") else () message(STATUS "env MX_SDK_HOME: ${MX_SDK_HOME}") endif() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/") #需要用到的头文件 include_directories( ${MX_SDK_HOME}/include ${MX_SDK_HOME}/opensource/include ${MX_SDK_HOME}/opensource/include/opencv4 ) #链接的动态库 link_directories( ${MX_SDK_HOME}/lib ${MX_SDK_HOME}/opensource/lib ) add_executable(Main main.cpp ) target_link_libraries(Main glog mxbase mxpidatatype plugintoolkit streammanager cpprest mindxsdk_protobuf opencv_world ) ``` #### 主函数编写 ```C++ #include "MxBase/Log/Log.h" #include "MxBase/MemoryHelper/MemoryHelper.h" #include "MxStream/StreamManager/MxStreamManager.h" #include "opencv2/opencv.hpp" #include PostProcessBases/PostProcessDataType.h> #include #include int main(int argc, char *argv[]) { // 读取pipeline文件信息 std::string pipelineConfigPath = "pipeline配置文件路径"; std::string pipelineConfig = ReadPipelineConfig(pipelineConfigPath); if (pipelineConfig == "") { return APP_ERR_COMM_INIT_FAIL; } std::string streamName = "center_face"; // 新建一个流管理MxStreamManager对象并初始化 auto mxStreamManager = std::make_shared(); APP_ERROR ret = mxStreamManager->InitManager(); // 加载pipeline得到的信息,创建一个新的stream业务流 ret = mxStreamManager->CreateMultipleStreams(pipelineConfig); MxStream::MxstDataInput dataBuffer; // 将图片的信息读取到dataBuffer中 ret = readfile(fileName, dataBuffer); // 通过SendData函数传递输入信息到指定的工作元件模块 // streamName是pipeline文件中业务流名称;inPluginId为输入端口编号,对应输入元件的编号 ret = mxStreamManager->SendData(streamName, 0, dataBuffer); delete dataBuffer.dataPtr; // 获取Stream上后处理插件的处理结果 std::vector keyVec = {"mxpi_imagedecoder0", "mxpi_objectpostprocessor0", "mxpi_objectpostprocessor1"}; std::vector output =mxStreamManager->GetProtobuf(streamName, 0, keyVec); // mxpi_imagedecoder0图片解码插件输出信息 auto mxpiVision = std::static_pointer_cast(output[0].messagePtr); // mxpi_objectpostprocessor0 后处理插件1输出信息 auto objectList = std::static_pointer_cast(output[1].messagePtr); // mxpi_objectpostprocessor1 后处理插件2输出信息 auto keypointList = std::static_pointer_cast(output[2].messagePtr); // 把经过后处理之后的推理结果写入到图片中 SaveResult(mxpiVision, objectList, keypointList); mxStreamManager->DestroyAllStreams(); return 0; } ``` > 以上代码仅作主函数的大体逻辑展示 #### pipeline流程编排 模型的推理业务流程如下: 1. 使用StreamManager的api从外部把图片二进制数据送入appsrc插件。 2. 使用图像解码mxpi_imagedecoder对图片进行解码。 3. 通过图像缩放插件mxpi_imageresize将图形缩放至指定的分辨率档位。 4. 缩放后的图形输入模型推理插件mxpi_tensorinfer得到模型输出。 5. 把模型得到的四个输出送入两个后处理插件。 6. 人脸检测插件用来得到人脸检测框,关键点检测插件得到五个关键点。 7. 人脸检测框送入OSD可视化插件编码到原图中。 8. 最后两个后处理插件分支汇聚到appsink结束整个推理流程。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654843473547740298.png) 在pipeline的编排中,对于上图分叉的部分,使用了内置的tee插件接queue插件处理,使得两个后处理插件都能获得模型推理的输出数据。此外OSD可视化插件的输入用到了图像解码挂载的metadata,同样使用了tee插件接queue,图上并未标明。 对应的pipeline文件: ```json { "center_face": { "stream_config": { "deviceId": "0" }, "appsrc0": { "props": { "blocksize": "409600" }, "factory": "appsrc", "next": "mxpi_imagedecoder0" }, "mxpi_imagedecoder0": { "props": { "deviceId": "0" }, "factory": "mxpi_imagedecoder", "next": "mxpi_imageresizer0" }, "mxpi_imageresizer0": { "props": { "dataSource": "mxpi_imagedecoder0", "resizeHeight": "768", "resizeWidth": "1024" }, "factory": "mxpi_imageresize", "next": "tee0" }, "tee0": { "factory": "tee", "next": [ "queue0", "queue1" ] }, "queue0": { "props": { "max-size-buffers": "50" }, "factory": "queue", "next": "mxpi_tensorinfer0" }, "queue1": { "props": { "max-size-buffers": "50" }, "factory": "queue", "next": "mxpi_opencvosd0:0" }, "tee1": { "factory": "tee", "next": [ "queue2", "queue3" ] }, "queue2": { "props": { "max-size-buffers": "50" }, "factory": "queue", "next": "mxpi_objectpostprocessor0" }, "queue3": { "props": { "max-size-buffers": "50" }, "factory": "queue", "next": "mxpi_objectpostprocessor1" }, "mxpi_tensorinfer0": { "props": { "dataSource": "mxpi_imageresizer0", "modelPath": "../model/centerface_offical.om" }, "factory": "mxpi_tensorinfer", "next": "tee1" }, "mxpi_objectpostprocessor0": { "props": { "dataSource": "mxpi_tensorinfer0", "postProcessConfigPath": "../model/centerface.cfg", "postProcessLibPath": "../plugins/lib/plugins/libmxpi_centerfacepostprocessor.so" }, "factory": "mxpi_objectpostprocessor", "next": "mxpi_object2osdinstances0" }, "mxpi_objectpostprocessor1": { "props": { "dataSource": "mxpi_tensorinfer0", "postProcessConfigPath": "../model/centerface.cfg", "postProcessLibPath": "../plugins/lib/plugins/libmxpi_centerfacekeypointpostprocessor.so" }, "factory": "mxpi_keypointpostprocessor", "next": "mxpi_synchronize0:1" }, "mxpi_object2osdinstances0": { "props": { "colorMap": "128, 128, 255|200,200,200|0,128,255|255,128,0", "fontFace": "16", "fontScale": "0.5", "fontThickness": "1", "fontLineType": "16", "rectThickness": "1", "rectLineType": "16" }, "factory": "mxpi_object2osdinstances", "next": "mxpi_opencvosd0:1" }, "mxpi_opencvosd0": { "props": { "dataSourceImage": "mxpi_imagedecoder0", "dataSourceOsd": "mxpi_object2osdinstances0" }, "factory": "mxpi_opencvosd", "next": "mxpi_synchronize0:0" }, "mxpi_synchronize0": { "factory": "mxpi_synchronize", "next": "appsink0" }, "appsink0": { "props": { "blocksize": "4096000" }, "factory": "appsink" } } } ``` > 上述涉及的路径均使用相对路径,相对于main函数文件所在位置。 ### 5.4 后处理插件开发 后处理插件主要包含两个,一个对应人脸检测的功能,另一个对应人脸关键点。后处理插件需要对这模型的四个输出进行处理(包含热图、中心点偏移、目标框宽高和人脸五个关键点的相对目标框大小归一化后的偏移)得到各自的结果,以完成推理。 而后处理插件的开发一般根据自己的任务需求,先在api中寻找是否有已经实现好的基类,继承合适的基类可减少开发的任务量,如果没有便只能继承最基础的基类。通常包含以下几个步骤(下面以人脸检测后处理插件作为演示): #### 编写CMakeLists.txt 编写CMakeLists.txt,主要用于设置后处理动态库的目标文件以及链接相关的第三方库。每一个后处理插件都有一个CMakeLists.txt文件与之对应。编写后处理的CMakeLists.txt文件时,用户只需修改生成的后处理名和生成动态库的目标文件即可,延用原有的配置。人脸检测后处理插件的CMakeList.txt如下: ```cmake cmake_minimum_required(VERSION 3.10) project(mxpi_centerfacepostprocessor) add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0 -Dgoogle=mindxsdk_private) set(PLUGIN_NAME "mxpi_centerfacepostprocessor") set(TARGET_LIBRARY ${PLUGIN_NAME}) # 插件生成的位置 set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../lib/plugins) #在本地IDE上运行时请替换为MX_SDK_HOME的绝对路径,如set(MX_SDK_HOME /home/MindX_SDK/mxvision) set(MX_SDK_HOME $ENV{MX_SDK_HOME}) if (NOT DEFINED ENV{MX_SDK_HOME}) string(REGEX REPLACE "(.*)/(.*)/(.*)/(.*)" "\\1" MX_SDK_HOME ${CMAKE_CURRENT_SOURCE_DIR}) message(STATUS "set default MX_SDK_HOME: ${MX_SDK_HOME}") else () message(STATUS "env MX_SDK_HOME: ${MX_SDK_HOME}") endif() #包含的头文件 include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${MX_SDK_HOME}/include) include_directories(${MX_SDK_HOME}/opensource/include) include_directories(${MX_SDK_HOME}/opensource/include/gstreamer-1.0) include_directories(${MX_SDK_HOME}/opensource/include/glib-2.0) include_directories(${MX_SDK_HOME}/opensource/lib/glib-2.0/include) #链接动态库 link_directories(${MX_SDK_HOME}/lib) link_directories(${MX_SDK_HOME}/opensource/lib) add_compile_options(-std=c++11 -fPIC -fstack-protector-all -pie -Wno-deprecated-declarations) add_compile_options("-DPLUGIN_NAME=${PLUGIN_NAME}") add_definitions(-DENABLE_DVPP_INTERFACE) add_library(${TARGET_LIBRARY} SHARED MxCenterfacePostProcessor.cpp MxCenterfacePostProcessor.h) target_link_libraries(${TARGET_LIBRARY} glib-2.0 gstreamer-1.0 gobject-2.0 gstbase-1.0 gmodule-2.0 glog) target_link_libraries(${TARGET_LIBRARY} mxpidatatype plugintoolkit mxbase mindxsdk_protobuf ) #target_link_libraries(${TARGET_LIBRARY} -Wl,-z,relro,-z,now,-z,noexecstack -s) ``` #### 实现人脸框检测的cpp文件 首先,根据任务类型,选择SDK已经支持的后处理基类去派生一个新的子类,这些后处理基类分别为目标检测,分类任务,语义分割,文本生成。这些基类都继承自相同的父类——PostProcessBase。人脸检测框后处理插件继承的类为ObjectPostProcessBase; ```c++ class MxCenterfacePostProcessor : public MxBase::ObjectPostProcessBase ``` **实现基类方法:** inti函数: ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654844885252208174.png) Process函数: ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654844937949430601.png) | 输入参数 | 解释 | | ----------------- | ------------------------------------------------------------ | | tensors | 上游插件的输出,当把后处理插件编排在模型推理插件之后,这里tensors便是模型的输出。 | | objectInfos | MxBase::ObjectInfo是api内置的目标框数据结构,后处理的结果需要存放在里面。 | | resizedImageInfos | 图片size信息,包含原图的宽高和resize之后的宽高,用于后处理逻辑的计算。 | | configParamMap | 从配置文件读取得到的map,在init函数读入。 | 在Process函数中,首先调用父类的CheckAndMoveTensors()接口,对tensors的形状进行校验并搬运内存至Host侧。然后再调用后处理业务函数,这里featLayerData包含单张图片四个输出对应void*指针,数据都是以一维数组形式存储,具体的数据size、type及数据含义需要参考论文原作者的实现。在CenterFace模型中,MxCenterfacePostProcessor后处理插件逻辑包括根据置信度筛选数据、计算出人脸框、使用nms算法对人脸框去重这一系列操作,最终将结果放入objInfo中。 后处理开发完成后,增加一个对外的接口如GetObjectInstance(),以便于让业务流中后处理插件的factory能加载生成的so文件。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654845275412513003.png) > 这里对外接口根据继承的基类有不同名称,通常为Get+基类名称+Instance #### 生成插件 在Clion中点击运行按钮,即可生成对应so文件: ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654843383135724620.png) 生成插件后即可在pipeline配置文件中使用,如下: ```json "mxpi_objectpostprocessor1": { "props": { "dataSource": "mxpi_tensorinfer0", "postProcessConfigPath": "../model/centerface.cfg", "postProcessLibPath": "../plugins/lib/plugins/libmxpi_centerfacekeypointpostprocessor.so" }, "factory": "mxpi_keypointpostprocessor", "next": "XXXX"} ``` factory为后处理插件基类对应插件的名字,后处理插件实际也是用的内置的插件,开发后处理插件相当于在插件的基础上编写,仅需关注业务的具体逻辑代码,如databuff的传递等其他操作由插件完成。 如果不知道factory该填什么,可以在MX_SDK_HOME/lib/plugins下查找。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654843337671691251.png) ## 6 运行过程展示 ### 6.1 本地IDE运行 第一步:配置FaceDetectPostProcessor、KeyPointPostProcessor、C++中CMakeLists文件的SDK路径, ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654843287648131732.png) 第二步:设置cmake编译的环境变量,编译插件。 在设置>构建、执行、部署>cmake界面,找到环境,配置如下: ```bash LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/acllib/lib64:/usr/local/Ascend/driver/lib64/; ``` ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654843212875646606.png) 加载FaceDetectPostProcessor中的CMakeLists文件,第一次加载CmakeLists文件时,选择Load Cmake Project,之后重新加载选择Reload Cmake Project。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654843151269379700.png) 加载CmakeLists文件之后,首先在①号位置选择对应的插件,这里选择的是FaceDetectPostProcessor插件,点击②编译 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654843096561305478.png) 点击之后,程序运行,运行完成会显示Finished,得到一个cmake-build-debug文件夹,并且远程会有编译生成的文件夹。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654843018575639101.png) 按以上方法将另一个插件KeyPointPostProcessor中CMakeLists文件进行编译,生成的插件都会存放在plugins目录下。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654842947888770306.png) 第三步:编译、运行main文件 接着加载main.cpp对应的CmakeLists,加载成功会自动生成Main的运行配置,准备图片test.jpg放置在C++目录下,编辑Main运行配置,添加程序实参,并配置环境变量,点击确定。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654842914614432881.png) ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654842871463441960.png) 环境变量如下,其中的MX_SDK_HOME需要替换为昇腾服务器上的绝对路径。 ```bash MX_SDK_HOME=${SDK安装路径} LD_LIBRARY_PATH=${MX_SDK_HOME}/lib:${MX_SDK_HOME}/opensource/lib:${MX_SDK_HOME}/opensource/lib64:/usr/local/Ascend/ascend-toolkit/latest/acllib/lib64:/usr/local/Ascend/driver/lib64/ GST_PLUGIN_SCANNER=${MX_SDK_HOME}/opensource/libexec/gstreamer-1.0/gst-plugin-scanner GST_PLUGIN_PATH=${MX_SDK_HOME}/opensource/lib/gstreamer-1.0:${MX_SDK_HOME}/lib/plugins ``` 然后点击运行。 成功运行后,可在远程的C++/result文件夹中查看结果。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654842804364324769.png) ### 6.2 远程命令行运行 相对于本地运行,远程运行方式会更加简洁方便,具体参考以下操作 第一步:进入到远程文件夹 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654842773435869432.png) 第二步:输入命令: ```bash bash build.sh ``` 此时它会自动将三个CMakeLists全部编译。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654842685849257801.png) 第三步:进入C++文件夹,输入命令运行run.sh文件,假如测试图片在run.sh同级目录,名字为test.jpg,测试命令如下: ```bash cd C++ bash run.sh test.jpg #这里相对路径是相对C++/Main可执行文件的。 #或者准备图片放置在文件夹下,以下命令会依次对文件夹下的图片进行检测。 bash run.sh images ``` ![1653201293644](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654842550524626553.png) 第四步:在result文件中查看运行结果,刷新可以看到是否是刚刚运行生成的结果。 ![1653201442455](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654842482875232996.png) ## 7 常见问题 - 报错:$'\r': command not found![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654842427382418968.png) 解决方案:原因是windows和linux换行符不一致,在CLion右下角将CRLF改成LF,然后上传到远程服务器。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20226/10/1654842131426441396.png)
  • [问题求助] 复现摄像头人脸检测例程报错
    【功能模块】复现人脸检测例程报错,数据由摄像头输入,今天早上可以跑通,但是晚上再测试时,就出现了报错【截图信息】设置的ip如下:尝试更改过以上的两个ip,但是没有效果,请问要如何解决呢?早上才跑通的,晚上测试就不行了,也不大清楚是不是误改了哪里。十分感谢!【日志信息】(可选,上传日志内容或者附件)
  • [行业资讯] 可怕!一张照片就能破解人脸识别
    根据技术人员演示,只要拿到一张人脸图片,运用深度合成的办法,就能让人脸动起来,以假乱真,轻松攻破一些人脸识别系统。在专业技术人员的眼中,目前大部分人脸识别技术的算法并不是牢不可破。(央视新闻)转载于CSDN
  • [技术干货] 人脸识别:使用Scikit-Learn构建人脸识别系统
    什么是人脸识别人脸识别是将未知个体的人脸与存储记录数据库中的图像进行比较的任务。映射可以是一对一或一对多,这取决于我们是在运行人脸验证还是人脸识别。在本教程中,我们感兴趣的是构建一个面部识别系统,该系统将验证图像(通常称为探测图像)是否存在于预先存在的面部数据库(通常称为评估集)中。直觉建立这样一个系统涉及四个主要步骤:1.检测图像中的人脸可用的人脸检测模型包括MTCNN、FaceNet、Dlib等。2.裁剪和对齐人面OpenCV库提供了此步骤所需的所有工具。3.查找每个面的向量表示由于程序不能直接处理jpg或png文件,我们需要某种方法将图像转换为数字。在本教程中,我们将使用Insightface模型为人脸创建多维(512-d)嵌入,从而封装与人脸相关的有用语义信息。要使用单个库处理所有三个步骤,我们将使用insightface。特别是,我们将使用Insightface的ArcFace模型。InsightFace是一个开源的深度人脸分析模型,用于人脸识别、人脸检测和人脸对齐任务。4.比较嵌入一旦我们将每个唯一的人脸转换成一个向量,比较特征就归结为比较相应的嵌入。我们将利用这些嵌入来训练scikit-learn模型。另外,如果你想继续,代码可以在Github上找到:https://github.com/V-Sher/Face-Search。安装程序创建虚拟环境(可选):python3 -m venv face_search_env激活此环境:source face_search_env/bin/activate此环境中的必要安装:pip install mxnet==1.8.0.post0pip install -U insightface==0.2.1pip install onnx==1.10.1pip install onnxruntime==1.8.1更重要的是,完成pip安装insightface后:从onedrive下载antelope模型版本。(它包含两个预训练的检测和识别模型)。把它放在*~/.insightface/models/下,所以在~/.insightface/models/antelope.onnx*上有onnx模型。这是正确完成设置后的外观:如果你查看antelope目录,你会发现用于人脸检测和识别的两个onnx模型:注意:自从上周insightface 0.4.1的最新版本发布以来,安装并不像我希望的那样简单(至少对我来说)。因此,我将在本教程中使用0.2.1。将来,我将相应地更新Github上的代码。如果你被卡住了,请看这里的说明。数据集我们将使用Kaggle上提供的Yale人脸数据集,该数据集包含15个人的大约165张灰度图像(即每个人大概11张唯一图像)。这些图像由各种表情、姿势和照明组成。获得数据集后,继续将其解压缩到项目中新创建的数据目录中(请参阅Github上的项目目录结构)开始如果你想继续,可以在Github上找到Jupyter笔记本:https://github.com/V-Sher/Face-Search/blob/main/notebooks/face-search-yale.ipynb。导入import osimport pickleimport numpy as npfrom PIL import Imagefrom typing import Listfrom tqdm import tqdmfrom insightface.app import FaceAnalysisfrom sklearn.neighbors import NearestNeighbors加载Insightface模型安装insightface后,我们必须调用app=FaceAnalysis(name="model_name")来加载模型。由于我们将onnx模型存储在antelope目录中:app = FaceAnalysis(name="antelope")app.prepare(ctx_id=0, det_size=(640, 640))生成Insightface嵌入使用insightface模型为图像生成嵌入非常简单。例如:# 为图像生成嵌入img_emb_results = app.get(np.asarray(img))img_emb = img_emb_results[0].embeddingimg_emb.shape------------OUTPUT---------------(512,)数据集在使用此数据集之前,我们必须修复目录中文件的扩展名,使文件名以.gif结尾。(或.jpg、.png等)。例如,以下代码段将文件名subject01.glasses更改为subject01_glasses.gif。# 修复扩展名YALE_DIR = "../data/yalefaces"files = os.listdir(YALE_DIR)[1:]for i, img in enumerate(files):# print("original name: ", img)new_ext_name = "_".join(img.split(".")) + ".gif"# print("new name: ",  new_ext_name)os.rename(os.path.join(YALE_DIR, img), os.path.join(YALE_DIR, new_ext_name))接下来,我们将数据分为评估集和探测集:每个受试者90%或10张图像将成为评估集的一部分,每个受试者剩余的10%或1张图像将用于探测集中。为了避免采样偏差,将使用名为create_probe_eval_set的辅助函数随机选择每个对象的探测图像。它将包含属于特定主题的11个图像(文件名)的列表作为输入,并返回长度为1和10的两个列表。前者包含用于探测集的文件名,而后者包含用于评估集的文件名。def create_probe_eval_set(files: List):# 选择0和len(files)-1之间的随机索引random_idx = np.random.randint(0,len(files))probe_img_fpaths = [files[random_idx]]eval_img_fpaths = [files[idx] for idx in range(len(files)) if idx != random_idx]return probe_img_fpaths, eval_img_fpaths生成嵌入create_probe_eval_set返回的两个列表都按顺序送到名为generate_embs的助手函数。对于列表中的每个文件名,它读取灰度图像,将其转换为RGB,计算相应的嵌入,最后返回嵌入以及图像标签。def generate_embs(img_fpaths: List[str])embs_set = list()embs_label = list()for img_fpath in img_fpaths:  # 读取灰度图       img = Image.open(os.path.join(YALE_DIR, img_fpath))       img_arr = np.asarray(img)         # 将灰度转换为RGB       im = Image.fromarray((img_arr * 255).astype(np.uint8))       rgb_arr = np.asarray(im.convert('RGB'))            # 生成Insightface嵌入       res = app.get(rgb_arr)                 # 将emb添加到eval set       embs_set.append(res)                 # 添加标签到eval_label set       embs_label.append(img_fpath.split("_")[0])          return embs_set, embs_label现在我们有了一个生成嵌入的框架,让我们继续使用generate_embs()为探测和评估集创建嵌入。# 排序文件files = os.listdir(YALE_DIR)files.sort()eval_set = list()eval_labels = list()probe_set = list()probe_labels = list()IMAGES_PER_IDENTITY = 11for i in tqdm(range(1, len(files), IMAGES_PER_IDENTITY), unit_divisor=True): # 忽略在files[0]的README.txt文件# print(i)probe, eval = create_probe_eval_set(files[i:i+IMAGES_PER_IDENTITY])# 存储eval embs和标签eval_set_t, eval_labels_t = generate_embs(eval)eval_set.extend(eval_set_t)eval_labels.extend(eval_labels_t)# 存储探测embs和标签probe_set_t, probe_labels_t = generate_embs(probe)probe_set.extend(probe_set_t)probe_labels.extend(probe_labels_t)需要考虑的几件事:os.listdir返回的文件是完全随机的,因此第3行的排序很重要。不带排序和带排序的os.listdir输出:[可选]如果我们使用sklearn提供的分层训练测试功能,我们本可以替换create_probe_eval_set函数,去掉forloop,并简化上述代码段中的几行。然而,在本教程中,我将清晰性置于代码简单性之上。通常情况下,insightface无法检测到人脸,并随后为其生成空嵌入。这解释了为什么probe_setor eval_set列表中的某些条目可能为空。重要的是我们要过滤掉它们,只保留非空值。为此,我们创建了另一个名为filter_empty_embs的助手函数:def filter_empty_embs(img_set: List, img_labels: List[str]):# 在insightface无法生成嵌入的地方过滤filtering where insightface could not generate an embeddinggood_idx = [i for i,x in enumerate(img_set) if x]if len(good_idx) == len(img_set):       clean_embs = [e[0].embedding for e in img_set]       clean_labels = img_labels       else:       # 保留good_idx       clean_labels = np.array(img_labels)[good_idx]       clean_set = np.array(img_set, dtype=object)[good_idx]       # 生成embs       clean_embs = [e[0].embedding for e in clean_set]return clean_embs, clean_labels它将图像集(probe_set或eval_set)作为输入,并删除insightface无法生成嵌入的元素(参见第6行)。随后,它还会更新标签(probe_labels或eval_labels)(请参见第7行),以使集合和标签具有相同的长度。最后,对于评估集和探测集中,我们可以获得512维嵌入:evaluation_embs, evaluation_labels = filter_empty_embs(eval_set, eval_labels)probe_embs, probe_labels = filter_empty_embs(probe_set, probe_labels)assert len(evaluation_embs) == len(evaluation_labels)assert len(probe_embs) == len(probe_labels)有了这两套设备,我们现在可以使用Sklearn库中实现的一种流行的无监督学习方法来构建人脸识别系统。创建人脸识别系统我们使用.fit训练最近邻模型,评估嵌入为X。这是一种用于无监督最近邻学习的简洁技术。注:一般来说,距离可以是任何度量单位,如欧几里德、曼哈顿、余弦、闵可夫斯基等。# 最近邻学习方法nn = NearestNeighbors(n_neighbors=3, metric="cosine")nn.fit(X=evaluation_embs)# 保存模型到磁盘filename = 'faceID_model.pkl'with open(filename, 'wb') as file:pickle.dump(nn, file)# 过了一段时间…# 从磁盘加载模型# with open(filename, 'rb') as file:#     pickle_model = pickle.load(file)因为我们正在实施一种无监督的学习方法,请注意,我们没有将任何标签传递给fit方法,即评估标签。我们在这里所做的就是将评估集中的人脸嵌入映射到一个潜在空间中。为什么??简单回答:通过提前将训练集存储在内存中,我们可以在推理过程中加快搜索最近邻的速度。它是如何做到这一点的?简单回答:在内存中以优化的方式存储树是非常有用的,尤其是当训练集很大并且搜索新点的邻居时,计算成本会很高。基于邻域的方法被称为非泛化机器学习方法,因为它们只是“记住”其所有训练数据推理对于每个新的探测图像,我们可以通过使用nn.neights方法搜索其前k个邻域来确定它是否存在于评估集中。例如,# 测试图像的实例推理dists, inds = nn.kneighbors(X = probe_img_emb.reshape(1,-1),                           n_neighbors = 3,                           return_distances = True                           )如果评估集中返回索引(IND)处的标签与图像的原始/真实标签完全匹配,则我们知道我们在验证系统中找到了自己的脸。我们已经将上述逻辑包装到print_ID_results方法中。它将探测图像路径、评估集标签和详细标志作为输入,以指定是否应显示详细结果。def print_ID_results(img_fpath: str, evaluation_labels: np.ndarray, verbose: bool = False):   img = Image.open(img_fpath)img_emb = app.get(np.asarray(img))[0].embedding# 从KNN获取预测dists, inds = nn.kneighbors(X=img_emb.reshape(1,-1), n_neighbors=3, return_distance=True)# 获取邻居的标签pred_labels = [evaluation_labels[i] for i in inds[0]]# 检查dist是否大于0.5,如果是,打印结果no_of_matching_faces = np.sum([1 if d <=0.6 else 0 for d in dists[0]])if no_of_matching_faces > 0:       print("Matching face(s) found in database! ")       verbose = Trueelse:       print("No matching face(s) not found in database!")       # 打印标签和相应的距离 if verbose:       for label, dist in zip(pred_labels, dists[0]):print(f"Nearest neighbours found in the database have labels {label} and is at a distance of {dist}")这里需要注意的几个重要事项:IND包含评估标签集中最近邻的索引(第6行)。例如,inds=[[2,0,11]]意味着评估中索引=2处的标签被发现最靠近探测图像,然后是索引=0处的标签。因为对于任何图像,nn.neighbors都会返回非空响应。我们要过滤一些,如果返回的距离小于或等于0.6(行12),我们只考虑这些结果。(请注意,0.6的选择完全是任意的)。例如,继续上面的例子,其中Inds= [[2,0,11 ] ]和例子= [[ 0.4,0.6,0.9 ] ],我们将只考虑在索引=2和索引=0,因为最后一个邻居的距离太大。作为一个快速的健康检查,让我们看看当我们输入婴儿的脸作为探测图像时系统的响应。正如所料,它显示没有找到匹配的脸!但是,我们将verbose设置为True,因此我们可以在数据库中看到其伪近邻的标签和距离,所有这些都非常大(>0.8)。人脸识别系统的评价测试此系统是否良好的方法之一是查看前k个邻居中存在多少相关结果。相关结果是真实标签与预测标签匹配的结果。该度量通常称为k处的精确度,其中k是预先确定的。例如,从探测集中选择一个图像(或者更确切地说是一个嵌入),其真实标签为“subject01”。如果nn.Neighers为该图像返回的前两个pred_labels为['subject01','subject01'],则表示k处的精度(p@k)k=2时为100%。类似地,如果pred_labels中只有一个值等于“subject05”,p@k将是50%,依此类推…dists, inds = nn.kneighbors(X=probe_embs_example.reshape(1, -1),n_neighbors=2, return_distance=True)pred_labels = [evaluation_labels[i] for i in inds[0] ]pred_labels----- OUTPUT ------['002', '002']让我们继续计算整个探测集上p@k的平均值:# 探测集上的推理dists, inds = nn.kneighbors(X=probe_embs, n_neighbors=2, return_distance=True)# 计算平均p@kp_at_k = np.zeros(len(probe_embs))for i in range(len(probe_embs)):true_label = probe_labels[i]pred_neighbr_idx = inds[i]pred_labels = [evaluation_labels[id] for id in pred_neighbr_idx]pred_is_labels = [1 if label == true_label else 0 for label in pred_labels]p_at_k[i] = np.mean(pred_is_labels)p_at_k.mean()------ OUTPUT --------0.990%!还可以,但肯定可以继续改进。