EII(Edge Insights for Industrial) 使用说明

一、简介

Edge Insights for Industrial

EII Documentation

github

Edge Insights for Industrial 是一组预先验证的成分,用于在边缘计算节点上集成视频和时间序列数据分析。

它有助于解决各种工业和制造用途,包括在整个工厂车间的各种硬件节点上进行数据收集、存储和分析。

1.软件架构

image

2.EII中的服务

Common EII services

  1. EtcdUI
  2. InfluxDBConnector
  3. OpcuaExport - Optional service to read from VideoAnalytics container to publish data to opcua clients
  4. RestDataExport - Optional service to read the metadata and image blob from InfluxDBConnector and ImageStore services respectively

Video related services

  1. VideoIngestion
  2. VideoAnalytics
  3. Visualizer
  4. WebVisualizer
  5. ImageStore
  6. AzureBridge
  7. FactoryControlApp - Optional service to read from VideoAnalytics container if one wants to control the light based on defective/non-defective data

Timeseries related services

  1. Telegraf
  2. Kapacitor
  3. Grafana
  4. ZMQ Broker

3.其他功能组件

1)TLS Server

Training and Learning Suite 2.0 (TLS) 使数据工程师只需点击几下即可轻松注释图像并创建 AI 模型。

2)TLS Remote Agent

是EII中的通信代理,与Training and Learning Suite(TLS)服务器交互,是EII的另一个模块应用,通过Web用户界面提供深度学习模型训练能力。TLS 可以设置为深度学习训练服务器,可以在单独的系统中或与 EII 相同的本地系统中。TLS 和 EII 都使用 mqtt 协议通过 RabbitMQ 代理进行通信。

二、安装

1.系统要求

  • Target System

    • One of the following processors:

      • 6th, 7th, or 8th generation Intel® Core™ processor.
      • 6th, 7th, or 8th generation Intel® Xeon® processor.
      • Intel® Pentium® processor N4200/5, N3350/5, N3450/5 with Intel® HD Graphics.
    • At least 16 GB RAM for video ingestion and analytics. (At least 2 GB RAM for time-series ingestion and analytics.)

    • At least 64 GB hard drive.

    • An Internet connection.

    • Ubuntu* 18.04.3 LTS

2.通过Intel官网下载安装

镜像免编译,从官方hub下载

1)下载安装包

  • 下载 Edge Insights for Industrial package,文件名为 edge_insights_industrial.zip。

    • 根据所用功能选择
    2021-08-11 15-29-29 的屏幕截图.png
    • 保存product key,安装需要使用。

2)安装EII

  • 解压安装包

    $ unzip edge_insights_industrial.zip
    $ ls
    config_install.yml  edgesoftware  edgesoftware_configuration.xml  readme.txt
    
  • 配置安装

    可以使用config_install.yml文件配置安装

    [图片上传失败...(image-dbac42-1634094655794)]

    • dev_mode: 安装模式

      • true:开发模式
        • 会关闭安全和加密,方便用户自定义程序运行,一般用于评估和开发。
      • false:产品模式
        • 开启安全和加密。
    • remove_previous_eis:

      • true:在安装EII前移除已经存在的容器。
    • default_use_case: 如果下载了“Video Analytics + Time Series” user case版本的EII,可以有如下的user case选择配置。

      • video-timeseries: Includes modules for video and timeseries, i.e., the full use-case.
      • video: Includes only video modules and adds databases for storing images and metadata of analytics results.
      • time-series: Includes modules for time-series data ingestion, storage and analytics.

      备注:这些user case其实对应Edge_Insights_for_Industrial_2.6/IEdgeInsights/build/usecases/目录下yml文件,用于选择安装对应的容器app。

  • 运行安装

    apt-get install apt-utils
    
    cd edge_insights_industrial/
    chmod 775 edgesoftware
    sudo su
    ./edgesoftware install
      # 输入product key
      Starting the setup...
        ESB CLI version: 2021.3
        Target OS: Ubuntu 18.04
        Python version: 3.6.9
        Checking Internet connection
        Connected to the Internet
        Validating product key
        Successfully validated Product Key
        Checking for prerequisites
        Installing prerequisites. This may take some time...
      ...
      --------Succesfuly installed prerequisites--------
        All dependencies met
        -------------------SYSTEM INFO--------------------
        Package Name: Edge Insights for Industrial 2.6
        Product Name: ELSKY M600se-M700se
        CPU SKU: Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz
        Memory Size: 16 GB
        Operating System: Ubuntu 18.04 LTS
        Kernel Version: 4.15.0-154-generic
        Accelerator(VPU): 1
        CPU Utilization: 0.5%
        Available Disk Space: 42 GB
        ...
        Successfully installed eii_installer took 18 minutes 25.67 seconds
        Installation of package complete
        ***Recommended to reboot system after installation***
        +--------------------------+-----------------------------+---------+
        |            Id            |            Module           |  Status |
        +--------------------------+-----------------------------+---------+
        | 5f21392e9e63c9002a6fd88d | Docker Community Edition CE | SUCCESS |
        | 60e327614c1e9d002a6d6a7a |        Docker Compose       | SUCCESS |
        | 60c727ad4b40e5002ad9b795 |        eii installer        | SUCCESS |
        +--------------------------+-----------------------------+---------+
    
  • 已安装的容器镜像

    通过edgesoftware_configuration.xml文件从Edge Software Hub下载。

    $ docker images
    REPOSITORY                                    TAG       IMAGE ID       CREATED          SIZE
    ia_factoryctrl_app                            2.6       c0450a608f15   8 minutes ago    417MB
    ia_common                                     2.6       cbdedd05dc26   9 minutes ago    1.82GB
    ia_eiibase                                    2.6       e14cede0060b   15 minutes ago   936MB
    openedgeinsights/ia_video_ingestion           2.6       7e07ff0b3451   13 days ago      2.86GB
    openedgeinsights/ia_zmq_broker                2.6       d7a13dcfba80   13 days ago      318MB
    openedgeinsights/ia_web_visualizer            2.6       5bbe38bd3e84   13 days ago      2.21GB
    openedgeinsights/ia_visualizer                2.6       528bd7e26603   13 days ago      2.23GB
    openedgeinsights/ia_video_analytics           2.6       ffe3b44354e1   13 days ago      2.82GB
    openedgeinsights/ia_opcua_export              2.6       71ed8025d914   13 days ago      357MB
    openedgeinsights/ia_etcd_ui                   2.6       cd272efd64dc   13 days ago      545MB
    openedgeinsights/ia_azure_bridge              2.6       f22f543b059c   13 days ago      496MB
    openedgeinsights/ia_imagestore                2.6       6dda3f552598   2 weeks ago      380MB
    openedgeinsights/ia_influxdbconnector         2.6       ec879fa33bce   2 weeks ago      455MB
    openedgeinsights/ia_rest_export               2.6       9811aa474d89   2 weeks ago      329MB
    ubuntu                                        20.04     1318b700e415   2 weeks ago      72.8MB
    openedgeinsights/ia_azure_simple_subscriber   2.6       da665f9f3983   4 weeks ago      164MB
    openedgeinsights/ia_etcd_provision            2.6       2683c27dd3c6   4 weeks ago      197MB
    openedgeinsights/ia_etcd                      2.6       2e8ec01a3d93   4 weeks ago      119MB
    

3)EII配置文件安装路径

$ tree  /opt/intel/eii/
├── data
│   ├── etcd
│   │   ├── data
│   │   ├── fixtures
│   │   │   └── peer
│   │   │       ├── cert.pem
│   │   │       └── key.pem
│   │   └── member
│   │       ├── snap
│   │       │   └── db
│   │       └── wal
│   │           ├── 0000000000000000-0000000000000000.wal
│   │           └── 0.tmp
│   ├── image-store-bucket  # 存储持久化图像
│   └── influxdata  #  influxdb 的备份数据
│       └── influxdb
│           ├── data
│           │   ├── datain
│           │   │   ├── autogen
│           │   │   └── _series
│           │   └── _internal
│           │       ├── monitor
│           │       └── _series
│           ├── meta
│           │   └── meta.db
│           └── wal
│               ├── datain
│               │   └── autogen
│               └── _internal
│                   └── monitor
├── model_repo
├── rde_server_cert.der
├── rde_server_key.der
├── saved_images
├── sockets     # 存储 IPC ZMQ 套接字文件
│   └── camera1_stream
└── tools_output

4)edgesoftware工具

EII 服务安装管理工具

Usage: edgesoftware [OPTIONS] COMMAND [ARGS]...
A CLI wrapper for management of Intel® Edge Software Hub packages

Options:
  -v, --version   Show the version number and exit.
  ––help          Show this message and exit.

Commands:
  download  Download modules of a package.
  export    Exports the modules installed as a part of a package.
  install   Install modules of a package.
  list      List the modules of a package.
  log       Show log of CLI events.
  pull      Pull Docker image.
  uninstall Uninstall the modules of a package.
  update    Update the modules of a package.
  upgrade   Upgrade a package. 

使用示例

