使用DeepStream SDK进行实时视频识别,并发送通知给LINE

使用DeepStream SDK进行快速视频识别,然后通过LINE进行通知。

本文是NTT DOCOMO公司2019年度冒险日历第七天的文章。

我是NTT DoCoMo服务创新部的酒井。在工作中,我们致力于使用深度学习进行图像识别引擎的研究开发和服务化。这次,我们使用了基于GPU的视频识别加速工具”DeepStream SDK”,在高速识别视频中的车辆的同时,将结果通知到LINE上。我们可以制作类似AI摄像头的设备,用于监控摄像头中的人员检测,或者从摄像头画面中检测违停等。

動画gif

我在网上找不到太多关于使用DeepStream SDK和AMQP进行消息传递的案例,因此我也尝试了这种方法。

workflow.jpg

动机

随着近年来深度学习技术在图像识别中的应用不断扩大,视频数据的图像识别也逐渐兴起。然而,利用深度学习进行视频识别相较于一般图像识别而言,需要更高的机器配置,并且处理时间往往会变慢。

在那种情况下,有传闻称使用英伟达的DeepStream SDK可以利用GPU快速进行视频识别和加速视频处理。而且现在还可以用于docker,安装变得更加简单,所以我试着使用了一下。

深流SDK

深度学习的推理(+学习)经常使用GPU来加速。DeepStream SDK是一个使用GPU来高速处理视频的软件开发工具包(SDK)。它以GStreamer插件的形式提供了各种在视频处理中所需的功能。

    • GPUや、エッジGPUデバイスの専用ハードウェアを用いて、動画のエンコード/デコードを高速化

 

    • Deep Learningを用いた物体検出、画像分類を、GPUおよびTensorRT1を用いて高速化

 

    • トラッキング機能も提供

 

    画像認識結果を、Kafka、MQTT、AMQPなどを用いて、送信可能

方式

环境

我已经准备好了支持NVIDIA Docker 2.0的GPU服务器。

    • OS: Ubuntu 18.04

 

    • nvidia-driver: 418.67

 

    • docker: 19.03.4

 

    • docker-compose: 1.24.1

 

    nvidia-docker: 2.0.3

尽管在docker中原生支持GPU,但在docker-compose中似乎无法指定GPU,因此我选择了使用较旧的runtime表达方式。

文件结构

deepstream-line
├── docker
│   ├── Dockerfile
│   ├── requirements.txt
│   └── src
│       └── receive.py
├── docker-compose-amqp.yml
├── docker-compose.yml
├── rabbitmq
├   ├── custom_definitions.json
├   └── rabbitmq.conf
└── videos
    └── test.h264

我們使用了免費的動態素材網站Mixkit中的Chinatown夜景街道影片資料。

为了保持为H264格式的流,需要使用FFmpeg进行转换。

ffmpeg -i ./2012-1080.mp4  -vcodec copy -an -bsf:v h264_mp4toannexb videos/test.h264

启动AMQP服务器

DeepStream SDK可以将识别结果作为消息发送到Kafka、MQTT、AMQP等系统。
在这次实验中,我们选择使用docker来启动RabbitMQ。
RabbitMQ是一种发布/订阅类型的消息服务,由以下元素组成。

rabbitmq.jpg
    • producer: メッセージを送る側。今回は、DeepStream SDKがこれに当たる

 

    • exchange: 送られてきたメッセージを適当なqueueに割り振る

 

    • queue: キュー

 

    consumer: メッセージをqueueに取りに行って利用する。今回は、LINEに通知を送るpythonスクリプト

准备AMQP的配置文件。用户名和密码都设置为guest。

{
    "rabbit_version": "3.8.0",
    "users": [{
        "name": "guest",
        "password_hash": "CV/8dVC+gRd5cY08tFu4h/7YlGWEdE5lJo4sn4CfbCoRz8ez",
        "hashing_algorithm": "rabbit_password_hashing_sha256",
        "tags": "administrator"
    }],
    "vhosts": [{
        "name": "/"
    }],
    "permissions": [{
        "user": "guest",
        "vhost": "/",
        "configure": ".*",
        "write": ".*",
        "read": ".*"
    }],
    "topic_permissions": [],
    "parameters": [],
    "global_parameters": [{
        "name": "cluster_name",
        "value": "rabbit@rabbitmq"
    }],
    "policies": [],
    "queues": [{
        "name": "test",
        "vhost": "/",
        "durable": true,
        "auto_delete": false,
        "arguments": {
            "x-queue-type": "classic"
        }
    }],
    "exchanges": [],
    "bindings": [{
        "source": "amq.topic",
        "vhost": "/",
        "destination": "test",
        "destination_type": "queue",
        "routing_key": "deepstream",
        "arguments": {}
    }]
}

在queues中,我将队列的名称设置为test。另外,我决定使用默认设置的amq.topic作为交换机,并在bindings中设置当带有”deepstream”主题的消息到达时,将其路由到test队列。

为了读取先前的json文件,准备好conf文件。

loopback_users.guest = false
listeners.tcp.default = 5672
management.tcp.port = 15672
management.load_definitions = /etc/rabbitmq/custom_definitions.json

通过这个,可以在端口15672上进行消息的交流,并读取之前的JSON文件。

使用docker-compose启动RabbitMQ。使用RabbitMQ官方基础镜像,并通过volumes选项将先前的conf/json挂载到容器内可见。

version: "2.3"
services:
  rabbitmq:
    image: rabbitmq:3.8-management
    hostname: rabbitmq
    container_name: rabbitmq
    expose: #他のコンテナ向けの公開
      - "5672"
      - "15672"
    ports: #
      - "5672:5672"
      - "15672:15672"
    volumes:
      - ./rabbitmq:/etc/rabbitmq
networks:
  default:

此外,我们通过定义网络,以便在以后可以从DeepStream SDK和消费者容器进行连接。使用以下命令启动。

docker-compose -f docker-compose-amqp.yml up -d

准备DeepStream SDK。

我打算使用NVIDIA GPU CLOUD提供的官方图像来使用DeepStream SDK。

使用的图像是nvcr.io/nvidia/deepstream:4.0.1-19.09-samples。

我会提前拉取。

docker pull nvcr.io/nvidia/deepstream:4.0.1-19.09-samples

这个镜像中包含编译好的DeepStream SDK、示例、TensorRT和Gstreamer等所需的一切。本次我们将使用此docker镜像中包含的示例应用程序之一,即DeepStream Test 4。该应用程序可以进行车辆/人员的检测和识别,并每30帧发送一次识别结果消息。

目录:/sources/apps/sample_apps/deepstream-test4
描述:此示例基于deepstream-test1样本,用于单个H.264流 – filesrc、decode、nvstreammux、nvinfer、nvosd、renderer,以展示在IOT连接中使用”nvmsgconv”和”nvmsgbroker”插件的使用。对于测试4,用户需要修改kafka broker连接字符串以实现成功连接。在运行test4之前,需要设置分析服务器docker。DeepStream分析文档中有关于设置分析服务器的更多信息。

您可以使用以下命令查看README和使用说明。

docker run --gpus 0 --rm -it \
    nvcr.io/nvidia/deepstream:4.0.1-19.09-samples \
    cat /root/deepstream_sdk_v4.0.1_x86_64/sources/apps/sample_apps/deepstream-test4/README
# READMEが表示される
docker run --gpus 0 --rm -it \
    nvcr.io/nvidia/deepstream:4.0.1-19.09-samples \
    /opt/nvidia/deepstream/deepstream-4.0/bin/deepstream-test4-app
# /opt/nvidia/deepstream/deepstream-4.0/bin/deepstream-test4-app \
# -i <H264 filename> -p <Proto adaptor library> --conn-str=<Connection string

根据这个README,通过设置–proto-lib、–conn-str和–topic选项,可以使用AMQP。

1. 使用 –proto-lib 或 -p 命令行选项设置适配器库的路径。
适配器库可以在 /opt/nvidia/deepstream/deepstream-/lib 找到。

Kafka 库 – libnvds_kafka_proto.so
Azure 设备客户端 – libnvds_azure_proto.so
AMQP 库 – libnvds_amqp_proto.so

2. 根据需要使用 –conn-str 命令行选项设置与后端服务器的连接。
对于 Azure – 完整的 Azure 连接字符串
对于 Kafka – 格式为:host;port;topic 的连接字符串
对于 Amqp – 格式为:host;port;username 的连接字符串。密码应在 cfg_amqp.txt 中提供。

3. 使用 –topic 或 -t 命令行选项提供消息主题(可选)。
Kafka 消息适配器也可以在连接字符串格式中嵌入主题参数
在这种情况下,命令行中的 “topic” 应与连接字符串中的主题匹配。

在启动AMQP服务器时,您可以将主机设置为rabbitmq,端口设置为15672,用户设置为guest,然后执行-c rabbitmq;5672;guesttopic deepstream -c cfg_amqp.txt。

libnvds_amqp_proto.so的存储位置位于docker镜像内的/opt/nvidia/deepstream/deepstream-4.0/lib/libnvds_amqp_proto.so。

消费者的准备

准备一个容器来从AMQP服务器接收消息并发送通知到LINE。
为了发送通知到LINE,我们将使用LINE Notify。
当与Web服务进行协作时,通知将来自LINE提供的官方账号“LINE Notify”。
我们将使用Python编写用于这个“Web服务”的专用TOKEN。
获取并保存专用TOKEN,在执行时使用该TOKEN进行身份验证,并通过向特定URL发送POST请求来发送通知。
根据这篇文章,我们已经获取了TOKEN并将在执行过程中使用它,所以请记下在某个地方备忘。

import pika
import json
import requests
import os

LINE_URL = "https://notify-api.line.me/api/notify"
line_token = os.environ['LINE_TOKEN'] #環境変数からTOKENを持ってくる
message_format = '''
時刻: {time:s}
場所: {place:s}
カメラ: {camera:s}
検出された物体: {object:s}
'''


def main():
    # mqttに接続する
    credentials = pika.PlainCredentials('guest', 'guest')
    connect_param = pika.ConnectionParameters(
        host='rabbitmq',  #docker-compsoe-amqpでcontainerにつけたホスト名
        credentials=credentials
    )
    connection = pika.BlockingConnection(connect_param)
    channel = connection.channel()

    # line認証用のtoken
    line_headers = {"Authorization": "Bearer {}".format(line_token)}

    for method_frame, properties, body in channel.consume(
        'test',
        inactivity_timeout=30  # 30秒新規メッセージがなければbreakする
    ):
        if method_frame is None:
            break

        message = json.loads(body)
        if 'vehicle' in message['object'].keys():
            obj_data = '{} {} {}'.format(
                message['object']['vehicle']['make'],
                message['object']['vehicle']['model'],
                message['object']['vehicle']['color'],
            )
        elif 'person' in message['object'].keys():
            obj_data = 'Person {} {}'.format(
                message['object']['person']['age'],
                message['object']['person']['gender']
            )
        else:
            obj_data = ''
        payload = {
            "message": message_format.format(
                time=message['@timestamp'],
                place='{}_{}({})'.format(
                    message['place']['id'],
                    message['place']['name'],
                    message['place']['type'],
                ),
                camera=message['sensor']['id'],
                object=obj_data,
            )
        }
        r = requests.post(
            LINE_URL,
            headers=line_headers,
            params=payload
        )
        channel.basic_ack(method_frame.delivery_tag)

    # consumerをキャンセルし、pending中のメッセージがあればreturn
    requeued_messages = channel.cancel()
    print('Requeued %i messages' % requeued_messages)
    connection.close()


if __name__ == '__main__':
    main()

载荷 = 在这里,我们正在处理通过DeepStream SDK从AMQP传递的消息。
来自DeepStream Test 4的消息以json格式每30帧发送一次(DeepStream Test 4的设置)。
消息中包含以下格式中存储的一个检测到的对象的信息:
保存在place中的拍摄地点信息,保存在camera中的相机信息,时间,提取物体信息。

{
    "messageid": "a8c57c62-5e25-478d-a909-3ab064cbf11f",
    "mdsversion": "1.0",
    "@timestamp": "2019-11-17T13:42:39.807Z",
    "place": {
        "id": "1",
        "name": "XYZ",
        "type": "garage",
        "location": {
            "lat": 30.32,
            "lon": -40.55,
            "alt": 100.0
        },
        "aisle": {
            "id": "walsh",
            "name": "lane1",
            "level": "P2",
            "coordinate": {
                "x": 1.0,
                "y": 2.0,
                "z": 3.0
            }
        }
    },
    "sensor": {
        "id": "CAMERA_ID",
        "type": "Camera",
        "description": "\"Entrance of Garage Right Lane\"",
        "location": {
            "lat": 45.293701447,
            "lon": -75.8303914499,
            "alt": 48.1557479338
        },
        "coordinate": {
            "x": 5.2,
            "y": 10.1,
            "z": 11.2
        }
    },
    "analyticsModule": {
        "id": "XYZ",
        "description": "\"Vehicle Detection and License Plate Recognition\"",
        "source": "OpenALR",
        "version": "1.0",
        "confidence": 0.0
    },
    "object": {
        "id": "-1",
        "speed": 0.0,
        "direction": 0.0,
        "orientation": 0.0,
        "vehicle": {
            "type": "sedan",
            "make": "Bugatti",
            "model": "M",
            "color": "blue",
            "licenseState": "CA",
            "license": "XX1234",
            "confidence": 0.0
        },
        "bbox": {
            "topleftx": 585,
            "toplefty": 472,
            "bottomrightx": 642,
            "bottomrighty": 518
        },
        "location": {
            "lat": 0.0,
            "lon": 0.0,
            "alt": 0.0
        },
        "coordinate": {
            "x": 0.0,
            "y": 0.0,
            "z": 0.0
        }
    },
    "event": {
        "id": "4f8436ab-c611-4257-8b83-b9134c6cab0d",
        "type": "moving"
    },
    "videoPath": ""
}

