SpringBoot中集成海康威视出入口抓拍实现多个摄像头布防与抓拍照片存储+普通摄像头手动抓拍图片并将素材存储进redis+mysql,附完整示例代码下载
场景
SpringBoot中集成海康威视SDK实现布防报警数据上传/交通违章图片上传并在linux上部署(附示例代码资源):
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/143018157
以上针对单个摄像头以布防的方式实现抓拍照片在磁盘上的存储。
如果针对多个出入口抓拍摄像头且需要在windows电脑上进行部署,需要将所有摄像头的
抓拍图片进行存储到本地磁盘,且存储车牌号数据到redis中,最终结合业务流程并进行手动抓拍
普通摄像头的照片文件,然后将车牌号数据以及映射磁盘路径图片为网络url之后的路径等数据进行
存储入库。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
实现
新建springboot项目,并引入相关依赖
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.badao
hkdemo
1.0
system
${basedir}/lib/examples.jar
com.badao
jna
1.0
system
${basedir}/lib/jna.jar
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-configuration-processor
true
com.alibaba
fastjson
${fastjson.version}
org.springframework.boot
spring-boot-dependencies
mysql
mysql-connector-java
com.baomidou
mybatis-plus-boot-starter
3.5.6
引入海康威视官网sdk
去海康威视官网下载对应的sdk包,在项目目录下新建lib目录,将所有的lib资源全放在此目录下,
也可直接将windows下所有的lib直接复制过来。
注意,为避免打包时无法将lib依赖打入到jar包中,在项目部署时需要在jar包所在的目录也新建lib目录,
然后将所有的资源文件复制过去

