BV相机产品Profile介绍

本文介绍标准品类中BV相机的产品Profile。BV相机来源于看门狗服务系统,是适配于看门狗的品类。该产品主要涉及视频监控、媒体流处理及分析服务。

基本信息

属性

属性定义了产品或设备的特性。每个属性包括名称、标识符、数据类型、读写权限、是否必须以及添加时间等信息。读写权限:0(只读),1(读写)。是否必须:0(可选),2(必须)。

属性名称 标识符 数据类型 读写权限 是否必须 描述/数据定义
设备编号 deviceGuid text(字符串) 1 2 数据长度:128
测试图片掩膜 TestImageMask ImageMask(图片掩膜) 0 2 (空)
吸引力得分 score int32(整数型) 0 2 (空)
媒体源编号 mediaSourceGuid text(字符串) 0 0 数据长度:128
出厂编号 factoryGuid text(字符串) 0 0 数据长度:128
dash流媒体访问地址 dashMediaUrl text(字符串) 0 0 数据长度:255
hls流媒体访问地址 hlsMediaUrl text(字符串) 0 0 数据长度:255
rtmp流媒体访问地址 rtmpMediaUrl text(字符串) 0 0 数据长度:255
rtsp流媒体访问地址 rtspMediaUrl text(字符串) 0 0 数据长度:255
媒体流访问token mediaToken text(字符串) 0 0 数据长度:255
客户端token clientToken text(字符串) 0 0 数据长度:255
当前设备视频片段 deviceVideoUrl text(字符串) 0 0 数据长度:255; 视频片段,默认3s
设备名称 deviceName text(字符串) 1 2 数据长度:128
设备类型 deviceType enum(枚举型) 1 2 枚举值: 0=全景, 1=枪机, 2=球机, 3=鱼眼
扩展数据 extendData text(字符串) 1 2 数据长度:128
当前设备截图地址 deviceImage text(字符串) 1 0 数据长度:255; 示例: http://192.168.100.37:44040/image/20241018/d46815304.jpg
服务请求编号 requestId text(字符串) 1 0 数据长度:255

事件

事件定义了通知或回调机制。事件类型:0(信息),1(告警)。QoS:0(最多一次)。持久化存储:0(无),1(有)。是否必须:2(必须)。

设备信息更新

车辆识别计数

行人识别计数

服务

服务定义了可调用的操作,包括输入/输出数据、结果返回方式等配置。结果返回方式:0(无),2(HTTP),3(Redis),4(属性),5(事件)。调用方式:0(同步),1(异步)。部分服务会关联事件用于回调。

异步视频服务_代理

异步抓图服务_代理

异步视频服务

异步抓图服务

请求截图

获取视频片段

关闭视频流

打开视频流

获取视频设备列表

获取 PTZ 位置

请求云台控制

吸引力排序

视频吸引力得分

图片分析-ws

图片分析-http

行人识别计数服务

车辆识别计数服务

消息解析脚本

实体映射(上行)

/**
 * 将第三方服务的自定义数据格式转换为数字孪生服务协议的数据格式,设备、属性上报、事件上报到DTS时调用。
 * @param {object} body - 入参
 * @param {object} body._bv_callback_message - 服务回调参数,回调时存在
 * @param {string} body._bv_callback_message.productId - 产品编号
 * @param {string} body._bv_callback_message.deviceId - 设备编号
 * @param {string} body._bv_callback_message.identifier - 事件的唯一标识符,回调方式为事件上报时存在
 * @param {object} body._bv_callback_message._bv_service_callback - 服务调用参数
 * @param {string} body._bv_callback_message._bv_service_callback.requestId - 用于标记请求
 * @param {string} body._bv_callback_message._bv_service_callback.identifier - 服务唯一标识
 * @param {string} body._bv_callback_message._bv_service_callback.version - 消息格式版本
 * @param {number} body._bv_callback_message._bv_service_callback.timestamp - 服务调用时间
 * @param {number} flag - 入参,0 设备上报 1 属性上报 2 事件上报
 * @returns {object} 出参
 */
