本章主要讲述unity开发中,人脸识别SDK的选择以及调用,实现简单的图片检测。
一 人脸识别引擎
(一)集成WebAPI
目前以百度AI,腾讯等的互联网公司提供了基于WEBAPI的集成方式,可以通过HTTP的方式提交识别请求,识别结果通过JSON串的方式返回。基于HTTP的方式识别人脸是比较慢的,慢的原因在于IO性能,相对来讲,离线版本的API则能够充分利用本机的机器资源,不用往返于所谓的算法云服务器,直接在本地就能完成人脸识别和比对工作。
(二)集成SDK
以Face++和讯飞语音为例,这些公司即提供了在线识别的方式也提供了基于SDK的本地识别方式。本地识别的优点是速度快,集成度高。而且,作为C#,我们还可以搭建自己的云识别平台。如果采用了WEBAPI的,每一笔请求都需要再经过WEBAPI中转,性能上会大打折扣。
软件的成本包括人力成本和采购成本,在考虑成本的时候,自然会想到,我们使用的引擎是否收费呢?即使收费再便宜,一旦流量上来了,也是一笔不小的开支。那么有没有免费的呢。虹软公司开发的人脸识别SDK即可以实现本地识别而且对于中小型企业基本无成本消耗。
二 引用ArcFace人脸识别离线SDK
(一)下载ArcFace人脸识别离线SDK
(1)打开虹软官网,登陆成功后,点击开发者中心,点击创建应用。

(2)创建完成后,点击添加SDK,选择ArcFace,然后填写相关选项,最后确认。(没有认证信息的需要认证一下信息,大约需要半天左右)

(3)添加完成后,点击查看激活码,这里的APP_ID和SDK_KEY需要记下啦,在项目中需要使用,最后点击下载SDK。

(二)将libarcsoft_face_engine.dll,libarcsoft_face.dll文件导入Unity
(1)打开下载的压缩包,找到bin下的x64文件夹内的libarcsoft_face.dll以及libarcsoft_face_engine.dll文件。

(2)Unity中新建Plugins文件夹,导入刚刚的两个dll文件。

