使用Delphi接入虹软人脸识别ArcFace,开发人脸库服务器

409 阅读13分钟

利用虹软 SDK 开发局域网人脸库服务器

一、选择开发平台

以前做单位食堂人脸识别就餐时,会用到在线人脸识别,终端设备必须并入互联网,单位对人脸信息比较敏感,客户会要求提供内部网人脸库使用。

利用人脸识别 SDK 开发人脸库服务器,多终端人脸识别,集中管理人脸库,以适用于小范围内人脸识别应用,如单位食堂、会馆、学校以及工厂的内部管理等场景。

内部网人脸库服务器的主要构成包括人脸识别,HTTP 网络通讯 ,数据库三个部分,网络通讯与数据库是常规技术,现在关键是选用人脸识别算法,人脸识别技术可不是一般程序员能够开发出来的,国内也就几家在做核心研发,比较出名的有百度,虹软件等。在比较了百度,虹软等 SDK 后,从开发难度,识别灵敏度考虑,百度的 SDK 离线文件下载就 700M,虹软件只有几十M,看来使用虹软 SDK 更容易掌握技术, 就决定选择虹软 SDK 吧。

我使用的开发工具是 DELPHI,这个已算是老人首先工具了,很多新手并不熟悉这个工具,delphi 可以调用 C++库,也可以跟 C 一样做底层开发,是很高效的开发工具。刚好虹软提供了 C++库 SDK,好利用它来做人脸识别部分。

如何利用虹软技术来做人脸识别开发,网上可查询到的资料很多,首先登录虹软开发者中心网站,注册成为虹软开发者,进入我的应用,新建应用,下载人脸识别 (ArcFace)Windows(X86) ,语言选择``C++ ,我下载的是**ArcSoft_ArcFace_Windows_x86_V3.0.zip,记得此时还要复制你刚创建应用时相应的APP_ID** 与**SDK_KEY**,后面编程中调用 SDK 时要用到。

虹软提供的 SDK 很简单,并非开源,只有函数库文件并没有提供源码,提供了 PDF 文档很详细,我非C开发,DEMO 源码我无法使用,还有一组 C++库头文件,我用的是 DELPHI 开发工具,C 函数与 **PAS**调用约定不一样,函数说明对不上,必须自己改为 ****PAS 的函数说明文件,sdk 中 amcomdef.h 就有函数中要用到数据类型定义,先对照自己的开发工具版本,把数据类型定义好,在 DELPHI 中按 C++调用函数方法就可以了,这里选择 cdecl 约定调用,改造后数据类型定义与函数说明放在一个文件中,如下:

`unit faceUtils;
interface
uses
 Types;
var
 FFaceHandle: pointer; 
const
 ASF_DETECT_MODE_VIDEO = $00000000; //Video 模式,一般用于多帧连续检测
 ASF_DETECT_MODE_IMAGE = $FFFFFFFF; // Image 模式,一般用于静态图的单次检测
 ASF_NONE = $00000000; //无属性
 ASF_FACE_DETECT = $00000001; //此处 detect 可以是 tracking 或者 detection 两个引擎之一,具体
的选择由 detect mode 确定
 ASF_FACERECOGNITION = $00000004; //人脸特征
 ASF_AGE = $00000008; //年龄
 ASF_GENDER = $00000010; //性别
 ASF_FACE3DANGLE = $00000020; //3D 角度
 ASF_LIVENESS = $00000080; //RGB 活体
 ASF_IR_LIVENESS = $00000400; //红外活体
//检测时候 Orientation 优先级别
 ASF_OP_0_ONLY = $1; // 0, 0, ...
 ASF_OP_90_ONLY = $2; // 90, 90, ...
 ASF_OP_270_ONLY = $3; // 270, 270, ...
 ASF_OP_180_ONLY = $4; // 180, 180, ...
 ASF_OP_0_HIGHER_EXT = $5; // 0, 90, 270, 180, 0, 90, 270, 180, ...
/// 当前 SDK 版本,VIDEO 模式下支持 ASF_OP_0_HIGHER_EXT 检测,IMAGE 模式不支持
 ASF_OC_0 = $1; // 0 degree
 ASF_OC_90 = $2; // 90 degree
 ASF_OC_270 = $3; // 270 degree
 ASF_OC_180 = $4; // 180 degree
 ASF_OC_30 = $5; // 30 degree
 ASF_OC_60 = $6; // 60 degree
 ASF_OC_120 = $7; // 120 degree
 ASF_OC_150 = $8; // 150 degree
 ASF_OC_210 = $9; // 210 degree
 ASF_OC_240 = $A; // 240 degree
 ASF_OC_300 = $B; // 300 degree
 ASF_OC_330 = $C; // 330 degree
 ASVL_PAF_I420 = $601;
 ASVL_PAF_RGB24_B8G8R8 = $201; // 513
 MOK = (0);
 MERR_NONE = (0);
type
 //检测模型
 ASF_DetectModel = (
 ASF_DETECT_MODEL_RGB = $1 //RGB 图像检测模型
 //预留扩展其他检测模型
 );
 //人脸比对可选的模型
 ASF_CompareModel = (
 ASF_LIFE_PHOTO = $1, //用于生活照之间的特征比对,推荐阈值 0.80
 ASF_ID_PHOTO = $2 //用于证件照或生活照与证件照之间的特征比对,推荐阈值 0.82
 );
//duDWORD,LongInt,Cardinal 三种数据 zhi 类型都一样,都是 32 位无符号 dao 整型(无符号就是没
有负的,
//最小值内为 0,和之相对的有符号 32 位整型就是最常用容的 Integer)
type
 MPChar = pchar;
 Mint8 = Shortint; // -128..127 signed 8-bit
 PMint8 = ^Mint8;
 Muint8 = Byte; //0..255 unsigned 8-bit
 PMuint8 = ^Muint8;
 Mint16 = Smallint; //-32768..32767 signed 16-bit
 PMint16 = ^Mint16;
 Muint16 = WORD; //word 是 2 字节 unsigned 16-bit
 PMuint16 = ^Muint16;
 MInt32 = Longint; //-2147483648..2147483647 signed 32-bit
 PMint32 = ^MInt32;
 MUInt32 = DWORD; //Longword unsigned 32-bit
 PMuint32 = ^MUInt32;
 pMByte = ^MByte;
 MByte = Byte; //0..255 unsigned 8-bit
 MWord = WORD; //word 是 2 字节 unsigned 16-bit
 MFloat = Single; // 这里必须是 Single ,4bytes 32 位浮点数.
//不能 double;8 bytes 64 位浮点数 否则无法输出人脸比较
 pMRECT = ^MRECT;
 MRECT = record
 left: MInt32;
 top: MInt32;
 right: MInt32;
 bottom: MInt32;
 end;
 //单人脸信息
 LPASF_SingleFaceInfo = ^ASF_SingleFaceInfo;
 ASF_SingleFaceInfo = record
 faceRect: MRECT; // 人脸框信息
 faceOrient: MInt32; // 输入图像的角度,可以参考 ArcFaceCompare_OrientCode
 end;
 //多人脸信息
 LPASF_MultiFaceInfo = ^ASF_MultiFaceInfo;
 ASF_MultiFaceInfo = record
 faceRect: array of MRECT; // 人脸框数组
 faceOrient: array of MInt32; // 人脸角度数组
 faceNum: MInt32; // 检测到的人脸个数
 faceID: MInt32; //在 VIDEO 模式下有效,IMAGE 模式下为空
 end;
 LPASF_FaceFeature = ^ASF_FaceFeature;
 ASF_FaceFeature = record
 feature: pMByte; //array[0..2048] of byte; // pMuint8; // 人脸特征信息
 featureSize: MInt32; // 人脸特征信息长度
 end;
 LPASF_FaceAngle = ^ASF_FaceAngle;
 ASF_FaceAngle = record
 roll: MFloat;
 yaw: MFloat;
 pitch: MFloat;
 status: integer; //0: 正常,其他数值:出错
 num: PMuint32
 end;
 LPASF_AgeInfo = ^ASF_AgeInfo;
 ASF_AgeInfo = record
 ageArray: MInt32; // "0" 代表不确定,
 num: MInt32; // 检测的人脸个数
 end;
 //结构体
 LPASVLOFFSCREEN = ^ASVLOFFSCREEN;
 ASVLOFFSCREEN = record
 u32PixelArrayFormat: MUInt32; //BMP 格式必须为 ASVL_PAF_RGB24_B8G8R8,
 i32Width: MInt32; //图像的每行像素数
 i32Height: MInt32;
 ppu8Plane: array[0..3] of pMUInt8; //ppu8Plane 为一个批向 byte 数组的指针数组,
 //这里面会保存我们刚刚转换后的图片数据。可以传递四幅图片。
 pi32Pitch: array[0..3] of MInt32; 。
 end;
 LPASVLOF = ^ASVLOFFSCREEN;
 __tag_ASVL_OFFSCREEN = ASVLOFFSCREEN;
 ASF_ImageData = ASVLOFFSCREEN;
 LPASF_ImageData = ^ASVLOFFSCREEN;
 LPASF_LivenessInfo = ^ASF_LivenessInfo;
 ASF_LivenessInfo = record
 isLive: pMInt32; // [out] 判断是否真人, 0:非真人;
 // 1:真人;
 // -1:不确定;
 // -2:传入人脸数>1;
 // -3: 人脸过小
 // -4: 角度过大
 // -5: 人脸超出边界
 num: MInt32;
 end;
// 激活文件信息
 LPASF_ActiveFileInfo = ^ASF_ActiveFileInfo;
 ASF_ActiveFileInfo = record
 startTime: MPChar; //开始时间
 endTime: MPChar; //截止时间
 platform: MPChar; //平台
 sdkType: MPChar; //sdk 类型
 appId: MPChar; //APPID
 sdkKey: MPChar; //SDKKEY
 sdkVersion: MPChar; //SDK 版本号
 fileVersion: MPChar; //激活文件版本号
 end;
 //版本信息
 LPASF_VERSION = ^ASF_VERSION;
 ASF_VERSION = record
 Version: MPChar; // 版本号
 BuildDate: MPChar; // 构建日期
 CopyRight: MPChar; // Copyright
 end;
function ASFActivation(appId: PAnsiChar; sdkKey: PAnsiChar): integer; cdecl;
function ASFInitEngine(detectMode: MInt32;
 detectFaceOrientPriority: MInt32;
 detectFaceScaleVal: MInt32; //人脸在图片中所占比例,有效数值为 2-32 ;
 detectFaceMaxNum: MInt32;
 combinedMask: MInt32; pEngine: pointer): integer; cdecl;
function ASFDetectFaces(pEngine: pointer; width: MInt32; height: MInt32;
 format: MInt32; imgData: PMuint8; detectedFaces: pointer): integer; cdecl;
function ASFDetectFacesEx(pEngine: pointer;
 imgData: LPASF_ImageData; // [in] 图片数据
 detectedFaces: LPASF_MultiFaceInfo; // [out] 检测到的人脸信息
 detectModel: ASF_DetectModel = ASF_DETECT_MODEL_RGB ): integer; cdecl; // [out]
function ASFFaceFeatureExtract(pEngine: pointer; width: MInt32; height: MInt32;
 format: MInt32; imgData: PMuint8;
 faceInfo: pointer; // [in] 单张人脸位置和角度信息
 feature: pointer): integer; cdecl; // [out] 人脸特征
function ASFGetFace3DAngle(pEngine: pointer; // [in] 引擎 handle
 p3DAngleInfo: LPASF_FaceAngle): integer; cdecl; // [out] 检测到脸部 3D 角度信息
function ASFProcess_IR(hEngine: pointer; width: MInt32; height: MInt32; format: MInt32; imgData: 
PMuint8; var detectedFaces: ASF_MultiFaceInfo; combinedMask: MInt32): integer; cdecl;
function ASFFaceFeatureCompare(
 pEngine: pointer; // [in] 引擎 handle
 var feature1: ASF_FaceFeature; // [in] 待比较人脸特征 1
 var feature2: ASF_FaceFeature; // [in] 待比较人脸特征 2
 var confidenceLevel: MFloat; // [out] 比较结果,置信度数值
 compareModel: ASF_CompareModel = ASF_LIFE_PHOTO
 ): integer; cdecl;
function ASFGetAge(
 pEngine: pointer; // [in] 引擎 handle
 ageInfo: LPASF_AgeInfo // [out] 检测到的年龄信息
 ): integer; cdecl;
function ASFGetLivenessScore(
 pEngine: pointer; // [in] 引擎 handle
 livenessInfo: LPASF_LivenessInfo // [out] 检测 RGB 活体结果
 ): integer; cdecl;
function ASFGetVersion(): ASF_VERSION; cdecl;
implementation
var
 FMASK: MInt32;
 iRet: integer;
 appId, sdkKey: string;
const
 DllName = 'libarcsoft_face_engine.dll';
function ASFActivation; cdecl; external DllName;
function ASFInitEngine; cdecl; external DllName;
function ASFDetectFaces; cdecl; external DllName;
function ASFDetectFacesEx; cdecl; external DllName;
function ASFFaceFeatureExtract; cdecl; external DllName;
function ASFGetFace3DAngle; cdecl; external DllName;
function ASFProcess_IR; cdecl; external DllName;
function ASFFaceFeatureCompare; cdecl; external DllName;
function ASFGetAge; cdecl; external DllName;
function ASFGetVersion; cdecl; external DllName;
function ASFGetLivenessScore; cdecl; external DllName;
initialization
 FFaceHandle := nil;
 FMASK := ASF_FACE_DETECT
 or ASF_FACERECOGNITION
 or ASF_AGE or ASF_GENDER
 or ASF_FACE3DANGLE
 or ASF_LIVENESS or ASF_IR_LIVENESS;
 appId := '9JwgKfrScuRs4vJ6huhPzbuqjS7ZTnK3ktYyk75j8xVS';
 sdkKey := 'AGjZPqgvNFnwy4brfUHNpnZXfTaivDubCK6evzGqMmMk';
 iRet := ASFActivation(pchar(appId), pchar(sdkKey));
 iRet := ASFInitEngine(ASF_DETECT_MODE_IMAGE,
 ASF_OP_0_HIGHER_EXT,
 16, //2-32 最小人脸
 5, FMASK, @FFACEHandle);
…
END;

经过测试 ,虹软 SDK 完全能正常工作,能检测到人脸,并比较两张人脸图片的相似度。下面正式开发我的人脸库服务器。

二、编写人脸库 WEB 服务器

DELPHI 开发人脸库服务器,必须实现人脸图片的接收、保存以及信息的返回,采用第三方控件**RealThinClient** 控件集来实现 webserver功能,MYSQL 数据库来保存人脸特征信息及身份信息,另外为了访问 MYSQL 数据库,还采用 MySQL.Dac 控件集。

 

 

 Mysql 数据库采用嵌入式安装,以便于打包,在 mysql 中创建库 facelib , facelib 中创建两个表**dbfacelibdbuserlib** 结构如下:

CREATE TABLE `dbfacelib` (
人脸库服务器
WebServer
ArcFace SDK
Mysql
客户端人脸识别
 `row_id` int(11) NOT NULL AUTO_INCREMENT,
 `feature` varchar(1536) CHARACTER SET ascii DEFAULT NULL,
 `session` varchar(32) CHARACTER SET ascii DEFAULT NULL,
 `id` varchar(10) CHARACTER SET ascii DEFAULT NULL,
 PRIMARY KEY (`row_id`),
 UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=429 DEFAULT CHARSET=utf8;
CREATE TABLE `dbuserlib` (
 `row_id` int(11) NOT NULL AUTO_INCREMENT,
 `facerowid` int(11) DEFAULT '0',
 `id` varchar(20) CHARACTER SET ascii DEFAULT NULL,
 `name` varchar(10) DEFAULT NULL,
 `sex` varchar(4) DEFAULT NULL,
 `descript` varchar(100) DEFAULT NULL,
 `identificatin` varchar(18) DEFAULT NULL,
 `address` varchar(40) DEFAULT NULL,
 `mobile` varchar(11) DEFAULT NULL,
 `marriage` smallint(6) DEFAULT NULL,
 `birthday` date DEFAULT NULL,
 `url` varchar(120) DEFAULT NULL,
 PRIMARY KEY (`row_id`),
 UNIQUE KEY `id` (`id`),
 KEY `facerowid` (`facerowid`)
) ENGINE=MyISAM AUTO_INCREMENT=429 DEFAULT CHARSET=utf8;

dbfacelib 的 feature 字段用于保存人脸特征串,人脸比较是不用建索引的,独条比较,搜索到相似度大于 80 时返回 rowid ,再在 dbuserlib 中查询对应身份信息。返回给客户端。

在 DELPHI 的 IDE 上 点击菜单“File->New->Application”,新建一个应用,form1 改名 frMain, 在 frMain上放一个TRtcHttpServer 命名 两个 TRtcDataProvider 改名为 rtcGet 与 rtcPut ,分别实现 httpsever的 GET 命令与 put 命令的解释执行,rtcGet 实现了 web 网页解释输出,数据库访问输出,rtcPut 则负责实现文件上传,人脸上传后识别、保存、比较等功能,工程保存为 facesrvr.dpr

rtcGet 与 rtcPut 的 CheckRequest 与 DataReceived 事件中代码如下:

procedure TfrMainDm.rtcGetCheckRequest(Sender: TRtcConnection);
var
 fname, FileName: string;
begin
 with TRtcDataServer(Sender) do begin
 if (Request.Method = 'PUT') then exit;
 FileName := UpperCase(Request.FileName);
 if FileName = '/' then FileName := '/index.html';
 fname := GetFullFileName(FileName);
 if (fname <> '') and (File_Exists(fname)) then begin
 { We will store the file name in our request,
 so we don't have to recreate it again later. }
 Request.Info['fname'] := fname;
 { We need to set the Response.ContentLength,
 to tell the RtcDataServer how large the content
 (data) in our response will be.
 If we do not set the Response.ContentLength,
 RtcDataServer will assume that the first event which
 calls Write has prepared the complete response and
 will calculate the ContentLength for us. }
 Response.ContentLength := File_Size(fname);
 Response.ContentType := aMimeTable.GetFileMIMEType(FileName);
 // showmessage(FileName+' ContentType='+Response.ContentType);
 { We will send the Response Header out,
 so we don't have to call Write
 in case our File size is zero. }
 WriteHeader;
 Accept;
 end else if posEx(FileName, '/IP/TIME/QRCODE/SYSTIME/PRINT/BROAD/JOBS') > 0 then begin
 Accept;
 end else if ExtractFileExt(FileName) = '.JSON' then
 Accept;
 end;
end;
procedure TfrMainDm.rtcGetDataReceived(Sender: TRtcConnection);
var
 svr: TRtcDataServer absolute Sender;
 url, FileName, NamePart, extPart, OutputData: string;
 abar: TQRCode;
 i: integer;
 ms: TmemoryStream;
 sql, data, msg: string;
 ret, lastid: integer;
 li: TStringDynArray;
 Fsobook: string;
var
 fname: string;
 params: TRtcHttpValues;
 rowid, mchid: string;
 len: cardinal;
 MyConn: TMyConnection;
 RtcRes: TRtcValue;
 qry: TMyQuery;
 cdds: TCopyDataStruct;
 wnd: HWND;
begin
 with svr do begin
 if Request.Complete then begin
 FileName := LowerCase(Request.FileName);
 NamePart := getNamePart(pchar(FileName));
 extPart := ExtractFileExt(FileName);
 if FileName = '/time' then begin
 Write(DateTimeToStr(Now));
 end else begin // 读取文件返回客户端
 // Check if we have to send more data.
 if Response.ContentLength > Response.ContentOut then begin
 fname := Request.Info['fname'];.
 if File_Size(fname) = Response.ContentLength then begin
 len := Response.ContentLength - Response.ContentOut;
 if len > 16000 then len := 16000;
 Write(Read_File(fname, Response.ContentOut, len));
 end else
 { Disconnect the client, because our file
 has changed and we have sent
 the wrong header and file beginning out. }
 Disconnect;
 end;
 end;
 end; //Request.Complete
 end; //svr
end;
procedure TfrMainDm.rtcGetDataSent(Sender: TRtcConnection);
var
 fname, FileName: string;
 len: cardinal;
begin
 with TRtcDataServer(Sender) do
 if Request.Complete then begin
 FileName := UpperCase(Request.FileName);
 if (FileName <> '/') 
and (pos(FileName, '/IP/TIME') > 0) then begin 
 end else begin
 if Response.ContentLength > Response.ContentOut then begin
 fname := Request.Info['fname'];
 if File_Size(fname) = Response.ContentLength then begin
 len := Response.ContentLength - Response.ContentOut;
 if len > 16000 then len := 16000;
 Write(Read_File(fname, Response.ContentOut, len));
 end else
 Disconnect;
 end;
 end;
 end;
end;
procedure TfrMainDm.rtcPutCheckRequest(Sender: TRtcConnection);
begin
 with Sender as TRtcDataServer do begin
 if (Request.Method = 'PUT') then
 Accept;
 end;
end;
procedure TfrMainDm.rtcPutDataReceived(Sender: TRtcConnection);
var
 svr: TRtcDataServer absolute Sender;
 ret, i, rowid: integer;
 blod, FileName, Path, FullName, tmp: ansistring;
 pblod: Pstring;
 ContentType, FileExt, queryString, username, id: string;
var
 MemStream: TMemoryStream;
 MyIStream: TStreamAdapter;
 imgData: ASF_ImageData;
 Stride: integer;
 ScanLines: array of Byte;
var
 featurestr: ansistring;
 EncodeStr: ansistring;
 featureSize: integer;
 feature1: ASF_FaceFeature; // [in] 待比较人脸特征 1
 feature2: ASF_FaceFeature; // [in] 待比较人脸特征 2
 similar, confidenceLevel: MFloat;
var
 MyConn: TMyConnection;
 qry: TMyQuery;
 sql, data, url: string;
 sj: ISuperObject;
begin
 with svr do begin
 FileName := Utf8ToAnsi(URLDecode(svr.Request.FileName));
 Path := GetUrlPath(FileName); //showmessage(FilePath);
 FullName := rootPath + FileName;
 if Request.Started then begin
 svr.OpenSession(); 
 end;
 tmp := curPath + 'temp' + svr.Session.ID + '.tmp';
 blod := Read;
 rtcInfo.Write_File(tmp, blod, Request.ContentIn - length(blod), rtc_ShareDenyNone);
 if FileName = '/register' then begin
 i := pos('?', Request.URI); //
 queryString := Utf8ToAnsi(URLDecode(copy(Request.URI, i + 1, length(Request.URI) - i)));
 Request.Params.Text := queryString;
 path := Request.Params['path'];
 id := Request.Params['id'];
 username := Request.Params['name'];
 if id = '' then begin
 Write('{"ret":-1,"errmsg":"not id"}'); 
 exit;
 end;
 if Request.Complete then begin
 ContentType := Request.ContentType;
 FileExt := aMimeTable.GetDefaultFileExt(ContentType); 
 svr.CloseSession(svr.Session.ID);
 try
 imagesBlod := Read_File(tmp);
 finally
 end;
 try
 featureSize := getFeature(imagesBlod, featurestr);
 except
 Write('{"ret":-1,"errmsg":"error blod base64 featureSize=' + inttostr(featureSize) + ' "}'); 
//ret==0 无影响 <0 影响行数 >0 新行号
 exit;
 end;
 featurestr := EncodeString(featurestr);
 if featureSize = 0 then begin
 Write('{"ret":-1,"errmsg":"not face "}'); 
 Response.Status(200, 'OK');
 exit;
 end;
 FullName := rootPath + '\facelib' + svr.Session.ID + FileExt; //保存的人脸文件
 Delete_File(FullName); //先删除同名文件
 rtcInfo.Rename_File(tmp, FullName); // 临时文件改为(移动到)文件名 
 MyConn := TMyConnection.Create(nil);
 try
 MyConn.Server := fServer;
 MyConn.Username := fUsername;
 MyConn.Password := fPassword;
 MyConn.Port := fPort;
 MyConn.Options.Charset := 'utf8';
 MyConn.Options.UseUnicode := true;
 MyConn.Database := 'facelib';
 MyConn.Open;
 MyConn.StartTransaction;
 ret := 0;
 try
 sql := ' INSERT INTO dbfacelib(id,feature,session) '
 + ' VALUES("' + id + '","' + featurestr + '","' + svr.Session.ID + '") '
+ ' ON DUPLICATE KEY UPDATE session=VALUES(session) ,feature=VALUES(feature) ;';
 
 ret := ExecutSql(MyConn, sql, 1); // // 0 --无影响,-影响行数 ,+ 插入后行号
 if ret > 0 then begin
 sql := ' INSERT INTO dbuserlib(facerowid,name,id,url) '
 + ' VALUES(' + inttostr(ret) + ',"' + username + '","' + id + '","' + '/facelib/' + 
svr.Session.ID + FileExt + '" ) '
 + ' ON DUPLICATE KEY UPDATE facerowid=VALUES(facerowid),url=VALUES(url);';
 ret := ExecutSql(MyConn, sql); 
 end;
 MyConn.Commit;
 Write('{"ret":' + inttostr(abs(ret)) + '}');
 except
 on E: Exception do begin
 Write('{"ret":-1}'); //0--无影响 - 影响行数 + 返回插入行号
 MyConn.RollBack;
 raise Exception.Create(E.Message);
 end;
 end;
 finally
 MyConn.Free;
 end;
 Response.Status(200, 'OK');
 Write('');
 end; // Request.Complete
 end else if FileName = '/search' then begin
 if Request.Complete then begin
 ContentType := Request.ContentType;
 FileExt := aMimeTable.GetDefaultFileExt(ContentType);
 svr.CloseSession(svr.Session.ID);
 try
 imagesBlod := Read_File(tmp);
 finally
 end;
 try
 featureSize := getFeature(imagesBlod, featurestr);
 except
 Write('{"ret":-1,"errmsg":"error imges blod"}'); //ret==0 无影响 <0 影响行数 >0 新
行号
 exit;
 end;
 // featurestr := EncodeString(featurestr); //不要压缩
 Delete_File(tmp);
 if featureSize = 0 then begin
 Write('{"ret":-1,"errmsg":"not face "}'); //ret==0 无影响 <0 影响行数 >0 新行号
 Response.Status(200, 'OK');
 exit;
 end;
 MyConn := TMyConnection.Create(nil);
 qry := TMyQuery.Create(nil);
 feature1.featureSize := 1032;
 GetMem(feature1.feature, 1033);
 CopyMemory(feature1.feature, pchar(featurestr), 1032);
 feature2.featureSize := 1032;
 GetMem(Feature2.feature, 1033);
 try
 MyConn.Server := fServer;
 MyConn.Username := fUsername;
 MyConn.Password := fPassword;
 MyConn.Port := fPort;
 MyConn.Options.Charset := 'utf8';
 MyConn.Options.UseUnicode := true;
 MyConn.Database := 'facelib';
 MyConn.Open;
 MyConn.StartTransaction;
 qry.Connection := MyConn;
 sql := ' select row_id,feature from dbfacelib; ';
 qry.Close;
 qry.SQL.Text := string(sql);
 qry.Open;
 qry.DisableControls;
 qry.First;
 rowid := 0; similar := 0;
 while not qry.Eof do begin
 data := qry.FieldByName('feature').AsString;
 data := DecodeString(data); 
 CopyMemory(Feature2.feature, pchar(data), 1032);
 ret := ASFFaceFeatureCompare(FFACEHandle, feature1, Feature2, confidenceLevel, 
ASF_LIFE_PHOTO);
 if (ret = 0) and (confidenceLevel > 0.75) then begin
 similar := confidenceLevel;
 rowid := qry.FieldByName('row_id').AsInteger;
 if (similar > 0.8) then Break;
 end; 
 qry.Next;
 end;
 if (rowid > 0) then begin
sql := ' SELECT row_id, id,url,name,sex, identificatin,address,marriage,
TIMESTAMPDIFF(YEAR, birthday, CURDATE())as age '
 +' FROM dbuserlib WHERE facerowid =' + inttostr(rowid);
 qry.Close;
 qry.SQL.Text := string(sql);
 qry.Open;
 if qry.RecordCount = 1 then begin
 id := qry.FieldByName('id').AsString;
 url := qry.FieldByName('url').AsString;
 sj := JSonFromDataSet(qry);
 sj.AsArray[0].D['similar'] := similar;
 sj.AsArray[0].i['ret'] := 0;
 Write(sj.AsArray[0].AsString);
 end else
 Write('{"ret":-1,"errmsg":"face not register ,not find in Records "} ');
 end else begin
 Write('{"ret":-1,"errmsg":"face not register,not find in Records "} ');
 end;
 qry.EnableControls;
 MyConn.Commit;
 finally 
 FreeMem(feature2.feature);
 FreeMem(feature1.feature);
 qry.Free;
 MyConn.Free;
 end;
 Response.Status(200, 'OK');
 end;
 end else begin
 if Request.Complete then begin
 svr.CloseSession(svr.Session.ID);
 ContentType := Request.ContentType;
 FileExt := aMimeTable.GetDefaultFileExt(ContentType);
 FullName := rootPath + '\files' + FileName;
 ChangeFileExt(FullName, FileExt);
 Delete_File(FullName); //先删除同名文件
 rtcInfo.Rename_File(tmp, FullName); 
 Response.Status(200, 'OK'); // Set response status to a standard "OK"
 Write('"ret":-1,"msg":"' + ansitoUtf8(FileName + '上传成功.') + '"'); 
 end;
 end;
 end;
end;

上述代码省掉非必要部分,但已可实现众人脸库功能了,而且性能还是不错的,运行非常稳定,但由于虹软人脸串比较函数没有独立出来,要与引擎放在同一库文件,而且必须引用引擎指针,因此无法把人脸比较功能做成 MYSQL 的 UDF 自定义函数, 人脸比较速度不快,在 2000 人脸数时,搜索比较人脸可以在 500 毫秒返回,10 个并发可以使用,接近百度人脸库速度,但人脸数比较多时,性能下降,比如 10000人脸,返回时间达 6 秒,2 个并发时返回时间要 10 多秒无法使用。但局域网内人脸库本来适用人数就不多,如果可改成多个线程并行比较,可提高查询比较速度。

为了减少编幅,没有提供服务程序部分代码,所以程序不能做服务启动,只能手动启动,如果必要的话,自己再添加此功能。

三、人脸库服务器的安装打包

人脸库服务器就此完成,使用方法,先启动 mysql 服务,再启动. 可以先测试一下,在浏览器中打入 http://127.0.0.1:8080/可登录进入测试页面,先注册上传一些人脸,再上传人脸搜索对比,转出相似度 80%匹配的人脸信息如下。

 


如果人脸库服务器安装在与测试不同的电脑,应该防火墙设置 8080 端口例外通过程序的打包比较复杂,方法如下:
1 打包 MYSQL 并设置为服务启动。
2 打包 facesrvr 并设置为服务启动
3 安装完毕,设置防火墙端口例外通过

完整演示下载地址:
icloud.cdn.bcebos.com/%E8%99%B9%E…

注意要使用到虹软开发平台 appId 与 sdkKey 在安装目录编辑 facesrvr.ini 文件,设置

[arcface]
appId= 你的 appId
sdkKey= 你的 sdkKey

源码下载:
链接: pan.baidu.com/s/1PvCgEvfE…
提取码: mfqn

 

了解更多人脸识别产品相关内容请到虹软视觉开放平台