海康威視攝像頭人臉識別開發流程及踩過的坑
前段時間,接手了公司的海康攝像頭的二次開發的工作,在網上查了很多資料,但基本上只能解決其中一部分問題,還有很多問題,在不停糾纏海康技術支持后,才勉強搞定,在這里把自己的開發心得及踩過的坑寫下來,希望對大家有所幫助。
我的開發環境使用的是 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 進行查看。