function rawDataToProtocol(body, flag) {
    var jsonMap = {};
    var inputData = body;
    if (flag === 0) {
        /**
         * @returns {string} return.deviceId - 设备编号(保证唯一),必传
         * @returns {string} return.deviceName - 设备名称(最大长度255),必传
         * @returns {number} return.deviceStatus - 设备状态 0:禁用 1:启用(默认),非必传
         * @returns {number} return.deviceType - 设备类型 0:动态资源 1:静态资源,必传
         * @returns {string} return.location - 设备位置纬经度(默认0 0 0 = latitude longitude elevation),非必传
         * @returns {string} return.version - 版本号,默认default,非必传
         * @returns {Object} return.payload - 扩展数据,产品属性,可用于更新设备属性值(该数据根据产品Profile中定义的属性进行过滤,已存在定义的数据会保留,其余数据则弃用),非必传
         */
        return body;
    }
    if (flag === 1) {
        /**
         * @returns {string} return.deviceId - 设备编号(保证唯一),必传
         * @returns {string} return.deviceSecret - 设备签名(不传时将系统匹配),非必传
         * @returns {string} return.deviceName - 设备名称(最大长度255),非必传
         * @returns {number} return.deviceStatus - 设备状态 0:禁用 1:启用(默认),非必传
         * @returns {string} return.location - 设备位置纬经度(默认0 0 0 = latitude longitude elevation),非必传
         * @returns {number} return.timestamp - 上行数据传输时间。时间戳格式为数值,值为自GMT 1970年1月1日0时0分到当前时间点所经过的毫秒数,非必传
         * @returns {string} return.version - 版本号,默认default,非必传
         * @returns {Object} return.payload - 扩展数据,产品属性,可用于更新设备属性值(该数据根据产品Profile中定义的属性进行过滤,已存在定义的数据会保留,其余数据则弃用),非必传
         */
        if (inputData._bv_callback_message) {
            var bv_callback_message = inputData._bv_callback_message;
            var bv_service_callback = bv_callback_message._bv_service_callback;
            var payload = inputData.payload ? inputData.payload : {};
            if (bv_service_callback.identifier == "captureServerImage" || bv_service_callback.identifier == "captureServerVideo") {
                if (inputData.result != 0) {
                    return;
                }
                if (bv_service_callback.identifier == "captureServerImage") {
                    var resourceUrl = FileStorage.cache(inputData.file, inputData.suffix, bv_callback_message.deviceId);
                    payload.deviceImage = resourceUrl;
                }
                if (bv_service_callback.identifier == "captureServerVideo") {
                    var resourceUrl = FileStorage.disk(inputData.file, inputData.suffix);
                    payload.deviceVideoUrl = resourceUrl;
                }
            }
            if (bv_service_callback.identifier == "video_attractive_score") {
                payload.score = inputData.score;
            }
            if (bv_service_callback.identifier == "captureImage") {
                //payload.deviceImage = inputData.payload.deviceImage.replace("172.27.240.1","10.132.25.2");
            }
            jsonMap.deviceId = bv_callback_message.deviceId;
            jsonMap.payload = payload;
            return jsonMap;
        } else {
            return body;
        }
    }
    if (flag === 2) {
        /**
         * @returns {string} return.deviceId - 设备编号(保证唯一),必传
         * @returns {string} return.deviceSecret - 设备签名(不传时将系统匹配),非必传
         * @returns {string} return.deviceName - 设备名称(最大长度255),非必传
         * @returns {number} return.deviceStatus - 设备状态 0:禁用 1:启用(默认),非必传
         * @returns {string} return.location - 设备位置纬经度(默认0 0 0 = latitude longitude elevation),非必传
         * @returns {string} return.identifier - 事件的唯一标识符,必传
         * @returns {number} return.timestamp - 上行数据传输时间。时间戳格式为数值,值为自GMT 1970年1月1日0时0分到当前时间点所经过的毫秒数,非必传
         * @returns {string} return.version - 版本号,默认default,非必传
         * @returns {Object} return.payload - 扩展数据,事件的输出参数(该数据根据产品Profile中定义的事件输出参数进行过滤,已存在定义的数据会保留,其余数据则弃用),非必传
         */
        if (inputData._bv_callback_message) {
            jsonMap['deviceId'] = inputData._bv_callback_message.deviceId;
            jsonMap['identifier'] = inputData._bv_callback_message.identifier;
            // 添加mask过滤
            var entityAttribute = CoreApi.getEntityAttribute(inputData._bv_callback_message.productId, inputData._bv_callback_message.deviceId);
          entityAttribute = JSON.parse(entityAttribute);
            if (typeof entityAttribute === 'object' && entityAttribute !== null && entityAttribute.hasOwnProperty('TestImageMask') && entityAttribute.hasOwnProperty('deviceImage')) {
                const mask = JSON.parse(entityAttribute.TestImageMask);
                inputData = maskFilter(inputData, mask)
                inputData.boundingBoxImage = drawBox(inputData, entityAttribute.deviceImage)
            }

            // 存在文件资源时将存储至网关服务下
            if (inputData.heatMapImage) {
                inputData.heatMapImageUrl = FileStorage.disk(inputData.heatMapImage, "jpg");
            }
            if (inputData.boundingBoxImage) {
                inputData.boundingBoxImageUrl = FileStorage.disk(inputData.boundingBoxImage, "jpg");
            }
            jsonMap['payload'] = {
                "analysisTime": inputData.analysisTime,
                "objectNumber": inputData.objectNumber,
                "objectBoundingBox": inputData.objectBoundingBox,
                "boundingBoxImageUrl": inputData.boundingBoxImageUrl,
                "heatMapImageUrl": inputData.heatMapImageUrl
            }
        }
        return jsonMap;
    }
}

