利用虹软 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
usesTypes;
varFFaceHandle: pointer;
constASF_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 degreeASF_OC_90 = $2; // 90 degreeASF_OC_270 = $3; // 270 degreeASF_OC_180 = $4; // 180 degreeASF_OC_30 = $5; // 30 degreeASF_OC_60 = $6; // 60 degreeASF_OC_120 = $7; // 120 degreeASF_OC_150 = $8; // 150 degreeASF_OC_210 = $9; // 210 degreeASF_OC_240 = $A; // 240 degreeASF_OC_300 = $B; // 300 degreeASF_OC_330 = $C; // 330 degreeASVL_PAF_I420 = $601;ASVL_PAF_RGB24_B8G8R8 = $201; // 513MOK = (0);MERR_NONE = (0);
type//检测模型ASF_DetectModel = (ASF_DETECT_MODEL_RGB = $1 //RGB 图像检测模型//预留扩展其他检测模型);//人脸比对可选的模型ASF_CompareModel = (ASF_LIFE_PHOTO = $1, //用于生活照之间的特征比对,推荐阈值 0.80ASF_ID_PHOTO = $2 //用于证件照或生活照与证件照之间的特征比对,推荐阈值 0.82);
//duDWORD,LongInt,Cardinal 三种数据 zhi 类型都一样,都是 32 位无符号 dao 整型(无符号就是没
有负的,
//最小值内为 0,和之相对的有符号 32 位整型就是最常用容的 Integer)
typeMPChar = pchar;Mint8 = Shortint; // -128..127 signed 8-bitPMint8 = ^Mint8;Muint8 = Byte; //0..255 unsigned 8-bitPMuint8 = ^Muint8;Mint16 = Smallint; //-32768..32767 signed 16-bitPMint16 = ^Mint16;Muint16 = WORD; //word 是 2 字节 unsigned 16-bitPMuint16 = ^Muint16;MInt32 = Longint; //-2147483648..2147483647 signed 32-bitPMint32 = ^MInt32;MUInt32 = DWORD; //Longword unsigned 32-bitPMuint32 = ^MUInt32;pMByte = ^MByte;MByte = Byte; //0..255 unsigned 8-bitMWord = WORD; //word 是 2 字节 unsigned 16-bitMFloat = Single; // 这里必须是 Single ,4bytes 32 位浮点数.
//不能 double;8 bytes 64 位浮点数 否则无法输出人脸比较pMRECT = ^MRECT;MRECT = recordleft: MInt32;top: MInt32;right: MInt32;bottom: MInt32;end;//单人脸信息LPASF_SingleFaceInfo = ^ASF_SingleFaceInfo;ASF_SingleFaceInfo = recordfaceRect: MRECT; // 人脸框信息faceOrient: MInt32; // 输入图像的角度,可以参考 ArcFaceCompare_OrientCodeend;//多人脸信息LPASF_MultiFaceInfo = ^ASF_MultiFaceInfo;ASF_MultiFaceInfo = recordfaceRect: array of MRECT; // 人脸框数组faceOrient: array of MInt32; // 人脸角度数组faceNum: MInt32; // 检测到的人脸个数faceID: MInt32; //在 VIDEO 模式下有效,IMAGE 模式下为空end;LPASF_FaceFeature = ^ASF_FaceFeature;ASF_FaceFeature = recordfeature: pMByte; //array[0..2048] of byte; // pMuint8; // 人脸特征信息featureSize: MInt32; // 人脸特征信息长度end;LPASF_FaceAngle = ^ASF_FaceAngle;ASF_FaceAngle = recordroll: MFloat;yaw: MFloat;pitch: MFloat;status: integer; //0: 正常,其他数值:出错num: PMuint32end;LPASF_AgeInfo = ^ASF_AgeInfo;ASF_AgeInfo = recordageArray: MInt32; // "0" 代表不确定,num: MInt32; // 检测的人脸个数end;//结构体LPASVLOFFSCREEN = ^ASVLOFFSCREEN;ASVLOFFSCREEN = recordu32PixelArrayFormat: 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 = recordisLive: pMInt32; // [out] 判断是否真人, 0:非真人;// 1:真人;// -1:不确定;// -2:传入人脸数>1;// -3: 人脸过小// -4: 角度过大// -5: 人脸超出边界num: MInt32;end;
// 激活文件信息LPASF_ActiveFileInfo = ^ASF_ActiveFileInfo;ASF_ActiveFileInfo = recordstartTime: MPChar; //开始时间endTime: MPChar; //截止时间platform: MPChar; //平台sdkType: MPChar; //sdk 类型appId: MPChar; //APPIDsdkKey: MPChar; //SDKKEYsdkVersion: MPChar; //SDK 版本号fileVersion: MPChar; //激活文件版本号end;//版本信息LPASF_VERSION = ^ASF_VERSION;ASF_VERSION = recordVersion: MPChar; // 版本号BuildDate: MPChar; // 构建日期CopyRight: MPChar; // Copyrightend;
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] 引擎 handlep3DAngleInfo: 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] 引擎 handlevar feature1: ASF_FaceFeature; // [in] 待比较人脸特征 1var feature2: ASF_FaceFeature; // [in] 待比较人脸特征 2var confidenceLevel: MFloat; // [out] 比较结果,置信度数值compareModel: ASF_CompareModel = ASF_LIFE_PHOTO): integer; cdecl;
function ASFGetAge(pEngine: pointer; // [in] 引擎 handleageInfo: LPASF_AgeInfo // [out] 检测到的年龄信息): integer; cdecl;
function ASFGetLivenessScore(pEngine: pointer; // [in] 引擎 handlelivenessInfo: LPASF_LivenessInfo // [out] 检测 RGB 活体结果): integer; cdecl;
function ASFGetVersion(): ASF_VERSION; cdecl;
implementation
varFMASK: MInt32;iRet: integer;appId, sdkKey: string;
constDllName = '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;
initializationFFaceHandle := nil;FMASK := ASF_FACE_DETECTor ASF_FACERECOGNITIONor ASF_AGE or ASF_GENDERor ASF_FACE3DANGLEor 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
中创建两个表dbfacelib
,dbuserlib
结构如下:
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);
varfname, FileName: string;
beginwith TRtcDataServer(Sender) do beginif (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 whichcalls Write has prepared the complete response andwill 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 Writein case our File size is zero. }WriteHeader;Accept;end else if posEx(FileName, '/IP/TIME/QRCODE/SYSTIME/PRINT/BROAD/JOBS') > 0 then beginAccept;end else if ExtractFileExt(FileName) = '.JSON' thenAccept;end;
end;
procedure TfrMainDm.rtcGetDataReceived(Sender: TRtcConnection);
varsvr: 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;
varfname: string;params: TRtcHttpValues;rowid, mchid: string;len: cardinal;MyConn: TMyConnection;RtcRes: TRtcValue;qry: TMyQuery;cdds: TCopyDataStruct;wnd: HWND;
beginwith svr do beginif Request.Complete then beginFileName := LowerCase(Request.FileName);NamePart := getNamePart(pchar(FileName));extPart := ExtractFileExt(FileName);if FileName = '/time' then beginWrite(DateTimeToStr(Now));end else begin // 读取文件返回客户端// Check if we have to send more data.if Response.ContentLength > Response.ContentOut then beginfname := Request.Info['fname'];.if File_Size(fname) = Response.ContentLength then beginlen := Response.ContentLength - Response.ContentOut;if len > 16000 then len := 16000;Write(Read_File(fname, Response.ContentOut, len));end else{ Disconnect the client, because our filehas changed and we have sentthe wrong header and file beginning out. }Disconnect;end;end;end; //Request.Completeend; //svr
end;
procedure TfrMainDm.rtcGetDataSent(Sender: TRtcConnection);
varfname, FileName: string;len: cardinal;
beginwith TRtcDataServer(Sender) doif Request.Complete then beginFileName := UpperCase(Request.FileName);if (FileName <> '/')
and (pos(FileName, '/IP/TIME') > 0) then begin end else beginif Response.ContentLength > Response.ContentOut then beginfname := Request.Info['fname'];if File_Size(fname) = Response.ContentLength then beginlen := Response.ContentLength - Response.ContentOut;if len > 16000 then len := 16000;Write(Read_File(fname, Response.ContentOut, len));end elseDisconnect;end;end;end;
end;
procedure TfrMainDm.rtcPutCheckRequest(Sender: TRtcConnection);
beginwith Sender as TRtcDataServer do beginif (Request.Method = 'PUT') thenAccept;end;
end;
procedure TfrMainDm.rtcPutDataReceived(Sender: TRtcConnection);
varsvr: TRtcDataServer absolute Sender;ret, i, rowid: integer;blod, FileName, Path, FullName, tmp: ansistring;pblod: Pstring;ContentType, FileExt, queryString, username, id: string;
varMemStream: TMemoryStream;MyIStream: TStreamAdapter;imgData: ASF_ImageData;Stride: integer;ScanLines: array of Byte;
varfeaturestr: ansistring;EncodeStr: ansistring;featureSize: integer;feature1: ASF_FaceFeature; // [in] 待比较人脸特征 1feature2: ASF_FaceFeature; // [in] 待比较人脸特征 2similar, confidenceLevel: MFloat;
varMyConn: TMyConnection;qry: TMyQuery;sql, data, url: string;sj: ISuperObject;
beginwith svr do beginFileName := Utf8ToAnsi(URLDecode(svr.Request.FileName));Path := GetUrlPath(FileName); //showmessage(FilePath);FullName := rootPath + FileName;if Request.Started then beginsvr.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 begini := 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 beginWrite('{"ret":-1,"errmsg":"not id"}'); exit;end;if Request.Complete then beginContentType := Request.ContentType;FileExt := aMimeTable.GetDefaultFileExt(ContentType); svr.CloseSession(svr.Session.ID);tryimagesBlod := Read_File(tmp);finallyend;tryfeatureSize := getFeature(imagesBlod, featurestr);exceptWrite('{"ret":-1,"errmsg":"error blod base64 featureSize=' + inttostr(featureSize) + ' "}');
//ret==0 无影响 <0 影响行数 >0 新行号exit;end;featurestr := EncodeString(featurestr);if featureSize = 0 then beginWrite('{"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);tryMyConn.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;trysql := ' 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 beginsql := ' 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)) + '}');excepton E: Exception do beginWrite('{"ret":-1}'); //0--无影响 - 影响行数 + 返回插入行号MyConn.RollBack;raise Exception.Create(E.Message);end;end;finallyMyConn.Free;end;Response.Status(200, 'OK');Write('');end; // Request.Completeend else if FileName = '/search' then beginif Request.Complete then beginContentType := Request.ContentType;FileExt := aMimeTable.GetDefaultFileExt(ContentType);svr.CloseSession(svr.Session.ID);tryimagesBlod := Read_File(tmp);finallyend;tryfeatureSize := getFeature(imagesBlod, featurestr);exceptWrite('{"ret":-1,"errmsg":"error imges blod"}'); //ret==0 无影响 <0 影响行数 >0 新
行号exit;end;// featurestr := EncodeString(featurestr); //不要压缩Delete_File(tmp);if featureSize = 0 then beginWrite('{"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);tryMyConn.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 begindata := 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 beginsimilar := 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 beginid := 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 elseWrite('{"ret":-1,"errmsg":"face not register ,not find in Records "} ');end else beginWrite('{"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 beginif Request.Complete then beginsvr.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 安装完毕,设置防火墙端口例外通过
完整演示下载地址:
https://icloud.cdn.bcebos.com/%E8%99%B9%E8%BD%AF%E4%BA%BA%E8%84%B8%E5%BA%93%E6%9C%8D%E5%8A%A1%E5%99%A8.exe
注意要使用到虹软开发平台 appId
与 sdkKey
在安装目录编辑 facesrvr.ini
文件,设置
[arcface]
appId= 你的 appId
sdkKey= 你的 sdkKey
源码下载:
链接: https://pan.baidu.com/s/1PvCgEvfE8CltFhXuzmwVHA
提取码: mfqn
了解更多人脸识别产品相关内容请到虹软视觉开放平台哦