同步新建pic目录,用来存放出入口抓拍的图片
同步新建sdklog目录,用来存放海康sdk的日志
注意lib目录要将sdk中的lib文件复制全,不好区分的话,直接复制所有文件,示例文件目录如下:
添加多个摄像头配置读取
yml中关于redis以及mysql等相关配置省略,这里的多个摄像头使用yml文件中配置中读取。
yml中添加如下:
modbus:
task:
rate: "10000" # 定时任务执行周期
timeout: 3000 # 读取超时(ms)
# PLC寄存器配置
devices:
#1号洗车机
- ip: 192.168.1.101
port: 502
slaveId: 1
name: "洗车机#1"
registers:
runSignal:
address: "400153.0" # V152.0运行信号
type: "bool"
pressure:
address: "400201" # VD200运行压力
type: "float32"
# 绑定摄像头配置
cameras:
recognition: # 识别摄像头
ip: "192.168.1.35"
port: 8000
username: "admin"
password: "123456"
channel: 1 # 摄像头通道号
userId: 1 #摄像头登录之后的userId
capture: # 截图摄像头
ip: "192.168.1.37"
port: 8000
username: "admin"
password: "123456"
channel: 2
userId: 2 #摄像头登录之后的userId
#2号洗车机
- ip: 192.168.2.102
port: 502
slaveId: 2
name: "洗车机#2"
registers:
runSignal:
address: "400154.0" # V153.0运行信号
type: "bool"
pressure:
address: "400202" # VD201运行压力
type: "float32"
# 绑定摄像头配置
cameras:
recognition: # 识别摄像头
ip: "192.168.2.34"
port: 8000
username: "admin"
password: "123456"
channel: 1 # 摄像头通道号
userId: 3 #摄像头登录之后的userId
capture: # 截图摄像头
ip: "192.168.1.36"
port: 8000
username: "admin"
password: "123456"
channel: 2
userId: 4 #摄像头登录之后的userId
#3号洗车机
- ip: 192.168.1.103
port: 502
slaveId: 3
name: "洗车机#3"
registers:
runSignal:
address: "400154.0" # V153.0运行信号
type: "bool"
pressure:
address: "400202" # VD201运行压力
type: "float32"
# 绑定摄像头配置
cameras:
recognition: # 识别摄像头
ip: "192.168.1.40"
port: 8000
username: "admin"
password: "123456"
channel: 1 # 摄像头通道号
userId: 5 #摄像头登录之后的userId
capture: # 截图摄像头
ip: "192.168.1.38"
port: 8000
username: "admin"
password: "123456"
channel: 2
userId: 6 #摄像头登录之后的userId
#抓拍图片保存路径
hk:
pngPath: D:/hikvision_capture
如上配置类配置了每组洗车机分别对应两个摄像头的相关信息,一个是出入口抓拍摄像头,一个是洗车过程摄像头。
对应添加配置类:
@ConfigurationProperties(prefix = "modbus")
@Configuration
@Getter
@Setter
@Component
public class ModbusConfig {
private transient Map recognitionIpToDeviceMap;
@PostConstruct
public void initMapping() {
// 初始化IP映射关系
recognitionIpToDeviceMap = devices.stream()
.filter(device -> device.getCameras() != null)
.filter(device -> device.getCameras().getRecognition() != null)
.collect(Collectors.toMap(
device -> device.getCameras().getRecognition().getIp(),
Function.identity()
));
System.out.println("已初始化摄像头映射关系"+recognitionIpToDeviceMap);
}
/*通过识别摄像头IP查找设备配置
* @param recognitionIp 识别摄像头IP
* @return 对应的设备配置,未找到返回null
*/
public DeviceConfig findDeviceByRecognitionIp(String recognitionIp) {
return recognitionIpToDeviceMap.get(recognitionIp);
}
private TaskConfig task;
private List devices; // 改为List接收
@Data
public static class TaskConfig {
private String rate;
private int timeout;
}
@Data
public static class DeviceConfig {
private String ip; // 设备IP地址
private int port; // 端口号
private int slaveId; // 从站ID
private String name; // 设备名称
private Map registers; // 有序寄存器配置
// 摄像头绑定配置
private CameraBinding cameras;
@Data
public static class CameraBinding {
private CameraConfig recognition; // 识别摄像头
private CameraConfig capture; // 截图摄像头
}
@Data
public static class CameraConfig {
private String ip;
private int port;
private String username;
private String password;
private int channel; // 摄像头通道号
//摄像头登录后的userId
private int userId ;
}
}
@Data
public static class RegisterConfig {
private String address; // 寄存器地址(格式:400153.0 或 400201)
private String type; // 数据类型(bool/float32/int16等)
}
}
实现加载摄像头SDK并登录各个摄像头与布防流程
新建布防类使其实现ApplicationRunner类,从而实现在项目启动时加载SDK、登录多个摄像头、布防多个摄像头操作,并将登录布防后的用户句柄进行存储。
附关键部分代码:
@Component
public class BuFangAfterStart implements ApplicationRunner {
private static final Logger log = LoggerFactory.getLogger(AlarmDataParse.class);
public static HCNetSDK hCNetSDK = null;
public static int lUserID = -1;//用户句柄 实现对设备登录
static int lAlarmHandle =-1;//报警布防句柄
static int lListenHandle = -1;//报警监听句柄
static FMSGCallBack_V31 fMSFCallBack_V31 = null; //报警布防回调函数
static FMSGCallBack fMSFCallBack=null; //报警监听回调函数
//多摄像头布防时使用
//存储设备句柄
public static final Map alarmHandles = new ConcurrentHashMap<>();
public static final Map userHandles = new ConcurrentHashMap<>();
@Autowired
private ModbusConfig modbusConfig;
// 提供静态访问方法
public static Map getUserHandles() {
return userHandles;
}
/**
* 动态库加载
*
* @return
*/
private static boolean createSDKInstance() {
if (hCNetSDK == null) {
synchronized (HCNetSDK.class) {
String strDllPath = "";
try {
if (osSelect.isWindows())
//win系统加载库路径
strDllPath = System.getProperty("user.dir") + "\\lib\\HCNetSDK.dll";
else if (osSelect.isLinux())
//Linux系统加载库路径
strDllPath = System.getProperty("user.dir") + "/lib/libhcnetsdk.so";
hCNetSDK = (HCNetSDK) Native.loadLibrary(strDllPath, HCNetSDK.class);
} catch (Exception ex) {
System.out.println("loadLibrary: " + strDllPath + " Error: " + ex.getMessage());
return false;
}
}
}
return true;
}
/**
* 在程序启动后执行
* @param args
* @throws Exception
*/
@Override
public void run(ApplicationArguments args) {
try {
if (hCNetSDK == null) {
if (!createSDKInstance()) {
System.out.println("Load SDK fail");
log.error("Load SDK fail");
return;
}
}
log.info("Load SDK success");
//linux系统建议调用以下接口加载组件库
if (osSelect.isLinux()) {
HCNetSDK.BYTE_ARRAY ptrByteArray1 = new HCNetSDK.BYTE_ARRAY(256);
HCNetSDK.BYTE_ARRAY ptrByteArray2 = new HCNetSDK.BYTE_ARRAY(256);
//这里是库的绝对路径,请根据实际情况修改,注意改路径必须有访问权限
String strPath1 = System.getProperty("user.dir") + "/lib/libcrypto.so.1.1";
String strPath2 = System.getProperty("user.dir") + "/lib/libssl.so.1.1";
System.arraycopy(strPath1.getBytes(), 0, ptrByteArray1.byValue, 0, strPath1.length());
ptrByteArray1.write();
hCNetSDK.NET_DVR_SetSDKInitCfg(HCNetSDK.NET_SDK_INIT_CFG_LIBEAY_PATH, ptrByteArray1.getPointer());
System.arraycopy(strPath2.getBytes(), 0, ptrByteArray2.byValue, 0, strPath2.length());
ptrByteArray2.write();
hCNetSDK.NET_DVR_SetSDKInitCfg(HCNetSDK.NET_SDK_INIT_CFG_SSLEAY_PATH, ptrByteArray2.getPointer());
String strPathCom = System.getProperty("user.dir") + "/lib/";
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(HCNetSDK.NET_SDK_INIT_CFG_SDK_PATH, struComPath.getPointer());
}
/**初始化*/
hCNetSDK.NET_DVR_Init();
/**加载日志*/
hCNetSDK.NET_DVR_SetLogToFile(3, "./sdklog", false);
//网络超时时间 避免网络原因导致返回38错误码
hCNetSDK.NET_DVR_SetConnectTime(3000, 1); // 3秒超时,重试1次
//设置报警回调函数
if (fMSFCallBack_V31 == null) {
fMSFCallBack_V31 = new FMSGCallBack_V31();
Pointer pUser = null;
if (!hCNetSDK.NET_DVR_SetDVRMessageCallBack_V31(fMSFCallBack_V31, pUser)) {
System.out.println("设置回调函数失败!");
log.error("设置回调函数失败!");
return;
} else {
System.out.println("设置回调函数成功!");
log.info("设置回调函数成功!");
}
}
/** 设备上传的报警信息是COMM_VCA_ALARM(0x4993)类型,
在SDK初始化之后增加调用NET_DVR_SetSDKLocalCfg(enumType为NET_DVR_LOCAL_CFG_TYPE_GENERAL)设置通用参数NET_DVR_LOCAL_GENERAL_CFG的byAlarmJsonPictureSeparate为1,
将Json数据和图片数据分离上传,这样设置之后,报警布防回调函数里面接收到的报警信息类型为COMM_ISAPI_ALARM(0x6009),
报警信息结构体为NET_DVR_ALARM_ISAPI_INFO(与设备无关,SDK封装的数据结构),更便于解析。*/
HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG struNET_DVR_LOCAL_GENERAL_CFG = new HCNetSDK.NET_DVR_LOCAL_GENERAL_CFG();
struNET_DVR_LOCAL_GENERAL_CFG.byAlarmJsonPictureSeparate = 1; //设置JSON透传报警数据和图片分离
struNET_DVR_LOCAL_GENERAL_CFG.write();
Pointer pStrNET_DVR_LOCAL_GENERAL_CFG = struNET_DVR_LOCAL_GENERAL_CFG.getPointer();
hCNetSDK.NET_DVR_SetSDKLocalCfg(17, pStrNET_DVR_LOCAL_GENERAL_CFG);
//针对单个摄像头进行登录操作
//lUserID=BuFangAfterStart.loginDevice( ip, (short) port, username, password); //登录设备
//针对多个摄像头进行布防操作
modbusConfig.getDevices().forEach(camera -> {
try {
int userId = loginDevice(
camera.getCameras().getRecognition().getIp(),
(short) camera.getCameras().getRecognition().getPort(),
camera.getCameras().getRecognition().getUsername(),
camera.getCameras().getRecognition().getPassword());
//存储用户句柄
userHandles.put(camera.getCameras().getRecognition().getIp(), userId);
System.out.println("userHandles"+userHandles);
//camera.getCameras().getRecognition().setUserId(userId);
int alarmHandle = BuFangAfterStart.setAlarmChan(userId);//报警布防,和报警监听二选一即可
//存储报警布防句柄
alarmHandles.put(camera.getCameras().getRecognition().getIp(), alarmHandle);
} catch (Exception e) {
log.error(" 摄像头[{}]布防失败: {}", camera.getName(), e.getMessage());
}
});
// 3. 截图摄像头只需登录(根据实际需求调整)
modbusConfig.getDevices().forEach(camera -> {
try {
int userId = loginDevice(
camera.getCameras().getCapture().getIp(),
(short) camera.getCameras().getCapture().getPort(),
camera.getCameras().getCapture().getUsername(),
camera.getCameras().getCapture().getPassword());
//存储用户句柄
userHandles.put(camera.getCameras().getCapture().getIp(), userId);
System.out.println("userHandles1"+userHandles);
} catch (Exception e) {
log.error(" 摄像头[{}]登录失败: {}", camera.getName(), e.getMessage());
}
});
//执行报警布防
//System.out.println("执行报警布防示例代码");
log.info("执行报警布防示例代码");
//单个摄像头
//lAlarmHandle=BuFangAfterStart.setAlarmChan(lUserID);//报警布防,和报警监听二选一即可
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 在程序关闭后执行
*/
@PreDestroy
public void destroy(){
//多个设备注销时
//注销设备,每一台设备调用一次
userHandles.values().forEach(
userId -> BuFangAfterStart.logoutDev(userId)
);
//单个设备注销
//BuFangAfterStart.logoutDev(lUserID);
//log.info(lUserID+"设备注销");
//单个设备
//撤防
//hCNetSDK.NET_DVR_CloseAlarmChan_V30(lUserID);
//log.info(lUserID+"设备撤防");
//多个设备撤防
userHandles.values().forEach(
userId -> hCNetSDK.NET_DVR_CloseAlarmChan_V30(userId)
);
//最后调用NET_DVR_Cleanup释放SDK所有资源。
//释放SDK
hCNetSDK.NET_DVR_Cleanup();
log.info("释放SDK");
}
/**
* 登录设备,支持 V40 和 V30 版本,功能一致。
*
* @param ip 设备IP地址
* @param port SDK端口,默认为设备的8000端口
* @param user 设备用户名
* @param psw 设备密码
* @return 登录成功返回用户ID,失败返回-1
*/
public static int loginDevice(String ip, short port, String user, String psw) {
// 创建设备登录信息和设备信息对象
HCNetSDK.NET_DVR_USER_LOGIN_INFO loginInfo = new HCNetSDK.NET_DVR_USER_LOGIN_INFO();
HCNetSDK.NET_DVR_DEVICEINFO_V40 deviceInfo = new HCNetSDK.NET_DVR_DEVICEINFO_V40();
// 设置设备IP地址
byte[] deviceAddress = new byte[HCNetSDK.NET_DVR_DEV_ADDRESS_MAX_LEN];
byte[] ipBytes = ip.getBytes();
System.arraycopy(ipBytes, 0, deviceAddress, 0, Math.min(ipBytes.length, deviceAddress.length));
loginInfo.sDeviceAddress = deviceAddress;
// 设置用户名和密码
byte[] userName = new byte[HCNetSDK.NET_DVR_LOGIN_USERNAME_MAX_LEN];
byte[] password = psw.getBytes();
System.arraycopy(user.getBytes(), 0, userName, 0, Math.min(user.length(), userName.length));
System.arraycopy(password, 0, loginInfo.sPassword, 0, Math.min(password.length, loginInfo.sPassword.length));
loginInfo.sUserName = userName;
// 设置端口和登录模式
loginInfo.wPort = port;
loginInfo.bUseAsynLogin = false; // 同步登录
loginInfo.byLoginMode = 0; // 使用SDK私有协议
// 执行登录操作
int userID = hCNetSDK.NET_DVR_Login_V40(loginInfo, deviceInfo);
if (userID == -1) {
System.err.println("登录失败,错误码为: " + hCNetSDK.NET_DVR_GetLastError());
} else {
System.out.println(ip + " 设备登录成功!");
// 处理通道号逻辑
int startDChan = deviceInfo.struDeviceV30.byStartDChan;
System.out.println("预览起始通道号: " + startDChan);
}
return userID; // 返回登录结果
}
报警布防代码:
/**
* 报警布防
* @param userID 设备登录句柄ID
* @return
*/
public static int setAlarmChan(int userID ) {
if (userID == -1) {
System.out.println("请先注册");
return -1;
}
if (lAlarmHandle < 0)//尚未布防,需要布防
{
//报警布防参数设置
HCNetSDK.NET_DVR_SETUPALARM_PARAM alarmInfo = new HCNetSDK.NET_DVR_SETUPALARM_PARAM();
alarmInfo.dwSize = alarmInfo.size();
alarmInfo.byLevel = 0; //布防等级
alarmInfo.byAlarmInfoType = 1; // 智能交通报警信息上传类型:0- 老报警信息(NET_DVR_PLATE_RESULT),1- 新报警信息(NET_ITS_PLATE_RESULT)
alarmInfo.byDeployType = 0; //布防类型:0-客户端布防,1-实时布防,客户端布防仅支持一路
alarmInfo.write();
lAlarmHandle= hCNetSDK.NET_DVR_SetupAlarmChan_V41(userID, alarmInfo);
if (lAlarmHandle == -1) {
System.err.println("布防失败,错误码为" + hCNetSDK.NET_DVR_GetLastError());
log.error("布防失败,错误码为" + hCNetSDK.NET_DVR_GetLastError());
return -1 ;
} else {
System.out.println(userID+"布防成功");
log.info(userID+"布防成功");
return lAlarmHandle;
}
} else {
System.out.println("设备已经布防,请先撤防!");
log.info(userID+"设备已经布防,请先撤防!");
return lAlarmHandle ;
}
}
设备注销方法:
/**
* 设备注销
* @param
*/
public static void logoutDev(int userID) {
if (userID>-1)
{
if (hCNetSDK.NET_DVR_Logout(userID)) {
System.out.println("注销成功");
return;
}
}else
{
System.out.println("设备未注册,请先注册");
return;
}
return;
}
布防照片抓拍回调方法实现并注入redis依赖实现缓存
继续上面的抓拍回调方法的实现。
注意这里需要注入redis工具类相关依赖的方式
@Component
public class AlarmDataParse {
private static final Logger log = LoggerFactory.getLogger(AlarmDataParse.class);
private static RedisCache redisCache;
@Autowired
private void init(RedisCache redisCache) {
AlarmDataParse.redisCache = redisCache;
}
注入逻辑说明:
注入方式:通过@Autowired标注的init方法实现依赖注入(属于方法注入的一种)。
执行时机:Spring在创建AlarmDataParse实例后,会自动调用该方法,并将容器中的RedisCache实例作为参数传入。
静态字段赋值:将注入的RedisCache实例赋值给静态字段redisCache,使得静态方法也能访问该依赖。
抓拍回调方法实现:
@Component
public class AlarmDataParse {
private static final Logger log = LoggerFactory.getLogger(AlarmDataParse.class);
private static RedisCache redisCache;
@Autowired
private void init(RedisCache redisCache) {
AlarmDataParse.redisCache = redisCache;
}
public static void alarmDataHandle(int lCommand, HCNetSDK.NET_DVR_ALARMER pAlarmer, Pointer pAlarmInfo, int dwBufLen, Pointer pUser) {
System.out.println("报警事件类型: lCommand:" + Integer.toHexString(lCommand));
String ip = new String(pAlarmer.sDeviceIP);
String sTime;
String MonitoringSiteID;
//lCommand是传的报警类型
switch (lCommand) {
case HCNetSDK.COMM_ITS_PLATE_RESULT://交通抓拍结果(新报警信息)
HCNetSDK.NET_ITS_PLATE_RESULT strItsPlateResult = new HCNetSDK.NET_ITS_PLATE_RESULT();
strItsPlateResult.write();
Pointer pItsPlateInfo = strItsPlateResult.getPointer();
pItsPlateInfo.write(0, pAlarmInfo.getByteArray(0, strItsPlateResult.size()), 0, strItsPlateResult.size());
strItsPlateResult.read();
String sLicense = Constants.DEFAULT_PLATE_NUM;
PlateData plateData = new PlateData();
try {
sLicense = new String(strItsPlateResult.struPlateInfo.sLicense, "GBK");
byte VehicleType = strItsPlateResult.byVehicleType; //0-其他车辆,1-小型车,2-大型车,3- 行人触发,4- 二轮车触发,5- 三轮车触发,6- 机动车触发
MonitoringSiteID = new String(strItsPlateResult.byMonitoringSiteID);
System.out.println("车牌号:" + sLicense + ":车辆类型:" + VehicleType + ":布防点编号:" + MonitoringSiteID);
//log.info("车牌号:" + sLicense + ":车辆类型:" + VehicleType + ":布防点编号:" + MonitoringSiteID);
plateData.setPlateNum(sLicense);
plateData.setSnapShotTime(LocalDateTime.now());
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
/**
* 报警图片保存,车牌,车辆图片
*/
String storeName = "";
for (int i = 0; i < strItsPlateResult.dwPicNum; i++) {
if (strItsPlateResult.struPicInfo[i].dwDataLen > 0) {
SimpleDateFormat sf = new SimpleDateFormat("yyyyMMddHHmmss");
String newName = sf.format(new Date());
FileOutputStream fout;
try {
String filename = "./pic/" + newName + "_type" + strItsPlateResult.struPicInfo[i].byType + "_ItsPlate.jpg";
//只存type1
storeName = newName + "_type1" + "_ItsPlate.jpg";
fout = new FileOutputStream(filename);
//将字节写入文件
long offset = 0;
ByteBuffer buffers = strItsPlateResult.struPicInfo[i].pBuffer.getByteBuffer(offset, strItsPlateResult.struPicInfo[i].dwDataLen);
byte[] bytes = new byte[strItsPlateResult.struPicInfo[i].dwDataLen];
buffers.rewind();
buffers.get(bytes);
fout.write(bytes);
fout.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//日志记录与数据存储
plateData.setPngPath(storeName);
String cleanIp = RedisKeyUtils.cleanIpAddress(ip);
plateData.setIp(cleanIp);
redisCache.setCacheObject(Constants.TEMP_CAMERA_KEY +cleanIp
,plateData,
24, TimeUnit.HOURS);
//log.error("plateData"+plateData);
//log.info("摄像头:"+ip+"识别到:"+plateData);
System.out.println("摄像头:"+ip+"识别到:"+plateData);
break;
以上会将对应抓拍摄像头ip的抓拍图片的路径存储进redis中
手动调用海康摄像头SDK方法实现单张照片抓拍存储
基于上面已经登录某个摄像头后并记录到了其用户句柄luserId
如果针对某个用户句柄对应的摄像头进行单张照片的抓拍与存储。
封装调用的service
public interface CaptureService {
String captureAndSave(int userId, int channel);
}
service的impl
@Service
public class CaptureServiceImpl implements CaptureService {
// Windows本地存储路径(可配置)
@Value("${hk.pngPath:D:/hikvision_capture}")
private String savePath;
@Override
public String captureAndSave(int userId, int channel) {
// 1. 准备保存路径
String filename = "";
Path saveDir = Paths.get(savePath);
System.out.println("保存路径{}:"+saveDir);
// 2. 自动创建目录(如果不存在)
if (!Files.exists(saveDir)) {
try {
Files.createDirectories(saveDir);
} catch (IOException e) {
throw new RuntimeException("创建目录失败: " + saveDir, e);
}
}
try {
filename = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmssSSS")) + ".jpg";
// 3. 执行抓拍
HCNetSDK.NET_DVR_JPEGPARA jpegpara = new HCNetSDK.NET_DVR_JPEGPARA();
jpegpara.wPicQuality = 3; // 图片质量(0-6)
jpegpara.wPicSize = 0xff; // 0xff表示使用原始分辨率
System.out.println("执行抓拍");
String fullPath = saveDir.resolve(filename).toString();
System.out.println("fullPath:"+fullPath);
boolean success = BuFangAfterStart.hCNetSDK.NET_DVR_CaptureJPEGPicture(
userId,
channel,
jpegpara,
fullPath.getBytes(Charset.defaultCharset())
);
System.out.println("success:"+success);
if (!success) {
System.out.println("抓拍失败");
int code = BuFangAfterStart.hCNetSDK.NET_DVR_GetLastError();
System.out.println("错误码:"+code);
log.error("抓拍失败,错误码:{}",code);
return Constants.CAPTURE_FAIL;
}else{
System.out.println(" 抓拍成功,图片保存至:" + savePath+filename);
}
}catch (Exception e){
log.error("抓拍洗车照片异常:"+e.getMessage());
}
return filename;
}
}
编写测试controller进行测试
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private CaptureService captureService;
@GetMapping("/capture")
public String capture() {
//摄像头抓拍测试
//以下来自手册说明-常见问题
//如果每秒抓取1~2张(具体由设备性能决定),不是很频繁抓图,
//可以登录之后调用NET_DVR_CaptureJPEGPicture或NET_DVR_CaptureJPEGPicture_NEW进行抓图。
//该抓图方式需要设备支持。可以通过能力集判断设备是否支持抓图以及支持的抓图分辨率,
//即调用NET_DVR_GetDeviceAbility获取设备抓图能力集:能力集类型PIC_CAPTURE_ABILITY(DVR、NVR设备)
//和DEVICE_JPEG_CAP_ABILITY(IPC、IPD设备)。
Map handles = BuFangAfterStart.getUserHandles();
if (handles.isEmpty()) {
return "设备未登录";
}
Integer lUserID = handles.isEmpty() ? null : handles.entrySet().iterator().next().getValue();
System.out.println("test-lUserID"+lUserID);
if ((lUserID == -1) || (lUserID == 0xFFFFFFFF)) {
return "设备未登录";
}else{
String s = captureService.captureAndSave(lUserID, 1);
}
return "test";
}
}
实现本地磁盘目录映射网络url路径
@Configuration
public class ResourceConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/recognition/**")
// 注意文件路径需要以file:开头且使用正斜杠
.addResourceLocations("file:D:/dataUpload/pic/")
// 设置浏览器缓存1小时(单位秒)
.setCachePeriod(3600)
.resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected Resource getResource(String resourcePath, Resource location)
throws IOException {
// 替换未编码的特殊字符(防御性处理)
String safePath = resourcePath
.replace("[", "%5B")
.replace("]", "%5D");
try {
String decodedPath = URLDecoder.decode(safePath, "UTF-8");
Path filePath = Paths.get(location.getFile().getAbsolutePath(), decodedPath);
// 安全校验
if (!filePath.normalize().startsWith(location.getFile().getAbsolutePath())) {
throw new AccessDeniedException("路径越界访问");
}
Resource res = new FileSystemResource(filePath.toFile());
return res.exists() ? res : null;
} catch (UnsupportedEncodingException e) {
throw new IOException("路径解码异常", e);
}
}
});
registry.addResourceHandler("/capture/**")
// 注意文件路径需要以file:开头且使用正斜杠
.addResourceLocations("file:D:/hikvision_capture/")
// 设置浏览器缓存1小时(单位秒)
.setCachePeriod(3600);
}
}
注意这里的抓拍图片名称中自动会有[]等特殊符号
为了避免预览时url编码错误导致提示400
可以将特殊符号去掉,或添加替换未编码的特殊字符。
定时任务扫描redis缓存数据并存储入Mysql
@Configuration
@EnableScheduling
@Slf4j
public class Record2MysqlTask {
@Autowired
private CarWashingStatisticsService carWashingStatisticsService;
@Autowired
private RedisCache redisCache;
@Scheduled(fixedRateString = "1500")
public void judgeData() {
//扫描所有抓拍摄像头数据
Collection keys = redisCache.keys(Constants.TEMP_CAMERA_KEY + "*");
if(!CollectionUtils.isEmpty(keys)){
for (String key : keys) {
PlateData plateData = redisCache.getCacheObject(key);
//其他业务逻辑处理省略
redisCache.setCacheObject(key,plateData,24,TimeUnit.MINUTES);
}
}
}
@Scheduled(fixedRateString = "5000")
public void record() {
Collection keys = redisCache.keys(Constants.TEMP_CAMERA_KEY + "*");
if(!CollectionUtils.isEmpty(keys)){
for (String key : keys) {
PlateData plateData = redisCache.getCacheObject(key);
CarWashingStatistics build = CarWashingStatistics
.builder()
.carWashingMachineId(1)
.carNumber("111")
.startTime(plateData.getStartTime())
.endTime(plateData.getEndTime())
.washTime(1)
.washPress(4D)
.url(Constants.URL_PREFIX_SNOP + plateData.getPngPath())
.build();
if(null!=plateData.getCapturePath() && plateData.getCapturePath().length()>0){
build.setUrlBody(Constants.URL_PREFIX_CAPTURE+plateData.getCapturePath());
}
carWashingStatisticsService.save(build);
redisCache.deleteObject(key);
}
}
}
}
测试抓拍结果
布防抓拍结果

入库测试结果
完整示例代码下载
https://download.csdn.net/download/BADAO_LIUMANG_QIZHI/92343595



浙公网安备 33010602011771号