Java对接大华云睿摄像头(人流统计/客流量统计)

Java对接大华云睿摄像头(人流统计/客流量统计)

环境准备(注意区分开发环境和正式服务器环境)

  • 开发环境
    win11 + IDEA + spring boot + mysql
  • 正式服务器环境
    Linux
  • 下载开发环境和正式环境的SDK
    SDK下载地址:https://support.dahuatech.com/tools/sdkExploit
    ** 注意选择对应的语言和操作系统 **
    Java对接大华云睿摄像头(人流统计/客流量统计)_第1张图片

打开下载的(win64)SDK,查看人流统计部分实现逻辑

  • 在SDK中找到人流统计部分的代码逻辑(HumanNumberStatistic.class 和 HumanNumberStatisticFrame.class)
    Java对接大华云睿摄像头(人流统计/客流量统计)_第2张图片
  • 启动HumanNumberStatistic,查看摄像头的参数面板

填入设备的外网IP地址、外网端口、用户名、密码,登录设备Java对接大华云睿摄像头(人流统计/客流量统计)_第3张图片选择需要订阅的通道,这个通常在设备的管理后台可以查到,需要预先知晓每个摄像头对应的通道号。
点击只能订阅,在摄像头有人流变化时发生回调。
注意:智能订阅的同时,不能点击实时预览,会造成人流回调没有数据,具体原因尚未明确。Java对接大华云睿摄像头(人流统计/客流量统计)_第4张图片如下图,订阅的通道发生回调后,就能在事件列表中展示数据。
Java对接大华云睿摄像头(人流统计/客流量统计)_第5张图片
基于这样的逻辑,我们可以将SDK中的逻辑整合在项目中来。

整合SDK中的逻辑到项目中

根据SDK中大华的JAVA_编程指导手册,了解整个事件的订阅和回调流程。
Java对接大华云睿摄像头(人流统计/客流量统计)_第6张图片每一个通道都需要经过这个过程。将SDK打包成jar包(这里需要打两个版本的,就是下载下来的两个开发包,一个win64,一个Linux64,下面会用到)。
Java对接大华云睿摄像头(人流统计/客流量统计)_第7张图片jar包可以打入本地maven仓库,在pom.xml中引用,或者直接在项目中添加到libs文件夹,并在pom.xml中引用。两种方式如下。
Java对接大华云睿摄像头(人流统计/客流量统计)_第8张图片

  • 打包到本地maven仓库
mvn install:install-file -Dfile=D:\netsdk-1.0-demo.jar -DgroupId=com.netsdk -DartifactId=netsdk -Dversion=1.0 -Dpackaging=jar -DgeneratePom=true

Java对接大华云睿摄像头(人流统计/客流量统计)_第9张图片然后就可以在maven中直接引用maven坐标了。

<dependency>
   <groupId>com.netsdk</groupId>
    <artifactId>netsdk</artifactId>
    <version>1.0</version>
</dependency>
  • 不打入本地maven,直接在pom中引用

Java对接大华云睿摄像头(人流统计/客流量统计)_第10张图片

<dependency>
    <groupId>com.netsdk</groupId>
    <artifactId>netsdk</artifactId>
    <version>1.0-demo</version>
    <scope>system</scope>
    <systemPath>${basedir}/src/main/resources/libs/netsdk-1.0-demo.jar</systemPath>
</dependency>

<!-- 注意打包到线上要用Linux包里的jar包 -->
<dependency>
    <groupId>com.netsdk</groupId>
    <artifactId>netsdk</artifactId>
    <version>1.0-demo</version>
    <scope>system</scope>
    <systemPath>${basedir}/src/main/resources/libs/linux/netsdk-1.0-demo.jar</systemPath>
</dependency>

创建数据表(非必须)

