# 基于QT的Jetson Xavier部署Demo
> 该项目中的QT设计代码,也支持其它平台下QT的运行——但需要保证各平台下拥有opencv的编译库(动态链接库与头文件(include)库),以及正常编译得到的动态链接库(*.a 或 *.so).
项目文档目录:
- 1 环境准备
- 2 配置编译脚本
- 2.1 修改`jetson_build.sh`编译参数
- 2.2 修改`CMakeList.txt`参数
- 2.3 修改`yaml.cmake`参数
- 3 代码编译(生成模型预测的动态链接库)
- 3.1 修改`model_infer.cpp`文件
- 3.2 修改`CMakeList.txt`文件
- 3.3 执行`jetson_build.sh`编译
- 4 启动并配置QT工程(移植流程)
- 4.1 启动QT工程项目
- 4.2 载入动态链接库
- 4.3 配置QT的Opencv路径
- 5 启动QT可视化界面
- 5.1 功能介绍
- 5.2 使用说明
- 5.3 使用效果展示
- 6 QT开发注解
- 6.1 利用`QDebug`实现运行日志输出
- 6.2 本项目的组织结构
- 6.3 控制读取图片的推理大小
- 6.4 子线程控制主线程控件的建议
- 6.5 修改model_infer.cpp函数后的动态链接库导入指导
- 6.6 QT动态链接库的导入说明
- 6.7 移植小贴士
- 本项目的Demo的GUI也同时支持Linux、Windows上进行使用,但请自行编译好opencv,安装QT,移植流程区别如下。
在新版本的PaddleX中,对于CPP的部署代码方式做了非常大的变化:
* 支持用户将PaddleDetection PaddleSeg PaddleClas训练出来的模型通过一套部署代码实现快速部署,实现了多个模型的打通。
* 对于一个视觉任务里面既有检测,又有分割来说,极大的简化了使用的流程。
下面我们具体以**Jetson Xavier**系统为例,基于PaddleX的这套CPP(deploy/),说明一下如何实现QT的部署
项目使用基本环境说明:
* CUDA10.2 Cudnn 8
* Jetson原生opencv4.1.1 / opencv3.4.6等(原生应该都支持,自己编译的opencv则建议为3.4.6)
* Jetpack4.4: nv-jetson-cuda10.2-cudnn8-trt7(xavier)——PaddleInference 10.2的预测库(2.1.1版本)
* Cmake 3.10.2
* QT5.9.5
* QMake3.1
> 查看Jetpack版本: `cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A 2`
> (4.3可以使用4.4版本的预测库)
>
> 查看QT版本: `qmake -v`
>
> 查看CUDA版本: `cat /usr/local/cuda/version.txt`
>
> 查看Cudnn版本: `cat /usr/include/cudnn_version.h | grep CUDNN_MAJOR -A 2`
## 1 环境准备
* 下载好PaddleX代码和PaddleInference预测库
* 下载QT以及QT-Creater
1. 下载QT:
`sudo apt-get install qt5-default qtcreator -y`
2. 下载PaddleX+PaddleInference预测库:
> 可查看文档:
- [基于PaddleInference的推理-Jetson环境编译](../../deploy/cpp/docs/compile/paddle/jetson.md)
3. 查看cmake版本: `cmake -version`
> 保证版本大于3.5即可,如小于,可尝试安装`cmake3.10.2`。
**QT-Creater的启动,可以通过`应用搜索`,输入`QT`,在出现的应用图标中选中QTCreater,点击即可启动!**
--------
## 2 配置编译脚本
> 如果已经查看`Jetson环境编译`文档后,并已经成功编译出可执行程序后,可跳过该部分的`deploy/cpp/scripts/jetson_build.sh`、`deploy/cpp/CMakeList.txt`以及`deploy/cpp/cmake/yaml.cmake`的修改说明.
> 以下3部分的修改完全同[基于PaddleInference的推理-Jetson环境编译](../../deploy/cpp/docs/compile/paddle/jetson.md)一样,可前往参阅。
### 2.1 修改`jetson_build.sh`编译参数
根据自己的系统环境,修改`PaddleX/deploy/cpp/script/jetson_build.sh`脚本中的参数,主要修改的参数为以下几个
| 参数 | 说明 |
| :------------ | :----------------------------------------------------------------------------------- |
| WITH_GPU | ON或OFF,表示是否使用GPU,当下载的为CPU预测库时,设为OFF |
| PADDLE_DIR | 预测库所在路径,默认为`PaddleX/deploy/cpp/paddle_inference`目录下 |
| CUDA_LIB | cuda相关lib文件所在的目录路径 -- 请注意jetson预装的cuda所在路径(如:/usr/local/cuda/lib64) |
| CUDNN_LIB | cudnn相关lib文件所在的目录路径 -- 请注意jetson预装的cuda所在路径(如:/usr/lib/aarch64-linux-gnu) |
| WITH_TENSORRT | ON或OFF,表示是否使用开启TensorRT |
| TENSORRT_DIR | TensorRT 的路径,如果开启TensorRT开关WITH_TENSORRT,需修改为您实际安装的TensorRT路径 |
| WITH_ENCRYPTION | ON或OFF,表示是否开启加密模块 |
| OPENSSL_DIR | OPENSSL所在路径,解密所需。默认为`PaddleX/deploy/cpp/deps/penssl-1.1.0k`目录下 |
> **要注意相关参数路径不要有误——特别是CUDA_LIB以及CUDNN_LIB,如果需要启动TensorRt,也需指定当前的路径。**
> 不需要添加oepncv路径,在jetson中编译可直接使用环境本身预装的opencv进行deploy编译——具体配置在Step4中。
### 2.2 修改`CMakeList.txt`参数
> 该修改仅适合Jetson系统的部署编译。
根据自己的系统环境,修改`PaddleX/deploy/cpp/CMakeLists.txt`脚本中的参数,主要修改的参数为以下几个:位于其中注释`#OPENCV`之后的部分
| 参数 | 说明 |
| :------------ | :----------------------------------------------------------------------------------- |
| set(OpenCV_INCLUDE_DIRS "/usr/include/opencv") | 配置Jetson预置opencv的include路径 |
| file(GLOB OpenCV_LIBS /usr/lib/libopencv_*${CMAKE_SHARED_LIBRARY_SUFFIX}) | 配置opencv动态链接库*.so |
替换具体如下:(xavier为例)
1. /usr/include/opencv --> /usr/include/opencv4
> 具体路径,以部署环境中opencv的include路径为准。
> opencv4 中包含: opencv, opencv2
2. /usr/lib/libopencv_*${CMAKE_SHARED_LIBRARY_SUFFIX} --> /usr/lib/libopencv_*${CMAKE_SHARED_LIBRARY_SUFFIX}
> 具体路径,以部署环境中opencv的*.so路径为准, 主要修改libopencv_前的路径。
### 2.3 修改`yaml.cmake`参数
由于Jetson环境下编译还需要yaml,所以这里需要手动下载yaml包,保证编译的正常运行。
> 1. 点击[下载yaml依赖包](https://bj.bcebos.com/paddlex/deploy/deps/yaml-cpp.zip),无需解压
> 2. 修改`PaddleX/deploy/cpp/cmake/yaml.cmake`文件,将`URL https://bj.bcebos.com/paddlex/deploy/deps/yaml-cpp.zip`中网址替换为第3步中下载的路径,如改为`URL /Users/Download/yaml-cpp.zip`
**这里yaml存放路径为了确保使用最好保证全英文路径**
eg:
> 其它支持的加密操作以及TensorRT,可参考[Linux环境编译指南](./linux.md).
-------
## 3 代码编译(生成模型预测的动态链接库)
该部分需要修改两个地方,以保证动态链接库的正常生成。
> 接下来的操作,请在执行以下命令正常生成可执行程序后再往下继续配置,以确保修改前的工作是正确可执行的。
>
```
sh script/jetson_build.sh
```
> 编译时,如果存在cmake多线程问题——请前往`jetson_build.sh`末尾,将`make -j8`改为`make`或者小于8.
>
> 编译后会在`PaddleX/deploy/cpp/build/demo`目录下生成`model_infer`、`multi_gpu_model_infer`和`batch_infer`等几个可执行二进制文件.
### 3.1 修改`model_infer.cpp`文件
接下来我们需要在QT中去调用动态链接库,所以原有的`deploy/cpp/demo/model_infer.cpp`文件生成的可执行文件不能支持QT去进行调用,所以需要对其进行一定的修改。
现已将修改后满足需求的文件放于本文档处,即——**将当前文件的`model_infer.cpp`用于替换`deploy/cpp/demo/model_infer.cpp`即可.**
- 其中主要开辟了多个共享接口函数,分别对应:模型初始化,模型推理(用于推理PaddleDetection、PaddleSeg、PaddleClas、PaddleX产出的部署模型),模型销毁。
> 该model_infer.cpp仅支持使用一个模型——用于单线程调用
>
> 如需多线程推理,同时创建多个模型用于预测推理,可以参考该项目下cpp的实现: [PaddleDeploy在C#端的多线程推理demo](https://github.com/cjh3020889729/PaddleDeploy-CSharp-ManyThreadInfer-Demo)
- 模型初始化接口: `InitModel(const char* model_type, const char* model_filename, const char* params_filename, const char* cfg_file, bool use_gpu, int gpu_id, char* paddlex_model_type)`
- 目标检测推理接口: `Det_ModelPredict(const unsigned char* img, int nWidth, int nHeight, int nChannel, float* output, int* nBoxesNum, char* LabelList)`
- 语义分割推理接口: `Seg_ModelPredict(const unsigned char* img, int nWidth, int nHeight, int nChannel, unsigned char* output)`
- 图像识别推理接口: `Cls_ModelPredict(const unsigned char* img, int nWidth, int nHeight, int nChannel, float* score, char* category, int* category_id)`
- 实例分割推理接口: `Mask_ModelPredict(const unsigned char* img, int nWidth, int nHeight, int nChannel, float* box_output, unsigned char* mask_output, int* nBoxesNum, char* LabelList)`
- 模型销毁接口: `DestructModel()`
> 详细说明,请查看[`model_infer.cpp`](./model_infer.cpp)的实现.
### 3.2 修改`CMakeList.txt`文件
以上完成了`model_infer.cpp`的修改后,需要修改当前目录下的`CMakeList.txt`文件,位于: `deploy/cpp/demo/CMakeList.txt`.
需要修改的内容,如图所示:
用`ADD_library`那句脚本替换下划线那句脚本即可,也可直接使用本项目提供的`CMakeList.txt`进行替换`deploy/cpp/demo/CMakeList.txt`.
### 3.3 执行`jetson_build.sh`编译
运行以下命令行, 编译完成即可获得所需推理接口的动态链接库(libmodel_infer.so)
```
sh script/jetson_build.sh
```
运行完成,会在`PaddleX/deploy/cpp/build/lib`中生成`libmodel_infer.so`动态链接库。
## 4 启动并配置QT工程(移植流程)
以下步骤请确保QT安装完成,且可以正常启动QT桌面项目。
### 4.1 启动QT工程项目
1. 首先打开`Qtcreator`,新建项目,取名为`Deploy_infer`,选择项目路径为自己所熟悉的路径即可,然后一直顺着往下不必多设置或者勾选什么,直到`finished`。
> 如果存在多个编译环境供QT使用,请确保QT使用的编译环境与前边生成动态链接库的编译环境一致——或者至少保证生成的所有库都允许相互调用,即避免出现32位与64位不兼容等情况。
2. 进入QT崭新的工程项目后,工程项目中会存在`*.pro`,`Headers`,`Sources`,`Forms`四个主要组件,其中后三个为项目分支(目录)
3. 右键点击项目,选择`Add New`, 进入子界面选择`c++`,选中`Class`, 点击`choise`.
4. 在新出来的子界面中,输入`InferThread`作为**Class name**, 然后一直往下生成即可.
5. 将本项目`Deploy_infer`中的`inferthread.cpp`与`inferthread.h`中的内容分别复制过去即可.
6. 然后,再将本项目`Deploy_infer`中的`mainwindow.cpp`与`mainwindow.h`中的内容也复制过去.
7. 最后,将本项目的`mainwindow.ui`替换新建的QT-GUI项目的ui文件.
> 此时,QT项目的移植就完成了——之所以新建项目,看起来比较复杂,是为了避免直接移植导致的QT版本不匹配,发生一些意料之外的问题。
>
> 此时QT项目中,会出现标红的错误,原因可能如下:
- 1. 还未导入动态链接库
- 2. 还未导入opencv的编译好的库
- 因此,现在暂时不用担心标红的问题
### 4.2 载入动态链接库
在创建的QT项目文件夹下新建`infer_lib`文件夹,将生成的`libmodel_infer.so`文件移入其中即可。
**然后打开`Qtcreator`, 打开项目选择本QT项目启动**,可观察到QT工程目录结构如图所示:
双击`mainwindow.cpp`进入,**确保第一个函数中,导入的动态链接库绝对路径无误!**
> 此时QT项目中,会出现标红的错误,原因可能如下:
- 1. 还未导入opencv的编译好的库
- 因此,现在暂时不用担心标红的问题
### 4.3 配置QT的Opencv路径
> 请保证编译opencv的编译环境与当前QT使用的编译环境一致,否则可能无法导入opencv中的函数.
该部分主要配置两个部分: `Include Path` 和 `Lib Path`.
双击`Deploy_infer.pro`进入,按照如图所示写入当前环境下系统原装预编译的`Opencv路径`:
- `INCLUDEPATH` : 表示`opencv`的头文件所在路径,包含头文件根目录,`opencv`子目录以及`opencv2`子目录
- `LIBS` : 表示`opencv`的动态链接库`so文件`所在路径,这里使用`正则匹配`,自动匹配路径下的所有`opencv.so`文件
> 在本测试Jetson环境上,**预编译opencv由于没有同ffmpeg一同编译**,因此不支持视频流的处理与预测(无法打开视频文件,如mp4,avi等)
>
> 如有需要可在此时另外编译opencv,不覆盖原预编译的opencv版本,仅用于QT程序进行图像/视频的读取和简单处理。
>
> 此时编译的新opencv,在编译时要选择编译参数使其支持QT、GL以及ffmpeg.(ffmpeg可能需要自行编译)
>
> 该方案的编译指导,可参考网上的`linux下opencv与ffmpeg联合编译`资料。
>
> 因此,本QT的Demo在`Jetson Xavier`上,使用`原生opencv`,仅支持图片以及连续图片的预测,视频预测需要自行编译新的opencv,以支持视频读取——只要opencv编译成功,以上编译动态链接库的`opencv路径`仅需相应修改即可(**记得更改后重新编译生成**),同时修改QT导入的`opencv库路径`就可以正常使用该可视化Demo了。
> 此时QT项目中,标红的错误应该会消失——可以稍微等一下QT项目更新项目缓存,记得保存修改!
**PS:**
在运行项目时,请注意,如果报`cv::xxx`未定义,可能为以下情况:
- 1. opencv头文件引入有误,当前采用导入的形式,而非使用系统路径,因此,应该保证引入符号为`""`,而非`<>`.
- 2. 如果出现导入pro中的`LIBS`有误,显示没有该文件(No Such File or Path)等报错,请查看opencv动态链接库是否配置正确.
- 3. 当前测试中,所有路径均为`英文路径`,因此在使用`中文路径`时如遇报错,请换成`英文路径`重新导入.
- 4. 以上情况外,还可能时QT编译环境与opencv编译的环境不同所导致,QT无法引用opencv1的动态链接库中的内容.
> `Jetson`下仅测试了英文路径,`Windows`上中英文路径均无误。
## 5 启动QT可视化界面
启动后界面如下:
### 5.1 功能介绍
- 1.可加载PaddleSeg, PaddleClas, PaddleDetection以及PaddleX导出的部署模型, 分别对应模型选择中的: seg, clas, det, paddlex
- 2.目前也支持`GPU`下加载MaskRCNN进行实例分割可视化推理,需选择模型: mask
- 3.支持CPU与GPU推理,同时支持指定GPU运行 —— 当前在单卡上测试默认为0运行正常,非法指定不存在的id无法初始化模型;且可能引发异常导致程序崩溃
- 4.支持单张图片(png, jpg)、图片文件夹、**视频流(mp4)推理(Jetson原装opencv下不支持,Windows测试无误)**
- 5.支持目标检测时,设定检测结果显示阈值
- 6.支持图片文件夹推理(即连续图片推理)时,设定连续推理间隔,方便观察预测效果
- 7.支持推理中断:图片文件夹推理过程+视频流推理过程
### 5.2 使用说明
- 1.选择模型类型:det、seg、clas、mask、paddlex
- 2.选择运行环境:CPU、GPU
- 3.点击初始化模型,选择模型文件夹即可 —— 文件夹格式如下
- inference_model
- *.yml
- *.pdmodel
- *.pdiparams
- paddlex的模型含有两个yml,其余套件导出只有一个yml/yaml
- 4.加载图片/图片文件夹/视频流
- 5.模型推理
- 6.(非单张推理时支持)执行提前推理中断
- 7.加载新模型,需要先点击销毁模型,然后再设置模型类型以及运行环境,最后重新初始化新模型
- 8.在目标检测过程中,可设置检测阈值
- 9.在文件夹推理过程中,可设置连续推理间隔时间
- 10.可通过查看左上角实时推理耗时来查看模型预处理+推理-后处理的时间**(Jetson上,CPU推理过慢,建议直接使用GPU推理)**
- 11.可编辑GPU_id,设置初始化时模型运行在指定GPU上——请根据实际硬件设置,默认为0
### 5.3 使用效果展示
**CPU推理**
**GPU推理**
-----
## 6 QT开发注解
> 一些方便大家修改Demo界面源码,以实现一些适配工作。
### 6.1 利用`QDebug`实现运行日志输出
首先在mainwindow.cpp中导入`#include `,以支持QT程序运行过程中输出一些人为添加的日志输出。
实例代码:
输出如下:
### 6.2 本项目的组织结构
工程目录:
- inferthread.cpp/inferthread.h :`推理子线程` —— 包含具体的推理执行函数的实现,以及运行控件状态信号的发出(比如,更新图片显示,与控件的使能与关闭)
- mainwindow.cpp/mainwindow.h :`主线程` —— 包含界面布局控制的实现,推理文件加载(也包含动态链接库)的实现,以及推理线程的启动与中止信号的发出(比如,启动推离线程)
### 6.3 控制读取图片的推理大小
由于QT可视化界面可能用于不同的系统,因此对于可分配运行内存而言是有一定的考量的,因此对于读取的图片在进行推理以及显示前进行一定的缩放,能够减少内存的消耗。
> 不过,显示时要保证图像宽高均可被4整除,否则控件图片显示可能有误。
每一个具体推理的函数里,都有以下操作,对推理前的图片进行缩放:
### 6.4 子线程控制主线程控件的建议
不要直接使用子线程对主线程控件进行控制,避免导致线程报错,线程问题不易debug——因此,多用信号与槽来实现交互。
实现思路:
1. 在子线程需要对主线程中控件进行控制时,发送一个信号
2. 主线程在消息循环机制中持续运行时,接收信号,执行对应的槽,实现控件的控制
具体实现流程:
1. 在子线程的.h文件中,编写函数声明即可——不用具体实现。
2. 在主线程的.h文件中,先编写槽函数的声明,然后在.cpp中去实现它,完成信号的传递。
3. 在编写好槽函数定义后,进行connect完成槽函数与信号的链接。
**PS:**
子线程信号声明如下:
主线程槽声明如下:
### 6.5 修改model_infer.cpp函数后的动态链接库导入指导
1.进入`inferthread.h`与`mainwindow.h`中,使用`typdef定义函数指针`,按照`函数参数格式`进行定义。
即:model_infer.cpp中函数怎么定义的,这边就构建一个相同定义支持的函数指针。
2.`inferthread.h`中的定义展示:
3.`mainwindow.h`中的定义展示:
### 6.6 QT动态链接库的导入说明
本项目只需要导入一个自己定义的动态链接库,因此使用QLibrary进行导入,该方法不适用于多动态库的导入,容易代码量多导致混淆。
该项目的导入如下:
### 6.7 移植小贴士
1. 运行环境改变时,注意运行环境对编译的库的影响,配置正确的opencv路径以及编译器版本
2. 对于linux,QT默认使用系统gcc进行编译,不用选择编译器
3. 对于windows,QT使用MinGW或者MSVC等,此时需要注意QT使用的编译器与opencv、model_infer动态链接库等的编译器是否一致,或者是否可以兼容.
2. 避免QT版本移植问题,先创建一个新的项目,然后拷贝已有的旧项目中的ui、cpp与h文件到新项目,同时修改pro文件中的配置一致即可完成跨版本跨平台的移植。
## 本项目的Demo的GUI也同时支持Linux、Windows上进行使用,但请自行编译好opencv,安装QT,移植流程区别如下。
> 注意deploy编译所需的流程,可参考PaddleX模型推理部署deploy在Linux以及Windows上的[编译指南](https://github.com/cjh3020889729/PaddleX/tree/develop/deploy/cpp).
> 注意区分不同平台上,动态链接库的命名区别: windows-*.a , linux/jetson-*.so
- Windows平台移植测试完成 -- 自行编译opencv3.4.6/4.1.1,需保证支持QT,GL
- windows上编译opencv可参考: [为qt编译opencv](http://159.138.37.243/article/z634863434/89950961)
- 如cmake的configure中出现红字,说找不到ffmpeg相关包,属于网络问题,无法下载该相关dll,需要自行下载后进行相关处理,可参考: [ffmpeg下载失败处理方法](https://www.cxyzjd.com/article/pyt1234567890/106525475)
- Jetson Xavier平台移植测试完成 -- 预编译opencv4.1.1,已支持QT和GL
- Linux平台移植界面测试完成 -- opencv以及模型推理所需的动态链接库(可按照该项目的`CMakeList.txt`与`model_infer.cpp`替换原文件,然后按照[linux编译方法](../../deploy/cpp/docs/compile/paddle/linux.md)进行编译)去自行生成。