海康威视摄像头人脸识别开发流程及踩过的坑

前段时间,接手了公司的海康摄像头的二次开发的工作,在网上查了很多资料,但基本上只能解决其中一部分问题,还有很多问题,在不停纠缠海康技术支持后,才勉强搞定,在这里把自己的开发心得及踩过的坑写下来,希望对大家有所帮助。

我的开发环境使用的是 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 进行查看。