$ cd edge_insights_industrial/
# 列举可以下载的服务
$ ./edgesoftware list --default
Modules in the recommended configuration for 60c862aeb3d614002abd9c6a
+--------------------------+------------------------------------------+---------+
|            ID            |                  Module                  | Version |
+--------------------------+------------------------------------------+---------+
| 5f21392e9e63c9002a6fd88d |      Docker Community Edition (CE)*      | 20.10.5 |
| 60e327614c1e9d002a6d6a7a |             Docker Compose*              |  1.29.0 |
| 60c7274a4b40e5002ad9b793 |             Video Analytics              |   2.6   |
| 60c8502c4b40e5002ad9b7bf |               Image Store                |   2.6   |
| 60c739334b40e5002ad9b7a0 |                InfluxDB*                 |   2.6   |
| 60c854824b40e5002ad9b7c6 |            Video Custom UDFs             |   2.6   |
| 60c73a174b40e5002ad9b7a8 |    Sample Factory Control Application    |   2.6   |
| 60c728244b40e5002ad9b79c |          Time Series Analytics           |   2.6   |
| 60caceef3c2973002abe2b3b | Intel® Training and Learning Suite (TLS) |   2.6   |
| 60c73ae14b40e5002ad9b7ac |                ZMQ Broker                |   2.6   |
| 60caceb13c2973002abe2b38 |           Device Manageability           |   2.6   |
| 60c73b324b40e5002ad9b7af |             Azure* IOT Edge              |   2.6   |
| 60c727f14b40e5002ad9b799 |               Data Export                |   2.6   |
| 60c726844b40e5002ad9b78b |                 EII Core                 |   2.6   |
| 60c850574b40e5002ad9b7c0 |           Video-Web-Visualizer           |   2.6   |
| 60c73a864b40e5002ad9b7ab |               Rest Export                |   2.6   |
| 60c739934b40e5002ad9b7a7 |              OPC-UA Export               |   2.6   |
| 60c7385a4b40e5002ad9b79d |                ts-grafana                |   2.6   |
| 60c738834b40e5002ad9b79e |               ts-kapacitor               |   2.6   |
| 60c738d94b40e5002ad9b79f |               ts-telegraf                |   2.6   |
| 60c846a94b40e5002ad9b7ba |             TLS Remote Agent             |   2.6   |
| 60c847e24b40e5002ad9b7bc |             Video-Ingestion              |   2.6   |
| 60c8507e4b40e5002ad9b7c3 |         Video-Native-Visualizer          |   2.6   |
| 60c8549b4b40e5002ad9b7c9 |             Video-Analytics              |   2.6   |
| 60c854ba4b40e5002ad9b7cc |               Video Common               |   2.6   |
| 60c727184b40e5002ad9b791 |               EII Samples                |   2.6   |
| 60c85f764b40e5002ad9b7d1 |                EII-Tools                 |   2.6   |
| 60c726ee4b40e5002ad9b78f |              EII-MessageBus              |   2.6   |
| 60c726c84b40e5002ad9b78d |               EII-C-Utils                |   2.6   |
| 60dae3b34472c4002a91d2d8 |               EII ETCD UI                |   2.6   |
| 60c727ad4b40e5002ad9b795 |              EII Installer               |   2.6   |
| 60e3270c4c1e9d002a6d6a78 |         Weld Porosity Detection          |   2.6   |
+--------------------------+------------------------------------------+---------+

# 卸载所有服务
$ ./edgesoftware uninstall -a

# 根据根据config_install.yml安装服务
$ ./edgesoftware install 

3.通过git下载【推荐】

无需密钥,开源且商用免费,镜像需编译。

安装 repo 工具

$ curl https://storage.googleapis.com/git-repo-downloads/repo > repo
$ sudo mv repo /bin/repo
$ sudo chmod a+x /bin/repo

Pulling in the eii-manifests repo and using a manifest file

mkdir -p eii-manifests && cd eii-manifests
repo init -u "https://github.com/open-edge-insights/eii-manifests.git"
    # 将在当前目录下创建一个 .repo 文件夹,其中包含eii-manifests源代码.repo/manifests。
  • 不指定分支默认抓取master分支,且manifest默认选择default.xml(time_series.xml)。

    $ tree eii-manifests/.repo/manifests
    ├── core.xml
    ├── data_export.xml
    ├── default.xml
    ├── include
    │   ├── base_eii.xml
    │   ├── base_time_series.xml
    │   └── base_video.xml
    ├── LICENSE
    ├── README.md
    ├── time_series.xml
    └── video.xml
    
    # 切换不同的manifest文件
    $ repo init -m video.xml
    
    # 查看当前manifest文件配置
    $ repo manifest
    <manifest>
      <remote fetch="https://github.com/open-edge-insights" name="origin"/>
      
      <default remote="origin" revision="master" sync-j="4"/>
      
      <project name="eii-azure-bridge" path="IEdgeInsights/AzureBridge"/>
      <project name="eii-c-utils" path="IEdgeInsights/common/util/c"/>
      <project name="eii-core" path="IEdgeInsights"/>
      <project name="eii-etcd-ui" path="IEdgeInsights/EtcdUI"/>
      <project name="eii-factoryctrl" path="IEdgeInsights/FactoryControlApp"/>
      <project name="eii-influxdb-connector" path="IEdgeInsights/InfluxDBConnector"/>
      <project name="eii-messagebus" path="IEdgeInsights/common/libs/EIIMessageBus"/>
      <project name="eii-opcua-export" path="IEdgeInsights/OpcuaExport"/>
      <project name="eii-rest-data-export" path="IEdgeInsights/RestDataExport"/>
      <project name="eii-samples" path="IEdgeInsights/Samples"/>
      <project name="eii-tools" path="IEdgeInsights/tools"/>
      <project name="eii-zmq-broker" path="IEdgeInsights/ZmqBroker"/>
      <project name="video-analytics" path="IEdgeInsights/VideoAnalytics"/>
      <project name="video-common" path="IEdgeInsights/common/video"/>
      <project name="video-custom-udfs" path="IEdgeInsights/CustomUdfs"/>
      <project name="video-imagestore" path="IEdgeInsights/ImageStore"/>
      <project name="video-ingestion" path="IEdgeInsights/VideoIngestion"/>
      <project name="video-native-visualizer" path="IEdgeInsights/Visualizer"/>
      <project name="video-web-visualizer" path="IEdgeInsights/WebVisualizer"/>
    </manifest>
    
  • 同步工程

    repo sync
    

4.EII工程文件夹说明