由于我的项目中需要用到统计年月日的数据,所以需要将每天的数据记录下来。
设计了如下表(t_human_statistic_info.sql),用于之后统计

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_dahua_human_statistic_info
-- ----------------------------
CREATE TABLE IF NOT EXISTS `t_dahua_human_statistic_info` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `date` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '统计日期 (格式:yyyy/MM/dd)',
  `channel` int NOT NULL COMMENT '通道号',
  `entered_today` int NOT NULL COMMENT '今日进入总数',
  `entered_total` int NOT NULL COMMENT '进入总数',
  `exited_today` int NOT NULL COMMENT '今日出去总数',
  `exited_total` int NOT NULL COMMENT '出去总数',
  `event_time` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '回调事件最新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;

SET FOREIGN_KEY_CHECKS = 1;

配置人流初始化和回调监听

根据SDK中的流程调用,并配置各自的回调处理(回调处理中根据各自的业务需求去实现)

import com.netsdk.common.EventTaskCommonQueue;
import com.netsdk.demo.module.LoginModule;
import com.netsdk.demo.module.VideoStateSummaryModule;
import com.netsdk.lib.NetSDKLib;
import com.sun.jna.Pointer;

import java.sql.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 人流统计sdk集成
 *
 * @author Joi
 */
public class HumanNumberModel {

    private final String DEVICE_IP = "设备公网IP";

    private final int DEVICE_PORT = 8888;// 设备公网端口

    private final String DEVICE_USERNAME = "admin";// 用户名

    private final String DEVICE_PASSWORD = "zgh12345";// 密码

    /**
     * 设备断线通知回调
     */
    private static final HumanNumberModel.DisConnectCallBack disConnectCB = new HumanNumberModel.DisConnectCallBack();

    /**
     * 网络连接恢复
     */
    private static final HumanNumberModel.HaveReConnectCallBack haveReConnectCB = new HumanNumberModel.HaveReConnectCallBack();

    /**
     * 默认通道0
     */
    private final int channelId;

    /**
     * sdk lib实例
     */
    private static final NetSDKLib netSdk = NetSDKLib.NETSDK_INSTANCE;
    /**
     * sdk lib实例
     */
    private static final NetSDKLib config = NetSDKLib.CONFIG_INSTANCE;

    public HumanNumberModel(Integer channelId) {
        // 打开工程,SDK初始化,注册断线和重连回调函数
        LoginModule.init(disConnectCB, haveReConnectCB);
        // 登录
        LoginModule.login(DEVICE_IP, DEVICE_PORT, DEVICE_USERNAME, DEVICE_PASSWORD);
        this.channelId = channelId;
        VideoStateSummaryModule.attachVideoStatSummary(channelId, HumanNumberModel.humanNumberStatisticCB);
        System.out.println("人数统计初始化完成~~已登录~~订阅通道 channelId:" + channelId);
    }

    /**
     * 设备断线回调: 通过 CLIENT_Init 设置该回调函数,当设备出现断线时,SDK会调用该函数
     */
    private static class DisConnectCallBack implements NetSDKLib.fDisConnect {
        @Override
        public void invoke(NetSDKLib.LLong m_hLoginHandle, String pchDVRIP, int nDVRPort, Pointer dwUser) {
            System.out.printf("Device[%s] Port[%d] DisConnectCallBack!\n", pchDVRIP, nDVRPort);
        }
    }

    private static class HaveReConnectCallBack implements NetSDKLib.fHaveReConnect {
        @Override
        public void invoke(NetSDKLib.LLong m_hLoginHandle, String pchDVRIP, int nDVRPort, Pointer dwUser) {
            System.out.printf("ReConnect Device[%s] Port[%d]\n", pchDVRIP, nDVRPort);

            // 断线后需要重新订阅
            ExecutorService service = Executors.newSingleThreadExecutor();
            service.execute(new Runnable() {
                @Override
                public void run() {
                    VideoStateSummaryModule.reAttachAllVideoStatSummary(humanNumberStatisticCB);
                }
            });
            service.shutdown();
        }
    }

    /**
     * 人数统计回调事件
     */
    public static fHumanNumberStatisticCallBack humanNumberStatisticCB = fHumanNumberStatisticCallBack.getInstance();

    public static class fHumanNumberStatisticCallBack implements NetSDKLib.fVideoStatSumCallBack {

        private static fHumanNumberStatisticCallBack instance = new fHumanNumberStatisticCallBack();

