C#虹软 人脸识别1:N 10万级人脸库对比实现
本帖最后由 jeongong 于 2022-3-21 16:55 编辑最近在做一个医疗自助报到项目,本想用指纹识别方式去做,后来了解虹软的人脸识别还是比较人性化的,最重要的是免费。于是就用上了。按官方Demo写好后,发现还是挺快的,当人脸库图片加到10万级别时,算了下加载时间一张人脸得160毫秒左右,10万张单纯加载就得大半天了。(人脸图片直接存数据库,循环加载再提取人脸特征到内存)。后来看到可以预先保存人脸库到数据库。于是又倒腾了下,最终测试效果还是比较满意的,10万张人脸特征库,加载时间2~3秒。最最关键的是,可以不保存人脸图片了{:1_918:}。对比时,在最坏的情况要对比到第10万张,得花7秒左右的时间对比。这个对于实际业务运用中还是不够的。这个可以使用多引擎多线程加快对比,但由于本项目特定业务需求,只加载了有业务需求的库,所以大大降低了人脸库的数量。主要实现代码:
人脸库采集、人脸特征保存:
/// <summary>
/// 注册一张人脸照片到特征库中
/// 返回1成功,如果返回其他就是注册失败,有可能是提取人脸特征失败(没有人脸,人脸太小,太模糊等原因)
/// </summary>
/// <param name="image">待注册的张人脸照片</param>
/// <returns>"1"成功,其他就是失败的提示信息</returns>
public static string RegisterImageEx(Image image)
{
cutImage = null;
ifeature = new FaceFeature();
if (image == null)
{
return "图片为空";
}
if (image.Width > 1536 || image.Height > 1536)
{
image = ImageUtil.ScaleImage(image, 1536, 1536);
}
if (image == null)
{
return "图片缩放后为空";
}
if (image.Width % 4 != 0)
{
image = ImageUtil.ScaleImage(image, image.Width - (image.Width % 4), image.Height);
}
//人脸检测
ASF_MultiFaceInfo multiFaceInfo = FaceUtil.DetectFace(pImageEngine, image);
//判断检测结果
if (multiFaceInfo.faceNum > 0)
{
// 存在人脸
MRECT rect = MemoryUtil.PtrToStructure<MRECT>(multiFaceInfo.faceRects);
image = ImageUtil.CutImage(image, rect.left, rect.top, rect.right, rect.bottom);
if (image.Width % 4 != 0)
{
image = ImageUtil.ScaleImage(image, image.Width - (image.Width % 4), image.Height);
}
cutImage = image;
}
else
{
//图片中没有人脸
return "图片中没有人脸";
}
ASF_SingleFaceInfo singleFaceInfo = new ASF_SingleFaceInfo();
FaceFeature feature = FaceUtil.ExtractFeatureEx(pImageEngine, image, out singleFaceInfo);
if (singleFaceInfo.faceRect.left == 0 && singleFaceInfo.faceRect.right == 0)
{
return "特征值提取失败:未检测到人脸";
}
else
{
// 先清空,模拟人证比对,一次只能注册一张照片
imagesFeatureListEx.Clear();
// 添加特征值到人脸库
imagesFeatureListEx.Add(feature);
ifeature = feature;
return "1";
}
}
/// <summary>
/// 提取人脸特征
/// </summary>
/// <param name="pEngine">引擎Handle</param>
/// <param name="image">图像</param>
/// <returns>保存人脸特征结构体指针</returns>
public static FaceFeature ExtractFeatureEx(IntPtr pEngine, Image image, out ASF_SingleFaceInfo singleFaceInfo)
{
FaceFeature localFeature = new FaceFeature();
if (image.Width > 1536 || image.Height > 1536)
{
image = ImageUtil.ScaleImage(image, 1536, 1536);
}
else
{
image = ImageUtil.ScaleImage(image, image.Width, image.Height);
}
if (image == null)
{
singleFaceInfo = new ASF_SingleFaceInfo();
return localFeature;
}
ImageInfo imageInfo = ImageUtil.ReadBMP(image);
if (imageInfo == null)
{
singleFaceInfo = new ASF_SingleFaceInfo();
return localFeature;
}
ASF_MultiFaceInfo multiFaceInfo = DetectFace(pEngine, imageInfo);
singleFaceInfo = new ASF_SingleFaceInfo();
localFeature = ExtractFeatureEx(pEngine, imageInfo, multiFaceInfo, out singleFaceInfo);
MemoryUtil.Free(imageInfo.imgData);
return localFeature;
}
保存人脸特征库时,转成base64 字符串保存。
/// <summary>
/// 传入一张摄像头获取的人脸图片,与特征库中的图片进行比较,返回比较的结果
/// 含RBG活体检测
/// </summary>
/// <param name="bitmap">摄像头拍摄的图片</param>
/// <param name="rgbVideoSourceWidth">摄像头镜头的宽度(程序中视频的宽度)</param>
/// <param name="rgbVideoSourceHeight">摄像头镜头的高度(程序中视频的高度)</param>
/// <returns></returns>
public static FaceCompareResult DoRgbCompareEx(Bitmap bitmap)
{
faceIndex = -1;
if (bitmap == null)
{
faceCompareResult.faceIndex = -1;
faceCompareResult.msg = "待识别图片为空";
return faceCompareResult;
}
if (null == patientFaceList || patientFaceList.Count <= 0)
{
faceCompareResult.faceIndex = -1;
faceCompareResult.msg = "人脸库中没有图片";
return faceCompareResult;
}
//检测得到图片中的全部人脸,得到Rect框
ASF_MultiFaceInfo multiFaceInfo = FaceUtil.DetectFace(pVideoEngine, bitmap);
//得到最大人脸
ASF_SingleFaceInfo maxFace = FaceUtil.GetMaxFace(multiFaceInfo);
//得到Rect
MRECT rect = maxFace.faceRect;
float offsetX = VideoSource.Width * 1f / bitmap.Width;
float offsetY = VideoSource.Height * 1f / bitmap.Height;
//保证只检测一帧,防止页面卡顿以及出现其他内存被占用情况
if (isRGBLock == false)
{
isRGBLock = true;
//异步处理提取特征值和比对,不然页面会比较卡
ThreadPool.QueueUserWorkItem(new WaitCallback(delegate
{
if (rect.left != 0 && rect.right != 0 && rect.top != 0 && rect.bottom != 0)
{
try
{
if (isUninitEngine)
{
return;
}
lock (rectLock)
{
allRect.left = (int)(rect.left * offsetX);
allRect.top = (int)(rect.top * offsetY);
allRect.right = (int)(rect.right * offsetX);
allRect.bottom = (int)(rect.bottom * offsetY);
}
//调整图片数据,非常重要
ImageInfo imageInfo = ImageUtil.ReadBMP(bitmap);
if (imageInfo == null)
{
faceCompareResult.faceIndex = -1;
faceCompareResult.msg = "bitmap转ImageInfo失败";
return;
}
int retCode_Liveness = -1;
bool isLiveness = false;
if (isUninitEngine)
{
return;
}
//RGB活体检测
ASF_LivenessInfo liveInfo = FaceUtil.LivenessInfo_RGB(pVideoRGBImageEngine, imageInfo, multiFaceInfo, out retCode_Liveness);
//判断检测结果
if (retCode_Liveness == 0 && liveInfo.num > 0)
{
int isLive = MemoryUtil.PtrToStructure<int>(liveInfo.isLive);
isLiveness = (isLive == 1) ? true : false;
}
if (imageInfo != null)
{
MemoryUtil.Free(imageInfo.imgData);
}
if (isLiveness)
{
//提取人脸特征
FaceFeature feature = FaceUtil.ExtractFeatureEx(pVideoRGBImageEngine, bitmap, maxFace);
float similarity = 0f;
//得到比对结果
int result = CompareFeatureEx(feature, 0, patientFaceList.Count, out similarity);
if (result > -1)
{
//将比对结果放到显示消息中,用于最新显示
faceCompareResult.faceIndex = result;
faceCompareResult.similarity = similarity;
faceCompareResult.isLiveness = isLiveness;
faceCompareResult.image = (Bitmap)bitmap.Clone(); //传回图像
//faceCompareResult.msg = $"RGB活体,{result}号,相似度:{similarity}";
faceIndex = result;
}
else
{
//显示消息
faceCompareResult.faceIndex = result;
faceCompareResult.similarity = similarity;
faceCompareResult.isLiveness = isLiveness;
//faceCompareResult.msg = $"RGB活体,{result}号,相似度:{similarity}";
faceIndex = result;
}
}
else
{
//显示消息
faceCompareResult.faceIndex = -1;
faceCompareResult.similarity = -1;
faceCompareResult.isLiveness = isLiveness;
faceCompareResult.msg = $"RGB假体";
}
}
catch (Exception ex)
{
//显示消息
faceCompareResult.faceIndex = -1;
faceCompareResult.similarity = -1;
faceCompareResult.isLiveness = false;
faceCompareResult.msg = ex.ToString();
Console.WriteLine(ex.Message);
}
finally
{
if (bitmap != null)
{
bitmap.Dispose();
}
isRGBLock = false;
}
}
else
{
//显示消息
faceCompareResult.faceIndex = -1;
faceCompareResult.similarity = -1;
faceCompareResult.isLiveness = false;
faceCompareResult.msg = "没有人脸";
lock (rectLock)
{
allRect.left = 0;
allRect.top = 0;
allRect.right = 0;
allRect.bottom = 0;
}
}
isRGBLock = false;
}));
}
return faceCompareResult;
}
/// <summary>
/// 得到feature比较结果
/// </summary>
/// <param name="feature"></param>
/// <returns></returns>
private static int CompareFeatureEx(FaceFeature feature,int start,int end ,out float similarity)
{
int result = -1;
similarity = 0f;
//如果人脸库不为空,则进行人脸匹配
for(int i=start;i<end;i++)
{
//调用人脸匹配方法,进行匹配
int refCode= FaceUtil.ASFFaceFeatureCompare(pVideoRGBImageEngine, feature, new FaceFeature() { feature = Convert.FromBase64String(patientFaceList.Feature), featureSize = patientFaceList.FeatureSize }, out similarity);
if (refCode==0&&similarity >= threshold)
{
result = patientFaceList.PatientID;
break;
}
}
Console.WriteLine("Get Result:" + result+ " Get similarity:"+ similarity);
return result;
}
RemMai 发表于 2022-3-29 19:34
做过对接,其实挺简单的。
就是工厂任务终端。
是的对接是比较简单,就处理人脸库对比麻烦点,业务需求要求高,对比的响应基本得在3秒内,用户才能接受 tb612443 发表于 2022-3-21 15:09
感谢分享,为什么我看到的虹软都是收费的接口
有免费版,增值版。免费个人版可以注册100台设备。只不过每年都要更新本地的SDK 先收藏,谢谢楼主 感谢分享。 不懂,但看着就很厉害·· 感谢楼主分享,方便的话,能否直接给个完整的demo,谢谢! 这也会,确实牛B的 感谢分享。 楼主,你这个有没有案例啊,就是登入界面,实现人脸 这个会不会有点太凌乱了,我这复制粘贴之后有很多连接不上的句子似的。 收藏一下 应该会很有用