System.Drawing.dll文件从网上https://cn./system.drawing.dll.html下载一个。
注意:这里Unity的Scripting Runtime Version以及Api Compatbility Level*都改为.Net4.X的版本
(三)C#调用(官网的vs窗体Demo含有以下内容)
(1) 自定义方法ASFFunction.cs
using System;
using System.Runtime.InteropServices;//添加引用,调用dll
namespace Ycx_Tool.ArcFace
{
public class ASFFunctions
{
/// <summary>
/// SDK动态链接库路径
/// </summary>
public const string Dll_PATH = "libarcsoft_face_engine";
/// <summary>
/// 激活人脸识别SDK引擎函数
/// </summary>
/// <param name="appID">SDK对应的AppID</param>
/// <param name="sdkKey">SDK对应的SDKKey</param>
/// <returns>调用结果</returns>
[DllImport(Dll_PATH,CallingConvention=CallingConvention.Cdecl)]
public static extern int ASFActivation(string appID, string sdkKey);
/// <summary>
/// 初始化引擎
/// </summary>
/// <param name="detectMode">AF_DETECT_MODE_VIDEO 视频模式 | AF_DETECT_MODE_IMAGE 图片模式</param>
/// <param name="detectFaceOrientPriority">检测脸部的角度优先值,推荐:ASF_OrientPriority.ASF_OP_0_HIGHER_EXT</param>
/// <param name="detectFaceScaleVal">用于数值化表示的最小人脸尺寸</param>
/// <param name="detectFaceMaxNum">最大需要检测的人脸个数</param>
/// <param name="combinedMask">用户选择需要检测的功能组合,可单个或多个</param>
/// <param name="pEngine">初始化返回的引擎handle</param>
/// <returns>调用结果</returns>
[DllImport(Dll_PATH,CallingConvention=CallingConvention.Cdecl)]
public static extern int ASFInitEngine(uint detectMode, int detectFaceOrientPriority, int detectFaceScaleVal, int detectFaceMaxNum, int combinedMask, ref IntPtr pEngine);
/// <summary>
/// 人脸检测
/// </summary>
/// <param name="pEngine">引擎handle</param>
/// <param name="width">图像宽度</param>
/// <param name="height">图像高度</param>
/// <param name="format">图像颜色空间</param>
/// <param name="imgData">图像数据</param>
/// <param name="detectedFaces">人脸检测结果</param>
/// <returns></returns>
[DllImport(Dll_PATH, CallingConvention = CallingConvention.Cdecl)]
public static extern int ASFDetectFaces(IntPtr pEngine, int width, int height, int format, IntPtr imgData, IntPtr detectedFaces);
/// <summary>
/// 人脸信息检测(年龄/性别/人脸3D角度)
/// </summary>
/// <param name="pEngine">引擎handle</param>
/// <param name="width">图像宽度</param>
/// <param name="height">图像高度</param>
/// <param name="format">图像颜色空间</param>
/// <param name="imgData">图像数据</param>
/// <param name="detectedFaces">人脸信息,用户根据待检测的功能裁减选择需要使用的人脸</param>
/// <param name="combinedMask">只支持初始化时候指定需要检测的功能,在proces时进一步在这个已经指定的功能集中筛选例如初始化的时候指定检测年龄和性别,在process的时候可以只检测年龄,但是不能检测除年龄和性别
/// 之外的功能</param>
/// <returns>调用结果</returns>
[DllImport(Dll_PATH,CallingConvention=CallingConvention.Cdecl)]
public static extern int ASFProcess(IntPtr pEngine, int width, int height, int format, IntPtr imgData, IntPtr detectedFaces, int combinedMask);
/// <summary>
/// 单人脸特征提取
/// </summary>
/// <param name="pEngine">引擎handle</param>
/// <param name="width">图像宽度</param>
/// <param name="height">图像高度</param>
/// <param name="format">图像颜色空间</param>
/// <param name="imgData">图像数据</param>
/// <param name="faceInfo">单张人脸位置和角度信息</param>
/// <param name="faceFeature">人脸特征</param>
/// <returns>调用结果</returns>
[DllImport(Dll_PATH, CallingConvention = CallingConvention.Cdecl)]
public static extern int ASFFaceFeatureExtract(IntPtr pEngine,int width,int height,int format,IntPtr imgData,IntPtr faceInfo,IntPtr faceFeature);
/// <summary>
/// 人脸特征比对
/// </summary>
/// <param name="pEngine">引擎handle</param>
/// <param name="faceFeature1">待比较人脸特征1</param>
/// <param name="faceFeature2">带比较人脸特征2</param>
/// <param name="similarity">相似度(0~1)</param>
/// <returns>调用结果</returns>
[DllImport(Dll_PATH,CallingConvention=CallingConvention.Cdecl)]
public static extern int ASFFaceFeatureCompare(IntPtr pEngine,IntPtr faceFeature1,IntPtr faceFeature2,ref float similarity);
/// <summary>
/// 获取性别信息
/// </summary>
/// <param name="pEngine">引擎handle</param>
/// <param name="GenderInfo">检测到的性别信息</param>
/// <returns></returns>
[DllImport(Dll_PATH,CallingConvention =CallingConvention.Cdecl)]
public static extern int ASFGetGender(IntPtr pEngine, IntPtr GenderInfo);
/// <summary>
/// 获取年龄信息
/// </summary>
/// <param name="pEngine">引擎handle</param>
/// <param name="ageInfo">检测到的性别信息</param>
/// <returns></returns>
[DllImport(Dll_PATH, CallingConvention = CallingConvention.Cdecl)]
public static extern int ASFGetAge(IntPtr pEngine, IntPtr ageInfo);
/// <summary>
/// 获取3D角度信息
/// </summary>
/// <param name="pEngine">引擎handle</param>
/// <param name="p3DAngleInfo">检测到脸部3D角度信息</param>
/// <returns>调用结果</returns>
[DllImport(Dll_PATH,CallingConvention =CallingConvention.Cdecl)]
public static extern int ASFGetFace3DAngle(IntPtr pEngine, IntPtr p3DAngleInfo);
/// <summary>
/// 销毁引擎
/// </summary>
/// <param name="pEngine">引擎handle</param>
/// <returns>调用结果</returns>
[DllImport(Dll_PATH, CallingConvention = CallingConvention.Cdecl)]
public static extern int ASFUninitEngine(IntPtr pEngine);
/// <summary>
/// 获取版本信息
/// </summary>
/// <param name="pEngine">引擎handle</param>
/// <returns>调用结果</returns>
[DllImport(Dll_PATH, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr ASFGetVersion(IntPtr pEngine);
}
}
(2)自定义数据类型
ImageInfo.cs
namespace Ycx_Tool.ArcFace
{
public class ImageInfo
{
/// <summary>
/// 图片的像素数据
/// </summary>
public IntPtr imgData { get; set; }
/// <summary>
/// 图片像素宽
/// </summary>
public int width { get; set; }
/// <summary>
/// 图片像素高
/// </summary>
public int height { get; set; }
/// <summary>
/// 图片格式
/// </summary>
public int format { get; set; }
}
}
(3)自定义结构体
ASF_AgeInfo.cs
namespace Ycx_Tool.ArcFace
{
/// <summary>
/// 年龄结果结构体
/// </summary>
public struct ASF_AgeInfo
{
/// <summary>
/// 年龄检测结果集合
/// </summary>
public IntPtr ageArray;
/// <summary>
/// 结果集大小
/// </summary>
public int num;
}
}
ASF_Face3DAngle.cs
namespace Ycx_Tool.ArcFace
{
/// <summary>
/// 3D人脸角度检测结构体,可参考https://ai./bbs/forum.php?mod=viewthread&tid=1459&page=1&extra=&_dsign=fd9e1a7a
/// </summary>
public struct ASF_Face3DAngle
{
public IntPtr roll;
public IntPtr yaw;
public IntPtr pitch;
/// <summary>
/// 是否检测成功,0成功,其他为失败
/// </summary>
public IntPtr status;
public int num;
}
}
ASF_FaceFeature.cs
namespace Ycx_Tool.ArcFace
{
/// <summary>
/// 人脸特征结构体
/// </summary>
public struct ASF_FaceFeature
{
/// <summary>
/// 特征值 byte[]
/// </summary>
public IntPtr feature;
/// <summary>
/// 结果集大小
/// </summary>
public int featureSize;
}
}
ASF_GenderInfo.cs
namespace Ycx_Tool.ArcFace
{
/// <summary>
/// 性别结构体
/// </summary>
public struct ASF_GenderInfo
{
/// <summary>
/// 性别检测结果集合
/// </summary>
public IntPtr genderArray;
/// <summary>
/// 结果集大小
/// </summary>
public int num;
}
}
ASF_ImagePixelFormat.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ycx_Tool.ArcFace
{
public struct ASF_ImagePixelFormat
{
/// <summary>
/// RGB24图片格式
/// </summary>
public const int ASVL_PAF_RGB24_B8G8R8 = 0x201;
}
}
ASF_MultiFaceInfo.cs
namespace Ycx_Tool.ArcFace
{
/// <summary>
/// 多人脸检测结构体
/// </summary>
public struct ASF_MultiFaceInfo
{
/// <summary>
/// 人脸Rect结果集
/// </summary>
public IntPtr faceRects;
/// <summary>
/// 人脸角度结果集,与faceRects一一对应 对应ASF_OrientCode
/// </summary>
public IntPtr faceOrients;
/// <summary>
/// 结果集大小
/// </summary>
public int faceNum;
}
}
ASF_OrientCode.cs
namespace Ycx_Tool.ArcFace
{
/// <summary>
/// 人脸角度结构体
/// </summary>
public struct ASF_OrientCode
{
public const int ASF_OC_0 = 0x1;
public const int ASF_OC_90 = 0x2;
public const int ASF_OC_270 = 0x3;
public const int ASF_OC_180 = 0x4;
public const int ASF_OC_30 = 0x5;
public const int ASF_OC_60 = 0x6;
public const int ASF_OC_120 = 0x7;
public const int ASF_OC_150 = 0x8;
public const int ASF_OC_210 = 0x9;
public const int ASF_OC_240 = 0xa;
public const int ASF_OC_300 = 0xb;
public const int ASF_OC_330 = 0xc;
}
}
ASF_OrientPriority.cs
namespace Ycx_Tool.ArcFace
{
/// <summary>
/// 人脸检测优先角度结构体,推荐ASF_OP_0_HIGHER_EXT
/// </summary>
public struct ASF_OrientPriority
{
public const int ASF_OP_0_ONLY = 0x1;
public const int ASF_OP_90_ONLY = 0x2;
public const int ASF_OP_270_ONLY = 0x3;
public const int ASF_OP_180_ONLY = 0x4;
public const int ASF_OP_0_HIGHER_EXT = 0x5;
}
}
ASF_SingleFaceInfo.cs
namespace Ycx_Tool.ArcFace
{
/// <summary>
/// 单人脸检测结构体
/// </summary>
public struct ASF_SingleFaceInfo
{
/// <summary>
/// 人脸坐标Rect结果
/// </summary>
public MRECT faceRect;
/// <summary>
/// 人脸角度
/// </summary>
public int faceOrient;
}
}
ASF_VERSION.cs
namespace Ycx_Tool.ArcFace
{
/// <summary>
/// SDK版本信息结构体
/// </summary>
public struct ASF_VERSION
{
public string version;
public string buildDate;
public string copyRight;
}
}
DetectionMode.cs
namespace Ycx_Tool.ArcFace
{
/// <summary>
/// 图片检测模式
/// </summary>
public struct DetectionMode
{
/// <summary>
/// Video模式,一般用于多帧连续检测
/// </summary>
public const uint ASF_DETECT_MODE_VIDEO = 0x00000000;
/// <summary>
/// Image模式,一般用于静态图的单次检测
/// </summary>
public const uint ASF_DETECT_MODE_IMAGE = 0xFFFFFFFF;
}
}
FaceEngineMask.cs
namespace Ycx_Tool.ArcFace
{
/// <summary>
/// 引擎方法类型结构体,在初始化时将用到的类型用|连接传入,如 ASF_NONE|ASF_FACE_DETECT|ASF_FACERECOGNITION
/// </summary>
public struct FaceEngineMask
{
/// <summary>
/// 不做方法初始化方法类型
/// </summary>
public const int ASF_NONE = 0x00000000;
/// <summary>
/// 人脸检测方法类型常量
/// </summary>
public const int ASF_FACE_DETECT = 0x00000001;
/// <summary>
/// 人脸识别方法类型常量,包含图片feature提取和feature比对
/// </summary>
public const int ASF_FACERECOGNITION = 0x00000004;
/// <summary>
/// 年龄检测方法类型常量
/// </summary>
public const int ASF_AGE = 0x00000008;
/// <summary>
/// 性别检测方法类型常量
/// </summary>
public const int ASF_GENDER = 0x00000010;
/// <summary>
/// 3D角度检测方法类型常量
/// </summary>
public const int ASF_FACE3DANGLE = 0x00000020;
}
}
MRECT.cs
namespace Ycx_Tool.ArcFace
{
/// <summary>
/// 人脸框信息结构体
/// </summary>
public struct MRECT
{
public int left;
public int top;
public int right;
public int bottom;
}
}
(3)内存管理类
MemoryUtil.cs
using System;
using System.Runtime.InteropServices;
namespace Ycx_Tool.ArcFace
{
public class MemoryUtil
{
/// <summary>
/// 申请内存
/// </summary>
/// <param name="len">内存长度(单位:字节)</param>
/// <returns>内存首地址</returns>
public static IntPtr Malloc(int len)
{
return Marshal.AllocHGlobal(len);
}
/// <summary>
/// 释放ptr托管的内存
/// </summary>
/// <param name="ptr">托管指针</param>
public static void Free(IntPtr ptr)
{
Marshal.FreeHGlobal(ptr);
}
/// <summary>
/// 将字节数组的内容拷贝到托管内存中
/// </summary>
/// <param name="source">元数据</param>
/// <param name="startIndex">元数据拷贝起始位置</param>
/// <param name="destination">托管内存</param>
/// <param name="length">拷贝长度</param>
public static void Copy(byte[] source, int startIndex, IntPtr destination, int length)
{
Marshal.Copy(source, startIndex, destination, length);
}
/// <summary>
/// 将托管内存的内容拷贝到字节数组中
/// </summary>
/// <param name="source">托管内存</param>
/// <param name="destination">目标字节数组</param>
/// <param name="startIndex">拷贝起始位置</param>
/// <param name="length">拷贝长度</param>
public static void Copy(IntPtr source, byte[] destination, int startIndex, int length)
{
Marshal.Copy(source, destination, startIndex, length);
}
/// <summary>
/// 将ptr托管的内存转化为结构体对象
/// </summary>
/// <typeparam name="T">泛型</typeparam>
/// <param name="ptr">托管指针</param>
/// <returns>转化后的对象</returns>
public static T PtrToStructure<T>(IntPtr ptr)
{
return Marshal.PtrToStructure<T>(ptr);
}
/// <summary>
/// 将结构体对象复制到ptr托管的内存
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <param name="ptr"></param>
public static void StructureToPtr<T>(T t, IntPtr ptr)
{
Marshal.StructureToPtr(t, ptr, false);
}
/// <summary>
/// 获取类型的大小
/// </summary>
/// <typeparam name="T">泛型</typeparam>
/// <returns>类型的大小</returns>
public static int SizeOf<T>()
{
return Marshal.SizeOf<T>();
}
}
}
(4)图片处理类
ImageUtil.cs
using System;
using System.Drawing;
using System.Drawing.Imaging;
using UnityEngine;
using System.IO;
namespace Ycx_Tool.ArcFace
{
public class ImageUtil
{
/// <summary>
/// 获取Image
/// </summary>
/// <returns></returns>
public static Image GetImage(string path)
{
Image image;
FileInfo file = new FileInfo(path);
long maxSize = 1024 * 1024 * 2;
if (file.Length > maxSize)
{
Debug.Log("图像文件最大为2MB,请压缩后在导入!");
return null;
}
image = Image.FromFile(path);
image = ScaleImage(image, 256, 256);
return image;
}
/// <summary>
/// 获取图片信息
/// </summary>
/// <param name="image">图片</param>
/// <returns>成功或失败</returns>
public static ImageInfo ReadBMP(Image image)
{
ImageInfo imageInfo = new ImageInfo();
//将Image转换为Format24bppRgb格式的BMP
Bitmap bm = new Bitmap(image);
BitmapData data = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
try
{
//位图中第一个像素数据的地址。它也可以看成是位图中的第一个扫描行
IntPtr ptr = data.Scan0;
//定义数组长度
int soureBitArrayLength = data.Height * Math.Abs(data.Stride);
byte[] sourceBitArray = new byte[soureBitArrayLength];
//将bitmap中的内容拷贝到ptr_bgr数组中
MemoryUtil.Copy(ptr, sourceBitArray, 0, soureBitArrayLength);
//填充引用对象字段值
imageInfo.width = data.Width;
imageInfo.height = data.Height;
imageInfo.format = ASF_ImagePixelFormat.ASVL_PAF_RGB24_B8G8R8;
//获取去除对齐位后度图像数据
int line = imageInfo.width * 3;
int pitch = Math.Abs(data.Stride);
int bgr_len = line * imageInfo.height;
byte[] destBitArray = new byte[bgr_len];
/*
* 图片像素数据在内存中是按行存储,一般图像库都会有一个内存对齐,在每行像素的末尾位置
* 每行的对齐位会使每行多出一个像素空间(三通道如RGB会多出3个字节,四通道RGBA会多出4个字节)
* 以下循环目的是去除每行末尾的对齐位,将有效的像素拷贝到新的数组
*/
for (int i = 0; i < imageInfo.height; ++i)
{
Array.Copy(sourceBitArray, i * pitch, destBitArray, i * line, line);
}
imageInfo.imgData = MemoryUtil.Malloc(destBitArray.Length);
MemoryUtil.Copy(destBitArray, 0, imageInfo.imgData, destBitArray.Length);
return imageInfo;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
bm.UnlockBits(data);
}
return null;
}
/// <summary>
/// 用矩形框标记图片指定区域
/// </summary>
/// <param name="image">图片</param>
/// <param name="startX">矩形框左上角X坐标</param>
/// <param name="startY">矩形框左上角Y坐标</param>
/// <param name="width">矩形框宽度</param>
/// <param name="height">矩形框高度</param>
/// <returns>标记后的图片</returns>
public static Image MarkRect(Image image, int startX, int startY, int width, int height)
{
Image clone = (Image)image.Clone();
System.Drawing.Graphics g =System.Drawing.Graphics.FromImage(clone);
try
{
Brush brush = new SolidBrush(System.Drawing.Color.Red);
Pen pen = new Pen(brush, 2);
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
g.DrawRectangle(pen, new Rectangle(startX, startY, width, height));
return clone;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
g.Dispose();
}
return null;
}
/// <summary>
/// 用矩形框标记图片指定区域,添加年龄和性别标注
/// </summary>
/// <param name="image">图片</param>
/// <param name="startX">矩形框左上角X坐标</param>
/// <param name="startY">矩形框左上角Y坐标</param>
/// <param name="width">矩形框宽度</param>
/// <param name="height">矩形框高度</param>
/// <param name="age">年龄</param>
/// <param name="gender">性别</param>
/// <returns>标记后的图片</returns>
public static Image MarkRectAndString(Image image, int startX, int startY, int width, int height, int age, int gender)
{
Image clone = (Image)image.Clone();
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(clone);
try
{
Brush brush = new SolidBrush(System.Drawing.Color.Red);
Pen pen = new Pen(brush, 2);
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
g.DrawRectangle(pen, new Rectangle(startX < 1 ? 0 : startX, startY < 1 ? 0 : startY, width, height));
string genderStr = "";
if (gender >= 0)
{
if (gender == 0)
{
genderStr = "男";
}
else if (gender == 1)
{
genderStr = "女";
}
}
g.DrawString(string.Format("Age:{0} Gender:{1}", age, genderStr), new System.Drawing.Font(FontFamily.GenericSerif, 12), brush, startX < 1 ? 0 : startX, (startY - 20) < 1 ? 0 : startY - 20);
return clone;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
g.Dispose();
}
return null;
}
/// <summary>
/// 按指定宽高缩放图片
/// </summary>
/// <param name="image">原图片</param>
/// <param name="dstWidth">目标图片宽</param>
/// <param name="dstHeight">目标图片高</param>
/// <returns></returns>
public static Image ScaleImage(Image image, int dstWidth, int dstHeight)
{
System.Drawing.Graphics g = null;
try
{
//按比例缩放
float scaleRate = 0.0f;
if (image.Width >= dstWidth && image.Height >= dstHeight)
{
int widthDis = image.Width - dstWidth;
int heightDis = image.Height - dstHeight;
if (widthDis > heightDis)
{
scaleRate = dstWidth * 1f / image.Width;
}
else
{
scaleRate = dstHeight * 1f / image.Height;
}
}
else if (image.Width >= dstWidth && image.Height < dstHeight)
{
scaleRate = dstWidth * 1f / image.Width;
}
else if (image.Width < dstWidth && image.Height >= dstHeight)
{
scaleRate = dstHeight * 1f / image.Height;
}
else
{
int widthDis = dstWidth - image.Width;
int heightDis = dstHeight - image.Height;
if (widthDis > heightDis)
{
scaleRate = dstHeight * 1f / image.Height;
}
else
{
scaleRate = dstWidth * 1f / image.Width;
}
}
int width = (int)(image.Width * scaleRate);
int height = (int)(image.Height * scaleRate);
//将宽度调整为4的整数倍
if (width % 4 != 0)
{
width = width - width % 4;
}
Bitmap destBitmap = new Bitmap(width, height);
g = System.Drawing.Graphics.FromImage(destBitmap);
g.Clear(System.Drawing.Color.Transparent);
//设置画布的描绘质量
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.DrawImage(image, new Rectangle((width - width) / 2, (height - height) / 2, width, height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel);
//设置压缩质量
EncoderParameters encoderParams = new EncoderParameters();
long[] quality = new long[1];
quality[0] = 100;
EncoderParameter encoderParam = new EncoderParameter(Encoder.Quality, quality);
encoderParams.Param[0] = encoderParam;
return destBitmap;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
if (g != null)
{
g.Dispose();
}
}
return null;
}
/// <summary>
/// 剪裁图片
/// </summary>
/// <param name="src">原图片</param>
/// <param name="left">左坐标</param>
/// <param name="top">顶部坐标</param>
/// <param name="right">右坐标</param>
/// <param name="bottom">底部坐标</param>
/// <returns>剪裁后的图片</returns>
public static Image CutImage(Image src, int left, int top, int right, int bottom)
{
try
{
Bitmap srcBitmap = new Bitmap(src);
Bitmap dstBitmap = srcBitmap.Clone(new Rectangle(left, top, right - left, bottom - top), PixelFormat.DontCare);
return dstBitmap;
}
catch (Exception e)
{
Console.WriteLine(e);
}
return null;
}
}
}
(5)人脸识别类
FaceUtil.cs
using System;
using System.Drawing;
namespace Ycx_Tool.ArcFace
{
public class FaceUtil
{
/// <summary>
/// 人脸检测(PS:检测RGB图像的人脸时,必须保证图像的宽度能被4整除,否则会失败)
/// </summary>
/// <param name="pEngine">引擎Handle</param>
/// <param name="imageInfo">图像数据</param>
/// <returns>人脸检测结果</returns>
public static ASF_MultiFaceInfo DetectFace(IntPtr pEngine, ImageInfo imageInfo)
{
ASF_MultiFaceInfo multiFaceInfo = new ASF_MultiFaceInfo();
IntPtr pMultiFaceInfo = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_MultiFaceInfo>());
int retCode = ASFFunctions.ASFDetectFaces(pEngine, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, pMultiFaceInfo);
multiFaceInfo = MemoryUtil.PtrToStructure<ASF_MultiFaceInfo>(pMultiFaceInfo);
return multiFaceInfo;
}
/// <summary>
/// 人脸检测
/// </summary>
/// <param name="pEngine">引擎Handle</param>
/// <param name="image">图像</param>
/// <returns></returns>
public static ASF_MultiFaceInfo DetectFace(IntPtr pEngine, string path)
{
ASF_MultiFaceInfo multiFaceInfo = new ASF_MultiFaceInfo();
Image image=ImageUtil.GetImage(path);
if (image != null)
{
image = ImageUtil.ScaleImage(image, image.Width, image.Height);
ImageInfo imageInfo = ImageUtil.ReadBMP(image);
multiFaceInfo = DetectFace(pEngine, imageInfo);
MemoryUtil.Free(imageInfo.imgData);
return multiFaceInfo;
}
else
{
return multiFaceInfo;
}
}
/// <summary>
/// 提取人脸特征
/// </summary>
/// <param name="pEngine">引擎Handle</param>
/// <param name="imageInfo">图像数据</param>
/// <param name="multiFaceInfo">人脸检测结果</param>
/// <returns>保存人脸特征结构体指针</returns>
public static IntPtr ExtractFeature(IntPtr pEngine, ImageInfo imageInfo, ASF_MultiFaceInfo multiFaceInfo, out ASF_SingleFaceInfo singleFaceInfo)
{
singleFaceInfo = new ASF_SingleFaceInfo();
singleFaceInfo.faceRect = MemoryUtil.PtrToStructure<MRECT>(multiFaceInfo.faceRects);
singleFaceInfo.faceOrient = MemoryUtil.PtrToStructure<int>(multiFaceInfo.faceOrients);
IntPtr pSingleFaceInfo = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_SingleFaceInfo>());
MemoryUtil.StructureToPtr(singleFaceInfo, pSingleFaceInfo);
IntPtr pFaceFeature = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_FaceFeature>());
int retCode = ASFFunctions.ASFFaceFeatureExtract(pEngine, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, pSingleFaceInfo, pFaceFeature);
Console.WriteLine("FR Extract Feature result:" + retCode);
if (retCode != 0)
{
//释放指针
MemoryUtil.Free(pSingleFaceInfo);
MemoryUtil.Free(pFaceFeature);
ASF_FaceFeature emptyFeature = new ASF_FaceFeature();
IntPtr pEmptyFeature = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_FaceFeature>());
MemoryUtil.StructureToPtr(emptyFeature, pEmptyFeature);
return pEmptyFeature;
}
//人脸特征feature过滤
ASF_FaceFeature faceFeature = MemoryUtil.PtrToStructure<ASF_FaceFeature>(pFaceFeature);
byte[] feature = new byte[faceFeature.featureSize];
MemoryUtil.Copy(faceFeature.feature, feature, 0, faceFeature.featureSize);
ASF_FaceFeature localFeature = new ASF_FaceFeature();
localFeature.feature = MemoryUtil.Malloc(feature.Length);
MemoryUtil.Copy(feature, 0, localFeature.feature, feature.Length);
localFeature.featureSize = feature.Length;
IntPtr pLocalFeature = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_FaceFeature>());
MemoryUtil.StructureToPtr(localFeature, pLocalFeature);
//释放指针
MemoryUtil.Free(pSingleFaceInfo);
MemoryUtil.Free(pFaceFeature);
return pLocalFeature;
}
/// <summary>
/// 提取人脸特征
/// </summary>
/// <param name="pEngine">引擎Handle</param>
/// <param name="image">图像</param>
/// <returns>保存人脸特征结构体指针</returns>
public static IntPtr ExtractFeature(IntPtr pEngine, Image image, out ASF_SingleFaceInfo singleFaceInfo)
{
image = ImageUtil.ScaleImage(image, image.Width, image.Height);
ImageInfo imageInfo = ImageUtil.ReadBMP(image);
ASF_MultiFaceInfo multiFaceInfo = DetectFace(pEngine, imageInfo);
singleFaceInfo = new ASF_SingleFaceInfo();
IntPtr pFaceModel = ExtractFeature(pEngine, imageInfo, multiFaceInfo, out singleFaceInfo);
MemoryUtil.Free(imageInfo.imgData);
return pFaceModel;
}
/// <summary>
/// 年龄检测
/// </summary>
/// <param name="pEngine">引擎Handle</param>
/// <param name="imageInfo">图像数据</param>
/// <param name="multiFaceInfo">人脸检测结果</param>
/// <returns>年龄检测结构体</returns>
public static ASF_AgeInfo AgeEstimation(IntPtr pEngine, ImageInfo imageInfo, ASF_MultiFaceInfo multiFaceInfo, out int retCode)
{
retCode = -1;
IntPtr pMultiFaceInfo = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_MultiFaceInfo>());
MemoryUtil.StructureToPtr(multiFaceInfo, pMultiFaceInfo);
if (multiFaceInfo.faceNum == 0)
{
return new ASF_AgeInfo();
}
//人脸信息处理
retCode = ASFFunctions.ASFProcess(pEngine, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, pMultiFaceInfo, FaceEngineMask.ASF_AGE);
if (retCode == 0)
{
//获取年龄信息
IntPtr pAgeInfo = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_AgeInfo>());
retCode = ASFFunctions.ASFGetAge(pEngine, pAgeInfo);
Console.WriteLine("Get Age Result:" + retCode);
ASF_AgeInfo ageInfo = MemoryUtil.PtrToStructure<ASF_AgeInfo>(pAgeInfo);
//释放内存
MemoryUtil.Free(pMultiFaceInfo);
MemoryUtil.Free(pAgeInfo);
return ageInfo;
}
else
{
return new ASF_AgeInfo();
}
}
/// <summary>
/// 性别检测
/// </summary>
/// <param name="pEngine">引擎Handle</param>
/// <param name="imageInfo">图像数据</param>
/// <param name="multiFaceInfo">人脸检测结果</param>
/// <returns>保存性别检测结果结构体</returns>
public static ASF_GenderInfo GenderEstimation(IntPtr pEngine, ImageInfo imageInfo, ASF_MultiFaceInfo multiFaceInfo, out int retCode)
{
retCode = -1;
IntPtr pMultiFaceInfo = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_MultiFaceInfo>());
MemoryUtil.StructureToPtr(multiFaceInfo, pMultiFaceInfo);
if (multiFaceInfo.faceNum == 0)
{
return new ASF_GenderInfo();
}
//人脸信息处理
retCode = ASFFunctions.ASFProcess(pEngine, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, pMultiFaceInfo, FaceEngineMask.ASF_GENDER);
if (retCode == 0)
{
//获取性别信息
IntPtr pGenderInfo = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_GenderInfo>());
retCode = ASFFunctions.ASFGetGender(pEngine, pGenderInfo);
Console.WriteLine("Get Gender Result:" + retCode);
ASF_GenderInfo genderInfo = MemoryUtil.PtrToStructure<ASF_GenderInfo>(pGenderInfo);
//释放内存
MemoryUtil.Free(pMultiFaceInfo);
MemoryUtil.Free(pGenderInfo);
return genderInfo;
}
else
{
return new ASF_GenderInfo();
}
}
/// <summary>
/// 人脸3D角度检测
/// </summary>
/// <param name="pEngine">引擎Handle</param>
/// <param name="imageInfo">图像数据</param>
/// <param name="multiFaceInfo">人脸检测结果</param>
/// <returns>保存人脸3D角度检测结果结构体</returns>
public static ASF_Face3DAngle Face3DAngleDetection(IntPtr pEngine, ImageInfo imageInfo, ASF_MultiFaceInfo multiFaceInfo, out int retCode)
{
IntPtr pMultiFaceInfo = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_MultiFaceInfo>());
MemoryUtil.StructureToPtr(multiFaceInfo, pMultiFaceInfo);
if (multiFaceInfo.faceNum == 0)
{
retCode = -1;
return new ASF_Face3DAngle();
}
//人脸信息处理
retCode = ASFFunctions.ASFProcess(pEngine, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, pMultiFaceInfo, FaceEngineMask.ASF_FACE3DANGLE);
if (retCode == 0)
{
//获取人脸3D角度
IntPtr pFace3DAngleInfo = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_Face3DAngle>());
retCode = ASFFunctions.ASFGetFace3DAngle(pEngine, pFace3DAngleInfo);
Console.WriteLine("Get Face3D Angle Result:" + retCode);
ASF_Face3DAngle face3DAngle = MemoryUtil.PtrToStructure<ASF_Face3DAngle>(pFace3DAngleInfo);
//释放内存
MemoryUtil.Free(pMultiFaceInfo);
MemoryUtil.Free(pFace3DAngleInfo);
return face3DAngle;
}
else
{
return new ASF_Face3DAngle();
}
}
}
}
(6)功能封装
SDKUtil.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Drawing;
namespace Ycx_Tool.ArcFace
{
public class SDKUtil
{
public static IntPtr _PEngine = IntPtr.Zero;
/// <summary>
/// 初始化SDK
/// </summary>
/// <param name="appid"></param>
/// <param name="sdkkey"></param>
/// <returns></returns>
public static void Init_Sdk(string appid,string sdkkey)
{
try
{
var ret_0 = ASFFunctions.ASFActivation(appid, sdkkey);
Debug.Log("SDK激活成功");
}
catch (Exception e)
{
Debug.Log("SDK激活失败" + e.Message);
return;
}
try
{
//初始化引擎
uint detectMode = DetectionMode.ASF_DETECT_MODE_IMAGE;
//检测脸部的角度优先值
int detectFaceOrientPriority = ASF_OrientPriority.ASF_OP_0_HIGHER_EXT;
//人脸在图片中所占比例,如果需要调整检测人脸尺寸请修改此值,有效数值为2-32
int detectFaceScaleVal = 16;
//最大需要检测的人脸个数
int detectFaceMaxNum = 5;
//引擎初始化时需要初始化的检测功能组合
int combinedMask = FaceEngineMask.ASF_FACE_DETECT | FaceEngineMask.ASF_FACERECOGNITION | FaceEngineMask.ASF_AGE | FaceEngineMask.ASF_GENDER | FaceEngineMask.ASF_FACE3DANGLE;
var ret_1 = ASFFunctions.ASFInitEngine(detectMode,detectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMask, ref _PEngine);
Debug.Log("引擎初始化成功");
}
catch (Exception e)
{
Debug.Log("引擎初始化失败" + e.Message);
return;
}
}
/// <summary>
/// 选择图片
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <param name="path"></param>
public static ImageInfo ChooseImg(string path)
{
if (_PEngine == IntPtr.Zero)
{
Debug.Log("请先初始化引擎");
return null;
}
ImageInfo imageInfo = ImageUtil.ReadBMP(ImageUtil.GetImage(path));
return imageInfo;
}
/// <summary>
/// 人脸检测
/// </summary>
/// <param name="image_Info"></param>
public static void Face_Detect(ImageInfo image_Info)
{
if (_PEngine == IntPtr.Zero)
{
Debug.Log("请先初始化引擎");
return;
}
if (image_Info == null)
{
Debug.Log("请先选择图片");
return;
}
ASF_MultiFaceInfo multiFaceInfo = FaceUtil.DetectFace(_PEngine, image_Info);
if (multiFaceInfo.faceNum < 1)
{
Debug.Log("未检测到人脸");
}
else
{
Debug.Log("检测到" + multiFaceInfo.faceNum + "人");
}
}
/// <summary>
/// 人脸对别
/// </summary>
/// <param name="imageInfo_0"></param>
/// <param name="imageInfo_1"></param>
public static string Face_Compare(string image_0_path, string image_1_path)
{
string result = "";
if (_PEngine == IntPtr.Zero)
{
Debug.Log("请先初始化引擎");
result = "请先初始化引擎";
return result;
}
if (image_0_path == null || image_1_path == null)
{
Debug.Log("图片不能为空");
result = "图片不能为空";
return result;
}
ASF_SingleFaceInfo singleFaceInfo = new ASF_SingleFaceInfo();
IntPtr feature_0 = FaceUtil.ExtractFeature(_PEngine, Image.FromFile(image_0_path), out singleFaceInfo);
IntPtr feature_1 = FaceUtil.ExtractFeature(_PEngine, Image.FromFile(image_1_path), out singleFaceInfo);
float similarity = 0f;
ASFFunctions.ASFFaceFeatureCompare(_PEngine, feature_0, feature_1, ref similarity);
Debug.Log("两张脸的相似度为:" + similarity);
result = similarity.ToString();
return result;
}
/// <summary>
/// 人脸对别
/// </summary>
/// <param name="imageInfo_0"></param>
/// <param name="imageInfo_1"></param>
public static string Face_Compare(IntPtr image_0_path, IntPtr image_1_path)
{
string result = "";
if (_PEngine == IntPtr.Zero)
{
Debug.Log("请先初始化引擎");
result = "请先初始化引擎";
return result;
}
float similarity = 0f;
ASFFunctions.ASFFaceFeatureCompare(_PEngine, image_0_path, image_1_path, ref similarity);
Debug.Log("两张脸的相似度为:" + similarity);
result = similarity.ToString();
return result;
}
}
}
|