function drawBox(box,image) {
    const labels = [];
    const boxes = [];
    Object.keys(box.objectBoundingBox).forEach(function (key) {
        const value = box.objectBoundingBox[key];
        boxes.push(JtsGeometry.createCoordinatesXYXY(value));
        labels.push(key);
    });
    return JtsGeometry.drawBox(image, boxes, "Number:" + box.objectNumber, labels);
}

/**
 * 根据掩码(mask)过滤 box 对象中的边界框信息。
 * @param {object} inputData - 包含边界框信息的对象,其中 objectBoundingBox 属性存储具体的边界框数据。
 * @param {object} mask - 掩码对象,包含几何信息用于与边界框进行交集判断。
 */
function maskFilter(inputData, mask) {
    if (typeof inputData.objectBoundingBox !== 'object' && inputData.objectBoundingBox === null) {
        console.log("inputData.objectBoundingBox 不是有效的对象")
        return inputData;
    }
    const in_mask = mask.properties.mask_type === "positive";
    var objectBoundingBoxObj = {};
    var objectNumber = 0;
    for (const key in inputData.objectBoundingBox) {
        let value = inputData.objectBoundingBox[key];

        // value保留三位小数 double类型
        for (let i = 0; i < value.length; i++) {
            if (typeof value[i] !== 'number') {
                value[i] = parseFloat(value[i].toString());
            }
            value[i] = parseFloat(value[i].toFixed(3));
        }

        // 使用 JtsGeometry 工具类将边界框坐标转换为多边形所需的坐标数组
        const boxCoor = JtsGeometry.createCoordinatesXYXY(value);
        // 创建边界框对应的多边形
        let boxPolygon = JtsGeometry.createPolygon(boxCoor);
        for (let i = 0; i < mask.geometry.annotations.length; i++) {
            const maskCoor = JtsGeometry.createCoordinates(mask.geometry.annotations[i].coor);
            const maskPolygon = JtsGeometry.createPolygon(maskCoor);
            // 如果 in_mask 为 true 且边界框多边形和掩码多边形有交集
            if (in_mask && JtsGeometry.isIntersects(maskPolygon, boxPolygon)) {
                //console.log("box和mask有交集,交集面积为", JtsGeometry.intersectionArea(maskPolygon, boxPolygon))
                objectBoundingBoxObj[key] = value;
                objectNumber++;
                break;
            }
            // 如果 in_mask 为 false 且边界框多边形和掩码多边形没有交集
            if (!in_mask && !JtsGeometry.isIntersects(maskPolygon, boxPolygon)) {
                //console.log("负掩码,box和mask没有交集即为有效区域")
                objectBoundingBoxObj[key] = value;
                objectNumber++;
                break;
            }
        }
    }
    inputData.objectBoundingBox = objectBoundingBoxObj;
    inputData.objectNumber = objectNumber;
    return inputData;
}

实体反控(下行)

/**
 * 将DTS平台的BV JSON数据转换为设备(或第三方服务)能识别的格式数据,DTS给设备(或第三方服务)下发数据时调用。
 * @param {JSONObject} serviceParams - 入参,请求时服务的固定参数
 * @param {string} serviceParams.requestId - 请求id
 * @param {string} serviceParams.productId - 请求产品编号
 * @param {string} serviceParams.deviceId - 请求设备编号
 * @param {string} serviceParams.serviceIdentifier - 请求服务标识
 * @param {string} serviceParams.serviceName - 请求服务名称
 * @param {JSONObject} inputParams - 入参,产品Profile中服务的输入参数
 * @param {number} flag - 入参,0=服务调用
 * @returns {JSONObject} - 出参,满足当前服务的输入参数,请注意数据类型以及参数标识符的统一性。
 */
function protocolToRawData(serviceParams, inputParams, flag) {
    if (flag === 0) {
        var data = inputParams;
        if (data.analysisType && data.analysisType.indexOf("100003") !== -1) {
            // 按分号分割 analysisMission 字符串
            var parts = data.analysisMission.split(';');
            var result = [];

            // 遍历每个分割后的部分
            for (var i = 0; i < parts.length; i++) {
                var item = parts[i].trim();
                if (item) {
                    var subParts = item.split(',');
                    var obj = {
                        deviceId: subParts[0].trim(),
                        programName: subParts[1].trim(),
                        originalVideoUrl: subParts[2].trim()
                    };
                    result.push(obj);
                }
            }
            data.analysisMission = [{
                    args: result,
                    sceneGuid: "0"
                }
            ];
            data.messageId = Date.now();
            return data;
        }
        if (data.beginTimestamp) {
              // 拿取事件发生时前5s的
            data.beginTimestamp = new Date(data.beginTimestamp).getTime() - 5000;
            return data;
        }

        //参考服务的输入参数
        return inputParams;
    }
}