        public static fHumanNumberStatisticCallBack getInstance() {
            return instance;
        }

        private EventTaskCommonQueue eventTaskQueue = new EventTaskCommonQueue();

        public fHumanNumberStatisticCallBack() {
            eventTaskQueue.init();
        }

        @Override
        public void invoke(NetSDKLib.LLong lAttachHandle, NetSDKLib.NET_VIDEOSTAT_SUMMARY stVideoState, int dwBufLen, Pointer dwUser) {
            System.out.println("消息回调开始!");
            SummaryInfo summaryInfo = new SummaryInfo(
                    stVideoState.nChannelID, stVideoState.stuTime.toStringTime(),
                    stVideoState.stuEnteredSubtotal.nToday,
                    stVideoState.stuEnteredSubtotal.nHour,
                    stVideoState.stuEnteredSubtotal.nTotal,
                    stVideoState.stuExitedSubtotal.nToday,
                    stVideoState.stuExitedSubtotal.nHour,
                    stVideoState.stuExitedSubtotal.nTotal);
            System.out.printf("\nChannel[%d] GetTime[%s]\n" +
                            "进入人数统计 People In  Information[Total[%d] Hour[%d] Today[%d]]\n" +
                            "出去人数统计 People Out Information[Total[%d] Hour[%d] Today[%d]]\n%n",
                    summaryInfo.nChannelID, summaryInfo.eventTime,
                    summaryInfo.enteredTotal, summaryInfo.enteredHour, summaryInfo.enteredToday,
                    summaryInfo.exitedTotal, summaryInfo.exitedHour, summaryInfo.exitedToday);

            // 查询向数据库中插入数据
            try {
                // 加载数据库驱动
                Class.forName("com.mysql.cj.jdbc.Driver");
                // 声明数据库view的URL
                String url = "jdbc:mysql://localhost:3306/dahua?useUnicode=true&characterEncoding=utf-8&useSSL=false";
                // 数据库用户名
                String user = "root";
                // 数据库密码
                String password = "123456";
                // 建立数据库连接,获得连接对象conn
                Connection conn = DriverManager.getConnection(url, user, password);
                // 查询当天是否有这条数据
                String selectSql = "SELECT `id`,`date`,`channel`,`entered_today`,`entered_total`,`exited_today`,`exited_total`,`event_time` FROM t_dahua_human_statistic_info WHERE `date` = ? and `channel` = ?";
                PreparedStatement selectPreparedStatement = conn.prepareStatement(selectSql);
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
                String date = LocalDate.now().format(formatter);
                selectPreparedStatement.setString(1, date);
                selectPreparedStatement.setInt(2, summaryInfo.nChannelID);
                ResultSet resultSet = selectPreparedStatement.executeQuery();
                if (resultSet.next()) {
                    String updateSql = "UPDATE t_dahua_human_statistic_info SET `entered_today` = ?,`entered_total` = ?,`exited_today` = ?,`exited_total` = ?,`event_time` = ? WHERE `date` = ? AND `channel` = ?";
                    // 创建一个Statment对象
                    PreparedStatement ps = conn.prepareStatement(updateSql);
                    // 为sql语句中第一个问号赋值
                    ps.setInt(1, summaryInfo.enteredToday);
                    // 为sql语句中第二个问号赋值
                    ps.setInt(2, summaryInfo.enteredTotal);
                    // 为sql语句中第三个问号赋值
                    ps.setInt(3, summaryInfo.exitedToday);
                    // 为sql语句中第四个问号赋值
                    ps.setInt(4, summaryInfo.exitedTotal);
                    ps.setString(5, summaryInfo.eventTime);
                    ps.setString(6, date);
                    ps.setInt(7, summaryInfo.nChannelID);
                    // 执行sql语句
                    ps.executeUpdate();
                    System.out.println("人数信息更新完毕!!! SummaryInfo: " + summaryInfo);
                } else {
                    String sql = "insert into t_dahua_human_statistic_info (date,channel,entered_today,entered_total,exited_today,exited_total,event_time) " +
                            "values(?,?,?,?,?,?,?)"; // 生成一条sql语句
                    // 创建一个Statment对象
                    PreparedStatement ps = conn.prepareStatement(sql);
                    // 为sql语句中第一个问号赋值
                    ps.setString(1, date);
                    // 为sql语句中第二个问号赋值
                    ps.setInt(2, summaryInfo.nChannelID);
                    // 为sql语句中第三个问号赋值
                    ps.setInt(3, summaryInfo.enteredToday);
                    // 为sql语句中第四个问号赋值
                    ps.setInt(4, summaryInfo.enteredTotal);
                    ps.setInt(5, summaryInfo.exitedToday);
                    ps.setInt(6, summaryInfo.exitedTotal);
                    ps.setString(7, summaryInfo.eventTime);
                    // 执行sql语句
                    ps.executeUpdate();
                    System.out.println("人数信息插入完毕!!! SummaryInfo: " + summaryInfo);
                }
                // 关闭数据库连接对象
                conn.close();
            } catch (ClassNotFoundException e) {
                System.out.println("ClassNotFoundException 加载数据库连接失败,保存SummaryInfo出错! " + e.getMessage());
            } catch (SQLException e) {
                System.out.println("SQLException 执行SQL出错!" + e.getMessage());
                e.printStackTrace();
            }
            System.out.println("消息回调结束!");
        }
    }

