寫在最前
SDK版本:CH-HCNetSDKV6.1.6.45_build20210302_win64
參考文檔:海康SDK使用手冊_V6.1
對接測試設備型號:DS-K1T671M
設備序列號:E50247795
業務目標
使用門禁設備實現對人臉的抓拍,將抓拍的人臉與其對應的數據進行上傳。
業務流程圖:
業務流程節點解釋:
1.初始化SDK(NET_DVR_Init):進行海康提供開發庫的載入,使用海康官方提供的文件庫,進入之后,修改載入路徑就可以了。
2.設置報警回調函數(NET_DVR_SetDVRMessageCallBack_V31):初始完SDK之后,進行報警回調函數的設置,當設備進行人臉抓拍之后,上傳報警信息到SDK,觸發回調函數進行內部業務邏輯處理。對於(門禁設備)人臉偵測,回調函數中的報警類型(lCommand)為COMM_ALARM_ACS,,報警信息(pAlarmInfo)對應結構體:NET_DVR_ACS_ALARM_INFO。
3.用戶注冊(NET_DVR_Login_V40):填寫設備對應的設備參數,進行設備的注冊,注冊成功會返回一個lUserID,使用這個lUserID進行下面一系列的操作。
4.獲取設備能力集(NET_DVR_GetDeviceAbility):能力集類型DEVICE_ABILITY_INFO,獲取智能通道分析能力集可以判斷設備是否支持相關功能。(可選功能)
5.設置人臉抓拍參數(NET_DVR_SetDVRConfig) (可選功能)
6.獲取人臉抓拍參數(NET_DVR_GetDVRConfig) (可選功能)
7.報警布防(NET_DVR_SetupAlarmChan_v41):布防即建立設備跟客戶端之間報警上傳的連接通道,這樣設備發生報警之后通過該連接上傳報警信息,SDK在報警回調函數中接收和處理報警信息數據即可。如果設備同時支持人臉偵測和人臉抓拍方式,調用該接口時,NET_DVR_SETUPALARM_PARAM布防參數中byFaceAlarmDetection賦值為0即選擇設備上傳的報警信息類型為人臉抓拍類型。
注意:在報警布防中需要設置連接的參數,設置不對或沒有設置會提示連接設備失敗。
8.報警回調函數里面接收和處理數據:報警類型:COMM_ALARM_ACS,報警信息結構體:NET_DVR_ACS_ALARM_INFO。對設備上傳來的數據信息進行接收
9.報警撤防(NET_DVR_CloseAlarmChan_v30)
10.注銷用戶(NET_DVR_logout)
11.釋放SDK資源(NET_DVR_Cleanup):關閉連接通道,釋放資源。
代碼示例
1.首先根據你需要開發的系統去海康官網下載對應的程序包。
比如我的win64
2.創建好springboot項目,將這個程序包里面的庫文件引進去。
3.將程序包里面它提供的 HCNetSDK.java 復制到你的項目里面,並修改你剛才放的庫文件路徑,注意以.dll結尾
4.接下來就是寫 demo 測試連接
package com.example.testsdk; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; /** * @author LH * @date 2021/11/29 10:37 */ public class startHCNetAlarm { private static final Logger LOGGER = LoggerFactory.getLogger(startHCNetAlarm.class); // 載入sdk庫文件 static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE; public static void main(String[] args) throws IOException { HCNetAlarm hcNetAlarm = new HCNetAlarm(); // 資源初始化 int row = hcNetAlarm.initDevice(); if (row == 1) { LOGGER.info("初始化失敗"); } // 設置連接超時時間與重連功能 hCNetSDK.NET_DVR_SetConnectTime(2000, 1); hCNetSDK.NET_DVR_SetReconnect(10000, true); // 設備注冊,注冊成功返回一個唯一標識符 lUserID,根據這個進行設備的其它操作 int luserID = hcNetAlarm.deviceRegister(-1, "填你設備的ip地址", "設備用戶名", "設備密碼", "設備端口,一般默認8000"); System.out.println(luserID); // 設置報警回調函數,建立報警上傳通道(啟用布防) int lAlarmHandle = hcNetAlarm.setupAlarmChan(luserID, -1); // 檢查設備狀態(是否在線),打印設備信息 hcNetAlarm.onlineState(luserID); // 設備抓拍功能, // hcNetAlarm.getDVRPic(luserID); try { // 等待設備上傳報警信息 LOGGER.info("等待設備上傳報警信息===================="); Thread.sleep(100 * 60 * 60); } catch (InterruptedException e) { e.printStackTrace(); } // 撤銷布防上傳通道 hcNetAlarm.closeAlarmChan(lAlarmHandle); // 注銷 釋放sdk資源 hcNetAlarm.logout(luserID); System.out.println("====== 設備注銷 ======"); } }
package com.example.testsdk; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; import com.sun.jna.ptr.IntByReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; /** * @author LH * @date 2021/11/29 9:06 */ public class HCNetAlarm { private static final Logger LOGGER = LoggerFactory.getLogger(HCNetAlarm.class); // 載入sdk庫文件 static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE; // 設備登錄信息 HCNetSDK.NET_DVR_USER_LOGIN_INFO m_strLoginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO(); // 設備信息 HCNetSDK.NET_DVR_DEVICEINFO_V40 m_strDeviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40(); // 已登錄設備的IP String m_sDeviceIP; // 設備用戶名 String m_sUsername; // 設備密碼 String m_sPassword; // 報警回調函數實現 public static HCNetSDK.FMSGCallBack_V31 fMSFCallBack_V31; /** * sdk初始化 * * @return */ public int initDevice() { if (!hCNetSDK.NET_DVR_Init()) { // sdk初始化失敗 return 1; } return 0; } /** * 注銷 * * @param lUserID 設備注冊成功唯一標識符 */ public void logout(int lUserID) { // 注銷 hCNetSDK.NET_DVR_Logout(lUserID); // 釋放sdk資源 hCNetSDK.NET_DVR_Cleanup(); } /** * 設備注冊 * * @param ip 設備ip * @param name 設備名 * @param password 設備密碼 */ public int deviceRegister(int lUserID, String ip, String name, String password, String port) { // 設備注冊之前先進行判斷,注銷已注冊的設備 if (lUserID > -1) { // 先注銷 hCNetSDK.NET_DVR_Logout(lUserID); lUserID = -1; } // ip地址 m_sDeviceIP = ip; m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN]; System.arraycopy(m_sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, m_sDeviceIP.length()); // 設備用戶名 m_sUsername = name; m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN]; System.arraycopy(m_sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, m_sUsername.length()); // 設備密碼 m_sPassword = password; m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN]; System.arraycopy(m_sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, m_sPassword.length()); m_strLoginInfo.wPort = (short) Integer.parseInt(port); // 是否異步登錄:0 - 否,1 - 是 m_strLoginInfo.bUseAsynLogin = false; m_strLoginInfo.write(); // 設備注冊調用 NET_DVR_Login_V40,注冊成功得到唯一標識符 lUserID // 設備注冊失敗,調用 NET_DVR_GetLastError,根據錯誤號判斷錯誤類型 lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo); if (lUserID == -1) { LOGGER.info("設備注冊失敗,錯誤號:", hCNetSDK.NET_DVR_GetLastError()); return -1; } else { LOGGER.info("設備注冊成功"); return lUserID; } } /** * 設置報警信息回調函數,根據上傳的數據進行回調觸發 */ public class FMSGCallBack_V31 implements HCNetSDK.FMSGCallBack_V31 { // lCommand 上傳消息類型,這個是設備上傳的數據類型,比如現在測試的門禁設備,回傳回來的是 COMM_ALARM_ACS = 0x5002; 門禁主機報警信息 // pAlarmer 報警設備信息 // pAlarmInfo 報警信息 根據 lCommand 來選擇接收的報警信息數據結構 // dwBufLen 報警信息緩存大小 // pUser 用戶數據 @Override public boolean invoke(int lCommand, HCNetSDK.NET_DVR_ALARMER pAlarmer, Pointer pAlarmInfo, int dwBufLen, Pointer pUser) { alarmDataHandle(lCommand, pAlarmer, pAlarmInfo, dwBufLen, pUser); return true; } } /** * 建立布防上傳通道,用於傳輸數據 * * @param lUserID 唯一標識符 * @param lAlarmHandle 報警處理器 */ public int setupAlarmChan(int lUserID, int lAlarmHandle) { // 根據設備注冊生成的lUserID建立布防的上傳通道,即數據的上傳通道 if (lUserID == -1) { LOGGER.info("請先注冊"); return lUserID; } if (lAlarmHandle < 0) { // 設備尚未布防,需要先進行布防 if (fMSFCallBack_V31 == null) { fMSFCallBack_V31 = new FMSGCallBack_V31(); Pointer pUser = null; if (!hCNetSDK.NET_DVR_SetDVRMessageCallBack_V31(fMSFCallBack_V31, pUser)) { LOGGER.info("設置回調函數失敗!", hCNetSDK.NET_DVR_GetLastError()); } } // 這里需要對設備進行相應的參數設置,不設置或設置錯誤都會導致設備注冊失敗 HCNetSDK.NET_DVR_SETUPALARM_PARAM m_strAlarmInfo = new HCNetSDK.NET_DVR_SETUPALARM_PARAM(); m_strAlarmInfo.dwSize = m_strAlarmInfo.size(); // 智能交通布防優先級:0 - 一等級(高),1 - 二等級(中),2 - 三等級(低) m_strAlarmInfo.byLevel = 1; // 智能交通報警信息上傳類型:0 - 老報警信息(NET_DVR_PLATE_RESULT), 1 - 新報警信息(NET_ITS_PLATE_RESULT) m_strAlarmInfo.byAlarmInfoType = 1; // 布防類型(僅針對門禁主機、人證設備):0 - 客戶端布防(會斷網續傳),1 - 實時布防(只上傳實時數據) m_strAlarmInfo.byDeployType = 1; // 抓拍,這個類型要設置為 0 ,最重要的一點設置 m_strAlarmInfo.byFaceAlarmDetection = 0; m_strAlarmInfo.write(); // 布防成功,返回布防成功的數據傳輸通道號 lAlarmHandle = hCNetSDK.NET_DVR_SetupAlarmChan_V41(lUserID, m_strAlarmInfo); if (lAlarmHandle == -1) { LOGGER.info("設備布防失敗,錯誤碼=========={}", hCNetSDK.NET_DVR_GetLastError()); // 注銷 釋放sdk資源 logout(lUserID); return lAlarmHandle; } else { LOGGER.info("設備布防成功"); return lAlarmHandle; } } return lAlarmHandle; } /** * 報警撤防 * * @param lAlarmHandle 報警處理器 */ public int closeAlarmChan(int lAlarmHandle) { if (lAlarmHandle > -1) { if (hCNetSDK.NET_DVR_CloseAlarmChan_V30(lAlarmHandle)) { LOGGER.info("撤防成功"); lAlarmHandle = -1; return lAlarmHandle; } return lAlarmHandle; } return lAlarmHandle; } /** * 接收設備上傳的報警信息,進行上傳數據的業務邏輯處理 * * @param lCommand 上傳消息類型 * @param pAlarmer 報警設備信息 * @param pAlarmInfo 報警信息 * @param dwBufLen 報警信息緩存大小 * @param pUser 用戶數據 */ public void alarmDataHandle(int lCommand, HCNetSDK.NET_DVR_ALARMER pAlarmer, Pointer pAlarmInfo, int dwBufLen, Pointer pUser) { System.out.println("報警監聽中================================"); System.out.println(pAlarmInfo); String sAlarmType = new String(); String[] newRow = new String[3]; //報警時間 Date today = new Date(); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); String[] sIP = new String[2]; sAlarmType = new String("lCommand=0x") + Integer.toHexString(lCommand); // lCommand是傳的報警類型 switch (lCommand) { // 攝像頭實時人臉抓拍上傳 case HCNetSDK.COMM_UPLOAD_FACESNAP_RESULT: // 分配存儲空間 HCNetSDK.NET_VCA_FACESNAP_RESULT strFaceSnapInfo = new HCNetSDK.NET_VCA_FACESNAP_RESULT(); strFaceSnapInfo.write(); Pointer pFaceSnapInfo = strFaceSnapInfo.getPointer(); // 寫入傳入數據 pFaceSnapInfo.write(0, pAlarmInfo.getByteArray(0, strFaceSnapInfo.size()), 0, strFaceSnapInfo.size()); strFaceSnapInfo.read(); sAlarmType = sAlarmType + ":人臉抓拍上傳[人臉評分:" + strFaceSnapInfo.dwFaceScore + ",年齡:" + strFaceSnapInfo.struFeature.byAge + ",性別:" + strFaceSnapInfo.struFeature.bySex + "]"; newRow[0] = dateFormat.format(today); // 報警類型 newRow[1] = sAlarmType; // 報警設備IP地址 sIP = new String(strFaceSnapInfo.struDevInfo.struDevIP.sIpV4).split("\0", 2); newRow[2] = sIP[0]; LOGGER.info("人臉抓拍========{}", Arrays.toString(newRow)); // 設置日期格式 SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); // new Date()為獲取當前系統時間 String time = df.format(new Date()); // 人臉圖片寫文件 File file = new File(System.getProperty("user.dir") + "\\pic1\\"); if (!file.exists()) { file.mkdir(); } try { FileOutputStream big = new FileOutputStream(System.getProperty("user.dir") + "\\pic1\\" + time + "background.jpg"); if (strFaceSnapInfo.dwFacePicLen > 0) { if (strFaceSnapInfo.dwFacePicLen > 0) { try { big.write(strFaceSnapInfo.pBuffer2.getByteArray(0, strFaceSnapInfo.dwBackgroundPicLen), 0, strFaceSnapInfo.dwBackgroundPicLen); big.close(); } catch (IOException e) { e.printStackTrace(); } } } } catch (FileNotFoundException e) { e.printStackTrace(); } break; // 門禁主機類型實時人臉抓拍上傳,走這里 case HCNetSDK.COMM_ALARM_ACS: // 分配存儲空間 System.out.println("============ 這是門禁主機的報警信息 ============"); HCNetSDK.NET_DVR_ACS_ALARM_INFO strFaceSnapInfo1 = new HCNetSDK.NET_DVR_ACS_ALARM_INFO(); strFaceSnapInfo1.write(); Pointer pFaceSnapInfo1 = strFaceSnapInfo1.getPointer(); // 寫入傳入數據 pFaceSnapInfo1.write(0, pAlarmInfo.getByteArray(0, strFaceSnapInfo1.size()), 0, strFaceSnapInfo1.size()); strFaceSnapInfo1.read(); // 設置日期格式 SimpleDateFormat df1 = new SimpleDateFormat("yyyyMMddHHmmss"); // new Date()為獲取當前系統時間 String time1 = df1.format(new Date()); // 人臉圖片寫文件 File file1 = new File(System.getProperty("user.dir") + "\\pic3\\"); if (!file1.exists()) { file1.mkdir(); } try { FileOutputStream big = new FileOutputStream(System.getProperty("user.dir") + "\\pic3\\" + time1 + ".jpg"); if (strFaceSnapInfo1.dwPicDataLen > 0) { System.out.println("========== 圖片有數據 ========"); if (strFaceSnapInfo1.dwPicDataLen > 0) { try { System.out.println("============ 圖片上傳成功 ============="); big.write(strFaceSnapInfo1.pPicData.getByteArray(0, strFaceSnapInfo1.dwPicDataLen), 0, strFaceSnapInfo1.dwPicDataLen); big.close(); System.out.println("設備唯一編碼=================" + strFaceSnapInfo1.struAcsEventInfo.byDeviceNo); System.out.println("數據采集時間=================" + strFaceSnapInfo1.struTime.dwYear + strFaceSnapInfo1.struTime.dwMonth + strFaceSnapInfo1.struTime.dwDay + strFaceSnapInfo1.struTime.dwHour + strFaceSnapInfo1.struTime.dwMinute + strFaceSnapInfo1.struTime.dwSecond); System.out.println("人員工號=================" + strFaceSnapInfo1.struAcsEventInfo.dwEmployeeNo); System.out.println("人員姓名=================" + strFaceSnapInfo1.sNetUser); System.out.println("通進類型(0:入場,1:離場)=================" + strFaceSnapInfo1.struAcsEventInfo.dwDoorNo); System.out.println("圖片唯一標識(工號加時間)=================" + strFaceSnapInfo1.struAcsEventInfo.dwEmployeeNo + time1 + ".jpg"); System.out.println("人員類型(0:白名單,1:訪客,2:黑名單)=================" + strFaceSnapInfo1.struAcsEventInfo.byCardType); } catch (IOException e) { e.printStackTrace(); } } } } catch (FileNotFoundException e) { e.printStackTrace(); } break; default: newRow[0] = dateFormat.format(today); // 報警類型 newRow[1] = sAlarmType; // 報警設備IP地址 sIP = new String(pAlarmer.sDeviceIP).split("\0", 2); newRow[2] = sIP[0]; LOGGER.info("其他報警信息=========={}", Arrays.toString(newRow)); break; } } // 抓拍圖片 public static void getDVRPic(int userId) throws IOException { // 設置通道號,其中 1 正常,-1不正常 NativeLong chanLong = new NativeLong(1); // 返回Boolean值,判斷是否獲取設備能力 HCNetSDK.NET_DVR_WORKSTATE_V30 devwork = new HCNetSDK.NET_DVR_WORKSTATE_V30(); if (!hCNetSDK.NET_DVR_GetDVRWorkState_V30(userId, devwork)) { System.out.println("返回設備狀態失敗"); } // JPEG圖像信息結構體 HCNetSDK.NET_DVR_JPEGPARA jpeg = new HCNetSDK.NET_DVR_JPEGPARA(); jpeg.wPicSize = 2; // 設置圖片的分辨率 jpeg.wPicQuality = 2; // 設置圖片質量 IntByReference a = new IntByReference(); SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss"); Date date = new Date(); int random = (int) (Math.random() * 1000); String fileNameString = sdf.format(date) + random + ".jpg"; // 設置字節緩存 ByteBuffer jpegBuffer = ByteBuffer.allocate(1024 * 1024); // 抓圖到文件 boolean is = hCNetSDK.NET_DVR_CaptureJPEGPicture(userId, chanLong.intValue(), jpeg, fileNameString); if (is) { System.out.println("圖片抓取成功,返回長度:" + a.getValue()); } else { System.out.println("圖片抓取失敗:" + hCNetSDK.NET_DVR_GetLastError()); } } /** * 設備狀態,是否在線,打印設備信息 */ public Boolean onlineState(int lUserID) { HCNetAlarm hcNetAlarm = new HCNetAlarm(); int row = hcNetAlarm.initDevice(); if (row == 1) { LOGGER.info("初始化失敗"); } // 檢查設備在線狀態 LOGGER.info("設備信息========={}", hcNetAlarm.m_strDeviceInfo.struDeviceV30); boolean isOnLine = hCNetSDK.NET_DVR_RemoteControl(lUserID, 20005, null, 0); LOGGER.info("checkDeviceOnLine---isOnLine============{}", isOnLine); return isOnLine; } }
寫在結尾
遇到的問題:無法上傳圖片。(官方文檔是個坑)
可能原因:剛開始以為是設備不支持抓拍功能。
解決方式:一遍一遍地閱讀官方文檔,換了一個又一個接口,最后發現,官方文檔上提示的抓拍功能流程圖是基於海康攝像頭的,但是我使用的設備是海康的門禁設備,兩者雖然大體相似,但是還是有不同之處,對於不同的設備需要進行不同的判斷。
如這次使用的設備是門禁設備,首先根據觸發回調返回的lCommand 進行設備區分,本次測試返回的 lCommand = 0x5002, 即門禁主機報警信息,然后去官方文檔上查看對應的sdk接收信息體,為NET_DVR_ACS_ALARM_INFO。這樣才能正確接收設備傳過來的數據,也能得到上傳的圖片及其對應的人員信息。