~/Downloads/eii-manifests$ tree -L 3
.
└── IEdgeInsights
    ├── AzureBridge     # EII 服务
    │   ├── config
    │   ├── config.json
    │   ├── docker-compose.yml
    │   ├── LICENSE
    │   ├── modules
    │   ├── README.md
    │   └── tools
    ├── build       # 镜像编译
    │   ├── ansible
    │   ├── builder_config.json
    │   ├── builder.py
    │   ├── builder_schema.json
    │   ├── common_config.json
    │   ├── common-docker-compose.yml
    │   ├── deploy
    │   ├── docker_security_recommendation.md
    │   ├── eii_uninstaller.sh
    │   ├── helm-eii
    │   ├── pre_requisites.sh
    │   ├── provision
    │   ├── remote_logging
    │   ├── requirements.txt
    │   └── usecases    # 根据不同场景定义的镜像集合
    ├── common
    │   ├── cmake
    │   ├── dockerfiles
    │   ├── eii_libs_installer.sh
    │   ├── libs
    │   ├── README.md
    │   ├── util
    │   └── video
    │       ├── config.json
    │       ├── docker-compose.yml
    │       ├── Dockerfile.openvino
    │       ├── Dockerfile.videocommon
    │       ├── install_pip_requirements.py
    │       ├── LICENSE
    │       ├── UDFLoader
    │       └── udfs    # 用户定义函数
    ├── CONTRIBUTING.md
    ├── CustomUdfs  # 单独容器化的Udf
    │   ├── GVASafetyGearIngestion
    │   ├── LICENSE
    │   ├── NativePclIngestion
    │   ├── NativeSafetyGearAnalytics
    │   ├── NativeSafetyGearIngestion
    │   ├── PyMultiClassificationIngestion
    │   ├── PySafetyGearAnalytics
    │   ├── PySafetyGearIngestion
    │   └── README.md
    ├── Etcd_Secrets_Configuration.md
    ├── EtcdUI
    │   ├── config.json
    │   ├── docker-compose.yml
    │   ├── Dockerfile
    │   ├── eii_nginx_dev.conf
    │   ├── eii_nginx_prod.conf
    │   ├── etcdkeeper
    │   ├── helm
    │   ├── img
    │   ├── nginx.conf
    │   ├── README.md
    │   ├── schema.json
    │   └── start_etcdkeeper.py
    ├── FactoryControlApp
    │   ├── config.json
    │   ├── docker-compose.yml
    │   ├── Dockerfile
    │   ├── factoryctrl_app.py
    │   ├── helm
    │   ├── HW_Configuration.pdf
    │   ├── __init__.py
    │   ├── LICENSE
    │   ├── README.md
    │   ├── requirements.txt
    │   └── schema.json
    ├── ImageStore
    │   ├── common
    │   ├── config.json
    │   ├── docker-compose.yml
    │   ├── Dockerfile
    │   ├── go
    │   ├── helm
    │   ├── isconfigmgr
    │   ├── LICENSE
    │   ├── main.go
    │   ├── README.md
    │   ├── schema.json
    │   ├── submanager
    │   └── test
    ├── InfluxDBConnector
    │   ├── common
    │   ├── config
    │   ├── config.json
    │   ├── configmanager
    │   ├── dbmanager
    │   ├── docker-compose.yml
    │   ├── Dockerfile
    │   ├── helm
    │   ├── InfluxDBConnector.go
    │   ├── influx_start.sh
    │   ├── LICENSE
    │   ├── pubmanager
    │   ├── README.md
    │   ├── schema.json
    │   ├── startup.sh
    │   ├── submanager
    │   └── test
    ├── Installing_docker_pre_requisites.md
    ├── LICENSE
    ├── licenses
    │   ├── ......
    │   └── wjelement
    ├── OpcuaExport
    │   ├── config.json
    │   ├── docker-compose.yml
    │   ├── Dockerfile
    │   ├── helm
    │   ├── LICENSE
    │   ├── OpcuaBusAbstraction
    │   ├── OpcuaExport.go
    │   ├── README.md
    │   └── schema.json
    ├── README.md
    ├── RestDataExport
    │   ├── config.json
    │   ├── docker-compose.yml
    │   ├── Dockerfile
    │   ├── etcd_update.py
    │   ├── helm
    │   ├── LICENSE
    │   ├── README.md
    │   ├── requirements.txt
    │   ├── RestDataExport.go
    │   └── schema.json
    ├── Samples
    │   ├── cpp_sample_app
    │   ├── go_sample_app
    │   ├── LICENSE
    │   ├── python_sample_app
    │   └── README.md
    ├── tools
    │   ├── Benchmarking
    │   ├── DiscoverHistory
    │   ├── EmbPublisher
    │   ├── EmbSubscriber
    │   ├── GigEConfig
    │   ├── HttpTestServer
    │   ├── JupyterNotebook
    │   ├── LICENSE
    │   ├── mqtt
    │   ├── SWTriggerUtility
    │   ├── TimeSeriesProfiler
    │   └── VideoProfiler
    ├── TROUBLESHOOT.md
    ├── VideoAnalytics
    │   ├── CMakeLists.txt
    │   ├── config.json
    │   ├── docker-compose-dev.override.yml
    │   ├── docker-compose.yml
    │   ├── Dockerfile
    │   ├── helm
    │   ├── include
    │   ├── LICENSE
    │   ├── README.md
    │   ├── schema.json
    │   ├── src
    │   └── va_classifier_start.sh
    ├── VideoIngestion
    │   ├── CMakeLists.txt
    │   ├── config.json
    │   ├── docker-compose-dev.override.yml
    │   ├── docker-compose.yml
    │   ├── Dockerfile
    │   ├── docs
    │   ├── gentl_producer_env.sh
    │   ├── helm
    │   ├── img
    │   ├── include
    │   ├── install_gencamsrc_gstreamer_plugin.sh
    │   ├── LICENSE
    │   ├── models
    │   ├── README.md
    │   ├── schema.json
    │   ├── src
    │   ├── src-gst-gencamsrc
    │   ├── test_videos
    │   └── vi_start.sh
    ├── Visualizer
    │   ├── config.json
    │   ├── docker-compose.yml
    │   ├── Dockerfile
    │   ├── helm
    │   ├── LICENSE
    │   ├── README.md
    │   ├── requirements.txt
    │   ├── schema.json
    │   ├── visualize.py
    │   └── visualizer_start.sh
    ├── WebVisualizer
    │   ├── config.json
    │   ├── docker-compose.yml
    │   ├── Dockerfile
    │   ├── helm
    │   ├── LICENSE
    │   ├── README.md
    │   ├── requirements.txt
    │   ├── schema.json
    │   ├── templates
    │   ├── web_visualizer.py
    │   └── web_visualizer_start.sh
    └── ZmqBroker
        ├── CMakeLists.txt
        ├── config.json
        ├── docker-compose.yml
        ├── Dockerfile
        ├── examples
        ├── helm
        ├── include
        ├── LICENSE
        ├── README.md
        ├── schema.json
        ├── src
        └── tests

5.编译安装与卸载EII服务(基于git)

安装依赖

cd eii-manifests/IEdgeInsights/build
sudo bash pre_requisites.sh

修改环境变量

cd eii-manifests/IEdgeInsights/build
vim .env
# 基于开发模式编译
    设置DEV_MODE=true
# 修改DOCKER_REGISTRY
    DOCKER_REGISTRY=registry.xxx:5000/
    DOCKER_USERNAME=
    DOCKER_PASSWORD=

设置环境变量并构建合并文件

cd eii-manifests/IEdgeInsights/build

# 使用 builder.py构建容器服务
python3 builder.py -h
usage: builder.py [-h] [-f YML_FILE] [-v VIDEO_PIPELINE_INSTANCES]
                  [-d OVERRIDE_DIRECTORY]
optional arguments:
  -h, --help            show this help message and exit
  -f YML_FILE, --yml_file YML_FILE
                        Optional config file for list of services to include.
                        Eg: python3 builder.py -f usecases/video-streaming.yml
                        (default: None)
  -v VIDEO_PIPELINE_INSTANCES, --video_pipeline_instances VIDEO_PIPELINE_INSTANCES
                        Optional number of video pipeline instances to be
                        created. Eg: python3 builder.py -v 6 (default: 1)
  -d OVERRIDE_DIRECTORY, --override_directory OVERRIDE_DIRECTORY
                        Optional directory consisting of of benchmarking
                        configs to be present ineach app directory. Eg:
                        python3 builder.py -d benchmarking (default: None)
                        
# 从IEdgeInsights顶层目录开始,轮询各级目录,结合所有EII服务生成合并文件
$ python3 builder.py 
$ ls docker-compose*
docker-compose-build.yml  docker-compose-push.yml  docker-compose.yml

    # 或者从IEdgeInsights顶层目录开始,根据不同应用场景,根据所选择的usecae文件包含的EII服务生成合并文件
    $ python3 builder.py -f usecases/video-streaming.yml

为EII服务生成证书和编译ia_etcd、ia_etcd_provision

cd provision && sudo -E ./provision.sh ../docker-compose.yml && cd -
  • 从位于 .json 的 json 文件加载初始 ETCD 值build/provision/config/eii_config.json

  • 仅适用于安全模式,为每个应用程序生成 ZMQ 秘密/公钥并将它们放入 ETCD。

  • 生成所需的 X509 证书并将它们放入 etcd。

  • 所有服务器证书都将使用build/.env 中提到的 127.0.0.1、localhost 和 HOST_IP生成。

  • 生成/opt/intel/eii/目录,用于EII服务的运行

    $  tree /opt/intel/eii/
    /opt/intel/eii/
    ├── data
    │   ├── etcd
    │   │   ├── data
    │   │   └── member
    │   │       ├── snap
    │   │       │   └── db
    │   │       └── wal
    │   │           ├── 0000000000000000-0000000000000000.wal
    │   │           └── 0.tmp
    │   └── influxdata
    ├── model_repo
    ├── sockets
    └── tools_output
    

编译与运行EII服务

cd eii-manifests/IEdgeInsights/build
# 通过EII各服务的Dockerfile构建镜像
docker-compose -f docker-compose-build.yml build
# 只构建某个镜像
docker-compose -f docker-compose-build.yml build ia_etcd_ui
# 启动容器
docker-compose up -d
docker-compose ps
2021-08-27 09-58-11 的屏幕截图.png

卸载EII服务

$ ./eii_uninstaller.sh -h
Usage: ./eii_uninstaller.sh [-h] [-d]

 This script uninstalls previous EII version.
 Where:
    -h show the help
    -d triggers the deletion of docker images (by default it will not trigger)

 Example:
  1) Deleting only EII Containers and Volumes
    $ ./eii_uninstaller.sh

  2) Deleting EII Containers, Volumes and Images
    $ export EII_VERSION=2.6
    $ ./eii_uninstaller.sh -d
    above example will delete EII containers, volumes and all the docker images having 2.4 version.

6.builder说明

builder是基于python开发的,可自动生成所需的配置文件,以在单/多节点上部署 EII 服务的工具。

1)builder关键文件

  • build/.env:定义EII环境变量
  • build/builder.py:构建脚本
  • docker-compose.yml:运行指定的EII服务镜像
  • docker-compose-build.yml:构建指定的EII服务镜像
  • docker-compose-push.yml:推送EII服务镜像到Registry
  • [服务]/config.json:服务在部署后运行所需的配置,包括运行应用程序所需的config和interfaces。
    • EII服务之间通过ZMQ_TCP进行通信,每个服务可以在接口中定义server、Subscribers(订阅其他服务的主题)和Publishers(发布主题给其他服务订阅)
  • [服务]/schema.json:针对每个服务config.json的示例用法

2)usecases

$ tree usecases/
usecases/
├── all.yml
├── services.yml
├── time-series.yml
├── video-streaming-all-udfs.yml
├── video-streaming-azure.yml
├── video-streaming-storage.yml
├── video-streaming.yml
├── video-timeseries.yml
└── video.yml

builder可以根据不同的use-cases构建各种服务

# 生成多实例配置:为 3 个视频流用例流生成多实例
$ python3 builder.py -v 3 -f usecases/video-streaming.yml
  • 注意:多实例功能支持仅适用于视频管道,即usecases/video-streaming.yml单独用例,而不适用于任何其他用例 yml 文件。

3)provision

Provision是EII基于安全模式的示例,用于生成EII服务的证书。

$ tree provision/ -L 2
provision/
├── cert_core.py
├── Certificates    # 证书文件
│   │   ├── AzureBridge
│   │   │   ├── AzureBridge_client_certificate.pem
│   │   │   └── AzureBridge_client_key.pem
│   │   ├── ca
│   │   │   ├── ca_certificate.der
│   │   │   ├── ca_certificate.pem
│   │   │   └── ca_key.pem
│   │   ├── etcdserver
│   │   │   ├── etcdserver_server_certificate.pem
│   │   │   └── etcdserver_server_key.pem
│   │   ├── EtcdUI
│   │   │   ├── EtcdUI_client_certificate.pem
│   │   │   └── EtcdUI_client_key.pem
│   │   ├── EtcdUI_Server
│   │   │   ├── EtcdUI_Server_server_certificate.pem
│   │   │   └── EtcdUI_Server_server_key.pem
│   │   ├── FactoryControlApp
│   │   │   ├── FactoryControlApp_client_certificate.pem
│   │   │   └── FactoryControlApp_client_key.pem
│   │   ├── ImageStore
│   │   │   ├── ImageStore_client_certificate.pem
│   │   │   └── ImageStore_client_key.pem
│   │   ├── InfluxDBConnector
│   │   │   ├── InfluxDBConnector_client_certificate.pem
│   │   │   └── InfluxDBConnector_client_key.pem
│   │   ├── InfluxDBConnector_Server
│   │   │   ├── InfluxDBConnector_Server_server_certificate.pem
│   │   │   └── InfluxDBConnector_Server_server_key.pem
│   │   ├── OpcuaExport
│   │   │   ├── OpcuaExport_client_certificate.pem
│   │   │   └── OpcuaExport_client_key.pem
│   │   ├── OpcuaExport_Server
│   │   │   ├── OpcuaExport_Server_server_certificate.der
│   │   │   └── OpcuaExport_Server_server_key.der
│   │   ├── RestDataExport
│   │   │   ├── RestDataExport_client_certificate.pem
│   │   │   └── RestDataExport_client_key.pem
│   │   ├── RestDataExport_Server
│   │   │   ├── RestDataExport_Server_server_certificate.pem
│   │   │   └── RestDataExport_Server_server_key.pem
│   │   ├── root
│   │   │   ├── root_client_certificate.pem
│   │   │   └── root_client_key.pem
│   │   ├── VideoAnalytics
│   │   │   ├── VideoAnalytics_client_certificate.pem
│   │   │   └── VideoAnalytics_client_key.pem
│   │   ├── VideoIngestion
│   │   │   ├── VideoIngestion_client_certificate.pem
│   │   │   └── VideoIngestion_client_key.pem
│   │   ├── Visualizer
│   │   │   ├── Visualizer_client_certificate.pem
│   │   │   └── Visualizer_client_key.pem
│   │   ├── WebVisualizer
│   │   │   ├── WebVisualizer_client_certificate.pem
│   │   │   └── WebVisualizer_client_key.pem
│   │   ├── WebVisualizer_Server
│   │   │   ├── WebVisualizer_Server_server_certificate.pem
│   │   │   └── WebVisualizer_Server_server_key.pem
│   │   └── ZmqBroker
│   │       ├── ZmqBroker_client_certificate.pem
│   │       └── ZmqBroker_client_key.pem
├── cert_requirements.txt
├── config
│   ├── eii_config.json     # EII配置示例
│   ├── openssl.cnf
│   └── x509_cert_config.json       #  X509 证书配置
├── dep
│   ├── docker-compose-etcd.override.build.yml
│   ├── docker-compose-etcd.override.prod.yml
│   ├── docker-compose-etcd-provision.override.build.yml
│   ├── docker-compose-etcd-provision.override.prod.yml
│   ├── docker-compose-etcd-provision.yml
│   ├── docker-compose-etcd.yml
│   ├── Dockerfile
│   ├── Dockerfile.provision
│   ├── etcd_config_update.py
│   ├── etcd_create_user.sh
│   ├── etcd_enable_auth.sh
│   ├── etcd_health_check.sh
│   ├── etcd_provision.py
│   ├── requirements.txt
│   ├── start_etcd.sh
│   └── util.py
├── etcd_capture.py
├── etcd_capture.sh
├── gen_certs.py
├── paths.py
├── provision.sh
└── rootca

4)helm-eii

用于启用GenICam GigE 相机。

三.EII核心服务

1.ETCD-UI

一旦 EII 配置管理 (ia_etcd) 服务成功启动,用户可以通过以下步骤访问 ETCD Web UI。这允许用户对相应的 EII 容器服务进行配置更改。

  • 打开浏览器并输入地址:https://<host ip>:7071/etcdkeeper/(当 EII 在安全模式下运行时)。在这种情况下,必须在浏览器中导入 CA 证书。
    • 对于非安全模式,即 DEV 模式,可以通过 http://<host ip>:7070/etcdkeeper/ 访问。
  • 点击标题的版本选择ETCD的版本,默认为 V3,重新开放将记住你的选择。
  • 右键单击树节点以添加或删除。
  • 对于安全模式,需要身份验证。需要在对话框中输入用户名和密码。
  • 用户名是“root”,默认密码位于build/provision/dep/docker-compose-provision.override.prod.yml环境部分下的 ETCD_ROOT_PASSWORD 键(默认eii123)。
  • 该服务可以从远程系统的地址访问:https://(HOST_IP):7071(当 EII 在安全模式下运行时)。在这种情况下,必须在浏览器中导入 CA 证书。对于不安全模式,即 DEV 模式,可以访问 http://(HOST_IP):7071

[站外图片上传中...(image-28682f-1634094655794)]

注意:

  • 如果 ETCD_ROOT_PASSWORD 更改,则必须重新配置 EII。

    $ cd [WORKDIR]/IEdgeInsights/build/provision
    $ sudo -E ./provision.sh <path_to_eii_docker_compose_file>
    
    $ # eq. $ sudo -E ./provision.sh ../docker-compose.yml
    
  • 只有基于 VideoIngestion 和 VideoAnalytics 的服务才会监视任何修改,对这些键所做的任何更改都将在行时反映在 EII 中。

    • etcd中服务配置的修改在数据库中记录
    • 如果修改AI处理设备,依然要重启容器才能生效
  • 对于对任何其他键所做的更改,需要重新启动 EII 堆栈才能生效。

    $ cd [工作目录]/IEdgeInsights/build
    # $ docker-compose -f docker-compose-build.yml build ia_etcd_ui
    $ docker-compose down
    $ docker-compose up -d
    

2.InfluxDBConnector

InfluxDBConnector 将订阅 InfluxDB 并根据 PubTopics、SubTopics 和 QueryTopics 配置启动 zmq 发布者线程、zmq 订阅者线程和 zmq 请求回复线程。

  • zmq 订阅者线程连接到 zmq 总线的 PUB 套接字上,VideoAnalytics 在其上发布数据并将其推送到 InfluxDB

  • zmq 发布者线程将发布 Telegraf 摄取的点数据和点数据分析得出的分类器结果。

  • zmq 回复请求服务将接收 InfluxDB 选择查询和响应以及历史数据。

{
    "config": {
        "influxdb": {
            "retention": "1h30m5s",
            "username": "admin",
            "password": "admin123",
            "dbname": "datain",
            "ssl": "True",
            "verifySsl": "False",
            "port": "8086"
        },
        "pub_workers": "5",
        "sub_workers": "5",
        "ignore_keys": [ "defects" ],
        "tag_keys": [],
        "blacklist_query": ["CREATE","DROP","DELETE","ALTER","<script>"]
    },
    "interfaces": {
        "Servers": [
            {
                "Name": "InfluxDBConnector",
                "Type": "zmq_tcp",
                "EndPoint": "0.0.0.0:65145",
                "AllowedClients": [
                    "*"
                ]
            }
        ],
        "Publishers": [
            {
                "Name": "PointData",
                "Type": "zmq_tcp",
                "EndPoint": "0.0.0.0:65033",
                "Topics": [
                    "point_data"
                ],
                "AllowedClients": [
                    "*"
                ]
            },
            {
                "Name": "PointClassifierResults",
                "Type": "zmq_tcp",
                "EndPoint": "0.0.0.0:65034",
                "Topics": [
                    "point_classifier_results"
                ],
                "AllowedClients": [
                    "*"
                ]
            },
            {
                "Name": "HumidityClassifierResults",
                "Type": "zmq_tcp",
                "EndPoint": "0.0.0.0:65030",
                "Topics": [
                    "humidity_classifier_results"
                ],
                "AllowedClients": [
                    "*"
                ]
            },
            {
                "Name": "TSData",
                "Type": "zmq_tcp",
                "EndPoint": "0.0.0.0:65031",
                "Topics": [
                    "ts_data"
                ],
                "AllowedClients": [
                    "*"
                ]
            },
            {
                "Name": "RFCResults",
                "Type": "zmq_tcp",
                "EndPoint": "0.0.0.0:65032",
                "Topics": [
                    "rfc_results"
                ],
                "AllowedClients": [
                    "*"
                ]
            }
        ],
        "Subscribers": [
            {
                "Name": "video",
                "Type": "zmq_tcp",
                "EndPoint": "ia_video_analytics:65013",
                "PublisherAppName": "VideoAnalytics",
                "Topics": [
                    "camera1_stream_results"
                ]
            }   
        ]
        }
}

3.RestDataExport

RestDataExport 服务从 EIIMessageBus 订阅任意主题,并开始通过 POST 请求向任何外部 HTTP 服务器发布元数据。它有一个内部 HTTP 服务器运行,以响应来自任何 HTTP 客户端的对所需帧的任何 GET 请求。

image

1)运行HttpTestServer并修改配置

RestDataExport默认并没有指定httpserver,因此需要运行一个httpserver,并且将订阅的主题指定到httpserver

$ cd IEdgeInsights/tools/HttpTestServer
# 基于本地批生产证书
$ ./generate_testserver_cert.sh 0.0.0.0
# 基于开发模式运行,无需密钥

$ go mod init HttpTestServer && go mod tidy
$ go run TestServer.go --dev_mode true --host=0.0.0.0 --port=8082 --rdehost=localhost --rdeport=8087

修改/RestDataExport/config

{
    "camera1_stream_results": "http://172.17.0.1:8082",
    "http_server_ca": "/opt/intel/eii/cert.pem",
    "point_classifier_results": "http://172.17.0.1:8082",
    "rest_export_server_host": "0.0.0.0",
    "rest_export_server_port": "8087"
}

数据交互

# ia_rest_export post request
ia_rest_export                     | I0828 02:54:19.587145       1 RestDataExport.go:414] Imghandle ef4519ab02 and md5sum [212 29 140 217 143 0 178 4 233 128 9 152 236 248 66 126]
ia_rest_export                     | I0828 02:54:19.588396       1 RestDataExport.go:308] Response : Received a POST request

# HttpTestServer接收数据
I0828 10:54:19.580769   10928 TestServer.go:241] Received metadata : map[channels:3 encoding_level:95 topic:camera1_stream_results width:1920 defects:[map[br:[1006 689] tl:[958 635] type:0] map[br:[1297 768] tl:[1242 733] type:0] map[br:[1315 913] tl:[1270 877] type:1]] encoding_type:jpeg frame_number:1306 height:1200 img_handle:ef4519ab02]
I0828 10:54:19.588081   10928 TestServer.go:189] imgHandle ef4519ab02 and md5sum [212 29 140 217 143 0 178 4 233 128 9 152 236 248 66 126]

# camera1_stream_results metadata
map[
    channels:3 
    encoding_level:95 
    topic:camera1_stream_results 
    width:1920 
    height:1200 
    defects:[
        map[br:[1006 689] tl:[958 635] type:0] 
        map[br:[1297 768] tl:[1242 733] type:0] 
        map[br:[1315 913] tl:[1270 877] type:1]] 
    encoding_type:jpeg 
    frame_number:1306 
    img_handle:ef4519ab02
]

2)RestExport API

RestExport API 源代码位于[WORK_DIR]/rest_export/RestExport.go 中。它负责读取 RestExport 配置并管理 RestExport 管道的生命周期。

  • RestExport POST Request API

    image
    • POST /metadata

      Example request:

      RestDataExport向HttpTestServer发送Metadata

      POST /metadata HTTP/1.1
      Host: localhost:8082    // HttpTestServer
      Accept: application/json
      

      Content

      HTTP/1.1 200 OK
      Vary: Accept
      Content-Type: application/json
      
      [{
          "channel":3,
          "topic":"camera1_stream_results",
          "encoding_level":100,
          "defects": "[]",
          "encoding_type":"jpg",
          "height":1200,
          "width":1920,
          "img_handle": "b7aa16b8",
          "user_data":1
      }]
      

      httpserver需要返回

      HTTP/1.1 200 OK
      Received a POST request
      
  • RestExport GET Request API

    image
    • GET /image

      Example request:

      GET /image?img_handle=$img_handle HTTP/1.1
      Host: localhost:8087    //Rest Export Server
      Accept: plaintext
      
      # 示例
      $ img_handle="ac474dbf71"
      $ curl http://10.55.5.27:8087/image?img_handle=$img_handle -o $img_handle.jpeg
      

      Example response:

      jpeg image data
      

3)示例config.json

{
    "config": {
        "camera1_stream_results": "http://<IP Address of Test Server>:8082",        // 目标http服务器
        "point_classifier_results": "http://<IP Address of Test Server>:8082",
        "http_server_ca": "/opt/intel/eii/cert.pem",
        "rest_export_server_host": "0.0.0.0",
        "rest_export_server_port": "8087"
    },
    "interfaces": {
        "Clients": [
            {
                "Name": "ImageStore",
                "ServerAppName": "ImageStore",
                "Type": "zmq_tcp",
                "EndPoint": "ia_imagestore:5669"
            }
        ],
        "Subscribers": [
            {
                "Name": "default",
                "Type": "zmq_tcp",
                "EndPoint": "ia_video_analytics:65013",
                "PublisherAppName": "VideoAnalytics",
                "Topics": [
                    "camera1_stream_results"
                ]
            },
            {
                "Name": "influxSub",
                "Type": "zmq_tcp",
                "EndPoint": "ia_influxdbconnector:65034",
                "PublisherAppName": "InfluxDBConnector",
                "Topics": [
                    "point_classifier_results"
                ]
            }
        ]
    }
}

4.VideoIngestion

VideoIngestion(VI) 模块主要负责将来自视频文件或 basler/RTSP/USB 相机等视频源的视频帧摄取到 EII 堆栈中以进行进一步处理。

[站外图片上传中...(image-f3d525-1634094655795)]

1)Video Ingestion 工作流程

  1. 应用程序通过 EII 配置管理器读取应用程序配置,其中包含ingestorencoding和 的详细信息udfs
  2. 根据摄取器配置,应用程序从视频文件或摄像头读取视频帧。
  3. [ 可选 ] 读取帧被传递到一个或多个链接的C++/python UDF 以进行任何预处理(通过 UDF 传递是可选的,如果不想对摄取的帧执行任何预处理,则不需要) 。 通过支持 UDF 链接,还可以拥有分类器 UDF 和任何后处理 UDF,如调整大小等,在udfskey 中配置 以获得分类结果。
  4. 应用程序从系统环境中获取 msgbus 端点配置,并根据配置,应用程序在 EII MessageBus 上发布有关上述主题的数据。

2)Ingestors

Video file

  • OpenCV Ingestor

    {
      "type": "opencv",
      "pipeline": "./test_videos/pcb_d2000.avi",
      "poll_interval": 0.2
      "loop_video": true
    }
    
  • Gstreamer Ingestor

    {
        "type": "gstreamer",
        "pipeline": "multifilesrc loop=TRUE stop-index=0 location=./test_videos/pcb_d2000.avi ! h264parse ! decodebin ! videoconvert ! video/x-raw,format=BGR ! appsink"
    }
    

RTSP Camera

  • OpenCV Ingestor

    {
      "type": "opencv",
      "pipeline": "rtsp://<USERNAME>:<PASSWORD>@<RTSP_CAMERA_IP>:<PORT>/<FEED>"
    }
    
  • Gstreamer Ingestor

    {
      "type": "gstreamer",
      "pipeline": "rtspsrc location=\"rtsp://<USERNAME>:<PASSWORD>@<RTSP_CAMERA_IP>:<PORT>/<FEED>\" latency=100 ! rtph264depay ! h264parse ! vaapih264dec ! vaapipostproc format=bgrx ! videoconvert ! video/x-raw,format=BGR ! appsink"
    }
    

USB Camera

  • OpenCV Ingestor

    {
      "type": "opencv",
      "pipeline": "/dev/video0"
    }
    
  • Gstreamer Ingestor

    {
      "type": "gstreamer",
      "pipeline": "v4l2src ! video/x-raw,format=YUY2 ! videoconvert ! video/x-raw,format=BGR ! appsink"
    }
    

3)encoding

  • 对于jpeg编码类型,level是质量从0 to 100(越高越好)
  • 对于png编码类型,level是来自0 to 9. 较高的值意味着较小的尺寸和较长的压缩时间。

4)示例config.json

