海康威视摄像头人脸识别开发流程及踩过的坑
前段时间,接手了公司的海康摄像头的二次开发的工作,在网上查了很多资料,但基本上只能解决其中一部分问题,还有很多问题,在不停纠缠海康技术支持后,才勉强搞定,在这里把自己的开发心得及踩过的坑写下来,希望对大家有所帮助。
我的开发环境使用的是 Java1.8 + Tomcat 8.5
所贴的代码仅为一部分代码,仅供参考,后面会放出完整代码,方便大家查看
PS:不得不说,海康的 SDK 真的非常不友好,好多文档中写的接口在我们使用的 HCNetSDK 中都没有声明,需要我们自己来进行声明,也因为这个问题,折腾了我好久。
开发流程及坑
下载海康SDK
这一步我就不过多介绍了,网上有很多说明,总之出现问题了,尝试各种解决方案,总会解决的,接着导入相关的文件。
初始化 SDK
在初始化 SDK 这一块,还好,基本上不会有什么问题,即便有问题,根据报错信息也能定位问题,这里贴上我的初始化代码。
public void init() {
lHandle = -1;
lListenHandle = -1;
Boolean login = this.login();
if (login) {
// 注册成功,进行布防
this.SetupAlarmChan();
//FaceSpot.set
* 用户注册
public Boolean login() {
// 初始化
boolean initSuc = hCNetSDK.NET_DVR_Init();
if (!initSuc) {
System.out.println("初始化失败, 错误代码:" + hCNetSDK.NET_DVR_GetLastError());
log.info("初始化失败, 错误代码:" + hCNetSDK.NET_DVR_GetLastError());
} else {
System.out.println("接口初始化成功");
log.info("初始化接口成功");
// 开启日志
boolean file = hCNetSDK.NET_DVR_SetLogToFile(3, "D:\\SdkLog\\", true);
System.out.println("开启日志:" + file);
hCNetSDK.NET_DVR_SetDVRMessageCallBack_V31(fMSFCallBack, null);
ip = HCNetDeviceConUtil.ip;
NET_DVR_DEVICEINFO_V30 s30 = new HCNetSDK.NET_DVR_DEVICEINFO_V30();
Map<String, String> map = RWProperties.getCameraInfo();
String port2 = map.get("port");
Short s = new Short(port2);
short myPort = s.shortValue();
userID = hCNetSDK.NET_DVR_Login_V30(map.get("ip"), myPort, map.get("username"), map.get("password"), s30);
// 设置回调
if (userID.longValue() == -1) {
System.out.println("注册失败,失败原因为:" + hCNetSDK.NET_DVR_GetLastError());
log.info("注册失败,失败原因为:" + hCNetSDK.NET_DVR_GetLastError());
return false;
} else {
System.out.println("注册成功");
log.info("注册成功:" + userID);
// 你也可以在这里做一些你想做的操作
return true;
报警布防后摄像头开始正式工作,在这里有一个坑就是,我们的人脸识别的结果都是通过回调函数返回给我们的,在这里,我们的回调函数一定要设置成静态全局的,不然很容易被回收掉,我当时没注意这个问题,一直纳闷,为什么部署到服务器上,没过多长时间,就无法进入回调函数了,后才发现是这个问题
* 报警布防
public void SetupAlarmChan() {
if (fMSFCallBack == null) {
// 这里是以前写的,后来没有删除,但是 fMSFCallBack 一定要声明成全局的
// 这里的 fMSFCallBack 已经在全局进行声明了,所以不会进入这个判断
fMSFCallBack = new FMSGCallBackController();
FMSGCallBack_V31 fMessageCallBack = new FMSGCallBackController();
Pointer pUser = null;
if (!hCNetSDK.NET_DVR_SetDVRMessageCallBack_V31(fMessageCallBack, pUser)) {
System.out.println("设置回调函数失败:" + hCNetSDK.NET_DVR_GetLastError());
log.info("设置回调函数失败:" + hCNetSDK.NET_DVR_GetLastError());
HCNetSDK.NET_DVR_SETUPALARM_PARAM m_strAlarmInfo = new HCNetSDK.NET_DVR_SETUPALARM_PARAM();
m_strAlarmInfo.dwSize = m_strAlarmInfo.size();
m_strAlarmInfo.byLevel = 1;
m_strAlarmInfo.byAlarmInfoType = 1;
m_strAlarmInfo.write();
// m_strAlarmInfo.byFaceAlarmDetection = 1;
// NativeLong test = new NativeLong(1);
// userID
NativeLong lHandle = hCNetSDK.NET_DVR_SetupAlarmChan_V41(userID, m_strAlarmInfo);
if (lHandle.longValue() == -1) {
System.out.println("布防失败,失败原因:" + hCNetSDK.NET_DVR_GetLastError());
log.info("布防失败,失败原因:" + hCNetSDK.NET_DVR_GetLastError());
} else {
System.out.println("布防成功");
log.info("布防成功");
// 要保证程序不会停止,想了一个笨办法,进行死循环,当然如果你有更好的办法欢迎指正
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
回调函数不知道怎么说,对着 SDK 开发文档,一步一步来就好了,回调函数代码就放在后面的代码里面了
FMSGCallBackController.java
创建人脸库
以上,基本上,你的功能就完成了,当然光这样我们的摄像头是无法工作的,我们还需要创建人脸库,上传图片,人脸建模等一系列操作,这里我们一步步的来。
* 创建人脸库
* @param FDLibName
* @return
@SuppressWarnings("unchecked")
public boolean CreateFDLib(String FDLibName) {
HCNetSDK.NET_DVR_XML_CONFIG_INPUT struInput = new HCNetSDK.NET_DVR_XML_CONFIG_INPUT();
struInput.dwSize = struInput.size();
String str = "POST /ISAPI/Intelligent/FDLib\r\n";
HCNetSDK.BYTE_ARRAY ptrUrl = new HCNetSDK.BYTE_ARRAY(HCNetSDK.BYTE_ARRAY_LEN);
System.arraycopy(str.getBytes(), 0, ptrUrl.byValue, 0, str.length());
ptrUrl.write();
struInput.lpRequestUrl = ptrUrl.getPointer();
struInput.dwRequestUrlLen = str.length();
String strInBuffer = new String("<CreateFDLibList><CreateFDLib><id>1</id><name>" + FDLibName
+ "</name><thresholdValue>1</thresholdValue><customInfo /></CreateFDLib></CreateFDLibList>");
HCNetSDK.BYTE_ARRAY ptrByte = new HCNetSDK.BYTE_ARRAY(10 * HCNetSDK.BYTE_ARRAY_LEN);
ptrByte.byValue = strInBuffer.getBytes();
ptrByte.write();
struInput.lpInBuffer = ptrByte.getPointer();
struInput.dwInBufferSize = strInBuffer.length();
struInput.write();
HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT struOutput = new HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT();
struOutput.dwSize = struOutput.size();
HCNetSDK.BYTE_ARRAY ptrOutByte = new HCNetSDK.BYTE_ARRAY(HCNetSDK.ISAPI_DATA_LEN);
struOutput.lpOutBuffer = ptrOutByte.getPointer();
struOutput.dwOutBufferSize = HCNetSDK.ISAPI_DATA_LEN;
HCNetSDK.BYTE_ARRAY ptrStatusByte = new HCNetSDK.BYTE_ARRAY(HCNetSDK.ISAPI_STATUS_LEN);
struOutput.lpStatusBuffer = ptrStatusByte.getPointer();
struOutput.dwStatusSize = HCNetSDK.ISAPI_STATUS_LEN;
struOutput.write();
if (hCNetSDK.NET_DVR_STDXMLConfig(userID, struInput, struOutput)) {
String xmlStr = struOutput.lpOutBuffer.getString(0);
// dom4j解析xml
try {
Document document;
document = DocumentHelper.parseText(xmlStr);
Element FDLibInfoList = document.getRootElement();
// 同时迭代当前节点下面的所有子节点
Iterator<Element> iterator = FDLibInfoList.elementIterator();
Element FDLibInfo = iterator.next();
Iterator<Element> iterator2 = FDLibInfo.elementIterator();
while (iterator2.hasNext()) {
Element e = iterator2.next();
if (e.getName().equals("FDID")) {
String id = e.getText();
m_FDID = Integer.parseInt(id);
} catch (DocumentException e1) {
e1.printStackTrace();
return false;
return true;
// 获取根节点元素对象
} else {
int code = hCNetSDK.NET_DVR_GetLastError();
JOptionPane.showMessageDialog(null, "创建人脸库失败: " + code);
return false;
在创建人脸库的时候,接口说明看了很久一直没看懂,后来也是参考网上的Demo ,后来我个人的理解是,通过某一种请求,去请求一个路径,然后传入他所需要的 XML 格式的信息,来对摄像头进行操作。
建立长连接
创建完人脸库后,就需要进行上传图片的操作了,但是上传图片之前,我们需要建立长连接,这样我们才能进行图片的上传
* 建立长连接
* @param index
* @return
public boolean UploadFile(int index) {
// 返回true,说明支持人脸
HCNetSDK.NET_DVR_FACELIB_COND struInput = new HCNetSDK.NET_DVR_FACELIB_COND();
struInput.dwSize = struInput.size();
struInput.szFDID = String.valueOf(index).getBytes();
struInput.byConcurrent = 0;
struInput.byCover = 1;
struInput.byCustomFaceLibID = 0;
struInput.write();
Pointer lpInput = struInput.getPointer();
NativeLong ret = hCNetSDK.NET_DVR_UploadFile_V40(userID, HCNetSDK.IMPORT_DATA_TO_FACELIB, lpInput,
struInput.size(), null, null, 0);
if (ret.longValue() == -1) {
// JOptionPane.showMessageDialog(null, "上传图片文件失败: " + code);
return false;
} else {
m_lUploadHandle = ret;
return true;
上传人脸数据
现在开始正式进行人脸图片的上传,因为海康摄像头对人脸图片的要求较为苛刻,所以我们在上传的时候,需要对图片进行一些处理,我这里是通过另一个接口获取到人脸信息,然后下载到本地,在上传至摄像头,届时,你可以通过自己的实际情况来进行修改
* 上传人脸数据
public void UploadSend() {
Map<String, String> map = RWProperties.getURL();
String imgFilePath = null;
String resJson = sendPost(map.get("getFaceURL"), "db="+map.get("db"));
try {
JSONArray ja = new JSONArray(resJson);
for ( int i = ja.length() - 1; i > 0; i-- ) {
//String remark = "http://192.168.0.210:8080/FaceServer";
JSONObject object = ja.getJSONObject(i);
String xh = object.getString("xh");
String dw = object.getString("dw");
String remark = map.get("projectURL") + object.getString("remark");
String xm = object.getString("xm");
String updateDate = object.getString("update_date");
// 将图片转为 base 64
String base64 = NetImageToBase64(remark);
// base64 转为图片
imgFilePath = GenerateImage(base64);
// **********************
Map<String,String> rMap = new HashMap<String,String>();
rMap.put("name", xm + "-" + xh + "-" + dw);
rMap.put("bornTime", updateDate);
// rMap.put("sex", "女");
// rMap.put("province", "123");
// rMap.put("city", "福州市");
String xml = "<FaceAppendData>";
for (String item : rMap.keySet())
xml += "<" + item + ">";
xml += rMap.get(item);
xml += "</" + item + ">";
xml += "</FaceAppendData>";
// 将 xml 写入文件
String filePathtoXml = filePath + xmlName + ".xml";
File myFile = new File(filePathtoXml);
if (!myFile.exists()) {
myFile.createNewFile();
FileWriter resultFile = new FileWriter( filePathtoXml );
PrintWriter myFile1 = new PrintWriter( resultFile );
myFile1.println( xml );
resultFile.close();
// **********************
// parames.add(new BasicNameValuePair("name", "测试"));
// ******************************
Thread.sleep( 5000 );
FileInputStream picfile = null;
FileInputStream xmlfile = null;
int picdataLength = 0;
int xmldataLength = 0;
picfile = new FileInputStream( new File( imgFilePath ) );
xmlfile = new FileInputStream( new File( filePathtoXml ) );
picdataLength = picfile.available();
xmldataLength = xmlfile.available();
if ( picdataLength < 0 || xmldataLength < 0 ) {
return;
HCNetSDK.BYTE_ARRAY ptrpicByte = new HCNetSDK.BYTE_ARRAY( picdataLength );
HCNetSDK.BYTE_ARRAY ptrxmlByte = new HCNetSDK.BYTE_ARRAY( xmldataLength );
picfile.read( ptrpicByte.byValue );
xmlfile.read( ptrxmlByte.byValue );
ptrpicByte.write();
ptrxmlByte.write();
Thread.sleep( 5000 );
HCNetSDK.NET_DVR_SEND_PARAM_IN struSendParam = new HCNetSDK.NET_DVR_SEND_PARAM_IN();
struSendParam.pSendData = ptrpicByte.getPointer();
struSendParam.dwSendDataLen = picdataLength;
struSendParam.pSendAppendData = ptrxmlByte.getPointer();
struSendParam.dwSendAppendDataLen = xmldataLength;
if ( struSendParam.pSendData == null || struSendParam.pSendAppendData == null ||
struSendParam.dwSendDataLen == 0 || struSendParam.dwSendAppendDataLen == 0 ) {
return;
struSendParam.byPicType = 1;
struSendParam.dwPicMangeNo = 0;
struSendParam.write();
//Thread.sleep(1000);
NativeLong iRet = hCNetSDK.NET_DVR_UploadSend(m_lUploadHandle, struSendParam.getPointer(), null);
while (true) {
NativeLong uploadState = getUploadState();
if (uploadState.toString().equals("1")) {
System.out.println("上传成功");
// NET_DVR_GetUploadResult(m_lUploadHandle, HCNetSDK.IMPORT_DATA_TO_FACELIB, 12);
break;
} else if (uploadState.toString().equals("2")) {
System.out.println("正在上传");
} else if (uploadState.toString().equals("29")) {
System.out.println("图片未识别到目标");
break;
} else {
System.out.println("其他错误:" + uploadState);
hCNetSDK.NET_DVR_UploadClose(uploadState);
UploadFile(m_FDID);
break;
//Thread.sleep(1000);
HCNetSDK.NET_DVR_UPLOAD_FILE_RET struPicRet = new HCNetSDK.NET_DVR_UPLOAD_FILE_RET();
Pointer lpPic= struPicRet.getPointer();
struPicRet.write();
boolean bRet = hCNetSDK.NET_DVR_GetUploadResult(m_lUploadHandle, lpPic, struPicRet.size());
if (bRet) {
System.out.println("继续下一次上传");
if (iRet.longValue() < 0) {
System.out.println("NET_DVR_UploadSend fail,error=" + hCNetSDK.NET_DVR_GetLastError());
} else {
System.out.println("NET_DVR_UploadSend success");
System.out.println("dwSendDataLen =" + struSendParam.dwSendDataLen);
System.out.println("dwSendAppendDataLen =" + struSendParam.dwSendAppendDataLen);
//Thread.sleep(5000);
try {
picfile.close();
xmlfile.close();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e3) {
e3.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e1) {
e1.printStackTrace();
开启人脸比对功能
当一切准备就绪的时候,我们就可以开启人脸比对的功能了,因为如果不开启人脸比对的功能话,我们可能无法进行人脸比对。
这里我不得不说海康的坑,开启人脸比对功能,文档中居然没有详细说明,后来联系了技术支持才知道,需要自己进入摄像头的管理页面,手动开启或关闭一次,然后自己抓包,去寻找参数。
这里我就不讲抓包的做法了,毕竟我还是一个菜鸟,怕误人子弟,如果不会的话,可以去看看相关的博客,不过我还是贴上,我抓包的过程。
在抓包工具中找了很久,找到类似和人脸比对开关有关的东西 注意:这里是 put 请求
双击查看详情,找到一个可疑的连接,点击它
发现了开关人脸比对的 XML
当然我为了保险起见,全部都复制下来了。
* 开启人脸库的比对信息
* @param FDID
public void getFDLib(String FDID) {
HCNetSDK.NET_DVR_XML_CONFIG_INPUT struInput = new HCNetSDK.NET_DVR_XML_CONFIG_INPUT();
struInput.dwSize = struInput.size();
String str = "PUT /ISAPI/Intelligent/channels/" + 1 + "/faceContrast";
HCNetSDK.BYTE_ARRAY ptrUrl = new HCNetSDK.BYTE_ARRAY(HCNetSDK.BYTE_ARRAY_LEN);
System.arraycopy(str.getBytes(), 0, ptrUrl.byValue, 0, str.length());
ptrUrl.write();
struInput.lpRequestUrl = ptrUrl.getPointer();
struInput.dwRequestUrlLen = str.length();
String strInBuffer = new String("<FaceContrastList xmlns=\"http://www.hikvision.com/ver20/XMLSchema\" version=\"2.0\">" +
"<FaceContrast>" +
"<id>1</id>" +
"<enable>true</enable>" +
"<AttendanceSaveEnable>false</AttendanceSaveEnable>" +
"<faceContrastType>faceContrast</faceContrastType>" +
"<contrastFailureAlarmUpload>false</contrastFailureAlarmUpload>" +
"<QuickContrast>" +
"<enabled>false</enabled>" +
"<snapTime>5.000</snapTime>" +
"<threshold>70</threshold>" +
"<quickConfigMode>custom</quickConfigMode>" +
"<Custom>" +
"<timeOutMode>infinite</timeOutMode>" +
"<duplicateContrastMode>success</duplicateContrastMode>" +
"</Custom>" +
"</QuickContrast>" +
"<alarmStorageEnable>false</alarmStorageEnable>" +
"<mixedTargetDetectionWithFaceContrast>false</mixedTargetDetectionWithFaceContrast>" +
"</FaceContrast>" +
"</FaceContrastList>");
HCNetSDK.BYTE_ARRAY ptrByte = new HCNetSDK.BYTE_ARRAY(10 * HCNetSDK.BYTE_ARRAY_LEN);
ptrByte.byValue = strInBuffer.getBytes();
ptrByte.write();
struInput.lpInBuffer = ptrByte.getPointer();
struInput.dwInBufferSize = strInBuffer.length();
struInput.write();
HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT struOutput = new HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT();
struOutput.dwSize = struOutput.size();
HCNetSDK.BYTE_ARRAY ptrOutByte = new HCNetSDK.BYTE_ARRAY(HCNetSDK.ISAPI_DATA_LEN);
struOutput.lpOutBuffer = ptrOutByte.getPointer();
struOutput.dwOutBufferSize = HCNetSDK.ISAPI_DATA_LEN;
HCNetSDK.BYTE_ARRAY ptrStatusByte = new HCNetSDK.BYTE_ARRAY(HCNetSDK.ISAPI_STATUS_LEN);
struOutput.lpStatusBuffer = ptrStatusByte.getPointer();
struOutput.dwStatusSize = HCNetSDK.ISAPI_STATUS_LEN;
struOutput.write();
if (hCNetSDK.NET_DVR_STDXMLConfig(userID, struInput, struOutput)) {
System.out.println("true111");
} else {
System.out.println("false2222");
删除人脸库
没过一段时间,都需要对人脸库进行一次同步,尝试了很多办法,最后发现,还是这样最简单直接,索性就删除以前的人脸库。
注意: 在你删除人脸库后,摄像头会自动关闭人脸比对的功能,此时你要在进行上面开启人脸比对的操作,这样摄像头才能正常工作,当然我觉得更好的方法是,在让摄像头重启一次。
* 删除人脸库
* @param FDID
public void delFDLib (String FDID) {
HCNetSDK.NET_DVR_XML_CONFIG_INPUT struInput = new HCNetSDK.NET_DVR_XML_CONFIG_INPUT();
struInput.dwSize = struInput.size();
String str = "DELETE /ISAPI/Intelligent/FDLib/" + FDID + "\r\n";
HCNetSDK.BYTE_ARRAY ptrUrl = new HCNetSDK.BYTE_ARRAY(HCNetSDK.BYTE_ARRAY_LEN);
System.arraycopy(str.getBytes(), 0, ptrUrl.byValue, 0, str.length());
ptrUrl.write();
struInput.lpRequestUrl = ptrUrl.getPointer();
struInput.dwRequestUrlLen = str.length();
String strInBuffer = new String("<CreateFDLibList><CreateFDLib><id>1</id><name></name><thresholdValue>1</thresholdValue><customInfo /></CreateFDLib></CreateFDLibList>");
HCNetSDK.BYTE_ARRAY ptrByte = new HCNetSDK.BYTE_ARRAY(10 * HCNetSDK.BYTE_ARRAY_LEN);
ptrByte.byValue = strInBuffer.getBytes();
ptrByte.write();
struInput.lpInBuffer = ptrByte.getPointer();
struInput.dwInBufferSize = strInBuffer.length();
struInput.write();
HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT struOutput = new HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT();
struOutput.dwSize = struOutput.size();
HCNetSDK.BYTE_ARRAY ptrOutByte = new HCNetSDK.BYTE_ARRAY(HCNetSDK.ISAPI_DATA_LEN);
struOutput.lpOutBuffer = ptrOutByte.getPointer();
struOutput.dwOutBufferSize = HCNetSDK.ISAPI_DATA_LEN;
HCNetSDK.BYTE_ARRAY ptrStatusByte = new HCNetSDK.BYTE_ARRAY(HCNetSDK.ISAPI_STATUS_LEN);
struOutput.lpStatusBuffer = ptrStatusByte.getPointer();
struOutput.dwStatusSize = HCNetSDK.ISAPI_STATUS_LEN;
struOutput.write();
if (hCNetSDK.NET_DVR_STDXMLConfig(userID, struInput, struOutput)) {
System.out.println("true");
} else {
System.out.println("false");
作为刚入行没多久的人,自己的代码写的还比较烂,如果对各位造成困扰,还请见谅,日后一定会努力提升自己的代码功底,让自己的代码更加规范,完整代码已经上传 Github,需要完整代码的,请移步 https://github.com/mxr1994/shexiang 进行查看。