海康威視攝像頭人臉識別開發流程及踩過的坑


海康威視攝像頭人臉識別開發流程及踩過的坑

前段時間,接手了公司的海康攝像頭的二次開發的工作,在網上查了很多資料,但基本上只能解決其中一部分問題,還有很多問題,在不停糾纏海康技術支持后,才勉強搞定,在這里把自己的開發心得及踩過的坑寫下來,希望對大家有所幫助。

我的開發環境使用的是 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 進行查看。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM