[深度学习] 超长文,一篇讲完 NVIDIA Jetson Hello AI World 全部教程(推理 & 训练)

目录

  • 一、开始
    • [1.1 安装](#1.1 安装)
    • [1.2 sdkmanger 细节介绍](#1.2 sdkmanger 细节介绍)
      • [1.2.1 host 和 target 配合](#1.2.1 host 和 target 配合)
      • [1.2.2 Jetson SDK Components 介绍](#1.2.2 Jetson SDK Components 介绍)
  • [二、Hello AI World](#二、Hello AI World)
    • [2.1 System Setup](#2.1 System Setup)
      • [2.1.1 Docker](#2.1.1 Docker)
      • [2.1.2 板上编译](#2.1.2 板上编译)
    • [2.2 推理](#2.2 推理)
      • [2.2.1 图像分类](#2.2.1 图像分类)
        • [2.2.1.1 在 Jetson 上使用 ImageNet 程序](#2.2.1.1 在 Jetson 上使用 ImageNet 程序)
        • [2.2.1.2 编写你自己的图像识别程序(Python)](#2.2.1.2 编写你自己的图像识别程序(Python))
        • [2.2.1.3 编写你自己的图像识别程序(C++)](#2.2.1.3 编写你自己的图像识别程序(C++))
        • [2.2.1.4 运行实时摄像头识别演示](#2.2.1.4 运行实时摄像头识别演示)
        • [2.2.1.5 图像标记的多标签分类](#2.2.1.5 图像标记的多标签分类)
      • [2.2.2 物体检测](#2.2.2 物体检测)
        • [2.2.2.1 使用 DetectNet 定位物体](#2.2.2.1 使用 DetectNet 定位物体)
        • [2.2.2.2 运行实时摄像头检测演示](#2.2.2.2 运行实时摄像头检测演示)
        • [2.2.2.3 编写你自己的物体检测程序](#2.2.2.3 编写你自己的物体检测程序)
        • [2.2.2.4 使用 TAO 检测模型](#2.2.2.4 使用 TAO 检测模型)
        • [2.2.2.5 视频中的对象跟踪](#2.2.2.5 视频中的对象跟踪)
      • [2.2.3 语义分割](#2.2.3 语义分割)
        • [2.2.3.1 使用 SegNet 进行语义分割](#2.2.3.1 使用 SegNet 进行语义分割)
        • [2.2.3.2 运行实时摄像头分割演示](#2.2.3.2 运行实时摄像头分割演示)
      • [2.2.4 姿态估计](#2.2.4 姿态估计)
      • [2.2.5 动作识别](#2.2.5 动作识别)
      • [2.2.6 背景去除](#2.2.6 背景去除)
      • [2.2.7 单目深度](#2.2.7 单目深度)
    • [2.3 训练](#2.3 训练)
      • [2.3.1 使用 PyTorch 进行迁移学习](#2.3.1 使用 PyTorch 进行迁移学习)
      • [2.3.2 分类/识别(ResNet-18)](#2.3.2 分类/识别(ResNet-18))
        • [2.3.2.1 在猫/狗数据集上进行重新训练](#2.3.2.1 在猫/狗数据集上进行重新训练)
        • [2.3.2.2 在 PlantCLEF 数据集上进行重新训练](#2.3.2.2 在 PlantCLEF 数据集上进行重新训练)
        • [2.3.2.3 收集你自己的分类数据集](#2.3.2.3 收集你自己的分类数据集)
      • [2.3.3 物体检测(SSD-Mobilenet)](#2.3.3 物体检测(SSD-Mobilenet))
        • [2.3.3.1 重新训练 SSD-Mobilenet](#2.3.3.1 重新训练 SSD-Mobilenet)
        • [2.3.3.2 收集您自己的检测数据集](#2.3.3.2 收集您自己的检测数据集)
    • [2.4 WebApp框架](#2.4 WebApp框架)
    • [2.5 附录](#2.5 附录)

一、开始

规格页面:https://developer.nvidia.com/embedded/jetson-tx1

GetStart 页面

  • 找到我们这一款设备,点击用户指南,发现分别存档了 23.1/23.2/24.1 三个版本;
  • 发现官方建议学习顺序是:购买套件 -> Hello AI World 教程 / Jetson AI 课程和认证 -> 注册开发者在论坛提问 -> 在社区寻找灵感;
  • 在 Tutorials / Resources 可以逐步深入;

1.1 安装

发现:

  • Jetson Nano、Orin Nano 和 Xavier NX 可以 SD 卡刷入系统
  • Jetson TX1/TX2、AGX Xavier 和 AGX Orin 需要 NVIDIA SDK Manager

发现:

  • 1)windows 版本的 sdk manager 不支持 jetson tx1 所需要的 sdk

  • 2)不要直接下载指导文档中的 sdkmanger 链接,其版本太老,会有问题

  • 3)ubuntu 版本的 sdkmanger 支持:ubuntu1804, ubuntu2004, ubuntu2204, ubuntu2404

    需要参考:https://developer.nvidia.com/sdk-manager#installation_get_started

    复制代码
    wget https://developer.download.nvidia.com/compute/cuda/repos/[distro]/x86_64/cuda-keyring_1.1-1_all.deb
    sudo dpkg -i cuda-keyring_1.1-1_all.deb
    sudo apt-get update
    sudo apt-get -y install sdkmanager

    PS: 根据自己系统 [distro] 填写:ubuntu1804, ubuntu2004, ubuntu2204, ubuntu2404

  • 4)发现我用的 Jetson TX1 的 SDK 只能再 ubuntu 16.04 和 ubuntu 18.04 上的 sdkmanager 才有

  • 5)发现可以用 docker(仅支持 CLI 版本):

    sdkmanager 的 docker 非常吃硬盘,因此先评估下 docker 默认存储位置是否够用,不够的话迁移到一个大的地方:

    复制代码
    sudo mkdir -p /home/docker_data
    sudo chown -R root:docker /home/docker_data
    sudo chmod -R 775 /home/docker_data
    
    sudo systemctl stop docker
    sudo mkdir /etc/docker
    sudo vim /etc/docker/daemon.json
    {
      "data-root": "/home/docker_data"
    }
    # 复制数据到新位置
    sudo rsync -aP /var/lib/docker/ /home/docker_data/
    sudo systemctl start docker

    在进行下面操作之前,要通过 USB 将开发板与运行 docker 的电脑相连,并且按住 recovery 按钮,然后短按 reset 按钮,然后松开 recovery 按钮(此时在宿主机中运行 sudo lsusb 能看到板子已经进入 recovery 模式):

    复制代码
    # 1)下载 ubuntu18.04 docker image
    # 2)加载容器:
    docker load -i sdkmanager-2.3.0.12617-Ubuntu_18.04_docker.tar.gz
    # 3)创建一个标签方便用:
    docker tag sdkmanager:2.3.0.12617-Ubuntu_18.04 sdkmanager:latest
    # 4)运行:
    docker run -it --rm sdkmanager --help
    # 5)查询
    docker run -it --rm sdkmanager --query
    # 6)根据查询到的命令安装:
    docker run -it --privileged -v /dev/bus/usb:/dev/bus/usb/ -v /dev:/dev -v /media/$USER:/media/nvidia:slave --name JetPack_Jetson_Tx1_Devkit sdkmanager --cli --action install --login-type devzone --product Jetson --version 4.6.6 --target-os Linux --host --target JETSON_TX1_TARGETS --flash --additional-sdk 'DeepStream 6.0.1'

    安装过程非常慢,需要FQ并且网速好,然后就会看到其自动配置宿主电脑和目标电脑。等到目标电脑的 Flash Jetson OS 成功之后,容器会继续让装 Jetson SDK Components:

    由于板子被重新刷了固件,从而板子重启,其会基于 USB 模拟出一个网关,在宿主机器内通过:ssh btfz@192.168.55.1 可以连接,但是如果进入到虚拟机内看 ip 却发现板子并没有连接到虚拟机内(此时如果选择安装 Jetson SDK Components 会一直报错):

    PS: 当您在宿主机上插拔外设(如 USB 设备)时,已运行的容器通常无法自动感知这些变化,因为 Docker 在容器启动时会固定设备列表。

    复制代码
    ➜  Desktop docker ps                         
    CONTAINER ID   IMAGE        COMMAND                  CREATED       STATUS       PORTS     NAMES
    c371f5ab0510   sdkmanager   "docker-entrypoint.s..."   3 hours ago   Up 3 hours             JetPack_Jetson_Tx1_Devkit
    ➜  Desktop docker exec -it c371f5ab0510 bash # 用来登陆容器看 ip 的
    ➜  Desktop docker commit c371f5ab0510 jetpack_jetson_tx1_devkit:4.6_flash # 46G 很慢,几十分钟

    之后 skip,然后退出之前的容器,然后:

    复制代码
    # 宿主机 ssh 连接板子,配置默认连接 wifi,之后退出
    ssh btfz@192.168.55.1
    sudo nmcli dev wifi # 搜索下,看看周围是否存在目标 SSID
    sudo nmcli dev wifi connect "SSID" password "YOUR_PASSWORD"
    exit
    
    # 重新进入容器,继续安装组件
    docker run -it --privileged -v /dev/bus/usb:/dev/bus/usb/ -v /dev:/dev -v /media/$USER:/media/nvidia:slave --network host jetpack_jetson_tx1_devkit:4.6_flash
    # 注意:由于只有 16G 空间,无法将所有组件安装,因此这里最后两个没有安装(Developer Tools、DeepStream)
    # 最后只剩下 100M,因此需要用下面命令删除一些没用的东西:
    sudo journalctl --vacuum-size=100M
    sudo apt-get install ncdu
    sudo ncdu /
    
    # 发现:usr/lib/aarch64-linux-gnu/libcudnn* 和 libnvinfer* 占了将近 3.2G
    # 发现:local/cuda 占了 2G
    # 发现浏览器占了不少,删除一些占用多的,没用的软件:
    sudo apt-get remove chromium-browser
    sudo sudo apt-get remove libreoffice
    sudo apt-get remove thunderbird
    
    sudo apt-get clean   # 清除安装留下的垃圾
    sudo rm -rf /tmp/*   # 清除临时文件

1.2 sdkmanger 细节介绍

1.2.1 host 和 target 配合

上面 sdkmanger 中包含了两部分:HOST COMPONENTS 和 TARGET COMPONENTS,这里对这两个做个详细介绍:

Host Components:

  • 定义:安装在 开发主机(如 PC 或服务器) 上的组件,用于构建、编译和部署应用程序。
  • 用途:
    • 交叉编译工具链:在 x86 主机上编译适用于 ARM 架构(Jetson 设备)的代码。
    • 开发环境:提供 TensorFlow、PyTorch 等框架的主机版本,用于模型训练和调试。
    • SDK 和库:如 CUDA Toolkit、cuDNN 等,在主机上模拟 Jetson 的 GPU 环境,加速开发过程。
    • 容器支持:配置 Docker 和 NVIDIA Container Runtime,实现容器化开发和部署。
  • 示例场景:
    • 在 PC 上使用 PyTorch 训练模型,然后将优化后的模型部署到 Jetson 设备。
    • 在主机上编译 C++ 代码,通过交叉编译工具链生成可在 Jetson 上运行的二进制文件。

TARGET COMPONENTS:

  • 定义:直接安装到 Jetson 目标设备 上的组件,用于在设备上运行应用程序。
  • 用途:
    • 基础系统:L4T(Linux for Tegra)操作系统,包含 Jetson 硬件驱动和内核。
    • 运行时环境:CUDA、cuDNN、TensorRT 等库的嵌入式版本,支持模型推理。
    • 应用框架:如 DeepStream、VisionWorks 等,提供特定领域的优化功能。
    • 多媒体支持:视频编解码库(NVDEC/NVENC)、GStreamer 插件等。
  • 示例场景:
    • Jetson Nano 设备上运行 TensorRT 优化后的目标检测模型。
    • Jetson AGX Orin 实时处理多路摄像头视频流(需 DeepStream 和 Multimedia 组件)。

如何选择?

  • 开发阶段:需要同时安装 Host Components 和 TARGET COMPONENTS
    • 在主机上开发和编译代码
    • 将编译好的应用部署到 Jetson 设备测试。
  • 生产部署:通常只需要 TARGET COMPONENTS
    • Jetson 设备作为独立运行单元,只需安装运行时依赖

1.2.2 Jetson SDK Components 介绍

名字 定位 功能 应用
CUDA 并行计算平台和编程模型 提供 C/C++、Fortran 等编程语言的接口,使开发者能够直接控制 GPU 资源,实现高效的并行算法 矩阵运算、深度学习模型训练(底层算力支持)、计算流体动力学等
CUDA-X AI 基于 CUDA 的人工智能扩展库集合 cuDNN:深度学习神经网络库,优化卷积、池化、归一化等操作,加速 TensorFlow、PyTorch 等框架的模型训练和推理 cuML:机器学习库,提供支持 GPU 加速的分类、聚类、回归等算法(如随机森林、PCA) Rapids:数据科学工具包,支持 GPU 加速的数据处理、分析和机器学习工作流 自动驾驶模型训练、自然语言处理、推荐系统开发
Computer Vision 计算机视觉算法和工具集合 OpenCV for Jetson:优化版 OpenCV,针对 Jetson GPU 加速图像和视频处理,支持特征检测、图像分割、目标跟踪等 VisionWorks:NVIDIA 自研视觉库,提供底层加速的视觉原语(如光流计算、立体视觉) TensorRT Vision Plugins:结合 TensorRT 的视觉推理优化插件,加速目标检测、姿态估计等任务 工业质检(缺陷检测)、无人机视觉导航、医疗影像分析
NVIDIA Container Runtime 容器运行时环境,支持在 Jetson 上部署容器化应用 允许开发者将应用及其依赖打包为 Docker 容器,实现跨环境的一致性部署 支持 GPU 资源的隔离和分配,确保多个容器化应用高效利用 Jetson 的硬件资源 多任务并行开发(如同时运行视觉处理和深度学习服务)、边缘设备的应用隔离部署
Multimedia 多媒体处理和编解码组件 NVDEC/NVENC:硬件加速的视频解码 / 编码引擎,支持 H.264、H.265 等格式,降低 CPU 负载 MediaSDK:提供 API 用于处理视频捕获、编码、解码、渲染等全流程,支持多摄像头输入和实时视频流处理 视频监控系统(多路视频流实时编码)、视频会议终端(4K 视频解码)、无人机航拍视频处理
Developer Tools 开发者调试、优化和部署工具 Nsight Systems/Compute:性能分析工具,用于跟踪 GPU/CPU 资源使用情况,定位代码瓶颈 Jetson Config Tool:配置 Jetson 设备的硬件参数(如 CPU/GPU 频率、功耗模式),平衡性能和能耗 交叉编译工具链:支持在 x86 平台编译代码并部署到 Jetson ARM 架构上 开发阶段的代码性能优化、设备功耗管理(如嵌入式场景下的低功耗部署)
DeepStream 端到端的 AI 视频分析框架 支持多摄像头视频流的接入、处理和分析,集成目标检测、分类、跟踪等 AI 模型 提供流水线式架构,可串联预处理(如去噪)、推理(如 YOLO 模型)、后处理(如结果聚合)等模块 支持实时分析和事件触发(如检测到异常行为时报警) 智能交通系统(车辆计数、违章检测)、零售场景客流分析、安防领域的异常行为识别

各组件的协同关系:

  • CUDA 提供底层算力,CUDA-X AI 基于此实现 AI 算法加速,Computer Vision/Multimedia 处理视觉和视频数据,DeepStream 整合这些能力形成完整的分析流水线。
  • NVIDIA Container Runtime 用于部署和管理这些组件的容器化应用,Developer Tools 则辅助开发和优化全流程。

只有 CUDA 是基础需求,其他根据需求安装,因为资源有限,全装放不下:

  • 纯深度学习推理:CUDA + CUDA-X AI(cuDNN + TensorRT)
  • 实时视频分析:CUDA + CUDA-X AI + DeepStream + Multimedia
  • 计算机视觉应用:CUDA + OpenCV for Jetson + VisionWorks(可选)
  • 生产环境部署:CUDA + NVIDIA Container Runtime + 所需的 AI / 视觉库

二、Hello AI World

Hello AI World 可以完全在您的 Jetson 上运行,包括使用 TensorRT 进行实时推理和使用 PyTorch 进行迁移学习。

有关安装说明,请参阅系统设置。建议您先从推理部分开始熟悉相关概念,然后再深入训练您自己的模型。

PS: 下面是参考 Hello AI World 中的各章节的指导,来上手 Hello AI World。只列出核心的一些概述或注意点,想要了解细节,建议看 Hello AI World 的 readme.md

2.1 System Setup

JetPack 简化了操作系统和驱动程序的安装,并包含 L4T Linux 内核、CUDA 工具包、cuDNN、TensorRT 等。

  • 对于带有可移动 microSD 存储的 Jetson 开发套件,建议的安装方法是刷入最新的SD 卡映像;
  • 其他 Jetson 设备需要通过下载NVIDIA SDK Manager下载到运行 Ubuntu x86_64 的主机电脑上进行刷写。将 Micro-USB 或 USB-C 端口连接到主机电脑,并将设备置于恢复模式,然后继续操作;

之后实验可以在 docker 中实施,也可以自己编译在 Jetson 内实施。

2.1.1 Docker

readme.md 链接中首先列出一些版本的 docker 容器,并说明需要和硬件匹配。

这个不重要,因为接下来就有全自动脚本来自动拉取(不用你自己找版本匹配再拉了)。

复制代码
$ git clone --recursive --depth=1 https://github.com/dusty-nv/jetson-inference
$ cd jetson-inference
$ docker/run.sh

# PS:如果 docker/run.sh 报错 docker: Error response from daemon: Unknown runtime specified nvidia. 
# 表明 Docker 无法识别 nvidia 运行时,这通常是因为 NVIDIA Container Toolkit 没有正确安装或配置
# 参考:https://www.doubao.com/chat/8647245920655362
# 用下面命令修复:
# 1) 添加存储库并安装
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update
sudo apt-get install -y nvidia-docker2

# 2) 重启 Docker 服务
sudo systemctl restart docker 

PS: 我的板子是 r32.7.1 但是找不到,脚本就安装了个 32.7.6
PS: 装完用 df -h 命令查看 14G 还剩 3.3G

run 启动容器后,执行推理 demo 发现报错:

复制代码
➜  jetson-inference git:(master) docker/run.sh
ARCH:  aarch64
reading L4T version from /etc/nv_tegra_release
L4T BSP Version:  L4T R32.7.6
CONTAINER_IMAGE:  dustynv/jetson-inference:r32.7.1
DATA_VOLUME:      --volume /home/xuyan/Downloads/jetson-inference/data:/jetson-inference/data --volume /home/xuyan/Downloads/jetson-inference/python/training/classification/data:/jetson-inference/python/training/classification/data --volume /home/xuyan/Downloads/jetson-inference/python/training/classification/models:/jetson-inference/python/training/classification/models --volume /home/xuyan/Downloads/jetson-inference/python/training/detection/ssd/data:/jetson-inference/python/training/detection/ssd/data --volume /home/xuyan/Downloads/jetson-inference/python/training/detection/ssd/models:/jetson-inference/python/training/detection/ssd/models --volume /home/xuyan/Downloads/jetson-inference/python/www/recognizer/data:/jetson-inference/python/www/recognizer/data 
root@xuyan-desktop:/jetson-inference# cd build/aarch64/bin
root@xuyan-desktop:/jetson-inference/build/aarch64/bin# ./imagenet images/jellyfish.jpg images/test/jellyfish.jpg
./imagenet: error while loading shared libraries: /usr/lib/aarch64-linux-gnu/libnvinfer.so.8: file too short

GEMINI 介绍说网络不稳定导致拉取不好,但是重新弄还是不行,因此准备重新编译。

2.1.2 板上编译

参考《板上编译》首先将 apt 和 python 源都改为清华源:

https://www.doubao.com/chat/8635589019524866

复制代码
# 创建工作空间(虽然 /run 里有 2G,但是每次重启都会清空,因此别放)
cd ~/Downloads
mkdir hello_ai
cd hello_ai

# 编译测试
sudo apt-get update
sudo apt-get install git cmake libpython3-dev python3-numpy
git clone --recursive --depth=1 https://github.com/dusty-nv/jetson-inference
cd jetson-inference
mkdir build
cd build
cmake ..
make -j$(nproc)
cd aarch64/bin
./imagenet images/jellyfish.jpg images/test/jellyfish.jpg

因为很多 demo 都需要 GUI,使用 tightvncserver 等各种都有问题,各种问题,直接放弃使用。这里使用:

复制代码
ssh -Y btfz@192.168.55.1 # 登陆
nautilus # 打开文件夹
eog jellyfish.jpg  # 打开图片

2.2 推理

2.2.1 图像分类

2.2.1.1 在 Jetson 上使用 ImageNet 程序

https://github.com/dusty-nv/jetson-inference/blob/master/docs/imagenet-console-2.md

深度学习网络有多种类型,包括识别、检测/定位和语义分割。本教程重点介绍的第一个深度学习功能是图像识别,它使用在大型数据集上训练过的分类网络来识别场景和物体。

imageNet 对象接受一张输入图像并输出每个类别的概率。GoogleNet 和 ResNet-18 模型已在包含 1000 个对象的 ImageNet ILSVRC 数据集上进行训练,并在构建步骤中自动下载。其他可供下载和使用的分类模型请参见下文。

作为使用该类的示例 imageNet,提供了 C++ 和 Python 的示例程序:

这些示例能够对图像、视频和摄像头信息流进行分类。如需详细了解所支持的各种输入/输出流类型,请参阅摄像头流式传输和多媒体页面。

1)在 Jetson 上使用 ImageNet 程序

首先,让我们尝试使用该 imagenet 程序在一些示例图像上测试 ImageNet 识别。它会加载一张(或多张)图像,使用 TensorRTimageNet 类进行推理,然后叠加分类结果并保存输出图像。该项目附带了示例图像,位于 images/ 目录下供您使用。

构建项目后,请确保您的终端位于以下 aarch64/bin 目录中:

复制代码
$ cd jetson-inference/build/aarch64/bin

接下来,让我们使用该程序对示例图像进行分类 imagenet,可以使用 C++Python 版本。如果您使用的是Docker 容器,建议将分类后的输出图像保存到 images/test 已挂载的目录中。这样,您就可以在主机设备上的jetson-inference/data/images/test 目录中轻松查看这些图像(更多信息,请参阅已挂载的数据卷)。

复制代码
# C++
$ ./imagenet images/orange_0.jpg images/test/output_0.jpg     # (default network is googlenet)

# Python
$ ./imagenet.py images/orange_0.jpg images/test/output_0.jpg  # (default network is googlenet)

注意 :第一次运行每个模型时,TensorRT 将花费几分钟来优化网络。

然后,此优化的网络文件将缓存到磁盘,因此将来使用该模型的运行将加载得更快。

复制代码
# C++
$ ./imagenet images/strawberry_0.jpg images/test/output_1.jpg

# Python
$ ./imagenet.py images/strawberry_0.jpg images/test/output_1.jpg

除了加载单张图片外,您还可以加载目录、图片序列或视频文件。更多信息,请参阅相机流媒体和多媒体页面,或使用 flag 启动应用程序 --help

2)下载其他分类模型

以下预先训练的图像分类模型可供使用并将自动下载(默认为googlenet):

网络 CLI 参数 NetworkType 枚举
AlexNet alexnet ALEXNET
GoogleNet googlenet GOOGLENET
GoogleNet-12 googlenet-12 GOOGLENET_12
ResNet-18 resnet-18 RESNET_18
ResNet-50 resnet-50 RESNET_50
ResNet-101 resnet-101 RESNET_101
ResNet-152 resnet-152 RESNET_152
VGG-16 vgg-16 VGG-16
VGG-19 vgg-19 VGG-19
Inception-v4 inception-v4 INCEPTION_V4

一般来说,更复杂的网络可以具有更高的分类精度,并且运行时间也更长。

3)使用不同的分类模型

--network 您可以通过将命令行上的标志设置为上表中相应的 CLI 参数之一来指定要加载的模型。默认情况下,如果 --network 未指定可选标志,则会加载 GoogleNet。

以下是使用 ResNet-18 模型的一些示例:

复制代码
# C++
$ ./imagenet --network=resnet-18 images/jellyfish.jpg images/test/output_jellyfish.jpg

# Python
$ ./imagenet.py --network=resnet-18 images/jellyfish.jpg images/test/output_jellyfish.jpg
复制代码
# C++
$ ./imagenet --network=resnet-18 images/stingray.jpg images/test/output_stingray.jpg

# Python
$ ./imagenet.py --network=resnet-18 images/stingray.jpg images/test/output_stingray.jpg
复制代码
# C++
$ ./imagenet --network=resnet-18 images/coral.jpg images/test/output_coral.jpg

# Python
$ ./imagenet.py --network=resnet-18 images/coral.jpg images/test/output_coral.jpg

您可以随意尝试不同的模型,看看它们的准确率和性能有何不同------您可以使用模型下载器工具下载更多模型。您还可以在下方找到各种测试图像 images/

4)处理视频

相机流媒体和多媒体页面显示了程序可以处理的不同类型的流 imagenet

以下是在磁盘视频上运行该程序的示例:

复制代码
# Download test video (thanks to jell.yfish.us)
$ wget https://nvidia.box.com/shared/static/tlswont1jnyu3ix2tbf7utaekpzcx4rc.mkv -O jellyfish.mkv

# C++
$ ./imagenet --network=resnet-18 jellyfish.mkv images/test/jellyfish_resnet18.mkv

# Python
$ ./imagenet.py --network=resnet-18 jellyfish.mkv images/test/jellyfish_resnet18.mkv

注意:如果直接用 ssh -Y/-X 执行上面命令会报错


接下来,我们将从头开始编写您自己的图像识别程序,首先使用 Python,然后使用 C++。

2.2.1.2 编写你自己的图像识别程序(Python)

在上一步中,我们运行了 jetson-inferencerepo 附带的示例应用程序。

现在,我们将学习如何用 Python 从头开始​​创建一个名为 my-recognition.py 的图像识别程序。该脚本将从磁盘加载任意图像,并使用 imageNet 对象对其进行分类。完整的源代码位于 python/examples/my-recognition.py

复制代码
#!/usr/bin/python3

import jetson.inference
import jetson.utils

import argparse

# parse the command line
parser = argparse.ArgumentParser()
parser.add_argument("filename", type=str, help="filename of the image to process")
parser.add_argument("--network", type=str, default="googlenet", help="model to use, can be:  googlenet, resnet-18, ect.")
args = parser.parse_args()

# load an image (into shared CPU/GPU memory)
img = jetson.utils.loadImage(args.filename)

# load the recognition network
net = jetson.inference.imageNet(args.network)

# classify the image
class_idx, confidence = net.Classify(img)

# find the object description
class_desc = net.GetClassDesc(class_idx)

# print out the result
print("image is recognized as '{:s}' (class #{:d}) with {:f}% confidence".format(class_desc, class_idx, confidence * 100))

1)设置项目

如果您正在使用 Docker 容器,则需要将代码存储在已挂载目录 (Mounted Directory)中。这样,当您关闭容器时,代码就不会丢失。为简单起见,本指南将在主机设备上位于用户主目录下的目录中创建它 ~/my-recognition-python ,然后将该路径挂载到容器中。

从终端(容器外部)运行以下命令来创建目录、源文件并下载一些测试图像:

复制代码
# run these commands outside of container
$ cd ~/
$ mkdir my-recognition-python
$ cd my-recognition-python
$ touch my-recognition.py
$ chmod +x my-recognition.py
$ wget https://github.com/dusty-nv/jetson-inference/raw/master/data/images/black_bear.jpg 
$ wget https://github.com/dusty-nv/jetson-inference/raw/master/data/images/brown_bear.jpg
$ wget https://github.com/dusty-nv/jetson-inference/raw/master/data/images/polar_bear.jpg 

然后,当你启动容器时,挂载刚刚创建的目录

复制代码
$ docker/run.sh --volume ~/my-recognition-python:/my-recognition-python   # mounted inside the container to /my-recognition-python 

接下来,我们将程序的 Python 代码添加到我们在此处创建的空源文件中。

2)源代码

my-recognition.py 在您选择的编辑器中打开(或运行 gedit my-recognition.py)。您可以在容器外部进行编辑。

首先,让我们在文件的最顶部添加一个生命序列以自动使用 Python 解释器:

复制代码
#!/usr/bin/python3

接下来,我们将导入脚本中要使用的 Python 模块。

导入模块

添加语句以 import 加载用于识别图像和加载图像的模块。我们还将加载用于解析命令行的标准包。
jetson.inference jetson.utils argparse

复制代码
import jetson.inference
import jetson.utils

import argparse

注意 :这些 Jetson 模块是在构建 repo sudo make install 的步骤中安装的。

如果您没有运行,那么当我们运行示例时将找不到这些包。

解析命令行

接下来,添加一些样板代码来解析图像文件名和可选 --network 参数:

复制代码
# parse the command line
parser = argparse.ArgumentParser()
parser.add_argument("filename", type=str, help="filename of the image to process")
parser.add_argument("--network", type=str, default="googlenet", help="model to use, can be:  googlenet, resnet-18, ect. (see --help for others)")
args = parser.parse_args()

此示例加载并分类用户指定的图像。预计运行方式如下:

复制代码
$ ./my-recognition.py my_image.jpg

需要加载的图像文件名应该替换为 my_image.jpg。您还可以选择指定 --network 参数来更改使用的分类网络(默认为 GoogleNet):

复制代码
$ ./my-recognition.py --network=resnet-18 my_image.jpg

有关下载其他网络的更多信息,请参阅上一页的下载其他分类模型部分。

从磁盘加载图像

您可以使用该函数将图像从磁盘加载到共享 CPU/GPU 内存中 loadImage()。支持的格式包括 JPG、PNG、TGA 和 BMP。

添加此行以使用从命令行指定的文件名加载图像:

复制代码
img = jetson.utils.loadImage(args.filename)

返回的图像将是一个 jetson.utils.cudaImage 包含宽度、高度和像素格式等属性的对象:

复制代码
<jetson.utils.cudaImage>
  .ptr      # memory address (not typically used)
  .size     # size in bytes
  .shape    # (height,width,channels) tuple
  .width    # width in pixels
  .height   # height in pixels
  .channels # number of color channels
  .format   # format string
  .mapped   # true if ZeroCopy

有关从 Python 访问图像的更多信息,请参阅使用 CUDA 进行图像处理页面。为简单起见,我们在此仅加载单幅图像。要加载视频或图像序列,您需要 videoSource 像上一个 imagenet.py 示例一样使用 API。

加载图像识别网络

使用该 imageNet 对象,以下代码将使用 TensorRT 加载所需的分类模型。除非您使用标志指定了其他网络,否则默认情况下它将加载 GoogleNet,该模型在您最初构建代码库 --network 时已下载(默认情况下也选择下载该模型)。jetson-inference ResNet-18

所有可用的分类模型均在 ImageNet ILSVRC 数据集上进行预训练,可以识别多达 1000 种不同类别的物体,例如不同种类的水果和蔬菜、许多不同种类的动物,以及日常人造物体,如车辆、办公家具、运动器材等。

复制代码
# load the recognition network
net = jetson.inference.imageNet(args.network)

对图像进行分类

接下来,我们将使用以下 imageNet.Classify() 函数通过识别网络对图像进行分类:

复制代码
# classify the image
class_idx, confidence = net.Classify(img)

imageNet.Classify() 接受图像及其尺寸,并使用 TensorRT 执行推理。

它返回一个元组,其中包含图像被识别为的对象类的整数索引,以及结果的浮点置信度值。

解释结果

作为最后一步,让我们检索类别描述并打印出分类结果:

复制代码
# find the object description
class_desc = net.GetClassDesc(class_idx)

# print out the result
print("image is recognized as '{:s}' (class #{:d}) with {:f}% confidence".format(class_desc, class_idx, confidence * 100))

imageNet.Classify() 返回已识别对象类别的索引(在 ILSVRC 上训练的模型中,介于 0 和 999 之间)。给定类别索引,该函数 imageNet.GetClassDesc() 将返回包含该类别文本描述的字符串。这些描述会自动从 ilsvrc12_synset_words.txt 加载。

就是这样!这就是我们进行图像分类所需的全部 Python 代码。请参阅上面完整的源代码

3)运行示例

现在我们的 Python 程序已经完成,让我们对本页开头下载的测试图像进​​行分类:

复制代码
$ ./my-recognition.py polar_bear.jpg
image is recognized as 'ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus' (class #296) with 99.999878% confidence

$ ./my-recognition.py brown_bear.jpg
image is recognized as 'brown bear, bruin, Ursus arctos' (class #294) with 99.928925% confidence

$ ./my-recognition.py black_bear.jpg
image is recognized as 'American black bear, black bear, Ursus americanus, Euarctos americanus' (class #295) with 98.898628% confidence

您还可以通过 --network 指定标志来选择使用不同的网络,如下所示:

复制代码
$ ./my-recognition.py --network=resnet-18 polar_bear.jpg
image is recognized as 'ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus' (class #296) with 99.743396% confidence

接下来,我们将逐步介绍如何创建该程序的 C++ 版本(这里先不介绍,人生苦短,我用 python)。

2.2.1.3 编写你自己的图像识别程序(C++)

2.2.1.4 运行实时摄像头识别演示

我们之前使用的 imagenet.cpp/imagenet.py 示例也可用于实时摄像头流式传输。支持的摄像头类型包括:

  • MIPI CSI 摄像头(csi://0
  • V4L2 摄像头(/dev/video0
  • RTP/RTSP 流(rtsp://username:password@ip:port

有关视频流和协议的更多信息,请参阅摄像机流和多媒体页面。

以下是通过摄像机启动该程序的一些典型场景(运行 `--help``以获取更多选项):

C++

复制代码
$ ./imagenet csi://0                    # MIPI CSI camera
$ ./imagenet /dev/video0                # V4L2 camera
$ ./imagenet /dev/video0 output.mp4     # save to video file

Python

复制代码
$ ./imagenet.py csi://0                 # MIPI CSI camera
$ ./imagenet.py /dev/video0             # V4L2 camera
$ ./imagenet.py /dev/video0 output.mp4  # save to video file

注意 :例如要使用的相机,请参阅 Jetson Wiki 的以下部分:

OpenGL 窗口中显示的是实时摄像头流、已分类对象的名称、已分类对象的置信度以及网络的帧率。在 Jetson Nano 上,GoogleNet 和 ResNet-18 的帧率最高可达约 75 FPS(在其他 Jetson 上速度更快)。

该应用程序最多可以识别 1000 种不同类型的对象,因为其分类模型是在包含 1000 个对象类别的 ILSVRC ImageNet 数据集上训练的。这 1000 种对象的名称映射,您可以在代码库中找到:data/networks/ilsvrc12_synset_words.txt

至此,Hello AI World 教程中关于图像分类的部分就结束了。接下来,我们将开始使用目标检测网络,该网络可以为我们提供每帧图像中多个目标的边界框坐标。

2.2.1.5 图像标记的多标签分类

多标签分类模型能够同时识别多个对象类别,用于执行图像标记等任务。多标签深度神经网络 (DNN) 的拓扑结构与普通的单类模型几乎相同,只是它们使用的是 S 型激活层,而不是 Softmax 激活层。resnet18-tagging-voc 目前有一个预训练的多标签模型,该模型是在 Pascal VOC 数据集上训练的:

要启用图像标记,您需要运行 imagenet/imagenet.py 以及 --topK=0--threshold 做选择:

复制代码
# C++
$ imagenet --model=resnet18-tagging-voc --topK=0 --threshold=0.25 "images/object_*.jpg" images/test/tagging_%i.jpg

# Python
$ imagenet.py --model=resnet18-tagging-voc --topK=0 --threshold=0.25 "images/object_*.jpg" images/test/tagging_%i.jpg

使用 --topK=0 意味着所有置信度得分超过阈值的类都将被分类器返回。

1)从代码中检索多个图像标签

当指定 topK 参数时,imageNet.Classify() 函数将返回元组 (classID, confidence)。有关使用多个分类结果的代码示例,请参阅 imagenet.cppimagenet.py

C++

复制代码
imageNet::Classifications classifications;	// std::vector<std::pair<uint32_t, float>>  (classID, confidence)

if( net->Classify(image, input->GetWidth(), input->GetHeight(), classifications, topK) < 0 )
	continue;

for( uint32_t n=0; n < classifications.size(); n++ )
{
	const uint32_t classID = classifications[n].first;
	const char* classLabel = net->GetClassLabel(classID);
	const float confidence = classifications[n].second * 100.0f;
	
	printf("imagenet:  %2.5f%% class #%i (%s)\n", confidence, classID, classLabel);	
}

Python

复制代码
predictions = net.Classify(img, topK=args.topK)

for n, (classID, confidence) in enumerate(predictions):
   classLabel = net.GetClassLabel(classID)
   confidence *= 100.0
   print(f"imagenet:  {confidence:05.2f}% class #{classID} ({classLabel})")

请注意,topK 也可以用于单类分类以获得前 N 个结果,尽管这些模型并未针对图像标记进行训练。

2.2.2 物体检测

2.2.2.1 使用 DetectNet 定位物体

前面的识别示例输出代表整个输入图像的类别概率。接下来我们将重点介绍物体检测,并通过提取边界框来查找帧中各种物体的位置。与图像分类不同,物体检测网络能够在每帧中检测许多不同的物体。

detectNet 对象接受图像作为输入,并输出检测到的边界框的坐标列表及其类别和置信度值。 可基于 Python 和 C++ 使用 detectNet。下文介绍了可供下载的各种预训练检测模型。默认使用的模型是在 MS COCO 数据集上训练的 91 类 SSD-Mobilenet-v2 模型,该模型借助 TensorRT 在 Jetson 上实现了实时推理。

作为使用该类的示例 detectNet,我们提供了 C++ 和 Python 的示例程序:

这些示例能够检测图像、视频和摄像头图像中的物体。如需详细了解所支持的各种输入/输出流类型,请参阅摄像头流和多媒体页面。

1)从图像中检测物体

首先,让我们尝试使用该 detectnet 程序在静态图像中定位对象。除了输入/输出路径外,还有一些其他命令行选项:

  • 可选 --network 标志,用于更改正在使用的检测模型(默认为 SSD-Mobilenet-v2)。
  • 可选 --overlay 标志,可以是逗号分隔的 boxlineslabelsconfnone 的组合
    • 默认 --overlay=box,labels,conf 显示框、标签和置信度值
    • box 选项绘制填充的边界框,而 lines 仅绘制未填充的轮廓
  • 可选 --alpha 值,用于设置叠加期间使用的 alpha 混合值(默认值为 120)。
  • 可选 --threshold 值,设置检测的最小阈值(默认值为 0.5)。

如果您使用的是 Docker 容器,建议将输出镜像保存到 images/test 已挂载的目录。这样,您就可以在主机设备上轻松查看这些镜像 jetson-inference/data/images/test(更多信息,请参阅已挂载的数据卷)。

以下是使用默认 SSD-Mobilenet-v2 模型检测图像中行人的一些示例:

复制代码
# C++
$ ./detectnet --network=ssd-mobilenet-v2 images/peds_0.jpg images/test/output.jpg     # --network flag is optional

# Python
$ ./detectnet.py --network=ssd-mobilenet-v2 images/peds_0.jpg images/test/output.jpg  # --network flag is optional
复制代码
# C++
$ ./detectnet images/peds_1.jpg images/test/output.jpg

# Python
$ ./detectnet.py images/peds_1.jpg images/test/output.jpg

注意:第一次运行每个模型时,TensorRT 将花费几分钟来优化网络。

然后,此优化的网络文件将缓存到磁盘,因此将来使用该模型的运行将加载得更快。

以下是控制台程序输出的更多检测示例。基于 SSD 的模型训练的 91 类 MS COCO 数据集包含人物、车辆、动物以及各种类型的家居物品。

images/ 下面有各种图像可供测试,例如 cat_*.jpgdog_*.jpghorse_*.jpgpeds_*.jpg

2)处理目录或图像序列

如果您有多个想要同时处理的图像,则可以detectnet使用包含图像或通配符序列的目录路径启动该程序:

复制代码
# C++
./detectnet "images/peds_*.jpg" images/test/peds_output_%i.jpg

# Python
./detectnet.py "images/peds_*.jpg" images/test/peds_output_%i.jpg

注意:使用通配符时,请务必将其括在引号 ( "*.jpg") 中。否则,操作系统将自动扩展序列并修改命令行上的参数顺序,这可能会导致其中一个输入图像被输出覆盖。

有关加载/保存图像序列的更多信息,请参阅相机流和多媒体页面

3)处理视频文件

您还可以处理磁盘中的视频。有关加载/保存视频的更多信息,请参阅此处

复制代码
# Download test video
wget https://nvidia.box.com/shared/static/veuuimq6pwvd62p9fresqhrrmfqz0e2f.mp4 -O pedestrians.mp4

# C++
./detectnet pedestrians.mp4 images/test/pedestrians_ssd.mp4

# Python
./detectnet.py pedestrians.mp4 images/test/pedestrians_ssd.mp4
复制代码
# Download test video
wget https://nvidia.box.com/shared/static/i5i81mkd9wdh4j7wx04th961zks0lfh9.avi -O parking.avi

# C++
./detectnet parking.avi images/test/parking_ssd.avi

# Python
./detectnet.py parking.avi images/test/parking_ssd.avi

请记住,您可以使用该--threshold设置来调高或调低检测灵敏度(默认值为 0.5)。

4)预训练检测模型可用

下面是可供使用的预训练对象检测网络的表格,以及用于加载预训练模型的相关--network参数:detectnet

Model CLI argument NetworkType enum Object classes
SSD-Mobilenet-v1 ssd-mobilenet-v1 SSD_MOBILENET_V1 91 (COCO classes)
SSD-Mobilenet-v2 ssd-mobilenet-v2 SSD_MOBILENET_V2 91 (COCO classes)
SSD-Inception-v2 ssd-inception-v2 SSD_INCEPTION_V2 91 (COCO classes)
TAO PeopleNet peoplenet PEOPLENET person, bag, face
TAO PeopleNet (已​​修剪) peoplenet-pruned PEOPLENET_PRUNED person, bag, face
TAO DashCamNet dashcamnet DASHCAMNET person, car, bike, sign
TAO TrafficCamNet trafficcamnet TRAFFICCAMNET person, car, bike, sign
TAO FaceDetect facedetect FACEDETECT face

其他:

Model CLI argument NetworkType enum Object classes
DetectNet-COCO-Dog coco-dog COCO_DOG
DetectNet-COCO-Bottle coco-bottle COCO_BOTTLE 瓶子
DetectNet-COCO-Chair coco-chair COCO_CHAIR 椅子
DetectNet-COCO-Airplane coco-airplane COCO_AIRPLANE 飞机
ped-100 pednet PEDNET pedestrians (行人)
multiped-500 multiped PEDNET_MULTI pedestrians (行人), luggage (行李)
facenet-120 facenet FACENET faces

5)运行不同的检测模型

您可以通过 --network 将命令行上的标志设置为上表中相应的 CLI 参数之一来指定要加载的模型。如果 --network 未指定可选标志,则默认加载 SSD-Mobilenet-v2。

例如,如果您选择使用模型下载器工具下载 SSD-Inception-v2,则可以像这样使用它:

复制代码
# C++
$ ./detectnet --network=ssd-inception-v2 input.jpg output.jpg

# Python
$ ./detectnet.py --network=ssd-inception-v2 input.jpg output.jpg

6)源代码

作为参考,下面是源代码 detectnet.py

复制代码
import jetson.inference
import jetson.utils

import argparse
import sys

# parse the command line
parser = argparse.ArgumentParser(description="Locate objects in a live camera stream using an object detection DNN.")

parser.add_argument("input_URI", type=str, default="", nargs='?', help="URI of the input stream")
parser.add_argument("output_URI", type=str, default="", nargs='?', help="URI of the output stream")
parser.add_argument("--network", type=str, default="ssd-mobilenet-v2", help="pre-trained model to load (see below for options)")
parser.add_argument("--overlay", type=str, default="box,labels,conf", help="detection overlay flags (e.g. --overlay=box,labels,conf)\nvalid combinations are:  'box', 'labels', 'conf', 'none'")
parser.add_argument("--threshold", type=float, default=0.5, help="minimum detection threshold to use") 

try:
	opt = parser.parse_known_args()[0]
except:
	print("")
	parser.print_help()
	sys.exit(0)

# load the object detection network
net = jetson.inference.detectNet(opt.network, sys.argv, opt.threshold)

# create video sources & outputs
input = jetson.utils.videoSource(opt.input_URI, argv=sys.argv)
output = jetson.utils.videoOutput(opt.output_URI, argv=sys.argv)

# process frames until the user exits
while True:
	# capture the next image
	img = input.Capture()

	# detect objects in the image (with overlay)
	detections = net.Detect(img, overlay=opt.overlay)

	# print the detections
	print("detected {:d} objects in image".format(len(detections)))

	for detection in detections:
		print(detection)

	# render the image
	output.Render(img)

	# update the title bar
	output.SetStatus("{:s} | Network {:.0f} FPS".format(opt.network, net.GetNetworkFPS()))

	# print out performance info
	net.PrintProfilerTimes()

	# exit on input/output EOS
	if not input.IsStreaming() or not output.IsStreaming():
		break

接下来,我们将在实时摄像机流上运行对象检测。

2.2.2.2 运行实时摄像头检测演示

我们之前使用的 detectnet.cpp/detectnet.py 示例也可用于实时摄像头流式传输。支持的摄像头类型包括:

  • MIPI CSI 摄像头(csi://0
  • V4L2 摄像头(/dev/video0
  • RTP/RTSP 流(rtsp://username:password@ip:port
  • WebRTC 流(webrtc://@:port/stream_name

有关视频流和协议的更多信息,请参阅摄像机流和多媒体页面。

运行该程序以 --help 查看完整的选项列表(其中一些特定于 detectNet 的选项包括):

  • --network 改变正在使用的检测模型的标志(默认为SSD-Mobilenet-v2)。
  • --overlay 标志可以是逗号分隔的 boxlabelsconftracknone
    • 默认 --overlay=box,labels,conf 显示框、标签和置信度值
  • --alpha 值设置了叠加期间使用的 alpha 混合值(默认值为 120)。
  • --threshold 设置检测的最小阈值的值(默认值为 0.5)。

以下是通过摄像头启动该程序的一些典型场景:

C++

复制代码
$ ./detectnet csi://0                    # MIPI CSI camera
$ ./detectnet /dev/video0                # V4L2 camera
$ ./detectnet /dev/video0 output.mp4     # save to video file

Python

复制代码
$ ./detectnet.py csi://0                 # MIPI CSI camera
$ ./detectnet.py /dev/video0             # V4L2 camera
$ ./detectnet.py /dev/video0 output.mp4  # save to video file

注意 :例如要使用的相机,请参阅 Jetson Wiki 的以下部分:

可视化

OpenGL 窗口中显示的是实时摄像头数据流,其上叠加了检测到的物体的边界框。请注意,基于 SSD 的模型目前性能最高。

如果视频源中未检测到所需的物体,或者您得到了虚假检测,请尝试使用参数降低或增加检测阈值 --threshold(默认值为 0.5)。

接下来,我们将介绍如何使用 Python 创建摄像头检测应用程序的代码。

2.2.2.3 编写你自己的物体检测程序

在本教程的这一步,我们将指导您如何创建自己的 Python 脚本,仅用 10-15 行代码即可在实时摄像头信号中进行实时物体检测。该程序将捕获视频帧,并使用检测深度神经网络 (DNN) 处理这些帧,并利用 detectNet 目标物体进行处理。

完整的源代码可以在 python/examples/my-detection.py repo 文件中获取,但以下指南将默认它们位于用户的主目录或您选择的任意目录中。以下是我们将要讲解的 Python 代码的快速预览:

复制代码
from jetson_inference import detectNet
from jetson_utils import videoSource, videoOutput

net = detectNet("ssd-mobilenet-v2", threshold=0.5)
camera = videoSource("csi://0")      # '/dev/video0' for V4L2
display = videoOutput("display://0") # 'my_video.mp4' for file

while display.IsStreaming():
    img = camera.Capture()

    if img is None: # capture timeout
        continue

    detections = net.Detect(img)
    
    display.Render(img)
    display.SetStatus("Object Detection | Network {:.0f} FPS".format(net.GetNetworkFPS()))

YouTube 上还有此编码教程的视频截屏:

1)源代码

首先,打开您选择的文本编辑器并创建一个新文件。下面我们假设您将该文件保存在主机设备上的用户主目录下 ~/my-detection.py,但您可以根据需要命名和存储它。如果您使用的是 Docker 容器,则需要将代码存储在已挂载目录中,类似于我们在 Python 图像识别示例中所做的操作。

导入模块

在源文件的顶部,我们将导入将在脚本中使用的 Python 模块。添加语句以 import 加载用于对象检测和摄像头捕捉的模块。

复制代码
from jetson_inference import detectNet
from jetson_utils import videoSource, videoOutput

注意 :这些 Jetson 模块是在构建 repo sudo make install 的步骤中安装的。

如果您没有运行,那么当我们运行示例时将找不到这些包。

加载检测模型

接下来使用以下行创建一个 detectNet 加载 91 类 SSD-Mobilenet-v2 模型的对象实例:

复制代码
# load the object detection model
net = detectNet("ssd-mobilenet-v2", threshold=0.5)

请注意,您可以将模型字符串更改为此表中的值之一,以加载不同的检测模型。0.5 为了便于说明,我们还在此处将检测阈值设置为默认值(您可以根据需要稍后进行调整)。

打开相机流

为了连接到摄像机设备进行流式传输,我们将创建该 videoSource 对象的一个​​实例:

复制代码
camera = videoSource("csi://0")      # '/dev/video0' for V4L2

传递给 videoSource() 的字符串实际上可以是任何有效的资源 URI,无论是相机、视频文件还是网络流。有关视频流和协议的更多信息,请参阅摄像机流和多媒体页面。

注意 :要使用兼容相机,请参阅 Jetson Wiki 的以下部分:

显示循环

接下来,我们将使用该对象创建一个视频输出接口 videoOutput,并创建一个一直运行直到用户退出的主循环:

复制代码
display = videoOutput("display://0") # 'my_video.mp4' for file

while display.IsStreaming():
	# main loop will go here

请注意,以下代码的其余部分应在此while循环下方缩进。与上述类似,您可以将 URI 字符串替换为此页面上的其他类型的输出(例如视频文件等)。

相机捕捉

主循环中发生的第一件事是从摄像机捕获下一个视频帧。camera.Capture() 将等到下一帧从摄像机发送并加载到 GPU 内存中。

复制代码
	img = camera.Capture()
	
	if img is None: # capture timeout
		continue

返回的图像将是一个 jetson.utils.cudaImage 包含宽度、高度和像素格式等属性的对象:

复制代码
<jetson.utils.cudaImage>
  .ptr      # memory address (not typically used)
  .size     # size in bytes
  .shape    # (height,width,channels) tuple
  .width    # width in pixels
  .height   # height in pixels
  .channels # number of color channels
  .format   # format string
  .mapped   # true if ZeroCopy

有关从 Python 访问图像的更多信息,请参阅使用 CUDA 进行图像处理页面。

检测物体

接下来,检测网络使用该函数 net.Detect() 处理图像。它从以下位置获取图像 camera.Capture() 并返回检测列表:

复制代码
	detections = net.Detect(img)

此功能还会自动将检测结果叠加在输入图像上。

如果需要,您可以在 print(detections) 此处添加一条语句,这样每次检测结果的坐标、置信度和类别信息都会打印到终端。另请参阅 detectNet 文档,了解返回的结构体中用于在自定义应用程序中直接访问的不同成员的信息 Detection

渲染

最后,我们将使用 OpenGL 将结果可视化,并更新窗口的标题以显示当前性能:

复制代码
	display.Render(img)
	display.SetStatus("Object Detection | Network {:.0f} FPS".format(net.GetNetworkFPS()))

Render() 功能将自动翻转后缓冲区并在屏幕上显示图像。

源列表

就是这样!为了完整起见,下面是我们刚刚创建的 Python 脚本的完整源代码:

复制代码
from jetson_inference import detectNet
from jetson_utils import videoSource, videoOutput

net = detectNet("ssd-mobilenet-v2", threshold=0.5)
camera = videoSource("csi://0")      # '/dev/video0' for V4L2
display = videoOutput("display://0") # 'my_video.mp4' for file

while display.IsStreaming():
    img = camera.Capture()

    if img is None: # capture timeout
        continue

    detections = net.Detect(img)
    
    display.Render(img)
    display.SetStatus("Object Detection | Network {:.0f} FPS".format(net.GetNetworkFPS()))

请注意,此版本假设您使用的是 MIPI CSI 摄像头。Opening the Camera Stream 有关如何将其更改为使用其他输入类型的信息,请参阅上文。

2)运行程序

要运行我们刚刚编写的应用程序,只需使用 Python 解释器从终端启动它:

复制代码
$ python3 my-detection.py

为了调整结果,您可以尝试更改加载的模型以及检测阈值。祝您玩得开心!

2.2.2.4 使用 TAO 检测模型

NVIDIA 的 TAO 工具包包含高精度高分辨率物体检测模型,针对 INT8 精度进行了优化/修剪和量化。jetson-inference 支持基于 DetectNet_v2 DNN 架构的 TAO 模型,包括以下预训练模型:

Model CLI argument Object classes
TAO PeopleNet peoplenet person, bag, face
TAO PeopleNet (pruned) peoplenet-pruned person, bag, face
TAO DashCamNet(行车记录仪) dashcamnet person, car, bike, sign
TAO TrafficCamNet trafficcamnet person, car, bike, sign
TAO FaceDetect facedetect face

尽管下面的部分介绍了如何加载您自己的 TAO 模型,但让我们先看看如何使用预先训练的模型。

1)PeopleNet

PeopleNet 是一个高分辨率 960x544 模型,在检测人物、包袋和人脸方面准确率高达 90%。它基于 DetectNet_v2 和 ResNet-34 主干网络。运行 detectnet/detectnet.py --model=peoplenet 即可在支持 TAO PeopleNet 的平台上以 INT8 精度运行该模型(其他平台则为 FP16)。此外,还有一个 peoplenet-pruned 速度更快但准确率略低的模型。

复制代码
# Download test video
wget https://nvidia.box.com/shared/static/veuuimq6pwvd62p9fresqhrrmfqz0e2f.mp4 -O pedestrians.mp4

# C++
$ detectnet --model=peoplenet pedestrians.mp4 pedestrians_peoplenet.mp4

# Python
$ detectnet.py --model=peoplenet pedestrians.mp4 pedestrians_peoplenet.mp4

您还可以调整 --confidence--clustering 阈值(由于准确率更高,这些 TAO 模型似乎不会在较低的阈值下引入太多误报)。Flask Web 应用是一个便捷的工具,可以以交互方式操作这些设置。

2)DashCamNet

与 PeopleNet 类似,DashCamNet 是一款基于 DetectNet_v2 和 ResNet-34 的 960x544 检测器。其预期用途是从街道视角和第一人称视角检测行人和车辆。TrafficCamNet 与之类似,但适用于从更高视角拍摄的图像。

复制代码
# C++
$ detectnet --model=dashcamnet input.mp4 output.mp4

# Python
$ detectnet.py --model=dashcamnet input.mp4 output.mp4

注意:您可以使用"相机流媒体和多媒体"页面中的任何输入/输出来运行它

3)FaceDetect

FaceDetect 是一个仅用于检测人脸的 TAO 模型。该模型基于一个包含超过 180 万个从各种摄像机角度采集的样本的数据集进行训练,准确率高达 85% 左右。其分辨率为 736x416,使用 DetectNet_v2 和 ResNet-18 主干网络。

复制代码
# C++
$ detectnet --model=facedetect "images/humans_*.jpg" images/test/facedetect_humans_%i.jpg

# Python
$ detectnet.py --model=facedetect "images/humans_*.jpg" images/test/facedetect_humans_%i.jpg

4)导入您自己的 TAO 检测模型

虽然 jetson-inference 可以自动下载、转换和加载上述预训练的 TAO 检测模型,但您可能希望使用这些模型的其他版本,或者您自己使用 TAO 训练或微调的 DetectNet_v2 模型。为此,请将训练好的 ETLT 模型以及相应版本的 tao-converter 工具复制到您的 Jetson 中。然后,根据模型的配置(详细信息通常可在模型卡上找到),您可以运行如下脚本,从 ETLT 生成 TensorRT 引擎:

复制代码
# model config
MODEL_DIR="peoplenet_deployable_quantized_v2.6.1"
MODEL_INPUT="$MODEL_DIR/resnet34_peoplenet_int8.etlt"
MODEL_OUTPUT="$MODEL_INPUT.engine"

INPUT_DIMS="3,544,960"
OUTPUT_LAYERS="output_bbox/BiasAdd,output_cov/Sigmoid"
MAX_BATCH_SIZE="1"

WORKSPACE="4294967296" # 4GB (default)
PRECISION="int8"       # fp32, fp16, int8
CALIBRATION="$MODEL_DIR/resnet34_peoplenet_int8.txt"

ENCRYPTION_KEY="tlt_encode"

# generate TensorRT engine
tao-converter \
	-k $ENCRYPTION_KEY \
	-d $INPUT_DIMS \
	-o $OUTPUT_LAYERS \
	-m $MAX_BATCH_SIZE \
	-w $WORKSPACE \
	-t $PRECISION \
	-c $CALIBRATION \
	-e $MODEL_OUTPUT \
	$MODEL_INPUT

转换后,您可以使用 detectnet/detectnet.py 加载它,如下所示:

复制代码
$ detectnet \
	--model=$MODEL_DIR/resnet34_peoplenet_int8.etlt.engine \
	--labels=$MODEL_DIR/labels.txt \
	--input-blob=input_1 \
	--output-cvg=output_cov/Sigmoid \
	--output-bbox=output_bbox/BiasAdd \
	input.mp4 output.mp4

注意:jetson-inference 目前仅支持 TAO DetectNet_v2 模型,因为它是为该网络的预/后处理设置的

在您自己的应用程序中,您还可以使用 detectNet API 的扩展形式直接从 C++ 或 Python 加载它们。

2.2.2.5 视频中的对象跟踪

尽管利用现代检测 DNN 的准确性,您基本上可以进行"通过检测进行跟踪",但一定程度的时间滤波有利于平滑检测中的闪烁和视频中的暂时遮挡。jetson-inference 包括使用帧间 IOU(交并比)边界框比较的基本(但快速)多对象跟踪(DeepStream 有更全面的跟踪实现,可从此处 High-Speed Tracking-by-Detection Without Using Image Information 获得)。

要使用 detectnet/detectnet.py 启用跟踪,请使用 --tracking 标志运行它。

复制代码
# Download test video
wget https://nvidia.box.com/shared/static/veuuimq6pwvd62p9fresqhrrmfqz0e2f.mp4 -O pedestrians.mp4

# C++
$ detectnet --model=peoplenet --tracking pedestrians.mp4 pedestrians_tracking.mp4

# Python
$ detectnet.py --model=peoplenet --tracking pedestrians.mp4 pedestrians_tracking.mp4

1)跟踪参数

您可以使用以下命令行选项更改其他跟踪器设置:

复制代码
objectTracker arguments:
  --tracking               flag to enable default tracker (IOU)
  --tracker-min-frames=N   the number of re-identified frames for a track to be considered valid (default: 3)
  --tracker-drop-frames=N  number of consecutive lost frames before a track is dropped (default: 15)
  --tracker-overlap=N      how much IOU overlap is required for a bounding box to be matched (default: 0.5)

这些设置也可以通过 Python 中的 detectNet.SetTrackerParams() 函数或 C++ 中的 objectTracker API 进行:

Python

复制代码
from jetson_utils import detectNet

net = detectNet()

net.SetTrackingEnabled(True)
net.SetTrackingParams(minFrames=3, dropFrames=15, overlapThreshold=0.5)

C++

复制代码
#include <jetson-inference/detectNet.h>
#include <jetson-inference/objectTrackerIOU.h>

detectNet* net = detectNet::Create();

net->SetTracker(objectTrackerIOU::Create(3, 15, 0.5f));

要以交互方式使用这些设置,您可以使用浏览器中的 Flask webapp

2)跟踪状态

当启用跟踪时,detectNet.Detection 从 Detect() 返回的结果将激活由跟踪器更新的附加变量并描述每个对象的跟踪状态(例如 TrackID、TrackStatus、TrackFrames 和 TrackLost):

复制代码
struct Detection
{
	// Detection Info
	uint32_t ClassID;	/**< Class index of the detected object. */
	float Confidence;	/**< Confidence value of the detected object. */

	// Tracking Info
	int TrackID;		/**< Unique tracking ID (or -1 if untracked) */
	int TrackStatus;	/**< -1 for dropped, 0 for initializing, 1 for active/valid */ 
	int TrackFrames;	/**< The number of frames the object has been re-identified for */
	int TrackLost;   	/**< The number of consecutive frames tracking has been lost for */
	
	// Bounding Box Coordinates
	float Left;		/**< Left bounding box coordinate (in pixels) */
	float Right;		/**< Right bounding box coordinate (in pixels) */
	float Top;		/**< Top bounding box cooridnate (in pixels) */
	float Bottom;		/**< Bottom bounding box coordinate (in pixels) */
};

这些都可以从 C++ 和 Python 中访问。例如,从 Python 中:

复制代码
detections = net.Detect(img)

for detection in detections:
    if detection.TrackStatus >= 0:  # actively tracking
        print(f"object {detection.TrackID} at ({detection.Left}, {detection.Top}) has been tracked for {detection.TrackFrames} frames")
    else:  # if tracking was lost, this object will be dropped the next frame
        print(f"object {detection.TrackID} has lost tracking")   

如果跟踪丢失(TrackStatus=-1),则该对象将被丢弃,并且不再包含在后续帧的检测数组中。

2.2.3 语义分割

2.2.3.1 使用 SegNet 进行语义分割

0)总述

本教程将介绍的下一个深度学习功能是语义分割 。语义分割基于图像识别,但分类发生在像素级别,而非整幅图像。这是通过对预先训练的图像识别主干进行卷积来实现的,这将模型转换为能够逐像素标记的全卷积网络 (FCN)。语义分割对于环境感知尤其有用,它可以对每个场景中的许多不同潜在对象(包括场景前景和背景)进行密集的逐像素分类。

segNet 接受二维图像作为输入,并输出带有逐像素分类掩码叠加的第二幅图像。掩码的每个像素对应于被分类的对象类别。

segNet 可从 Python 和 C++ 使用。

作为使用该类的示例 segNet,我们提供了 C++ 和 Python 示例程序:

这些示例能够分割图像、视频和摄像头信号。如需详细了解所支持的各种输入/输出流类型,请参阅摄像头流式传输和多媒体页面。

请参阅下文,了解各种基于 FCN-ResNet18 网络的预训练分割模型,这些模型可在 Jetson 上实时运行。这些模型适用于各种环境和主题,包括城市、越野路径以及室内办公空间和住宅。

1)可用的预训练分割模型

下表列出了可用的预训练语义分割模型,以及用于加载这些模型的相关 --network 参数 segnet。它们基于 21 类 FCN-ResNet18 网络,并使用 PyTorch 在各种数据集和分辨率上进行了训练,并导出为 ONNX 格式以便使用 TensorRT 加载。

Dataset Resolution CLI Argument Accuracy Jetson Nano Jetson Xavier
Cityscapes / 城市景观 512x256 fcn-resnet18-cityscapes-512x256 83.3% 48 FPS
Cityscapes / 城市景观 1024x512 fcn-resnet18-cityscapes-1024x512 87.3% 12 FPS
Cityscapes / 城市景观 2048x1024 fcn-resnet18-cityscapes-2048x1024 89.6% 3 FPS
DeepScene / 深度场景 576x320 fcn-resnet18-deepscene-576x320 96.4% 26 FPS
DeepScene / 深度场景 864x480 fcn-resnet18-deepscene-864x480 96.9% 14 FPS
Multi-Human / 多人 512x320 fcn-resnet18-mhp-512x320 86.5% 34 FPS
Multi-Human / 多人 640x360 fcn-resnet18-mhp-640x360 87.1% 23 FPS
Pascal VOC / 帕斯卡 VOC 320x320 fcn-resnet18-voc-320x320 85.9% 45 FPS
Pascal VOC / 帕斯卡 VOC 512x320 fcn-resnet18-voc-512x320 88.5% 34 FPS
SUN RGB-D / 太阳RGB-D 512x400 fcn-resnet18-sun-512x400 64.3% 28 FPS
SUN RGB-D / 太阳RGB-D 640x512 fcn-resnet18-sun-640x512 65.1% 17 FPS
  • 如果 CLI 参数中省略了分辨率,则加载最低分辨率模型
  • 准确度表示模型验证数据集的像素分类准确度
  • nvpmodel 0 使用 JetPack 4.2.1 (MAX-N) 测量 GPU FP16 模式的性能

2)从命令行分割图像

首先,让我们尝试使用该 segnet 程序分割静态图像。除了输入/输出路径之外,还有一些其他命令行选项:

  • 可选 --network 标志更改正在使用的分割模型(见上文)
  • 可选 --visualize 标志接受 mask 和/或 overlay 模式(默认为 overlay
  • 可选 --alpha 标志设置 alpha 混合值 overlay(默认为 120
  • 可选 --filter-mode 标志接受 pointlinear 采样(默认为 linear

启动带有标志的应用程序--help以获取更多信息,并参考相机流和多媒体页面了解支持的输入/输出协议。

以下是该程序的一些用法示例:

C++

复制代码
$ ./segnet --network=<model> input.jpg output.jpg                  # overlay segmentation on original
$ ./segnet --network=<model> --alpha=200 input.jpg output.jpg      # make the overlay less opaque
$ ./segnet --network=<model> --visualize=mask input.jpg output.jpg # output the solid segmentation mask

Python

复制代码
$ ./segnet.py --network=<model> input.jpg output.jpg                  # overlay segmentation on original
$ ./segnet.py --network=<model> --alpha=200 input.jpg output.jpg      # make the overlay less opaque
$ ./segnet.py --network=<model> --visualize=mask input.jpg output.jpg # output the segmentation mask

3)城市景观

让我们看一些不同的场景。以下是使用 Cityscapes 模型分割城市街道场景的示例:

复制代码
# C++
$ ./segnet --network=fcn-resnet18-cityscapes images/city_0.jpg images/test/output.jpg

# Python
$ ./segnet.py --network=fcn-resnet18-cityscapes images/city_0.jpg images/test/output.jpg

city-*.jpg 在子目录下有更多名为 "found" 的测试图像,images/ 用于试用 Cityscapes 模型。

4)深度场景

DeepScene 数据集包含越野森林小径和植被,有助于户外机器人进行路径跟踪。

以下是通过指定参数生成分割叠加层和蒙版的示例 --visualize

C++

复制代码
$ ./segnet --network=fcn-resnet18-deepscene images/trail_0.jpg images/test/output_overlay.jpg                # overlay
$ ./segnet --network=fcn-resnet18-deepscene --visualize=mask images/trail_0.jpg images/test/output_mask.jpg  # mask

Python

复制代码
$ ./segnet.py --network=fcn-resnet18-deepscene images/trail_0.jpg images/test/output_overlay.jpg               # overlay
$ ./segnet.py --network=fcn-resnet18-deepscene --visualize=mask images/trail_0.jpg images/test/output_mask.jpg # mask

trail-*.jpg 子目录下还有更多名为的示例图像 images/

5)多人解析(MHP)

多人解析可以对身体部位进行密集标注,例如手臂、腿、头部以及不同类型的衣服。

以下是一些测试图像,用于测试 MHP 模型:humans-*.jpgimages/

复制代码
# C++
$ ./segnet --network=fcn-resnet18-mhp images/humans_0.jpg images/test/output.jpg

# Python
$ ./segnet.py --network=fcn-resnet18-mhp images/humans_0.jpg images/test/output.jpg

6)帕斯卡 VOC

Pascal VOC 是用于语义分割的原始数据集之一,包含各种人物、动物、车辆和家居用品。

其中包含一些用于 object-*.jpg 测试 Pascal VOC 模型的示例图像:

复制代码
# C++
$ ./segnet --network=fcn-resnet18-voc images/object_0.jpg images/test/output.jpg

# Python
$ ./segnet.py --network=fcn-resnet18-voc images/object_0.jpg images/test/output.jpg

7)太阳 RGB-D

SUN [RGB-D][] 数据集为办公室和家庭中常见的许多室内物体和场景提供了分割真实值。

查看子目录 room-*.jpg 下名为 found 的图像 images/,以测试 SUN 模型:

复制代码
# C++
$ ./segnet --network=fcn-resnet18-sun images/room_0.jpg images/test/output.jpg

# Python
$ ./segnet.py --network=fcn-resnet18-sun images/room_0.jpg images/test/output.jpg

8)处理目录或图像序列

如果您想要处理目录或图像序列,则可以使用包含图像或通配符序列的目录路径启动程序:

复制代码
# C++
$ ./segnet --network=fcn-resnet18-sun "images/room_*.jpg" images/test/room_output_%i.jpg

# Python
$ ./segnet.py --network=fcn-resnet18-sun "images/room_*.jpg" images/test/room_output_%i.jpg

注意:使用通配符时,请务必将其括在引号 ("*.jpg") 中。否则,操作系统将自动扩展序列并修改命令行上的参数顺序,这可能会导致其中一个输入图像被输出覆盖。

有关加载/保存图像序列的更多信息,请参阅相机流和多媒体页面。接下来,我们将对实时相机或视频流进行分割。

2.2.3.2 运行实时摄像头分割演示

我们之前使用的 segnet.cpp / segnet.py 示例也可用于实时摄像头流式传输。支持的摄像头类型包括:

  • MIPI CSI 摄像头(csi://0
  • V4L2 摄像头(/dev/video0
  • RTP/RTSP 流(rtsp://username:password@ip:port

有关视频流和协议的更多信息,请参阅摄像机流和多媒体页面。

运行该程序以 --help 查看完整的选项列表(其中一些特定于 segNet 的选项包括):

  • 可选 --network 标志更改正在使用的分割模型(参见可用网络)
  • 可选 --visualize 标志接受 mask 和/或 overlay 模式(默认为 overlay
  • 可选 --alpha 标志设置覆盖层的 alpha 混合值(默认为 120
  • 可选 --filter-mode 标志接受 pointlinear``采样(默认为 linear`)

以下是启动该程序的一些典型场景 - 请参阅此表了解可用的模型。

C++

复制代码
$ ./segnet --network=<model> csi://0                    # MIPI CSI camera
$ ./segnet --network=<model> /dev/video0                # V4L2 camera
$ ./segnet --network=<model> /dev/video0 output.mp4     # save to video file

Python

复制代码
$ ./segnet.py --network=<model> csi://0                 # MIPI CSI camera
$ ./segnet.py --network=<model> /dev/video0             # V4L2 camera
$ ./segnet.py --network=<model> /dev/video0 output.mp4  # save to video file

注意 :例如要使用的相机,请参阅 Jetson Wiki 的以下部分:

可视化

OpenGL 窗口中显示的是实时摄像机数据流与分割输出叠加,为了清晰起见,还附上了实心分割蒙版。以下是一些在不同模型上的使用示例,可​​供您尝试:

复制代码
# C++
$ ./segnet --network=fcn-resnet18-mhp csi://0

# Python
$ ./segnet.py --network=fcn-resnet18-mhp csi://0
复制代码
# C++
$ ./segnet --network=fcn-resnet18-sun csi://0

# Python
$ ./segnet.py --network=fcn-resnet18-sun csi://0
复制代码
# C++
$ ./segnet --network=fcn-resnet18-deepscene csi://0

# Python
$ ./segnet.py --network=fcn-resnet18-deepscene csi://0

您可以随意尝试适合室内和室外环境的不同模型和分辨率。

2.2.4 姿态估计

姿势估计包括定位构成骨架拓扑(又称链接)的各个身体部位(又称关键点)。姿势估计具有多种应用,包括手势、AR/VR、HMI(人机界面)以及姿势/步态校正。我们提供用于人体和手势估计的预训练模型,能够每帧检测多个人物。

poseNet 对象接受图像作为输入,并输出一个物体姿态列表。每个物体姿态包含一个检测到的关键点列表,以及它们的位置和关键点之间的链接。您可以查询这些列表以查找特定特征。该对象 poseNet 可以在 PythonC++ 中使用。

  • posenet.cpp(C++)
  • posenet.py(Python)

这些示例能够检测图像、视频和摄像头图像中多个人物的姿势。如需详细了解所支持的各种输入/输出流类型,请参阅摄像头流式传输和多媒体页面。

图像姿态估计

首先,让我们尝试 posenet 在一些示例图像上运行该示例。除了输入/输出路径之外,还有一些可选的命令行选项:

  • 可选 --network 标志,用于改变正在使用的姿势模型(默认为 resnet18-body)。
  • 可选 --overlay 标志,可以是逗号分隔的 boxlinkskeypoints 的组合和 none
    • 默认设置是 --overlay=links,keypoints 在关键点上显示圆圈,在链接上显示线条
  • 可选 --keypoint-scale 值,控制叠加层中关键点圆的半径(默认值为 0.0052
  • 可选 --link-scale 值,控制覆盖层中链接线的线宽(默认值为 0.0013
  • 可选 --threshold 值,设置检测的最小阈值(默认值为 0.15)。

如果您使用的是 Docker 容器,建议将输出镜像保存到 images/test 已挂载的目录。这样,您就可以在主机设备上轻松查看这些镜像 jetson-inference/data/images/test(更多信息,请参阅已挂载的数据卷)。

以下是使用默认的 Pose-ResNet18-Body 模型进行人体姿势估计的一些示例:

复制代码
# C++
$ ./posenet "images/humans_*.jpg" images/test/pose_humans_%i.jpg

# Python
$ ./posenet.py "images/humans_*.jpg" images/test/pose_humans_%i.jpg

注意: 第一次运行每个模型时,TensorRT 将花费几分钟来优化网络。

然后,此优化的网络文件将缓存到磁盘,因此将来使用该模型的运行将加载得更快。

下面还有人物测试图像,images/peds_*.jpg 您也可以尝试一下。

视频姿态估计

要在实时摄像机流或视频上运行姿势估计,请从摄像机流和多媒体页面传入设备或文件路径。

复制代码
# C++
$ ./posenet /dev/video0     # csi://0 if using MIPI CSI camera

# Python
$ ./posenet.py /dev/video0  # csi://0 if using MIPI CSI camera
复制代码
# C++
$ ./posenet --network=resnet18-hand /dev/video0

# Python
$ ./posenet.py --network=resnet18-hand /dev/video0

预训练姿态估计模型

以下是可用的预训练姿态估计网络,以及用于加载预训练模型的相关 --network 参数:posenet

Model CLI argument NetworkType enum Keypoints
Pose-ResNet18-Body resnet18-body RESNET18_BODY 18
Pose-ResNet18-Hand resnet18-hand RESNET18_HAND 21
Pose-DenseNet121-Body densenet121-body DENSENET121_BODY 18

您可以通过将命令行上的 --network 标志设置为上表中相应的 CLI 参数之一来指定要加载的模型。默认情况下,如果 --network 未指定可选标志,则使用 Pose-ResNet18-Body。

处理物体姿势

如果您想访问姿势关键点位置,该 poseNet.Process() 函数将返回一个结构列表 poseNet.ObjectPose。每个物体姿势代表一个物体(例如一个人),并包含检测到的关键点和链接的列表(更多信息请参阅 PythonC++ 文档)。

下面是 Python 伪代码,通过在 left_shoulderleft_wrist 关键点之间形成一个向量,找到一个人指向的 2D 方向(在图像空间中) :

复制代码
poses = net.Process(img)

for pose in poses:
    # find the keypoint index from the list of detected keypoints
    # you can find these keypoint names in the model's JSON file, 
    # or with net.GetKeypointName() / net.GetNumKeypoints()
    left_wrist_idx = pose.FindKeypoint('left_wrist')
    left_shoulder_idx = pose.FindKeypoint('left_shoulder')

    # if the keypoint index is < 0, it means it wasn't found in the image
    if left_wrist_idx < 0 or left_shoulder_idx < 0:
        continue
	
    left_wrist = pose.Keypoints[left_wrist_idx]
    left_shoulder = pose.Keypoints[left_shoulder_idx]

    point_x = left_shoulder.x - left_wrist.x
    point_y = left_shoulder.y - left_wrist.y

    print(f"person {pose.ID} is pointing towards ({point_x}, {point_y})")

这是一个简单的例子,但你可以通过进一步操作向量并查找更多关键点来使其更加实用。还有一些更高级的技术,例如在 trt_hand_pose 项目中,使用机器学习对姿势结果进行手势分类。

2.2.5 动作识别

动作识别可以对视频帧序列中发生的活动、行为或手势进行分类。深度神经网络 (DNN) 通常使用添加了时间维度的图像分类主干模型。例如,基于 ResNet18 的预训练模型使用 16 帧的窗口。您还可以跳过帧来延长模型对动作进行分类的时间窗口。

actionNet 对象每次接收一个视频帧,将其缓冲为模型的输入,并输出置信度最高的类。 actionNet 可以在 Python 和 C++ 中使用。

作为使用该类 actionNet 的示例 ,有针对 C++ 和 Python 的示例程序:

运行示例

要在实时摄像机流或视频上运行动作识别,请从摄像机流和多媒体页面传入设备或文件路径。

复制代码
# C++
$ ./actionnet /dev/video0           # V4L2 camera input, display output (default) 
$ ./actionnet input.mp4 output.mp4  # video file input/output (mp4, mkv, avi, flv)

# Python
$ ./actionnet.py /dev/video0           # V4L2 camera input, display output (default) 
$ ./actionnet.py input.mp4 output.mp4  # video file input/output (mp4, mkv, avi, flv)

命令行参数

这些可选的命令行参数可与 actionnet/actionnet.py 一起使用:

复制代码
  --network=NETWORK    pre-trained model to load, one of the following:
                           * resnet-18 (default)
                           * resnet-34
  --model=MODEL        path to custom model to load (.onnx)
  --labels=LABELS      path to text file containing the labels for each class
  --input-blob=INPUT   name of the input layer (default is 'input')
  --output-blob=OUTPUT name of the output layer (default is 'output')
  --threshold=CONF     minimum confidence threshold for classification (default is 0.01)
  --skip-frames=SKIP   how many frames to skip between classifications (default is 1)

默认情况下,模型会每隔一帧进行处理,以延长对动作进行分类的时间窗口。您可以使用 --skip-frames 参数(使用 --skip-frames=0 将处理每一帧)更改此设置。

预训练动作识别模型

以下是可用的预先训练的动作识别模型,以及用于加载它们的相关 --network 参数:actionnet

Model CLI argument Classes
Action-ResNet18-Kinetics resnet18 1040
Action-ResNet34-Kinetics resnet34 1040

resnet18 是默认值,这些模型在 Kinetics 700Moments in Time 数据库上训练(请参阅[此处][]的类别标签列表)

2.2.6 背景去除

背景去除(又称背景减法或显著物体检测)会生成一个蒙版,将图像的前景与背景分割开来。您可以使用它来替换或模糊背景(类似于视频会议应用),或者它可以辅助其他视觉深度神经网络(例如物体检测/追踪或运动检测)的预处理。所使用的模型是全卷积网络 U²-Net

backgroundNet 对象拍摄图像并输出前景蒙版。backgroundNet 可以从 Python 和 C++ 使用。

作为使用该类的示例 backgroundNet,有针对 C++ 和 Python 的示例程序:

运行示例

以下是删除和替换图像背景的示例:

复制代码
# C++
$ ./backgroundnet images/bird_0.jpg images/test/bird_mask.png                                 # remove the background (with alpha)
$ ./backgroundnet --replace=images/snow.jpg images/bird_0.jpg images/test/bird_replace.jpg    # replace the background

# Python
$ ./backgroundnet.py images/bird_0.jpg images/test/bird_mask.png                              # remove the background (with alpha)
$ ./backgroundnet.py --replace=images/snow.jpg images/bird_0.jpg images/test/bird_replace.jpg # replace the background

命令行 --replace 参数接受用于替换背景的图像文件名。该图像将被重新缩放到与输入相同的分辨率。

直播

要在实时摄像头流上运行背景移除或替换,请从摄像头流和多媒体页面传入设备:

复制代码
# C++
$ ./backgroundnet /dev/video0                             # remove the background
$ ./backgroundnet --replace=images/coral.jpg /dev/video0  # replace the background

# Python
$ ./backgroundnet /dev/video0                             # remove the background
$ ./backgroundnet --replace=images/coral.jpg /dev/video0  # replace the background

通过指定输出流,您可以在显示器(默认)上、通过网络(如使用 WebRTC)查看它,或将其保存到视频文件中。

2.2.7 单目深度

深度感知在地图绘制、导航和障碍物检测等任务中非常有用,但过去它需要立体摄像头或 RGB-D 摄像头。现在,深度神经网络 (DNN) 能够从单目图像推断出相对深度(也称为单深度)。请参阅 MIT 的 FastDepth 论文,其中介绍了一种使用全卷积网络 (FCN) 实现此目标的方法。

depthNet 对象接受单张彩色图像作为输入,并输出深度图。深度图已着色以便于可视化,但原始深度场也可供直接访问。depthNet 可从 Python 和 C++ 使用。

作为使用该类 depthNet 的示例 ,我们提供了 C++ 和 Python 的示例程序:

这些示例能够从图像、视频和摄像头数据中推断深度。如需详细了解所支持的各种输入/输出流类型,请参阅摄像头流式传输和多媒体页面。

图像上的单色深度

首先,让我们尝试 depthnet 在一些示例图像上运行该示例。除了输入/输出路径之外,还有一些可选的命令行选项:

  • 可选 --network 标志,用于改变正在使用的深度模型(建议使用默认值 fcn-mobilenet)。
  • 可选 --visualize 标志,可以是逗号分隔的组合 inputdepth
    • 默认设置是 --visualize=input,depth 并排显示输入图像和深度图像
    • 要仅查看深度图像,请使用 --visualize=depth
  • 可选 --depth-size 值,用于缩放深度图相对于输入的大小(默认值为 1.0
  • 可选 --filter-mode 标志,用于选择 pointlinear 过滤用于上采样(默认为 linear
  • 可选 --colormap 标志,用于设置可视化期间使用的颜色映射(默认值为 viridis_inverted

如果您使用的是 Docker 容器,建议将输出镜像保存到 images/test 已挂载的目录。这样,您就可以在主机设备上轻松查看这些镜像 jetson-inference/data/images/test(更多信息,请参阅已挂载的数据卷)。

以下是室内场景中单声道深度估计的一些示例:

复制代码
# C++
$ ./depthnet "images/room_*.jpg" images/test/depth_room_%i.jpg

# Python
$ ./depthnet.py "images/room_*.jpg" images/test/depth_room_%i.jpg

注意: 第一次运行每个模型时,TensorRT 将花费几分钟来优化网络。

然后,此优化的网络文件将缓存到磁盘,因此将来使用该模型的运行将加载得更快。

以下是一些户外场景的拍摄:

复制代码
# C++
$ ./depthnet "images/trail_*.jpg" images/test/depth_trail_%i.jpg

# Python
$ ./depthnet.py "images/trail_*.jpg" images/test/depth_trail_%i.jpg

视频的单目深度

要在实时摄像机流或视频上运行单声道深度估计,请从摄像机流和多媒体页面传入设备或文件路径。

复制代码
# C++
$ ./depthnet /dev/video0     # csi://0 if using MIPI CSI camera

# Python
$ ./depthnet.py /dev/video0  # csi://0 if using MIPI CSI camera

注意: 如果屏幕太小,无法容纳输出,您可以使用 --depth-scale=0.5 减小深度图像的尺寸

,或者使用减小相机的尺寸 --input-width=X --input-height=Y

获取原始深度场

如果您想访问原始深度图,可以使用 depthNet.GetDepthField()。这将返回一个单通道浮点图像,该图像通常比原始输入更小(224x224),这代表了模型的原始输出。另一方面,用于可视化的彩色深度图像会进行上采样,以匹配原始输入的分辨率(或任何 --depth-size 比例设置的值)。

下面是用于访问原始深度场的 Python 和 C++ 伪代码:

Python

复制代码
import jetson.inference
import jetson.utils

import numpy as np

# load mono depth network
net = jetson.inference.depthNet()

# depthNet re-uses the same memory for the depth field,
# so you only need to do this once (not every frame)
depth_field = net.GetDepthField()

# cudaToNumpy() will map the depth field cudaImage to numpy
# this mapping is persistent, so you only need to do it once
depth_numpy = jetson.utils.cudaToNumpy(depth_field)

print(f"depth field resolution is {depth_field.width}x{depth_field.height}, format={depth_field.format})

while True:
    img = input.Capture()	# assumes you have created an input videoSource stream
    net.Process(img)
    jetson.utils.cudaDeviceSynchronize() # wait for GPU to finish processing, so we can use the results on CPU
	
    # find the min/max values with numpy
    min_depth = np.amin(depth_numpy)
    max_depth = np.amax(depth_numpy)

C++

复制代码
#include <jetson-inference/depthNet.h>

// load mono depth network
depthNet* net = depthNet::Create();

// depthNet re-uses the same memory for the depth field,
// so you only need to do this once (not every frame)
float* depth_field = net->GetDepthField();
const int depth_width = net->GetDepthWidth();
const int depth_height = net->GetDepthHeight();

while(true)
{
    uchar3* img = NUL;
    input->Capture(&img);  // assumes you have created an input videoSource stream
    net->Process(img, input->GetWidth(), input->GetHeight());

    // wait for the GPU to finish processing
    CUDA(cudaDeviceSynchronize()); 
	
    // you can now safely access depth_field from the CPU (or GPU)
    for( int y=0; y < depth_height; y++ )
        for( int x=0; x < depth_width; x++ )
	       printf("depth x=%i y=%i -> %f\n", x, y, depth_map[y * depth_width + x]);
}

尝试使用单深度测量绝对距离可能会导致不准确,因为它通常更适合相对深度估计。原始深度场中的值范围可能因场景而异,因此这些值通常会动态地重新计算。例如,在可视化过程中,会对深度场执行直方图均衡化,以使颜色图在深度值范围内更均匀地分布。

接下来,我们将介绍迁移学习的概念,并使用 PyTorch 在 Jetson 上训练我们自己的 DNN 模型。

2.3 训练

2.3.1 使用 PyTorch 进行迁移学习

迁移学习是一种在新数据集上重新训练 DNN 模型的技术,比从头开始训练网络耗时更少。通过迁移学习,可以对预训练模型的权重进行微调,使其能够对自定义数据集进行分类。在这些示例中,我们将使用 ResNet-18SSD-Mobilenet 网络,但您也可以尝试其他网络。

尽管由于通常使用的数据集很大且相关的计算需求较大,训练通常在具有独立 GPU 的 PC、服务器或云实例上进行,但通过使用迁移学习,我们能够重新训练 Jetson 上的各种网络,以开始训练和部署我们自己的 DNN 模型。

PS:

  • Jetson TX1 的推理和 RK3588 比不占优势,但是其训练有优势,因为有 cuda 加速

  • Jetson TX1 的 cuda 为 200 多,普通 RTX 英伟达显卡都有数千个,因此不如在 PC 上训练

  • Jetson TX1 的 cuda 为 200 多,比 i5 集成显卡跑推理和训练快很多很多,因此不能用 i5 集成显卡跑,太慢

  • Jetson TX1 的默认存储为 16G,安装完之后剩下的 1G 可怜,外加一个 8G U 盘,跑训练存储仍然不够,非常够呛,不建议在上面跑推理

    我通过命令删除无用的东西,最后只能腾出 1.6G:

    默认的 16G 存储跑推理还行,跑迁移装个 pytouch 不够,即使删除下面一堆东西,还是有问题:

    dpkg-query -Wf '{Installed-Size}\t{Package}\n' | sort -nr | awk '{printf "%.1f MB\t%s\n", 1/1024, 2}' | head -n 40
    sudo apt-get purge ubuntu-desktop
    sudo apt-get purge --auto-remove chromium-browser
    sudo apt-get purge thunderbird
    sudo apt-get purge docker.io
    sudo apt-get purge docker
    sudo apt-get purge openjdk
    sudo apt-get purge openjdk-11-jre-headless
    sudo apt-get purge libnvinfer-samples
    sudo apt-get purge cuda-samples-10-2

因此我直接不尝试官方的推荐,在 Jetson TX1 上训练模型了,不过官方的一些性能参数可以参考下(下表包含数据集及其相关训练时间的摘要):

类型 数据集 尺寸 课程 训练图像 每个时期的时间* 训练时间**
分类 Cat/Dog 800MB 2 5,000 约7-8分钟 约4小时
分类 PlantCLEF 1.5GB 20 10,475 约 15 分钟 ~8 小时
检测 Fruit 2GB 8 6,375 约 15 分钟 ~8 小时

* 使用 Jetson Nano 对数据集进行一次完整训练的大约时间

** 使用 Jetson Nano 对模型进行 35 个 epoch 训练的大约时间

不同型号设备训练对比:

型号 CUDA 核心数 CPU/GPU 架构及配置 训练 LeNet-5 时间(秒 /epoch) 训练 AlexNet 时间(秒 /epoch) 训练 ResNet-18 时间(秒 /epoch) 适用场景 适合搭配的 CPU
Jetson TX1 256 GPU: 256-core Maxwell,1.3 TFLOPS FP32 CPU: 4 核 ARM Cortex-A57 @ 1.73GHz 内存: 4GB LPDDR4 @ 25.6GB/s 功耗: 10-15W ~15 ~45 ~90 边缘计算、嵌入式视觉系统、小型机器人、低功耗 AI 应用 嵌入式模块,自带 ARM CPU,无需额外搭配
Jetson TX2 256 GPU: 256-core Pascal,1.6 TFLOPS FP32 CPU: 2 核 Denver2 + 4 核 Cortex-A57 @ 2.0GHz 内存: 8GB LPDDR4 @ 51.2GB/s 功耗: 7.5-15W ~10 ~30 ~60 高级边缘计算、无人机视觉处理、工业自动化、高性能嵌入式 AI 应用 嵌入式模块,自带 ARM CPU,无需额外搭配
Core i5-2540M 0 CPU: 双核 Intel Core i5-2540M @ 2.6GHz(4 线程),0.25 TFLOPS FP32 内存:最高 16GB DDR3 @ 21.3GB/s 功耗: 35W ~120 ~500 无法完成 老旧笔记本电脑、轻度办公、低性能计算任务 本身为 CPU,无需搭配其他 CPU
RTX 3060 3584 GPU: 3584-core Ampere,12.7 TFLOPS FP32 显存: 12GB GDDR6 @ 360GB/s 功耗: 170-200W ~1 ~5 ~10 中端游戏电脑、深度学习入门、中等规模数据训练、图形设计 Intel i5/i7 10 代及以上或 AMD Ryzen 5/7 系列
RTX 3070 5888 GPU: 5888-core Ampere,20.1 TFLOPS FP32 显存: 8GB GDDR6 @ 448GB/s 功耗: 220W ~0.8 ~4 ~8 高端游戏电脑、专业图形处理、深度学习研究、中等规模 AI 训练 Intel i7/i9 10 代及以上或 AMD Ryzen 7/9 系列
RTX 3080 8704 GPU: 8704-core Ampere,29.7 TFLOPS FP32 显存: 10GB GDDR6X @ 760GB/s 功耗: 320W ~0.5 ~3 ~6 高端游戏、专业 3D 渲染、大规模深度学习训练、AI 研究 Intel i9 或 AMD Ryzen 9 系列
RTX 4080 9728 GPU: 9728-core Ada Lovelace,49 TFLOPS FP32 显存: 16GB GDDR6X @ 1TB/s 功耗: 320W ~0.3 ~2 ~4 旗舰级游戏、专业级 AI 训练、大规模数据处理、高性能计算 Intel i9 12 代及以上或 AMD Ryzen 9 7000 系列及以上
RTX 4090 12432 GPU: 12432-core Ada Lovelace,82.6 TFLOPS FP32 显存: 24GB GDDR6X @ 1TB/s 功耗: 450W ~0.2 ~1 ~2 顶级游戏、超大规模 AI 训练、数据中心高性能计算、专业级 3D 渲染 Intel i9 13 代及以上或 AMD Ryzen 9 7000 系列及以上
A100 6912 GPU: 6912-core Ampere,19.5 TFLOPS FP32 / 312 TFLOPS Tensor FP16 显存: 40/80GB HBM2e @ 2TB/s 功耗: 400W ~0.1 ~0.5 ~1 数据中心大规模 AI 训练、科学计算、高性能数据分析 Intel Xeon 或 AMD EPYC 服务器级 CPU
H100 18432 GPU: 18432-core Hopper,51 TFLOPS FP32 / 1979 TFLOPS Tensor FP16 显存: 80GB HBM3 @ 3.35TB/s 功耗: 700W ~0.05 ~0.2 ~0.4 下一代数据中心 AI 训练、超大规模模型训练、高级科学计算 Intel Xeon 或 AMD EPYC 最新一代服务器级 CPU

2.3.2 分类/识别(ResNet-18)

2.3.2.1 在猫/狗数据集上进行重新训练

https://github.com/dusty-nv/jetson-inference/blob/master/docs/pytorch-cat-dog.md

1)概述

我们将要重新训练的第一个模型是一个简单的模型,它可以识别两个类别:猫或狗。

下面提供了一个 800MB 的数据集,其中包含 5000 张训练图像、1000 张验证图像和 200 张测试图像,每张图像均等地分布在猫类和狗类之间。训练图像集用于迁移学习,验证集用于评估训练期间的分类准确率,测试图像供我们在训练完成后使用。网络不会直接在验证集和测试集上进行训练,而只会在训练集上进行训练。

数据集中的图像包含许多不同品种的猫狗,包括老虎和美洲狮等大型猫科动物,因为可用的猫科动物图像数量略少于狗类图像。有些图像中还包含人类,检测器经过训练后,会忽略这些人类作为背景,而专注于猫狗图像。

首先,请确保您的 PC 上已安装 PyTorch,然后下载下方数据集并启动训练脚本。之后,我们将在 TensorRT 中使用一些静态图像和实时摄像头信号测试重新训练后的模型。

复制代码
# 注意安装过程中很慢,有时候需要多次实验,有一次网速变好
cd ./Desktop/08-tensorflow/
python3.9 -m venv .venv
. .venv/bin/activate
pip install --upgrade pip
pip install torch torchvision torchaudio
pip install tensorboard # 是 PyTorch 中用于可视化训练过程的工具

2)下载数据

采用下面命令拉取代码与下载数据:

复制代码
git clone --recursive --depth=1 https://github.com/dusty-nv/jetson-inference
cd jetson-inference/python/training/classification/data
wget https://nvidia.box.com/shared/static/o577zd8yp3lmxf5zhm38svrbrv45am3y.gz -O cat_dog.tar.gz
tar xvzf cat_dog.tar.gz

数据集的镜像可以在这里找到:

3)重新训练 ResNet-18 模型

PyTorch 训练脚本位于 jetson-inference/python/training/classification/ 中。这些脚本并不针对任何特定数据集,因此我们将对本教程中的每个示例数据集使用相同的 PyTorch 代码。默认情况下,它设置为训练 ResNet-18 模型,但您可以使用标志 --arch 进行更改 。

要启动训练,请运行以下命令:

复制代码
$ cd jetson-inference/python/training/classification
$ python3 train.py --model-dir=models/cat_dog data/cat_dog

训练开始时,您应该会看到控制台中出现如下文本:

复制代码
Use GPU: 0 for training
=> dataset classes:  2 ['cat', 'dog']
=> using pre-trained model 'resnet18'
=> reshaped ResNet fully-connected layer with: Linear(in_features=512, out_features=2, bias=True)
Epoch: [0][  0/625]	Time  0.932 ( 0.932)	Data  0.148 ( 0.148)	Loss 6.8126e-01 (6.8126e-01)	Acc@1  50.00 ( 50.00)	Acc@5 100.00 (100.00)
Epoch: [0][ 10/625]	Time  0.085 ( 0.163)	Data  0.000 ( 0.019)	Loss 2.3263e+01 (2.1190e+01)	Acc@1  25.00 ( 55.68)	Acc@5 100.00 (100.00)
Epoch: [0][ 20/625]	Time  0.079 ( 0.126)	Data  0.000 ( 0.013)	Loss 1.5674e+00 (1.8448e+01)	Acc@1  62.50 ( 52.38)	Acc@5 100.00 (100.00)
Epoch: [0][ 30/625]	Time  0.127 ( 0.114)	Data  0.000 ( 0.011)	Loss 1.7583e+00 (1.5975e+01)	Acc@1  25.00 ( 52.02)	Acc@5 100.00 (100.00)
Epoch: [0][ 40/625]	Time  0.118 ( 0.116)	Data  0.000 ( 0.010)	Loss 5.4494e+00 (1.2934e+01)	Acc@1  50.00 ( 50.30)	Acc@5 100.00 (100.00)
Epoch: [0][ 50/625]	Time  0.080 ( 0.111)	Data  0.000 ( 0.010)	Loss 1.8903e+01 (1.1359e+01)	Acc@1  50.00 ( 48.77)	Acc@5 100.00 (100.00)
Epoch: [0][ 60/625]	Time  0.082 ( 0.106)	Data  0.000 ( 0.009)	Loss 1.0540e+01 (1.0473e+01)	Acc@1  25.00 ( 49.39)	Acc@5 100.00 (100.00)
Epoch: [0][ 70/625]	Time  0.080 ( 0.102)	Data  0.000 ( 0.009)	Loss 5.1142e-01 (1.0354e+01)	Acc@1  75.00 ( 49.65)	Acc@5 100.00 (100.00)
Epoch: [0][ 80/625]	Time  0.076 ( 0.100)	Data  0.000 ( 0.009)	Loss 6.7064e-01 (9.2385e+00)	Acc@1  50.00 ( 49.38)	Acc@5 100.00 (100.00)
Epoch: [0][ 90/625]	Time  0.083 ( 0.098)	Data  0.000 ( 0.008)	Loss 7.3421e+00 (8.4755e+00)	Acc@1  37.50 ( 50.00)	Acc@5 100.00 (100.00)
Epoch: [0][100/625]	Time  0.093 ( 0.097)	Data  0.000 ( 0.008)	Loss 7.4379e-01 (7.8715e+00)	Acc@1  50.00 ( 50.12)	Acc@5 100.00 (100.00)

要随时停止训练,您可以按 Ctrl+C。您也可以稍后使用 --resume--epoch-start 标志重新开始训练,这样您无需等待训练完成后再测试模型。

运行 python3 train.py --help 以获取有关每个可供您使用的选项的更多信息,包括您可以使用 --arch 标志尝试的其他网络。

训练指标

训练过程中输出的上述统计数据分别对应以下信息:

  • epoch:一个 epoch 是对数据集进行一次完整的训练
    • Epoch: [N] 表示您当前处于时期 0、1、2 等。
    • 默认运行 35 个 epoch(您可以使用 --epochs=N 标志更改此设置)
  • N/625\] 是当前所在纪元的图像批次 * 训练图像以小批量处理以提高性能 * `--batch=N` 默认批量大小为 8 张图片,可以通过标志设置 * 将括号中的数字乘以批次大小(例如批次 `[100/625]` -\> 图像 `[800/5000]`)

  • 数据:当前图像批次的磁盘加载时间(以秒为单位)
  • 损失:模型产生的累积误差(预期与预测)
  • Acc@1:批次内 Top-1 分类准确率
    • Top-1,表示模型预测的类别完全正确
  • Acc@5:批次的 Top-5 分类准确率
    • Top-5,意味着正确的类别是模型预测的前 5 个输出之一
    • 由于这个猫/狗的例子只有 2 个类(猫和狗),所以 Top-5 总是 100%
    • 本教程中的其他数据集有 5 个以上的类别,其中 Top-5 是有效的

您可以在训练期间关注这些统计数据,以评估模型的训练效果,并决定是否要继续训练或停止并进行测试。如上所述,您可以根据需要稍后重新开始训练。

模型准确率

在这个包含 5000 幅图像的数据集上,在 Jetson Nano 上训练 ResNet-18 大约需要每轮 7-8 分钟,或者大约需要 4 个小时才能将模型训练到 35 轮,达到 80% 的分类准确率。下图是分析训练轮次与模型准确率之间关系的图表:

在第 30 个周期左右,ResNet-18 模型的准确率达到了 80%,在第 65 个周期时收敛到 82.5%。随着训练时间的增加,您可以通过增加数据集的大小(请参阅下文的"生成更多数据"部分)或尝试更复杂的模型来进一步提高准确率。

默认情况下,训练脚本的运行周期为 35 个 epoch,但如果您不想等待那么长时间来测试模型,可以提前退出训练并继续下一步(也可以选择稍后从上次中断的地方重新开始训练)。您也可以从此处下载这个已训练 100 个 epoch 的完整模型:

请注意,模型保存在 jetson-inference/python/training/classification/models/cat_dog/ 下,包括最新 epoch 的检查点以及分类准确率最高的性能最佳的模型。此 classification/models 目录会自动挂载到容器 中,因此即使容器关闭,您训练好的模型仍会保留。

4)将模型转换为 ONNX

为了使用 TensorRT 运行我们重新训练的 ResNet-18 模型进行测试和实时推理,首先我们需要将 PyTorch 模型转换为 ONNX 格式,以便 TensorRT 可以加载它。ONNX 是一种开放的模型格式,支持许多流行的机器学习框架,包括 PyTorch、TensorFlow、TensorRT 等,因此它简化了工具之间的模型传输。

PyTorch 内置了将 PyTorch 模型导出到 ONNX 的支持,因此请运行以下命令,使用提供的 onnx_export.py 脚本转换我们的 Cat/Dog 模型:

复制代码
python3 onnx_export.py --model-dir=models/cat_dog

这将在 jetson-inference/python/training/classification/models/cat_dog/ 下创建一个名为 resnet18.onnx 的模型。

5)使用 TensorRT 处理图像

为了对一些静态测试图像进​​行分类,我们将使用扩展的命令行参数来imagenet加载我们之前重新训练的自定义 ResNet-18 模型。要运行这些命令,终端的工作目录仍应位于: jetson-inference/python/training/classification/

复制代码
NET=models/cat_dog
DATASET=data/cat_dog

# C++
imagenet --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt $DATASET/test/cat/01.jpg cat.jpg

# Python
imagenet.py --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt $DATASET/test/cat/01.jpg cat.jpg
复制代码
# C++
imagenet --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt $DATASET/test/dog/01.jpg dog.jpg

# Python
imagenet.py --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt $DATASET/test/dog/01.jpg dog.jpg

处理所有测试图像

数据集中包含 200 张猫狗类别的测试图像,或者你可以下载自己的图片进行测试。你可以像这样处理它们:

复制代码
mkdir $DATASET/test_output_cat $DATASET/test_output_dog

imagenet --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/../labels.txt \
           $DATASET/test/cat $DATASET/test_output_cat

imagenet --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/../labels.txt \
           $DATASET/test/dog $DATASET/test_output_dog

在这种情况下,所有图像将从数据集的 test/ 目录中读取,并保存到该 test_output/ 目录中。

接下来,我们将尝试在实时摄像机画面上运行重新训练的模型。

6)运行实时摄像头程序

如果你家里有宠物,可以运行相机程序看看它是怎么工作的!与上一步类似,imagenet 支持扩展命令行参数来加载自定义模型:

复制代码
# C++ (MIPI CSI)
imagenet --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt csi://0

# Python (MIPI CSI)
imagenet.py --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt csi://0

7)生成更多数据(可选)

猫/狗数据集中的图像是使用脚本 cat-dog-dataset.sh 从更大的 22.5GB ILSCRV12 子集中随机提取的。为了缩短训练时间,特意将第一个猫/狗数据集保持较小,但您可以使用此脚本重新生成包含更多图像的数据集,从而创建更稳健的模型。

更大的数据集需要更多的时间来训练,因此您可以暂时继续下一个示例,但如果您想扩展猫/狗数据集,请首先从这里下载源数据:

提取此档案后,进行 tools/cat-dog-dataset.sh 以下修改:

  • IMAGENET_DIR 变量中替换提取的 ilsvrc12_subset 的位置
  • 然后为 cat_dog 创建一个空文件夹,并在 OUTPUT_DIR 中替换该位置
  • 通过修改 NUM_TRAINNUM_VALNUM_TEST 变量来更改数据集的大小

该脚本会在 OUTPUT_DIR 目录下创建 trainvaltest 子目录,并为每个目录填充指定数量的图像。之后,您可以按照上述方式训练模型 --resume,也可以选择使用和 --epoch-start 标志从上次中断的地方继续训练(如果您不想从头开始训练)。重新训练后,请务必将模型重新导出到 ONNX

在下面的示例中,我们将在支持 20 个对象类的植物和树木数据集上训练另一个模型。

2.3.2.2 在 PlantCLEF 数据集上进行重新训练

1)概述

接下来,我们将训练一个模型,该模型能够对 PlantCLEF 数据集中的 20 种不同种类的植物和树木进行分类。

下面提供的是一个 1.5GB 的子集,包含 20 个植物和树木类别的 10,475 张训练图像、1,155 张验证图像和 30 张测试图像。这些类别选自 PlantCLEF 2017 数据集,原始数据集中至少包含 500 张训练图像。

复制代码
• ash
• beech
• cat-tail
• cedar
• clover
• cyprus
• daisy
• dandelion
• dogwood
• elm
• fern
• fig
• fir
• juniper
• maple
• poison ivy
• sweetgum
• sycamore
• trout lily
• tulip tree

2)下载数据

运行以下命令下载并提取准备好的 PlantCLEF 数据集:

复制代码
$ cd jetson-inference/python/training/classification/data
$ wget https://nvidia.box.com/shared/static/vbsywpw5iqy7r38j78xs0ctalg7jrg79.gz -O PlantCLEF_Subset.tar.gz
$ tar xvzf PlantCLEF_Subset.tar.gz

数据集的镜像可以在这里找到:

3)重新训练 ResNet-18 模型

我们将使用与上一个示例相同的训练脚本,该脚本位于 python/training/classification/ 下。默认情况下,它设置为训练 ResNet-18 模型,但您可以使用 --arch 标志进行更改。

要启动训练,请运行以下命令:

复制代码
$ cd jetson-inference/python/training/classification
$ python3 train.py --model-dir=models/plants data/PlantCLEF_Subset

训练开始时,您应该会看到控制台中显示如下文本:

复制代码
Use GPU: 0 for training
=> dataset classes:  20 ['ash', 'beech', 'cattail', 'cedar', 'clover', 'cyprus', 'daisy', 'dandelion', 'dogwood', 'elm', 'fern', 'fig', 'fir', 'juniper', 'maple', 'poison_ivy', 'sweetgum', 'sycamore', 'trout_lily', 'tulip_tree']
=> using pre-trained model 'resnet18'
=> reshaped ResNet fully-connected layer with: Linear(in_features=512, out_features=20, bias=True)
Epoch: [0][   0/1307]	Time 49.345 (49.345)	Data  0.561 ( 0.561)	Loss 3.2172e+00 (3.2172e+00)	Acc@1   0.00 (  0.00)	Acc@5  25.00 ( 25.00)
Epoch: [0][  10/1307]	Time  0.779 ( 5.211)	Data  0.000 ( 0.060)	Loss 2.3915e+01 (1.5221e+01)	Acc@1   0.00 (  5.68)	Acc@5  12.50 ( 27.27)
Epoch: [0][  20/1307]	Time  0.765 ( 3.096)	Data  0.000 ( 0.053)	Loss 3.6293e+01 (2.1256e+01)	Acc@1   0.00 (  5.95)	Acc@5  37.50 ( 27.38)
Epoch: [0][  30/1307]	Time  0.773 ( 2.346)	Data  0.000 ( 0.051)	Loss 2.8803e+00 (1.9256e+01)	Acc@1  37.50 (  6.85)	Acc@5  62.50 ( 27.42)
Epoch: [0][  40/1307]	Time  0.774 ( 1.962)	Data  0.000 ( 0.050)	Loss 3.7734e+00 (1.5865e+01)	Acc@1  12.50 (  8.84)	Acc@5  37.50 ( 29.88)
Epoch: [0][  50/1307]	Time  0.772 ( 1.731)	Data  0.000 ( 0.049)	Loss 3.0311e+00 (1.3756e+01)	Acc@1  25.00 ( 10.29)	Acc@5  37.50 ( 32.35)
Epoch: [0][  60/1307]	Time  0.773 ( 1.574)	Data  0.000 ( 0.048)	Loss 3.2433e+00 (1.2093e+01)	Acc@1   0.00 (  9.84)	Acc@5  25.00 ( 32.79)
Epoch: [0][  70/1307]	Time  0.806 ( 1.462)	Data  0.000 ( 0.048)	Loss 2.9213e+00 (1.0843e+01)	Acc@1  12.50 (  8.98)	Acc@5  37.50 ( 33.27)
Epoch: [0][  80/1307]	Time  0.792 ( 1.379)	Data  0.000 ( 0.048)	Loss 3.2370e+00 (9.8715e+00)	Acc@1   0.00 (  9.26)	Acc@5  25.00 ( 34.41)
Epoch: [0][  90/1307]	Time  0.770 ( 1.314)	Data  0.000 ( 0.048)	Loss 2.4494e+00 (9.0905e+00)	Acc@1  25.00 (  9.75)	Acc@5  75.00 ( 36.26)
Epoch: [0][ 100/1307]	Time  0.801 ( 1.261)	Data  0.001 ( 0.048)	Loss 2.6449e+00 (8.4769e+00)	Acc@1  25.00 ( 10.40)	Acc@5  62.50 ( 37.00)

模型准确率

在包含 10,475 幅图像的 PlantCLEF 数据集上,使用 Jetson Nano 训练 ResNet-18 大约需要每轮约 15 分钟,或者训练 35 轮大约需要 8 小时。下图是分析训练轮次进度与模型准确率的图表:

大约在第 30 个周期时,ResNet-18 模型的 Top-5 准确率达到了 75%,而在第 65 个周期时,其 Top-5 准确率收敛到 85%。有趣的是,ResNet-18 模型的这些稳定点和收敛点出现的时间与之前的 Cat/Dog 模型相似。该模型的 Top-1 准确率为 55%,考虑到 PlantCLEF 数据集的多样性和挑战性(例如,每张图片包含多个重叠的植物品种,以及许多几乎难以区分的树叶和树干图片),我们会发现这个准确率在实践中非常有效。

默认情况下,训练脚本的运行周期为 35 个 epoch,但如果您不想等待那么长时间来测试模型,可以提前退出训练并继续下一步(也可以选择稍后从上次中断的地方重新开始训练)。您也可以从此处下载这个已训练 100 个 epoch 的完整模型:

请注意,模型保存在 jetson-inference/python/training/classification/data/plants/ 下,包括最新 epoch 的检查点和分类准确率最高的性能最佳的模型。您可以通过修改 --model-dir 标志来更改模型的保存目录。

4)将模型转换为 ONNX

就像猫/狗的例子一样,接下来我们需要将训练好的模型从 PyTorch 转换为 ONNX,以便我们可以用 TensorRT 加载它:

复制代码
python3 onnx_export.py --model-dir=models/plants

这将在 jetson-inference/python/training/classification/models/plants/ 下创建一个名为 resnet18.onnx 的模型。

5)使用 TensorRT 处理图像

为了对一些静态测试图像进​​行分类,我们将像之前一样使用扩展的命令行参数 imagenet 来加载我们之前重新训练的自定义 ResNet-18 模型。要运行这些命令,终端的工作目录仍应位于: jetson-inference/python/training/classification/

复制代码
NET=models/plants
DATASET=data/PlantCLEF_Subset

# C++
imagenet --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt $DATASET/test/cattail.jpg cattail.jpg

# Python
imagenet.py --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt $DATASET/test/cattail.jpg cattail.jpg
复制代码
# C++
imagenet --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt $DATASET/test/elm.jpg elm.jpg

# Python
imagenet.py --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt $DATASET/test/elm.jpg elm.jpg
复制代码
# C++
imagenet --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt $DATASET/test/juniper.jpg juniper.jpg

# Python
imagenet.py --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt $DATASET/test/juniper.jpg juniper.jpg

数据集中包含大量测试图像,或者您可以下载自己的图片进行尝试。

处理所有测试图像

如果要一次性对所有测试图像进​​行分类,可以对整个目录运行该程序:

复制代码
mkdir $DATASET/test_output

imagenet --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/../labels.txt \
           $DATASET/test $DATASET/test_output

在这种情况下,所有图像将从数据集的 test/ 目录中读取,并保存到 test_output/ 目录中。

6)运行实时摄像头程序

您还可以尝试在实时摄像机流上运行重新训练的植物模型,如下所示:

复制代码
# C++ (MIPI CSI)
imagenet --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt csi://0

# Python (MIPI CSI)
imagenet.py --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt csi://0

接下来,我们将介绍一种基于摄像头的工具,用于收集和标记从实时视频中捕获的数据集。

2.3.2.3 收集你自己的分类数据集

为了收集您自己的数据集来训练自定义模型来对您选择的对象或场景进行分类,我们创建了一个易于使用的工具,用于 camera-capture 从实时视频中捕获和标记 Jetson 上的图像:

该工具将在磁盘上创建具有以下目录结构的数据集:

复制代码
‣ train/
	• class-A/  # 每个对象类数据
	• class-B/
	• ...
‣ val/
	• class-A/
	• class-B/
	• ...
‣ test/
	• class-A/
	• class-B/
	• ...

请注意,以上是我们一直使用的 PyTorch 训练脚本所期望的组织结构。如果您检查 Cat/Dog 和 PlantCLEF 数据集,它们也以相同的方式组织。

创建标签文件

jetson-inference/python/training/classification/data 下,创建一个空目录用于存储数据集,并创建一个用于定义类标签的文本文件(通常名为 labels.txt)。该标签文件每行包含一个类标签,并按字母顺序排列(这一点很重要,因为标签文件中类的顺序与磁盘上相应子目录的顺序一致)。如上所述,该 camera-capture 工具将自动根据此标签文件为每个类填充必要的子目录。

这是一个 labels.txt 包含 5 个类的示例文件:

复制代码
background
brontosaurus
tree
triceratops
velociraptor

以下是该工具将创建的相应目录结构:

复制代码
‣ train/
	• background/
	• brontosaurus/
	• tree/
	• triceratops/
	• velociraptor/
‣ val/
	• background/
	• brontosaurus/
	• tree/
	• triceratops/
	• velociraptor/
‣ test/
	• background/
	• brontosaurus/
	• tree/
	• triceratops/
	• velociraptor/

启动工具

该工具的源代码 camera-capture 可以在 jetson-inference/tools/camera-capture/ 下找到,并且像 repo 中的其他程序一样,它被构建到 aarch64/bin 目录中并安装在 /usr/local/bin/

以下是启动该工具的一些示例命令:

复制代码
$ camera-capture csi://0       # using default MIPI CSI camera
$ camera-capture /dev/video0   # using V4L2 camera /dev/video0

收集数据

下面是 Data Capture Control 窗口,它允许您选择数据集的所需路径并加载上面创建的类标签文件,然后提供选择当前对象类和当前正在收集数据的训练/验证/测试集的选项:

首先,打开数据集路径和类标签。然后,该工具将创建上述数据集结构(除非这些子目录已经存在),您将看到您的对象标签填充在 Current Class 下拉列表中。将数 Dataset Type 保留为分类。

然后将相机放置在您当前在下拉列表中选择的对象或场景处,并在准备拍摄图像时单击"捕获"按钮(或按空格键)。图像将保存在 train, val, 或 test 集中的类子目录下。状态栏显示该类别下已保存的图像数量。

建议在尝试训练之前,每个类至少收集 100 张训练图像。验证集的经验法则是,它应该大约是训练集大小的 10-20%,测试集的大小只是由你想要测试的静态图像数量决定的。如果你愿意,你也可以运行相机来测试你的模型。

重要的是,您的数据是从不同的对象方向、相机视点、照明条件收集的,最好是使用不同的背景,以创建一个对噪声和环境变化具有鲁棒性的模型。如果你发现你的模型没有达到你想要的效果,试着添加更多的训练数据,并在各种条件下进行试验。

训练你的模型

当你收集了一堆数据后,你可以试着在上面训练一个模型,就像我们以前做的那样。训练过程与前面的示例相同,并且使用了相同的 PyTorch 脚本:

复制代码
$ cd jetson-inference/python/training/classification
$ python3 train.py --model-dir=models/<YOUR-MODEL> data/<YOUR-DATASET>
$ python3 onnx_export.py --model-dir=models/<YOUR-MODEL>

训练并转换后的模型存储在 models/<YOUR-MODEL>/resnet18.onnx,然后可以使用 imagenet 来测试它:

复制代码
NET=models/<YOUR-MODEL>
DATASET=data/<YOUR-DATASET>

# C++ (MIPI CSI)
imagenet --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt csi://0

# Python (MIPI CSI)
imagenet.py --model=$NET/resnet18.onnx --input_blob=input_0 --output_blob=output_0 --labels=$DATASET/labels.txt csi://0

接下来,我们将使用 PyTorch 训练我们自己的对象检测模型。

2.3.3 物体检测(SSD-Mobilenet)

2.3.3.1 重新训练 SSD-Mobilenet

接下来,我们将使用 PyTorch 和 Open Images 数据集训练我们自己的 SSD-Mobilenet 目标检测模型。SSD-Mobilenet 是一种流行的网络架构,用于在移动和嵌入式设备上进行实时目标检测,它结合了 SSD-300 单次多框检测器和 Mobilenet 主干网络。

在下面的示例中,我们将训练一个自定义检测模型,用于定位 8 种不同的水果品种。当然,您也可以从 Open Images 数据集的 600 个类别中任意选择,来训练您的模型。您可以点击此处直观地浏览数据集。

首先,请确保您的 Jetson 已安装 JetPack 4.4(或更高版本)和用于 Python3 的 PyTorch。JetPack 4.4 包含 TensorRT 7.1,这是支持通过 ONNX 加载 SSD-Mobilenet 的最低 TensorRT 版本。更高版本的 TensorRT 也可以。

设置

用于训练 SSD-Mobilenet 的 PyTorch 代码位于 jetson-inference/python/training/detection/ssd 下的仓库中。如果您尚未运行 Docker 容器,则在使用前需要执行以下几个步骤:

复制代码
# you only need to run these if you aren't using the container
$ cd jetson-inference/python/training/detection/ssd
$ wget https://nvidia.box.com/shared/static/djf5w54rjvpqocsiztzaandq1m3avr7c.pth -O models/mobilenet-v1-ssd-mp-0_675.pth
$ pip3 install -v -r requirements.txt

这将下载基础模型 ssd/models 并安装一些所需的 Python 包(这些包已安装到容器中)。基础模型已在另一个数据集 (PASCAL VOC) 上进行预训练,因此我们无需从头开始训练 SSD-Mobilenet,那样会花费更多时间。我们将使用迁移学习对其进行微调,以检测我们选择的新对象类别。

下载数据

Open Images 数据集包含 600 多个对象类别供您选择。我们提供一个名为 open_images_downloader.py 的脚本,可以自动为您下载所需的对象类别。

注意: 使用的类别越少,模型推理速度越快。Open Images 还可能包含数百 GB 的数据,具体取决于您选择的类别 - 因此在下载您自己的类别之前,请参阅下面的"限制数据量"部分。

我们将使用的类别是 "Apple,Orange,Banana,Strawberry,Grape,Pear,Pineapple,Watermelon",例如水果采摘机器人的类别------尽管您也可以从类别列表中替换您自己的选择。水果类别有大约 6500 张图片,这是一个合适的中间值。

复制代码
$ python3 open_images_downloader.py --class-names "Apple,Orange,Banana,Strawberry,Grape,Pear,Pineapple,Watermelon" --data=data/fruit
...
2020-07-09 16:20:42 - Starting to download 6360 images.
2020-07-09 16:20:42 - Downloaded 100 images.
2020-07-09 16:20:42 - Downloaded 200 images.
2020-07-09 16:20:42 - Downloaded 300 images.
2020-07-09 16:20:42 - Downloaded 400 images.
2020-07-09 16:20:42 - Downloaded 500 images.
2020-07-09 16:20:46 - Downloaded 600 images.
...
2020-07-09 16:32:12 - Task Done.

默认情况下,数据集将下载到 jetson-inference/python/training/detection/ssd 下的 data/ 目录中,但您可以通过 --data=<PATH> 指定选项来更改。根据数据集的大小,可能需要使用外部存储。如果您下载多个数据集,则应将每个数据集存储在各自的子目录中。

限制数据量

根据您选择的类别,Open Images 可能包含大量数据------在某些情况下,数据量太大,无法在合理的时间内完成训练,无法满足我们的目标。尤其是包含人物和车辆的类别,其图像数量非常庞大(>250GB)。

因此,在选择您自己的类别时,建议在下载数据之前先运行带有 --stats-only 该选项的下载程序脚本。这将显示您的类别有多少张图片,而无需实际下载任何图片。

复制代码
$ python3 open_images_downloader.py --stats-only --class-names "Apple,Orange,Banana,Strawberry,Grape,Pear,Pineapple,Watermelon" --data=data/fruit
...
2020-07-09 16:18:06 - Total available images: 6360
2020-07-09 16:18:06 - Total available boxes:  27188

-------------------------------------
 'train' set statistics
-------------------------------------
  Image count:  5145
  Bounding box count:  23539
  Bounding box distribution:
    Strawberry:  7553/23539 = 0.32
    Orange:  6186/23539 = 0.26
    Apple:  3622/23539 = 0.15
    Grape:  2560/23539 = 0.11
    Banana:  1574/23539 = 0.07
    Pear:  757/23539 = 0.03
    Watermelon:  753/23539 = 0.03
    Pineapple:  534/23539 = 0.02

...

-------------------------------------
 Overall statistics
-------------------------------------
  Image count:  6360
  Bounding box count:  27188

注意: --stats-only 确实下载了注释数据(大约~1GB),但尚未下载图像。

实际上,为了缩短训练时间(并节省磁盘空间),您可能希望将图像总数保持在 10K 以下。虽然使用的图像越多,模型的准确性就越高。您可以使用以下选项来限制下载的数据量:

  • --max-images 将总数据集限制为指定数量的图像,同时保持每个类别的图像分布与原始数据集大致相同。如果一个类别的图像数量多于另一个类别,则该比例将保持大致相同。
  • --max-annotations-per-class 将每个类别限制为指定数量的边界框,如果某个类别的可用数量少于该数量,则将使用它的所有数据 - 如果数据分布在各个类别之间不平衡,这将很有用。

例如,如果您只想使用 2500 张水果数据集图像,则可以像这样启动下载器:

复制代码
$ python3 open_images_downloader.py --max-images=2500 --class-names "Apple,Orange,Banana,Strawberry,Grape,Pear,Pineapple,Watermelon" --data=data/fruit

如果未设置 --max-boxes--max-annotations-per-class 选项 ,则默认下载所有可用数据 - 因此,请务必事先使用 --stats-only 检查数据量。遗憾的是,无法提前确定图像的实际磁盘大小要求,但此数据集的一般经验法则是每张图片预算约 350KB(水果约 2GB)。

训练的成绩

以下是 SSD-Mobilenet 训练的近似性能,以帮助估计训练所需的时间:

条目 Images/sec Time per epoch*
Nano 4.77 17 min 55 sec
Xavier NX 14.65 5 min 50 sec
  • 在水果测试集上进行的测试 (5145 training images, batch size 4)

训练 SSD-Mobilenet 模型

数据下载完成后,运行 train_ssd.py 脚本启动训练:

复制代码
python3 train_ssd.py --data=data/fruit --model-dir=models/fruit --batch-size=4 --epochs=30

以下是运行训练脚本时可以使用的一些常用选项:

Argument Default Description
--data data/ 数据集的位置
--model-dir models/ 输出训练好的模型检查点的目录
--resume None 恢复训练的现有检查点的路径
--batch-size 4 根据可用内存,尝试增加该参数
--epochs 30 最好到达 100,但是会增加训练时间
--workers 2 数据加载器线程数(0 = 禁用多线程)

随着时间的推移,你会看到损失减少:

复制代码
2020-07-10 13:14:12 - Epoch: 0, Step: 10/1287, Avg Loss: 12.4240, Avg Regression Loss 3.5747, Avg Classification Loss: 8.8493
2020-07-10 13:14:12 - Epoch: 0, Step: 20/1287, Avg Loss: 9.6947, Avg Regression Loss 4.1911, Avg Classification Loss: 5.5036
2020-07-10 13:14:13 - Epoch: 0, Step: 30/1287, Avg Loss: 8.7409, Avg Regression Loss 3.4078, Avg Classification Loss: 5.3332
2020-07-10 13:14:13 - Epoch: 0, Step: 40/1287, Avg Loss: 7.3736, Avg Regression Loss 2.5356, Avg Classification Loss: 4.8379
2020-07-10 13:14:14 - Epoch: 0, Step: 50/1287, Avg Loss: 6.3461, Avg Regression Loss 2.2286, Avg Classification Loss: 4.1175
...
2020-07-10 13:19:26 - Epoch: 0, Validation Loss: 5.6730, Validation Regression Loss 1.7096, Validation Classification Loss: 3.9634
2020-07-10 13:19:26 - Saved model models/fruit/mb1-ssd-Epoch-0-Loss-5.672993580500285.pth

要在完成所有 epoch 训练之前测试你的模型,你可以按下 Ctrl+C 来终止训练脚本,然后稍后使用参数重新开始训练。你可以在这里 --resume=<CHECKPOINT> 下载已经训练了 100 个 epoch 的水果模型。

将模型转换为 ONNX

接下来,我们需要将训练好的模型从 PyTorch 转换为 ONNX,以便可以使用 TensorRT 加载它:

复制代码
python3 onnx_export.py --model-dir=models/fruit

这将在 jetson-inference/python/training/detection/ssd/models/fruit/ 保存一个名为 ssd-mobilenet.onnx 的模型。

使用 TensorRT 处理图像

为了对一些静态测试图像进​​行分类,我们将使用 detectnet 并通过扩展命令行参数来加载自定义的 SSD-Mobilenet ONNX 模型。要运行这些命令,终端的工作目录仍应位于: jetson-inference/python/training/detection/ssd/

复制代码
IMAGES=<path-to-your-jetson-inference>/data/images   # substitute your jetson-inference path here

detectnet --model=models/fruit/ssd-mobilenet.onnx --labels=models/fruit/labels.txt \
          --input-blob=input_0 --output-cvg=scores --output-bbox=boxes \
            "$IMAGES/fruit_*.jpg" $IMAGES/test/fruit_%i.jpg

以下是输出到 $IMAGES/test 目录的一些图像:

运行实时摄像头程序

您还可以尝试在摄像机或视频流上运行重新训练的植物模型,如下所示:

复制代码
detectnet --model=models/fruit/ssd-mobilenet.onnx --labels=models/fruit/labels.txt \
          --input-blob=input_0 --output-cvg=scores --output-bbox=boxes \
            csi://0

2.3.3.2 收集您自己的检测数据集

之前使用的 camera-capture 工具还可以标记来自实时视频的对象检测数据集:

Dataset Type 下拉菜单处于 Detection 模式时,该工具会创建 Pascal VOC 格式的数据集。

注意:如果您想标记一组已有的图像(而不是从相机捕获的图像),请尝试使用类似 CVAT 的工具,并将数据集导出为 Pascal VOC 格式。然后在数据集中创建一个包含每个对象类别名称的 labels.txt 文件。

创建标签文件

jetson-inference/python/training/detection/ssd/data 下创建一个空目录用于存储数据集,以及一个用于定义类标签的文本文件(通常名为 labels.txt)。标签文件每行包含一个类标签,例如:

复制代码
Water
Nalgene
Coke
Diet Coke
Ginger ale

如果您正在使用容器,您将需要将数据集存储在像上面那样的已挂载目录中,以便在容器关闭后保存它。

启动工具

camera-capture 工具在命令行上接受与"相机流和多媒体"页面上相同的输入 URI。

以下是启动该工具的一些示例命令:

复制代码
$ camera-capture csi://0       # using default MIPI CSI camera
$ camera-capture /dev/video0   # using V4L2 camera /dev/video0

收集数据

下面是 Dataset Type 下拉菜单设置为检测模式后的 Data Capture Control 窗口。

然后,打开您创建的数据集路径和类标签。Freeze/EditSave 按钮将变为活动状态。

将摄像机对准场景中的对象,然后点击 Freeze/Edit 按钮(或按下空格键)。实时摄像机视图将被"冻结",您可以在对象上绘制边界框。然后,您可以在控制窗口的网格表中为每个边界框选择合适的对象类别。完成图像标记后,再次点击按下的按钮 Freeze/Edit 以保存数据并解冻摄像机视图以准备下一张图像。

控制窗口中的其他小部件包括:

  • Save on UnfreezeFreeze/Edit-解冻后自动保存数据
  • Clear on Unfreeze- 解冻时自动删除先前的边界框
  • Merge Sets- 在训练集、验证集和测试集中保存相同的数据
  • Current Set- 从训练/验证/测试集中选择
    • 对于物体检测,你至少需要训练集和测试集
    • 尽管如果你检查Merge Sets,数据将被复制为训练、验证和测试
  • JPEG Quality- 控制保存图像的编码质量和磁盘大小

重要的是,您的数据应从不同的物体方向、相机视角、光照条件以及理想情况下不同的背景中收集,以创建一个能够抵御噪声和环境变化的模型。如果您发现模型的性能不如预期,请尝试添加更多训练数据并尝试不同的条件。

训练你的模型

收集到大量数据后,您可以尝试使用相同的 train_ssd.py 脚本在其上训练模型。训练过程与上一个示例相同,但需要设置 --dataset-type=voc--data=<PATH> 参数。

复制代码
$ cd jetson-inference/python/training/detection/ssd
$ python3 train_ssd.py --dataset-type=voc --data=data/<YOUR-DATASET> --model-dir=models/<YOUR-MODEL>

与之前一样,训练结束后,您需要将 PyTorch 模型转换为 ONNX:

复制代码
$ python3 onnx_export.py --model-dir=models/<YOUR-MODEL>

转换后的模型将保存在 <YOUR-MODEL>/ssd-mobilenet.onnx 下,然后您可以像前面的示例中一样使用 detectnet 程序加载该模型:

复制代码
NET=models/<YOUR-MODEL>

detectnet --model=$NET/ssd-mobilenet.onnx --labels=$NET/labels.txt \
          --input-blob=input_0 --output-cvg=scores --output-bbox=boxes \
            csi://0

注意: 务必使用生成到模型目录的标签文件进行推理,而不是使用最初为数据集创建的标签文件。这是因为BACKGROUND 类会在 ``train_ssd.py` 过程中被添加到类标签中,并保存到模型目录(训练好的模型预期会使用)。

如果需要,请返回并收集更多训练数据,然后重新训练模型。您可以重新启动并使用 --resume 参数(运行 python3 train_ssd.py --help 以获取更多信息)从上次中断的地方继续。重新训练后,请务必将模型重新导出到 ONNX。

2.4 WebApp框架

1)WebRTC 服务器

jetson-inference 包含一个集成的 WebRTC 服务器,用于与 Web 浏览器进行低延迟实时视频流传输,可用于构建由 Jetson 和后端边缘 AI 驱动的动态 Web 应用程序和数据可视化工具。WebRTC 通过videoSource/videoOutputjetson-utils 的接口与 DNN 推理流水线无缝协作,后者通过 GStreamer 利用硬件加速的视频编码和解码。它支持同时向多个客户端发送和接收多个流(无需为每个独立客户端重新编码视频),并包含一个内置的 Web 服务器前端,用于远程观看视频流。

2)HTML / JavaScript

此 repo 中包含使用 WebRTC 的各种示例 webapps,可在 jetson-inference/python/www 下找到:

复制代码
+ python/
  + www/
    - dash       # Plotly Dashboard
    - html       # core HTML/JavaScript
    - flask      # Flask + REST
    - recognizer # interactive training

3)Flask + REST

Flask 是一个流行的 Python Web 微框架,它将 HTTP/HTTPS 请求路由到用户实现的 Python 函数。您还可以使用它轻松处理后端 REST 请求,客户端可以使用这些请求动态控制属性并根据用户输入触发前端内容。

4)Plotly 仪表板

Plotly Dash 是一个基于 Python 的 Web 框架,用于构建数据驱动的仪表板和交互式 UI。它在前端使用 React.js 客户端,将状态更改连接到服务器上运行的 Python 回调。借助它,您可以快速开发丰富的可视化效果,并与后端处理管道和数据分析集成。在此示例(位于 python/www/dash 下)中,用户可以动态创建流、加载 DNN 模型、可视化事件以及设置由事件触发的可扩展操作:

5)识别器(交互式训练)

Recognizer 是一款基于 Flask 的视频标记/分类 Web 应用,具有交互式数据收集和训练功能。在视频标记和录制过程中,更新后的模型会在后台使用 PyTorch 逐步重新训练,然后通过 TensorRT 进行推理。推理和训练可以同时进行,并且重新训练的模型会在运行时动态加载以进行推理。

2.5 附录

1)摄像机流媒体和多媒体
2)使用 CUDA 进行图像处理
3)ROS/ROS2 的 DNN 推理节点