{
    "config": {
        "encoding": {
            "type": "jpeg",
            "level": 95
        },
        "ingestor": {
            "type": "opencv",
            "pipeline": "./test_videos/pcb_d2000.avi",
            "loop_video": true,
            "queue_size": 10,
            "poll_interval": 0.2
        },
        "sw_trigger": {
            "init_state": "running"
        },
        "max_workers":4,
        "udfs": [{
            "name": "pcb.pcb_filter",
            "type": "python",
            "scale_ratio": 4,
            "training_mode": "false",
            "n_total_px": 300000,
            "n_left_px": 1000,
            "n_right_px": 1000
        }]
    },
    "interfaces": {
        "Servers": [
            {
                "Name": "default",
                "Type": "zmq_tcp",
                "EndPoint": "0.0.0.0:64013",
                "AllowedClients": [
                    "*"
                ]
            }
        ],
        "Publishers": [
            {
                "Name": "default",
                "Type": "zmq_ipc",
                "EndPoint": "/EII/sockets",
                "Topics": [
                    "camera1_stream"
                ],
                "AllowedClients": [
                    "VideoAnalytics", "Visualizer", "WebVisualizer", "TLSRemoteAgent", "RestDataExport"
                ]
            }
        ]
    }
}

5.VideoAnalytics

VideoAnalytics 模块主要负责运行分类器 UDF 并使用 openVINO 在选定的 Intel(R) 硬件(CPU、GPU、VPU、HDDL)上进行所需的推理。

VideoAnalytics 的运作流程如下:

  1. 应用程序通过 EII 配置管理器读取应用程序配置,其中包含encoding和 的详细信息udfs
  2. App 从系统环境中获取 msgbus 端点配置。
  3. 基于以上两种配置,应用程序订阅来自 VideoIngestion 模块的已发布主题/流。
  4. 订阅者收到的帧被传递到一个或多个链接的C++/python UDF 上,用于运行推理并根据需要进行任何后处理。
  5. 来自链式 udf 的帧发布在 EII MessageBus 上的不同主题/流上。

示例config.json

{
    "config": {
        "encoding": {
            "type": "jpeg",
            "level": 95
        },
        "queue_size": 10,
        "max_workers":4,
        "udfs": [{
            "name": "pcb.pcb_classifier",
            "type": "python",
            "ref_img": "common/video/udfs/python/pcb/ref/ref.png",
            "ref_config_roi": "common/video/udfs/python/pcb/ref/roi_2.json",
            "model_xml": "common/video/udfs/python/pcb/ref/model_2.xml",
            "model_bin": "common/video/udfs/python/pcb/ref/model_2.bin",
            "device": "CPU"
        }]
    },
    "interfaces": {
        "Subscribers": [
            {
                "Name": "default",
                "Type": "zmq_ipc",
                "EndPoint": "/EII/sockets",
                "PublisherAppName": "VideoIngestion",
                "Topics": [
                    "camera1_stream"
                ],
                "zmq_recv_hwm": 50
            }
        ],
        "Publishers": [
            {
                "Name": "default",
                "Type": "zmq_tcp",
                "EndPoint": "0.0.0.0:65013",
                "Topics": [
                    "camera1_stream_results"
                ],
                "AllowedClients": [
                    "*"
                ]
            }
        ]
    }
}

6.Visualizer

video-native-visualizer是一个本地应用程序,用于在本地桌面查看来自 EII 的分类图像/元数据,需要连接显示屏。

示例config.json

{
    "config": {
        "save_image": "false",
        "draw_results": "true",
        "labels" : {
            "camera1_stream_results": {
                "0": "MISSING",
                "1": "SHORT"
            },
            "native_safety_gear_stream_results": {
                "1": "safety_helmet",
                "2": "safety_jacket",
                "3": "Safe",
                "4": "Violation"
            },
            "py_safety_gear_stream_results": {
                "1": "safety_helmet",
                "2": "safety_jacket",
                "3": "Safe",
                "4": "Violation"

            },
            "gva_safety_gear_stream_results": {
                "1": "safety_helmet",
                "2": "safety_jacket",
                "3": "Safe",
                "4": "Violation"
            }

        }
    },
    "interfaces": {
        "Subscribers": [
            {
                "Name": "default",
                "Type": "zmq_tcp",
                "EndPoint": "ia_video_analytics:65013",
                "PublisherAppName": "VideoAnalytics",
                "Topics": [
                    "camera1_stream_results"
                ]
            }
        ]
    }
}

7.WebVisualizer

video-web-visualizer是一个基于 Web 的应用程序,用于在浏览器上查看来自 EII 的分类图像/元数据。

在浏览器中运行 Visualizer:

  • WebVisualizer 目前在 Chrome 浏览器中每个实例仅支持6 个并行流。

    2021-08-23 11-16-41 的屏幕截图.png
  • 在 DEV 模式下运行:

    • 转到浏览器 http://<host ip>:5001
  • 在 PROD 模式下运行:

    • https://<host ip>:5000
    • 需要在浏览器导入证书:build/provision/Certificates/ca/ca_certificate.pem

示例config.json

"/WebVisualizer/config": {
      "username": "admin",
      "password": "admin@123",
      "dev_port": 5001,
      "port": 5000,
      "labels" : {
          "camera1_stream": {
              "0": "MISSING",
              "1": "SHORT"
          },
          "native_safety_gear_stream_results": {
              "1": "safety_helmet",
              "2": "safety_jacket",
              "3": "Safe",
              "4": "Violation"
          },
          "py_safety_gear_stream_results": {
              "1": "safety_helmet",
              "2": "safety_jacket",
              "3": "Safe",
              "4": "Violation"

          },
          "gva_safety_gear_stream_results": {
              "1": "safety_helmet",
              "2": "safety_jacket",
              "3": "Safe",
              "4": "Violation"
          }

      }
  }

8.ImageStore

EII 的 Image Store 组件作为一个单独的容器出现,它主要通过 EII MessageBus 订阅来自 VideoAnalytics 应用程序的流,并将帧存储到 minio db 中以进行历史分析。

ImageStore 的工作流程如下:

  1. ImageStore 中的消息总线订阅者将订阅 VideoAnalytics 在消息总线上发布的分类结果(元数据、帧)。img_handle 是从元数据中提取出来的,用作键,帧作为该键的值存储在 minio 持久存储中。

    注意:测试发现订阅1路AI结果储存正常,订阅两路及以上无法正常储存图片。

  2. 为了对存储的分类图像进行历史分析,ImageStore 启动提供读取和存储接口的消息总线服务器。有效载荷格式如下:

    存储接口

    Request: map ("command": "store","img_handle":"$handle_name"),[]byte($binaryImage)
    Response : map ("img_handle":"$handle_name", "error":"$error_msg") ("error" is optional and available only in case of error in execution.)
    
    ia_imagestore                      | I0828 08:03:47.403717       1 subManager.go:126] Image with handle f8befd3737 stored successfully
    ia_imagestore                      | I0828 08:03:51.141884       1 main.go:268] Successfully read frame with handle:20bae8930f
    ia_imagestore                      | I0828 08:03:51.151968       1 main.go:268] Successfully read frame with handle:f8befd3737
    ia_imagestore                      | I0828 08:03:55.260218       1 subManager.go:111] 
    ia_imagestore                      | -- Received Message: map[channels:3 defects:[] encoding_level:95 encoding_type:jpeg frame_number:32385 height:1200 img_handle:d92ef1a2bb width:1920]
    ia_imagestore                      | I0828 08:03:55.260341       1 subManager.go:126] Image with handle d92ef1a2bb stored successfully
    ia_imagestore                      | I0828 08:03:55.290998       1 main.go:268] Successfully read frame with handle:d92ef1a2bb
    

    读取接口

    Request : map ("command": "read", "img_handle":"$handle_name")
    Response : map ("img_handle":"$handle_name", "error":"$error_msg"),[]byte($binaryImage)
    

    ("error" is optional and available only in case of error in execution. And $binaryImage is available only in case of successful read)

    可以通过RestDataExport取图

示例config.json

{
    "config": {
        "minio": {
            "accessKey": "admin",       // minio db所需的用户名
            "secretKey": "password",
            "retentionTime": "1h",
            "retentionPollInterval": "60s",
            "ssl": "false"
        }
    },
    "interfaces": {
        "Servers": [
            {
                "Name": "ImageStore",
                "Type": "zmq_tcp",
                "EndPoint": "0.0.0.0:5669",
                "AllowedClients": [
                    "*"
                ]
            }
        ],
        "Subscribers": [
            {
                "Name": "default",
                "Type": "zmq_tcp",
                "EndPoint": "ia_video_analytics:65013",
                "PublisherAppName": "VideoAnalytics",
                "Topics": [
                    "camera1_stream_results"
                ]
            }
        ]
    }
}

9.ZMQ Broker

ZeroMQ Broker can be used in Publish-Subscribe as well as Request-Response modes.

传统上,每个 EII 消息总线 ZeroMQ 发布者都有自己的 IPC 套接字或 TCP(主机、端口)组合。此代理允许发布者连接到代理的发布者 TCP 或 IPC 套接字以发送已发布的消息和订阅者连接到一个中央订阅者 TCP 或 IPC 套接字以接收来自代理的传入消息。

ZeroMQ Broker 建立在 ZeroMQ zmq_proxy()API提供的功能之上 ,它使用两个独立的套接字并将消息从一个套接字传递到另一个套接字。

EII Message Bus是对 ZeroMQ* 的抽象,用于所有容器间通信。