    public static class SummaryInfo {

        public int nChannelID;
        /**
         * 事件事件
         * 格式:yyyy/MM/dd HH:mm:ss
         * 例如:2023/07/14 17:40:19
         */
        public String eventTime;
        public int enteredToday;
        public int enteredHour;
        public int enteredTotal;
        public int exitedToday;
        public int exitedHour;
        public int exitedTotal;

        public SummaryInfo() {
        }

        public SummaryInfo(int nChannelID, String eventTime,
                           int enteredToday, int enteredHour,
                           int enteredTotal, int exitedToday,
                           int exitedHour, int exitedTotal) {
            this.nChannelID = nChannelID;
            this.eventTime = eventTime;
            this.enteredToday = enteredToday;
            this.enteredHour = enteredHour;
            this.enteredTotal = enteredTotal;
            this.exitedToday = exitedToday;
            this.exitedHour = exitedHour;
            this.exitedTotal = exitedTotal;
        }

    }

    /**
     * 登出
     */
    public void logout() {
        VideoStateSummaryModule.detachAllVideoStatSummary();  // 退订阅
        LoginModule.logout();  // 退出登录
    }

}

调用人流统计

项目启动时,配置需要监听的通道号,在摄像头有人流变化时,回调结果

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Configuration;

/**
 * Spring boot启动后调用
 * 也可以通过实现CommandLineRunner接口来登录硬件设备
 *
 * @author Joi
 */
@Configuration
public class ApplicationService implements ApplicationRunner {

    @Value("${dahua.camera.ip}")
    private String DEVICE_IP;

    @Value("${dahua.camera.port}")
    private int DEVICE_PORT;

    @Value("${dahua.camera.username}")
    private String DEVICE_USERNAME;

    @Value("${dahua.camera.password}")
    private String DEVICE_PASSWORD;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 初始化人流统计 需要监听的通道号
        new HumanNumberModel(0);
        new HumanNumberModel(1);
        new HumanNumberModel(2);
        new HumanNumberModel(3);
        new HumanNumberModel(4);
        new HumanNumberModel(5);
        new HumanNumberModel(6);
        new HumanNumberModel(7);
        new HumanNumberModel(8);
        new HumanNumberModel(9);
        new HumanNumberModel(10);
        new HumanNumberModel(11);
        new HumanNumberModel(12);
    }

}

执行结果如下:
Java对接大华云睿摄像头(人流统计/客流量统计)_第11张图片

常见问题

  1. 正式服务器(Linux)找不.so文件

Java对接大华云睿摄像头(人流统计/客流量统计)_第12张图片

  • 复制so文件到lib路径
    linux系统的so库一般存储与“/usr/lib/”路径中,可将动态库复制到该路径中。

你可能感兴趣的:(java,开发语言,后端)