使用Dockerfile创建包含上述consumer的容器映像。首先安装Python3,然后使用pip安装所需的Python包。最后将源代码复制进去即可。

FROM ubuntu:18.04

RUN apt-get update \
    && apt-get install -y python3-pip python3-dev \
    && cd /usr/local/bin \
    && ln -s /usr/bin/python3 python \
    && pip3 install --upgrade pip \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt /tmp/requirements.txt
RUN pip3 install -r /tmp/requirements.txt

COPY src /opt/src
WORKDIR /opt/src

CMD ["python3", "./receive.py"]
pika
requests

执行

使用docker-compose启动DeepStream SDK和consumer。

version: "2.3"
services:
  deepstream:
    image: nvcr.io/nvidia/deepstream:4.0.1-19.09-samples
    runtime: nvidia
    hostname: deepstream
    container_name: deepstream
    command: >
      /opt/nvidia/deepstream/deepstream-4.0/bin/deepstream-test4-app
      -i /root/videos/test.h264
      -p /opt/nvidia/deepstream/deepstream-4.0/lib/libnvds_amqp_proto.so
      -c cfg_amqp.txt
      -t deepstream
      --conn-str rabbitmq;5672;guest
    cap_add:
      - SYSLOG
    working_dir: /root/deepstream_sdk_v4.0.1_x86_64/sources/apps/sample_apps/deepstream-test4
    networks:
      - deepstream-line_default
    volumes:
      - ./videos:/root/videos
      - /tmp/.X11-unix:/tmp/.X11-unix
    environment:
      - DISPLAY=$DISPLAY
  consumer:
    build: ./docker
    hostname: consumer
    container_name: consumer
    environment:
      LINE_TOKEN: "XXX"
    networks:
      - deepstream-line_default
networks:
  deepstream-line_default:
    external: true

请将获取到的TOKEN粘贴到”XXXXXXXX”处。这样,您就可以收到通知了。

当执行以下操作时,处理程序将开始运行。

xhost + # dockerから結果のdisplay表示ができるように
docker-compose build # consumerのbuild処理
docker-compose up -d

由于这个假设是针对有显示屏的环境的,所以如果要在服务器或其他地方运行,需要进行一些简单的修正。

结果 (jié guǒ)

当运行docker-compose up时,会启动视频识别处理,并通过LINE发送通知。

result_all.gif

在电脑版LINE上,通知会以以下方式传达给您,但在手机上则会如下所示。

Screenshot_20191206-212712_LINE.jpg

最后

我們介紹了如何使用 DeepStream SDK 進行快速視頻識別並通過 LINE 進行通知的方法。雖然本次使用了普通伺服器,但是通過使用 deepstream-l4t 容器,應該也可以在像 Jetson 的邊緣 GPU 機器上進行相同處理。這擴大了我們自己製作 AI 相機之夢想的可能性。

另外,这次在DeepStream SDK的应用程序中,我们无法使用自己喜欢的识别模型,也无法更改输出数据的格式。如果有机会的话,我们将会介绍它。

非常感谢您一直阅读到最后。

为了加速在NVIDIA的GPU上进行深度学习推理处理而设计的库。我们之前有使用过TensorRT 5.0和Jetson Nano进行推理加速等方面的文章。↩

如果在没有显示器的服务器上运行,可以通过从docker-compose.yml文件的deepstream部分中移除环境变量并在命令行中添加–no-display选项来执行。↩

广告
将在 10 秒后关闭
bannerAds