目录
背景说明:
使用NVR网络录像机接入监控摄像头,NVR内置人脸库、人脸抓拍、比对功能、事件配置。
使用的设备型号:DS-9632NX-I8R/VPro
1、初始化配置
1.1、设置通道默认密码
1.2、添加摄像头
NVR具有自动广播功能,可以在下方的设备列表中显示所有摄像头。
然后选中添加上上方列表,开启监控。
注意勾选默认密码,否则会引起连接失败问题
1.3、设置不采集时间段
下图灰白区域表示摄像头不采集、不启用事件的事件范围,
选择一个摄像头进行配置
批量复制到其他监控
1.4、抓拍延迟设置
此处设置的开头、结尾延迟5秒,为后续的人物目标识别成功后,截取视频时间段前后五秒,共10秒的视频。默认延迟30s
1.5、录像保存时长设置
1.6、人脸库维护
1.7、导入照片
由于nvr限定jpg、jpeg类型,不支持png,所以需要进行转码
具体参见 Java工具-实现无损png转换jpg格式-CSDN博客
1.8、设置事件
1.8.1、引擎配置
必须要设置目标识别,否则人脸库无法建模,后续人脸目标比对无法进行。
1.8.2、事件设置
选择目标事件,确定必须启用
1.8.2.1、目标比对
选择目标比对,开启,
1.8.2.2、设置屏蔽区
点击右键绘图完毕
1.8.2.3、人脸库
关联人脸库和阈值设置
如果需要提高图片质量,可以设置仰俯角,避免低头、偏头图片
1.9、事件检索
在下图,设置检索条件
2、ISAPI透传对接
2.1、下载SDK包
地址:海康开放平台
下载对应操作系统的开发包
2.2、检查是否支持ISAPI
注意检查默认登录端口8000
选择内部的ISAPI透传的demo
2.3、引入依赖
将对应的库文件引入到项目的lib下
- Windows开发时需要
将"库文件"文件夹中的HCNetSDK.dll、HCCore.dll、HCNetSDKCom文件夹、
libssl-1_1-x64.dll、libcrypto-1_1-x64.dll、hlog.dll、hpr.dll、zlib1.dll等文件拷贝到lib文件夹下,
HCNetSDKCom文件夹(包含里面的功能组件dll库文件)需要和HCNetSDK.dll、HCCore.dll一起加载,放在同一个目录下,
且HCNetSDKCom文件夹名不能修改。如果自行开发软件不能正常实现相应功能,而且程序没有指定加载的dll库路径,
请在程序运行的情况下尝试删除HCNetSDK.dll。如果可以删除,说明程序可能调用到系统盘Windows->System32目录下的dll文件,
建议删除或者更新该目录下的相关dll文件;如果不能删除,dll文件右键选择属性确认SDK库版本。
- Linux开发时需要将"库文件"文件夹中libhcnetsdk.so、libHCCore.so、libcrypto.so.1.1、libssl.so.1.1、libhpr.so、libz.so
等文件拷贝到lib文件夹下。HCNetSDKCom文件夹(包含里面的功能组件dll库文件)需要和libhcnetsdk.so、libHCCore.so
一起加载,放在同一个目录下,且HCNetSDKCom文件夹名不能修改。如果库文件加载有问题,初始化失败,
也可以尝试将SDK所在路径添加到LD_LIBRARY_PATH环境变量中。
2.4、启动demo
启动类 JavaDemoSTDXMLConfigApp
启动后效果,使用这个demo可以进行api调试。
后续可以参照平台的接口出入参进行对接开发
3、java端接入
3.1、项目结构
win下存放dll文件,需要额外引入 sdk中的examples.jar、jna.jar文件
其中 HIKSDKStructure 类用于解决兼容低版本的Structure调用问题
import com.sun.jna.Structure;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
/***
* 由于 这个问题是因为海康jna.jar比较老,结构体定义没有getFiledOrder,可创建一个类继承 Structure
* 产生错误:Structure.getFieldOrder() on class com.xxx.sdk.HCNetSDK$NET_DVR_DEVICEINFO_V30 does not provide enough names [0] ([]) to match declared fields [31] ([byAlarmInPortNum,
* 解决:对 HCNetSDK 接口中的静态类里面,所有继承 Structure 替换为 HIKSDKStructure 即可
* @author xuancg
* @date 2024/6/13
*/
public class HIKSDKStructure extends Structure {
protected List<String> getFieldOrder(){
List<String> fieldOrderList = new ArrayList<String>();
for (Class<?> cls = getClass();
!cls.equals(HIKSDKStructure.class);
cls = cls.getSuperclass()) {
Field[] fields = cls.getDeclaredFields();
int modifiers;
for (Field field : fields) {
modifiers = field.getModifiers();
if (Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
continue;
}
fieldOrderList.add(field.getName());
}
}
return fieldOrderList;
}
}
HCNetSDK 类可以通过sdk中查找引入。但需要将所有的Structure 替换为HIKSDKStructure
public class OsSelect {
public static boolean isLinux() {
return System.getProperty("os.name").toLowerCase().contains("linux");
}
public static boolean isWindows() {
return System.getProperty("os.name").toLowerCase().contains("windows");
}
}
3.2、类属性定义
static HCNetSDK hCNetSDK = null;
static int lUserID;//用户句柄
private static String rootPath;
public static final int ISAPI_DATA_LEN = 1024*1024;
public static final int ISAPI_STATUS_LEN = 4*4096;
public static final int BYTE_ARRAY_LEN = 1024;
3.3、初始化
public static boolean init(){
if(null == rootPath){
rootPath = System.getProperty("user.dir");
//由于我是多模块下的,此处路径存在问题,实际最好通过外部配置定义
rootPath += "/my-project";
}
log.info("加载lib路径=" + rootPath);
if (hCNetSDK == null) {
if (!createSDKInstance()) {
System.out.println("Load SDK fail");
return false;
}
}
//linux系统建议调用以下接口加载组件库
if (OsSelect.isLinux()) {
HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);
HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);
// TODO 需要下载对应linux版本 并替换
//这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限
String strPath1 = rootPath + "/libs/libcrypto.so.1.1";
String strPath2 = rootPath + "/libs/libssl.so.1.1";
System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length());
ptrByteArray1.write();
hCNetSDK.NET_DVR_SetSDKInitCfg(3, ptrByteArray1.getPointer());
System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length());
ptrByteArray2.write();
hCNetSDK.NET_DVR_SetSDKInitCfg(4, ptrByteArray2.getPointer());
String strPathCom = rootPath + "/libs/";
HCNetSDK.NET_DVR_LOCAL_SDK_PATH struComPath = new HCNetSDK.NET_DVR_LOCAL_SDK_PATH();
System.arraycopy(strPathCom.getBytes(), 0, struComPath.sPath, 0, strPathCom.length());
struComPath.write();
hCNetSDK.NET_DVR_SetSDKInitCfg(2, struComPath.getPointer());
}
boolean initSuc = hCNetSDK.NET_DVR_Init();
if (initSuc != true)
{
log.info("初始化失败");
}
/**加载日志*/
return hCNetSDK.NET_DVR_SetLogToFile(3, "./sdklog", false);
}
private static boolean createSDKInstance() {
if (hCNetSDK == null) {
synchronized (HCNetSDK.class) {
String strDllPath = "";
try {
if (OsSelect.isWindows())
//win系统加载SDK库路径
strDllPath = rootPath + "\\libs\\win\\HCNetSDK.dll";
else if (OsSelect.isLinux())
//Linux系统加载SDK库路径
strDllPath = rootPath + "/libs/linux/libhcnetsdk.so";
hCNetSDK = (HCNetSDK) Native.loadLibrary(strDllPath, HCNetSDK.class);
} catch (Exception ex) {
System.out.println("loadLibrary: " + strDllPath + " Error: " + ex.getMessage());
return false;
}
}
}
return true;
}
3.4、登录
/**
* 登录,端口默认8000,成功后会将其他终端登录下线
* @param sDeviceIP ip地址
* @param sUsername 登录名
* @param sPassword 密码
*/
public static boolean loginByV40(String sDeviceIP,String sUsername,String sPassword){
//注册之前先注销已注册的用户,预览情况下不可注销
if (lUserID > -1) {
//先注销
hCNetSDK.NET_DVR_Logout(lUserID);
lUserID = -1;
}
//注册
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();//设备信息
m_strLoginInfo.sDeviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
System.arraycopy(sDeviceIP.getBytes(), 0, m_strLoginInfo.sDeviceAddress, 0, sDeviceIP.length());
m_strLoginInfo.sUserName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
System.arraycopy(sUsername.getBytes(), 0, m_strLoginInfo.sUserName, 0, sUsername.length());
m_strLoginInfo.sPassword = new byte[HCNetSDK.NET_DVR_LOGIN_PASSWD_MAX_LEN];
System.arraycopy(sPassword.getBytes(), 0, m_strLoginInfo.sPassword, 0, sPassword.length());
m_strLoginInfo.wPort = 8000;
// 此处只能设置0,否则errcode=9
m_strLoginInfo.byLoginMode = 0;
//登录模式(不同模式具体含义详见"Remarks"说明):0- SDK私有协议,1- ISAPI协议,2- 自适应(设备支持协议类型未知时使用,一般不建议)
m_strLoginInfo.byHttps = 0;
//ISAPI协议登录时是否启用HTTPS(byLoginMode为1时有效):0- 不启用,1- 启用,2- 自适应(设备支持协议类型未知时使用,一般不建议)
m_strLoginInfo.bUseAsynLogin = false; //是否异步登录:0- 否,1- 是
m_strLoginInfo.write();
lUserID = hCNetSDK.NET_DVR_Login_V40(m_strLoginInfo, m_strDeviceInfo);
if (lUserID == -1) {
// 9-从设备接收数据失败。
log.info("注册失败,错误号:" + hCNetSDK.NET_DVR_GetLastError());
} else {
log.info("注册成功,lUserID=" + lUserID);
}
return true;
}
// 登出
public void logout() {
//注销
if (lUserID > -1) {
//先注销
hCNetSDK.NET_DVR_Logout(lUserID);
lUserID = -1;
}
hCNetSDK.NET_DVR_Cleanup();
}
3.5、发起ISAPI调用
/**
* 事件搜索
* @param req
* @return
*/
public EventRecordSearchResp eventSearchApi(EventRecordSearchReq req) {
HCNetSDK.NET_DVR_XML_CONFIG_INPUT struXMLInput = new HCNetSDK.NET_DVR_XML_CONFIG_INPUT();
struXMLInput.read();
struXMLInput.dwSize = struXMLInput.size();
String strURL = "POST /ISAPI/ContentMgmt/eventRecordSearch?format=json";
int iURLlen = strURL.length();
HCNetSDK.BYTE_ARRAY ptrUrl = new HCNetSDK.BYTE_ARRAY(iURLlen+1);
System.arraycopy(strURL.getBytes(), 0, ptrUrl.byValue, 0, strURL.length());
ptrUrl.write();
struXMLInput.lpRequestUrl = ptrUrl.getPointer();
struXMLInput.dwRequestUrlLen = iURLlen;
String strInbuffer = JSONUtil.toJsonStr(req);
int iInBufLen = strInbuffer.length();
if(iInBufLen==0)
{
struXMLInput.lpInBuffer=null;
struXMLInput.dwInBufferSize=0;
struXMLInput.write();
}
else
{
HCNetSDK.BYTE_ARRAY ptrInBuffer = new HCNetSDK.BYTE_ARRAY(iInBufLen+1);
ptrInBuffer.read();
ptrInBuffer.byValue = strInbuffer.getBytes();
ptrInBuffer.write();
struXMLInput.lpInBuffer = ptrInBuffer.getPointer();
struXMLInput.dwInBufferSize = iInBufLen;
struXMLInput.write();
}
HCNetSDK.BYTE_ARRAY ptrStatusByte = new HCNetSDK.BYTE_ARRAY(ISAPI_STATUS_LEN);
ptrStatusByte.read();
HCNetSDK.BYTE_ARRAY ptrOutByte = new HCNetSDK.BYTE_ARRAY(ISAPI_DATA_LEN);
ptrOutByte.read();
HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT struXMLOutput = new HCNetSDK.NET_DVR_XML_CONFIG_OUTPUT();
struXMLOutput.read();
struXMLOutput.dwSize = struXMLOutput.size();
struXMLOutput.lpOutBuffer = ptrOutByte.getPointer();
struXMLOutput.dwOutBufferSize = ptrOutByte.size();
struXMLOutput.lpStatusBuffer = ptrStatusByte.getPointer();
struXMLOutput.dwStatusSize = ptrStatusByte.size();
struXMLOutput.write();
if(!hCNetSDK.NET_DVR_STDXMLConfig(lUserID, struXMLInput, struXMLOutput))
{
int iErr = hCNetSDK.NET_DVR_GetLastError();
log.error( "NET_DVR_STDXMLConfig失败,错误号:" + iErr);
return null;
}
else
{
struXMLOutput.read();
ptrOutByte.read();
ptrStatusByte.read();
// 输出结果
String strOutXML = new String(ptrOutByte.byValue).trim();
// 输出状态
String strStatus = new String(ptrStatusByte.byValue).trim();
return JSONUtil.toBean(strOutXML, EventRecordSearchResp.class);
}
}
3.6、入参对象
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import java.util.UUID;
/***
* 事件搜索入参
* @author xuancg
* @date 2024/6/14
*/
@Data
public class EventRecordSearchReq {
private EventSearchDescription EventSearchDescription;
public void setChannels(int...channels){
this.EventSearchDescription.setChannels(channels);
}
public void setEventType(EventType type){
this.EventSearchDescription.setEventType(type.name());
}
public void setAlarmResult(AlarmResult type){
this.EventSearchDescription.setAlarmResultAuxEventType(type.name());
}
public void setPageNum(int pageNum){
this.EventSearchDescription.setMaxResults(pageNum);
}
/**
* 默认从1开始
* @param pageSize
*/
public void setPageSize(int pageSize){
int offset = (pageSize - 1) * this.EventSearchDescription.getMaxResults();
if(offset < 0){
offset = 0;
}
this.EventSearchDescription.setSearchResultPosition(offset);
}
@Data
public class EventSearchDescription{
public EventSearchDescription() {
this.searchID = UUID.randomUUID().toString();
}
// 随机生成 搜索记录唯一标识, range:[,], desc:用来确认上层客户端是否为同一个(倘若是同一个,则设备记录内存,下次搜索加快速度)
private String searchID;
private String eventType;
private String alarmResultAuxEventType;
private int[] channels;
// 每页个数
private int maxResults = 30;
// 起始位置相当于offset ,如果第二页则=30
private int searchResultPosition = 0;
private String type = "all";
}
@Data
public class timeSpanList {
// 格式 2024-06-14T23:59:59 08:00
private String endTime;
private String startTime;
}
/***
* 检索类型
*/
@Getter
@AllArgsConstructor
public enum EventType{
alarmResult("目标事件"),
;
private String desc;
}
@Getter
@AllArgsConstructor
public enum AlarmResult{
alarmResultSuccess("目标比对成功"),
;
private String desc;
}
}
3.7、出参对象
import lombok.Data;
import java.util.List;
/***
* 事件搜索出参
* 日期格式均为=2024-06-13T11:39:00+08:00 东八区
* @author xuancg
* @date 2024/6/14
*/
@Data
public class EventRecordSearchResp {
private EventSearchResult EventSearchResult;
@Data
public class EventSearchResult{
/**搜索结果数*/
private int numOfMatches;
/**是否有更多结果 MORE=是*/
private String responseStatusStrg;
/**总结果数*/
private int totalMatches;
private List<Targets> Targets;
}
@Data
public class Targets{
/**抓拍时间*/
private String alarmEndTime;
private AlarmResult alarmResult;
private String alarmResultAuxEventType;
/**2024-06-13T11:39:00+08:00*/
private String alarmStartTime;
private int channel;
private String eventType;
private int id;
private MetadataMatches metadataMatches;
/**背景图地址http*/
private String pictureUrl;
/**人脸范围照地址http*/
private String smallPictureUrl;
/**前置后置延迟时间,单位秒 */
private int postRecordTimeSeconds;
private int preRecordTimeSeconds;
/**视频采集开始时间 用于换取采集视频*/
private String startTime;
private String endTime;
/**等同channel*/
private int triggerChannel;
}
@Data
public class AlarmResult{
private String FDID;
/**人脸库名称*/
private String FDLibName;
/**人脸库照片地址,http开头*/
private String FDPicURL;
private String ageGroup;
private String gender;
/**人物姓名*/
private String name;
/**相似度,小于1*/
private float similarity;
}
@Data
public class MetadataMatches{
private List<AssociatedMetadata> associatedMetadataUrlList;
private String eventType;
}
@Data
public class AssociatedMetadata {
private AssociatedMetadataUrl associatedMetadataUrl;
}
@Data
public class AssociatedMetadataUrl {
private int channelID;
/**video*/
private String metadataType;
}
}
具体完整的代码见资源绑定