1)ZeroMQ 代理运作流程

 +-----------+    +-----------+    +-----------+
 | Publisher |    | Publisher |    | Publisher |
 +-----------+    +-----------+    +-----------+
 |  ZMQ_PUB  |    |  ZMQ_PUB  |    |  ZMQ_PUB  |
 +-----------+    +-----------+    +-----------+
    connect          connect          connect
      |                |                 |
      +----------------+-----------------+
                       |
                      bind
               +----------------+
               |    ZMQ_XSUB    |       # 代理统一订阅端
               +----------------+
               |     proxy      |
               +----------------+
               |    ZMQ_XPUB    |       # 代理统一发布端
               +----------------+
                      bind
                       |
       +---------------+-------------------+
       |               |                   |
    connect          connect            connect
 +------------+    +------------+    +------------+
 |  ZMQ_SUB   |    |  ZMQ_SUB   |    |  ZMQ_SUB   |
 +------------+    +------------+    +------------+
 | Subscriber |    | Subscriber |    | Subscriber |
 +------------+    +------------+    +------------+

代理使用两个 ZeroMQ 套接字:

  1. 一个供发布者连接的ZMQ_XSUB socket ,简称前端socket
  2. 一个供订阅者连接的ZMQ_XPUB socket,称为后端socket

代理为其每个套接字绑定到 TCP(IP、端口)或 IPC 套接字文件。消息从发布者发送到前端套接字,然后中继到后端套接字,后者将消息转发给订阅者。

2)ZMQ_IPC

ZeroMQ 中的 IPC 通信模式使用 Linux* IPC 套接字,其速度比 TCP通信模式快。 IPC 模式不加密数据,它仅用于通信在同一节点上运行的应用程序之间以及需要传输大量以更快的速度传输数据(如视频帧)。视频帧通常非常大,加密它们会降低性能。

所有使用 IPC 通信方式的 EII 容器都在同一个 Linux 下执行用户组,所创建的套接字文件授予同一用户组的读取权限。因此,只有经过授权的 EII 应用程序才能访问和读取这些套接字文件中的数据。在 IPC 中的应用模式需要由管理员审查,因为他们可以访问以 IPC 模式发布的所有流。

"/VideoIngestion/interfaces": {
        "Publishers": [
            {
                "AllowedClients": [
                    "VideoAnalytics",
                    "Visualizer",
                    "WebVisualizer",
                    "TLSRemoteAgent",
                    "RestDataExport"
                ],
                "EndPoint": "/EII/sockets",
                "Name": "default",
                "Topics": [
                    "camera1_stream"
                ],
                "Type": "zmq_ipc"
            }
        ],
    }

"/VideoAnalytics/interfaces": {
        "Subscribers": [
            {
                "EndPoint": "/EII/sockets",
                "Name": "default",
                "PublisherAppName": "VideoIngestion",
                "Topics": [
                    "camera1_stream"
                ],
                "Type": "zmq_ipc",
                "zmq_recv_hwm": 50
            }
        ]
    },

3)ZMQ_TCP

在 TCP 模式下使用时,流的发布者将绑定到 TCP 套接字,之后订阅者连接到该套接字以接收数据。在请求-响应模式中,响应者充当服务器并绑定套接字,而请求者连接到它。

当我们需要将数据从生成数据的节点发送出去,将使用 TCP 模式。对于 TCP 模式,使用 CurveZMQ 协议启用全数据加密。

CurveZMQ 使用 CurveCP 握手机制,这与 mTLS 不同。 CurveZMQ不使用任何 CA;因此,客户端需要服务器的公钥,而服务器需要客户端的公钥以建立相互认证的连接。

Publish-Subscribe

分别作为Publisher和Subscriber

"/VideoAnalytics/interfaces": {
        "Publishers": [
            {
                "AllowedClients": [
                    "*"
                ],
                "EndPoint": "0.0.0.0:65013",
                "Name": "default",
                "Topics": [
                    "camera1_stream_results"
                ],
                "Type": "zmq_tcp"
            }
        ]
    }

"/ImageStore/interfaces": {
        "Subscribers": [
            {
                "EndPoint": "ia_video_analytics:65013",
                "Name": "default",
                "PublisherAppName": "VideoAnalytics",
                "Topics": [
                    "camera1_stream_results"
                ],
                "Type": "zmq_tcp"
            }
        ]
    }

Request-Response

分别作为Server和Client

 "/ImageStore/interfaces": {
        "Servers": [
            {
                "AllowedClients": [
                    "*"
                ],
                "EndPoint": "0.0.0.0:5669",
                "Name": "ImageStore",
                "Type": "zmq_tcp"
            }
        ],
    }

"/RestDataExport/interfaces": {
        "Clients": [
            {
                "EndPoint": "ia_imagestore:5669",
                "Name": "ImageStore",
                "ServerAppName": "ImageStore",
                "Type": "zmq_tcp"
            }
        ],
    }

四、UDFs(video-common)

UDFs编写指导

UDF(User Defined Function)是 EII 框架的主要功能之一,它是一块用户代码,用于对EII输入数据进行过滤、预处理或分类等操作。EII支持加载和运行Native C++或python版本的UDFs API。

UDF 使用内存消息传递而不是套接字来进行管道和 UDF 之间的通信,因此相比之下速度更快。

UDF层级结构如下:

2021-08-24 14-21-42 的屏幕截图.png

1.UDF目录说明

IEdgeInsights/common/video# tree -L 2
.
├── config.json
├── docker-compose.yml
├── Dockerfile.openvino
├── Dockerfile.videocommon
├── install_pip_requirements.py
├── LICENSE
├── UDFLoader
│   ├── cmake
│   ├── CMakeLists.txt
│   ├── examples
│   ├── include
│   ├── README.md
│   ├── requirements.txt
│   ├── src
│   └── tests
└── udfs    # UDP Samples
    ├── HOWTO_GUIDE_FOR_WRITING_UDF.md
    ├── native
    │   ├── CMakeLists.txt
    │   ├── dummy
    │   │   ├── CMakeLists.txt
    │   │   └── dummy.cpp   # 真正对应的udf代码
    │   ├── fps
    │   │   ├── CMakeLists.txt
    │   │   ├── fps.cpp
    │   │   └── fps.h
    │   ├── raw_dummy
    │   │   ├── CMakeLists.txt
    │   │   └── raw_dummy.cpp
    │   ├── resize
    │   │   ├── CMakeLists.txt
    │   │   └── resize.cpp
    │   └── sample_realsense
    │       ├── CMakeLists.txt
    │       └── sample_realsense.cpp
    ├── python
    │   ├── dummy.py
    │   ├── jupyter_connector.py
    │   ├── multi_frame_dummy.py
    │   ├── pcb
    │   │   ├── __init__.py
    │   │   ├── pcb_classifier.py
    │   │   ├── pcb_filter.py
    │   │   ├── README.md
    │   │   └── ref
    │   └── sample_onnx
    │       ├── __init__.py
    │       ├── onnx_udf.py
    │       └── requirements.txt
    └── README.md

2.UDP Samples

目前官方给出的UDP示例,用户需要根据实际情况,编写自己的UDF程序

1)Native(C++) UDF

C++(它也被称为Native UDF,因为 EII 核心组件是用 C++ 实现的)

  • Dummy

    接收帧并转发相同的帧而不做任何处理。

    UDF config:

    {
        "name": "dummy",  // 对应执行IEdgeInsights/common/video/udfs/native/dummy/dummy.cpp
        "type": "native"
    }
    
  • Resize

    接收帧,根据widthheight参数调整其大小。

    UDF config:

    {
        "name": "resize",
        "type": "native",
        "width": 600,
        "height": 600
    }
    
  • FPS

    FPS udf 可用于测量每秒接收的帧总数。它也可以与其他 udf 链接,在这种情况下,FPS 结果将受到使用的其他 udf 的影响。

    UDF config:

    {
        "name": "fps",
        "type": "native"
    }
    

    Config for chaining fps udf with other udfs:

    "udfs": [{
            "name": "dummy",
            "type": "native"
        },
        {
            "name": "fps",
            "type": "native"
        }]
    
  • Sample Realsense

    接收颜色和深度帧,通过使用 rs2::software_device 模拟转换为 rs2::frame 类型,使用 rs2::colorizer 在深度帧上启用颜色过滤器。

    UDF config:

    {
        "name": "sample_realsense",
        "type": "raw_native",
    }
    

2)Python UDFs

  • Dummy

    接收帧并转发相同的帧而不做任何处理。

    UDF config:

    {
        "name": "dummy",  // 对应执行IEdgeInsights/common/video/udfs/python/dummy.py
        "type": "python"
    }
    
  • Jupyter Connector

    接收帧并将其发布到 EII JupyterNotebook 服务,该服务处理该帧并将其发布回 jupyter_connector UDF。

    UDF config:

    {
        "name": "jupyter_connector",
        "type": "python"
    }
    
  • PCB Filter

    基于PCB板是否在帧的中心,来接收或丢弃帧。它基本上只发送关键帧到下一步的处理

    UDF config:

    {
        "name": "pcb.pcb_filter",     // 只要命名唯一即可,要与videoanalysis一致
        "type": "python",
        "training_mode": "false",
        "scale_ratio": 4,
        "n_total_px": 300000,
        "n_left_px": 1000,
        "n_right_px": 1000
    }
    
  • PCB Classifier

    接收帧,使用 openvino 推理引擎 API 来确定它是没有缺陷的pcb还是有缺陷的 pcb。相应地填充与帧相关联的元数据。

    UDF config:

    {
        "name": "pcb.pcb_classifier",
        "type": "python",
        "device": "CPU",
        "ref_img": "common/video/udfs/python/pcb/ref/ref.png",
        "ref_config_roi": "common/video/udfs/python/pcb/ref/roi_2.json",
        "model_xml": "common/video/udfs/python/pcb/ref/model_2.xml",
        "model_bin": "common/video/udfs/python/pcb/ref/model_2.bin"
    }
    

