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

posted @ 2025-12-18 14:06  gccbuaa  阅读(2)  评论(0)    收藏  举报