利用虹软 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
中创建两个表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);
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 安装完毕,设置防火墙端口例外通过
完整演示下载地址:
https://icloud.cdn.bcebos.com...
注意要使用到虹软开发平台 appId
与 sdkKey
在安装目录编辑 facesrvr.ini
文件,设置
[arcface]
appId= 你的 appId
sdkKey= 你的 sdkKey
源码下载:
链接: https://pan.baidu.com/s/1PvCg...
提取码: mfqn
了解更多人脸识别产品相关内容请到虹软视觉开放平台哦
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。