3)Metadata结构

保元数据中的数据应该是列表、元组、字典或原始数据类型(int、float、string 或 bool)。此外,具有 in list、tuple、dict 的数据必须仅包含原始数据类型。例如:任何类型为“numpy.float”或“numpy.int”的数据都应分别转换为 float 和 int 类型。

3.C++ UDF的编写步骤

每个 UDF 编写都有两个主要部分。

  • 使用 EII Public API 编写实际的前处理与后处理逻辑。
  • 添加用于部署它的 EII 基础设施配置组件

1)EII Public API

这些 API 必须作为从BaseUdf(Udf 类)继承的用户定义类的方法来实现。

  • 初始化和去初始化API

    class  DummyUdf : public  BaseUdf {       // 从BaseUdf继承的DummyUdf类
        public: 
        DummyUdf ( config_t * config) : BaseUdf(config) { // DummyUdf方法
            //初始化代码可以在这里添加
        }; ~DummyUdf () {
            //任何要在这里添加的反初始化逻辑
        }; 
    };
    

    上面代码片段中的DummyUdf是用户定义的类,类的构造函数初始化 UDF 的特定数据结构。

    传递给此函数的唯一参数是config,它描述了config.json处理 UDF 的应用程序中提到的配置详细信息。

  • 处理实际数据API

    class  DummyUdf : public  BaseUdf {       
        public: 
        ......
         //process方法
      UdfRetCode  process (cv::Mat& frame, cv::Mat& outputFrame, msg_envelope_t * meta) override {
             //处理帧并返回推理结果的逻辑。
        };
    };
    

    输入参数:

    • 参数 1(cv::Mat &frame):它代表推理的输入帧。
    • 参数 2(cv::Mat &outputFrame):代表用户修改后的帧。如果用户需要向前传递修改后的帧,则可以使用此选项。
    • 参数 3(msg_envelope_t* meta):表示 UDF 返回的推理结果。

    返回参数:

    • UdfRetCode:用户需要返回适当的宏,如下所述:
      • UDF_OK - UDF 已正常处理帧。
      • UDF_DROP_FRAME - 传递给进程函数的帧需要丢弃。
      • UDF_ERROR - 应该为 UDF 中的任何类型的错误返回。
  • 链接 UdfLoader 和 CUSTOM-UDF

    initialize_udf()函数需要定义UdfLoader模块和相应的UDF之间的链接。此函数确保 UdfLoader 调用相应 UDF 的正确构造函数和 process() 函数。

    extern  " C " {
         void * initialize_udf ( config_t *config) { 
            DummyUdf * udf = new  DummyUdf (config);
            return (void *)udf;
        } 
    }
    

    该DummyUdf是用户定义的自定义UDF的类名。

2)EII 基础设施变化

  • 所有UDF 代码应保存在.../common/video/udfs/native目录下。

  • 根据代码编写适当的CMakeLists.txt文件并链接适当的外部库。目前为两个不同的用例定义了两个示例 CMAKE 文件。

  • 如果使用 OpenVINO,需要链接 cpu_extension.so共享库,参考video-custom-udfs

  • EII应用的文件config.json中必须添加相应的 UDF 条目。

4.Python UDF 的编写步骤

编写 Python UDF 的过程,也有两个方面

  • 编写实际的UDF。它需要EII public Python API
  • 通过更改不同的配置将 UDF 添加到 EII 框架。

1)EII Public Python API

  • 初始化

    在 Python 的情况下,初始化回调也与native情况相同。用户必须创建一个Udf 类,并且类中定义的init()函数充当自定义 UDF 的初始化例程。

    class  Udf :
    """Example UDF 
    """ 
    def  __init__ ( self ):
         """Constructor 
        """ 
        # 在该方法中添加初始化代码。
    
  • 处理实际数据

    用于处理实际帧的 API 如下所示。

    process(self, frame, metadata):
        #在这个方法中处理frame 
        #metadata可以用来返回推理结果
    

    输入参数

    • frame : numpy.ndarray 格式的图像帧

    • metadata:空字典,推理结果可以插入到这个数据结构中。

      # 示例返回的metadata
        {'frame_number': 72, 'channels': 3, 'encoding_type': 'jpeg', 'height': 1080, 'img_handle': '8e30574760', 'width': 1920, 'encoding_level': 95, 'defects': [{'type': 1, 'tl': (1080, 110), 'br': (1172, 203)}, {'type': 1, 'tl': (1052, 110), 'br': (1140, 203)}, {'type': 1, 'tl': (936, 96), 'br': (1218, 484)}, {'type': 1, 'tl': (955, 387), 'br': (1195, 780)}]}
      

返回值:此函数返回三个值。

  • 第一个值:表示是否需要丢弃帧。它本质上是布尔值。如果失败,可以在此位置返回值中返回True

  • 第二个值:如果它已经被修改,它代表实际修改的帧。因此类型是numpy.ndarray。如果帧没有被修改,用户可以在这个地方返回一个None

  • 第三个值:在这个地方返回元数据。因此类型是dict。用户可以将传递的参数作为此函数的一部分返回。

2)EII 基础设施变化

  • 所有UDF 代码应保存在.../common/video/udfs/python目录下,条目name键必须具有文件层次结构,直到文件名作为 udf 的名称。

    • 例如:路径中存在的文件./python/pcb/pcb_filter.py的名称字段必须为pcb.pcb_filter
  • EII应用的文件config.json中必须添加相应的 UDF 条目。

5.UDF构建独立容器

UDF 可以在EII 构建环境的udfs-path(common/video/udfs)中创建,以便它可以编译到 VI(视频摄取)和 VA(视频分析)容器中。除此外,每个 UDF 都可以构建为基于 VI(VideoIngestion)或 VA(VideoAnalytics)容器镜像的独立容器。

独立容器的好处

  • 随着样例 UDF 数量的增加,VI 和 VA 不会越来越大。
  • 对 UDF 的算法或其逻辑的任何更新将仅编译和构建预期的 UDF 特定代码,而不是重新构建每个 UDF。
  • 每个 UDF 都可以独立进行版本控制。

示例CustomUdfs目录

# Edge_Insights_for_Industrial_2.6/IEdgeInsights
│       ├── CustomUdfs      # 自定义的UDF程序
│       │   ├── GVASafetyGearIngestion
│       │   ├── LICENSE
│       │   ├── NativePclIngestion
│       │   ├── NativeSafetyGearAnalytics
│       │   ├── NativeSafetyGearIngestion
│       │   ├── PyMultiClassificationIngestion
│       │   ├── PySafetyGearAnalytics
│       │   ├── PySafetyGearIngestion
│       │   └── README.md

1)UDF 容器目录布局

https://github.com/open-edge-insights/video-custom-udfs

Native C++ UDF 容器源示例

NativeSafetyGearAnalytics
├── config.json     # 定义了 UDF 特定配置和其他通用配置
├── docker-compose.yml
├── Dockerfile
└── safety_gear_demo
    ├── CMakeLists.txt
    ├── ref # AI模型
    │   ├── frozen_inference_graph.bin
    │   ├── frozen_inference_graph_fp16.bin
    │   ├── frozen_inference_graph_fp16.xml
    │   └── frozen_inference_graph.xml
    ├── safety_gear_demo.cpp
    └── safety_gear_demo.h

python UDF容器源示例

PyMultiClassificationIngestion
├── config.json
├── docker-compose.yml
├── Dockerfile
└── sample_classification
    ├── __init__.py
    ├── multi_class_classifier.py
    └── ref
        ├── squeezenet1.1_FP16.bin
        ├── squeezenet1.1_FP16.xml
        ├── squeezenet1.1_FP32.bin
        ├── squeezenet1.1_FP32.xml
        └── squeezenet1.1.labels

2)部署流程

  • 配置 Visualizer 和 WebVisualizer 服务config.json

  • EII默认user case并没有运行CustomUdf,因此需要通过builder.py构建CustomUdf容器。

    $ cd [WORKDIR]/IEdgeInsights/build/
    $ python3 builder.py -f usecases/video-streaming-all-udfs.yml
    
  • 生成证书文件【开发模式忽略】

    $ cd [WORKDIR]/IEdgeInsights/build/provision
    $ sudo -E ./provision.sh  ../docker-compose.yml
    
  • 构建镜像

    $ cd [WORKDIR]/IEdgeInsights/build/
    $ docker-compose -f docker-compose-build.yml build
    $ docker-compose up -d
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容