由于工作业务需要,笔者开发了一套新生入学身份核验系统,用于在新生入学时实时核验其身份(教育部要求新生入学时要进行人脸比对),同时实时统计新生的报到情况。核验流程如下:学生持身份证报到,通过身份证读卡器读取身份证信息(包含姓名、身份证号和芯片中的照片),根据身份证号获取学生的录取信息(学院、班级、学号及录取库中照片等),确定是录取库中新生后通过摄像头采集其现场照片,然后比对身份证照片、录取库照片和采集的现场照片,三者一致即可完成新生的身份认证,最终可导出数据结果文件和存档的照片文件。为降低使用门槛,系统采用C/S结构,服务器为asp.net core webapi,无需配置即可使用,客户端采用WPF开发。在人脸比对算法包选择时,笔者考察了主几个要厂家的sdk,在综合考虑易用性、文档质量及demo质量后,最终选择了虹软公司的产品。客户端运行界面如下:
在该项目开发过程中,有几点体会想和大家分享一下:
1. B/S、C/S结构的选择
由于客户端需要连接身份证读卡器及摄像头(对采集照片质量要求高的可采用单反相机,目前程序支持连接佳能相机),采用B/S结构需要单独开发websocket连接硬件,以及考虑到B/S服务器部署和运维的成本,本项目最终选择了C/S结构进行开发。服务器采用asp.net core self-host webapi,直接运行程序即可启动,服务器启动后界面如下:
2. 人脸比对算法的选择
由于是应用开发,也是初次接触人脸比对应用,笔者考察了百度、阿里、虹软等几家的sdk库,最后考虑到识别效率、文档质量、demo质量(主要是有免费版可以快速学习)及性价比后,最终选择虹软sdk进行开发。关于人脸比对在服务器端进行还是在客户端进行,最初也进行了抉择。在服务器端进行比对的优势是不需要对客户端进行sdk授权(节省成本),缺点是对服务器要求比较高,需要两次请求才能得到比对结果;在客户端进行比对的优势是获取到学生信息后即可进行比对获得结果,缺点是成本增加(每个客户端都需要独立授权)。考虑到本项目的实际情况,最终人脸比对在客户端进行,从而减少服务器的压力(服务器只作提供webapi服务,普通笔记本就可以胜任),客户端的激活成本在可以接收的范围。
人脸识别引擎初始化代码如下:
private void InitEngines()
{
//读取配置文件
AppSettingsReader reader = new AppSettingsReader();
string appId = (string)reader.GetValue("APPID", typeof(string));
string sdkKey64 = (string)reader.GetValue("SDKKEY64", typeof(string));
string sdkKey32 = (string)reader.GetValue("SDKKEY32", typeof(string));
string activeKey64 = (string)reader.GetValue("ACTIVEKEY64", typeof(string));
string activeKey32 = (string)reader.GetValue("ACTIVEKEY32", typeof(string));
//判断CPU位数
var is64CPU = Environment.Is64BitProcess;
string appId = is64CPU ? appId64 : appId32;
if (string.IsNullOrWhiteSpace(appId) || string.IsNullOrWhiteSpace(is64CPU ? sdkKey64 : sdkKey32) || string.IsNullOrWhiteSpace(is64CPU ? activeKey64 : activeKey32))
{
System.Windows.MessageBox.Show(string.Format("请先配置APP_ID和SDKKEY{0}!", is64CPU ? "64" : "32"));
return;
}
//在线激活引擎 如出现错误,1.请先确认从官网下载的sdk库已放到对应的bin中,2.当前选择的CPU为x86或者x64
int retCode = 0;
try
{
retCode = imageLiveEngine.ASFOnlineActivation(appId, is64CPU ? sdkKey64 : sdkKey32, is64CPU ? activeKey64: activeKey32);
if (retCode != 0 && retCode != 90114)
{
System.Windows.MessageBox.Show("激活SDK失败,错误码:" + retCode);
return;
}
}
catch (Exception ex)
{
if (ex.Message.Contains("无法加载 DLL"))
{
System.Windows.MessageBox.Show("请将SDK相关DLL放入bin对应的x86或x64下的文件夹中!");
}
else
{
MessageBox.Show("激活SDK失败,请先检查依赖环境及SDK的平台、版本是否正确! \r\n"+ex.Message);
}
System.Environment.Exit(0);
}
//初始化引擎
DetectionMode detectMode = DetectionMode.ASF_DETECT_MODE_IMAGE;
//Video模式下检测脸部的角度优先值
ASF_OrientPriority videoDetectFaceOrientPriority = ASF_OrientPriority.ASF_OP_ALL_OUT;
//Image模式下检测脸部的角度优先值
ASF_OrientPriority imageDetectFaceOrientPriority = ASF_OrientPriority.ASF_OP_ALL_OUT;
//人脸在图片中所占比例,如果需要调整检测人脸尺寸请修改此值,有效数值为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 | FaceEngineMask.ASF_IMAGEQUALITY;
//初始化引擎,正常值为0,其他返回值请参考http://ai.arcsoft.com.cn/bbs/forum.php?mod=viewthread&tid=19&_dsign=dbad527e
retCode = imageLiveEngine.ASFInitEngine(detectMode, imageDetectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMask);
txtLogAppend("InitEngine Result:" + retCode);
txtLogAppend((retCode == 0) ? "引擎初始化成功!" : string.Format("引擎初始化失败!错误码为:{0}", retCode));
if (retCode != 0)
{
return;
}
isFaceSDKEnabled = true;
}
本项目涉及到3张照片,用户可自定义选择需要比对的照片(至少2张,最多3张),首先获取3张照片的特征值,再进行两两比对,代码如下:
isPassed = true;
if (currentExam.IsCompareWithIDCardImage)//采集照片和身份证照片比对
{
similarity = 0f;
//调用对比函数
if (imageLiveFeature.Feature != null && imageICFeature.Feature != null)
ret = imageLiveEngine.ASFFaceFeatureCompare(imageLiveFeature.Feature, imageICFeature.Feature, out similarity, ASF_CompareModel.ASF_ID_PHOTO);
if (similarity.ToString().IndexOf("E") > -1)
similarity = 0f;
if (similarity * 100 < threashHold)
isPassed = false;
}
if (currentExam.IsCompareWithRegImage)//采集照片和录取库照片比对
{
similarity = 0f;
if (imageLiveFeature.Feature != null && imageStudentFeature.Feature != null)
ret = imageLiveEngine.ASFFaceFeatureCompare(imageLiveFeature.Feature, imageStudentFeature.Feature, out similarity, ASF_CompareModel.ASF_ID_PHOTO);
if (similarity.ToString().IndexOf("E") > -1)
{
similarity = 0f;
}
if (similarity * 100 < threashHold)
isPassed = false;
}
if (currentExam.IsCompareRegWithIDCardImage)//录取库照片和身份证照片比对
{
similarity = 0f;
if (imageICFeature.Feature != null && imageStudentFeature.Feature != null)
ret = imageLiveEngine.ASFFaceFeatureCompare(imageICFeature.Feature, imageStudentFeature.Feature, out similarity, ASF_CompareModel.ASF_ID_PHOTO);
if (similarity.ToString().IndexOf("E") > -1)
{
similarity = 0f;
}
if (similarity * 100 < threashHold)
isPassed = false;
}
人脸比对可选择照片模式,其中ASF_CompareModel.ASF_ID_PHOTO是证件照或生活照和证件照的比对模式。
3. 数据库选择及查询优化
基于部署成本及项目本身对数据库及并发要求不高的考虑,数据库采用SQLite,基于CodeFirst开发,数据库结构会自动生成。每年的数据保存在单独的数据库中,要实现数据库切换,可以修改数据库连接字符串,或者将已有数据库重命名,操作起来还是比较麻烦,本项目中实现了在管理端可设置当前使用的数据库及新建数据库,从而方便切换数据库进行查询。
数据库切换的代码如下:
/// <summary>
/// 更新数据库连接,实现切换数据库SQLite。
/// </summary>
public static void SqliteSelect(string strDatabaseName)
{
if (ConnectionPool.ContainsKey("Sqlite"))
ConnectionPool.Remove("Sqlite");
string conn = getConnectionString("Sqlite");
conn = conn.Replace("Database.db", strDatabaseName);
var freesql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(EnumHelper.StringConvertToEnum<DataType>("Sqlite"), conn)
.UseAutoSyncStructure(true)
.UseLazyLoading(false)
.Build();
freesql.Aop.ConfigEntityProperty += Aop_ConfigEntityProperty;
ConnectionPool.Add("Sqlite", freesql);
}
项目使用了国产的数据库ORM框架FreeSql,数据库使用了一个连接池,支持多种类型数据库连接。本项目只使用了SQLite,根据选择的文件名可以实时切换数据库。
在查询统计信息时,由于需要分学院、专业、班级来进行统计,当班级数量较多时,一次查询就非常耗时。由于专业、班级等分类数据没有做导航表,如有100个班级,要分别查询每个班级已报道男生人数、女生人数、班级总男生人数、总女生人数,就需要进行400次数据库查询,这个是不可接收的(当时开发阶段用的测试数据不够多,没有觉得这个地方有问题,项目上线前一天导入实际录取数据后才发现问题严重,干了一个通宵才把问题解决)。
作为一个解决办法,笔者给出的方案是一次查询出所有的学生数据,然后在内存中进行LING查询统计各项数据。
以上是笔者采用虹软人脸算法开发新生报到身份认证系统中的一些体会,不当之处请各位批评指正。