Fastdeploy与英特尔NUC推动智慧城市巡检应用落地

1.项目背景

智慧城市是指利用大数据、物联网(IoT)人工智能和5G等数字技术,提高政府公共服务水平、社会治理效能、推动经济增长和不断增强人民群众获得感、安全感和幸福感。自十四五规划以来,国家和各大主要城市一直加速推进新型智慧城市分级分类建设,但在实施的过程中也遇到了一些问题和困难,在AI方面常见的问题有 边缘设备硬件不统一、跨平台开发成本高和模型推理速度优化难度大 等。
对于AI系统的使用者也有困扰,比如环境工程师经常要在户外采集数据,如果要做到实时采集实时分析存储入库,必须要背一个沉重的带显卡的工作站到户外环境,还需要提XXXXL号的移动电源解决供电问题。因此,本方案演示了FastDeploy + OpenVINO + 轻量化模型的AI工作流模式,希望能为软件工程师降低开发难度,也为使用者提供更轻便、简单和高效的解决方案。

2. 项目介绍

本项目利用无人机控制端自带的实时消息传输协议(RTMP)将低空无人机的实时图像传输到推理硬件设备,通过模型推理计算绿化覆盖率、建筑率、车辆数量、人群数量等指标,可用于 城市大规模环境感知监测、国土低空遥感、道路交通巡检和无人机低空安防 等领域。
本任务是对无人机图像数据流的实时推理,对推理速度有极高的要求,同时为了减少开发难度和迁移成本,我们采用了x86 CPU架构的英特尔NUC®迷你电脑套件作为推理硬件,软件方面选择了FastDeploy套件快速开发后端推理引擎,并根据硬件特性选择了OpenVINO作为推理后端加速模型推理。此外,在AI模型选择上,我们分别选择了PP-LiteSeg和PP-YOLO tiny两个轻量化模型来完成语义分割和目标检测任务。

3.效果展示

3.环境准备

核心软硬件:

英特尔® NUC 迷你电脑套件(Intel i5 八代CPU)
FastDeploy >= 0.2.1
PaddleDetection >= 2.5
PaddleSeg >= 2.6
PySide6 >= 6.3.2

可选软硬件(户外实时采集时用):

大疆系列无人机及DJI APP
路由器(用于内网rmtp传输)

在这里插入图片描述

(特别感谢英特尔提供NUC硬件支持以及开发了OpenVINO这么强大的推理引擎!)

4.技术流程

在这里插入图片描述

本项目整体的流程如上图所示,首先在NUC上架设RTMP推流服务,无人机APP客户端连接内网RTMP服务器,实现无人机的图像实时通过路由器向NUC设备传输。NUC设备端获得图像后,渲染至PySide前端的显示窗口,同时FastDeploy的线程执行语义分割和目标检测的推理任务,并把结果和可视化图像实时渲染至前端窗口。本文将展示程序最核心的部分——模型推理部署。
核心部分主要分为三个环节:模型动静转换、推理脚本编写和前端集成

5.模型动静转换

首先我们需要把使用PaddleSeg和PaddleDetection开发套件训练好的动态图模型转换成静态图模型,这一步利用两个套件分别提供的脚本即可简单完成。
注:模型转换的详细教程可参考
https://github.com/PaddlePaddle/PaddleSeg/blob/release/2.6/docs/model_export_cn.md
https://github.com/PaddlePaddle/PaddleClas/blob/release/2.4/README_ch.md

# 首先Clone一下两个开发套件的仓库
!git clone https://gitee.com/paddlepaddle/PaddleSeg.git
!git clone https://gitee.com/paddlepaddle/PaddleDetection.git

# 训练好的语义分割模型保存在 output_ppliteseg_stdc1/best_model/model.pdparams
# 目标检测使用PaddleDetection仓库已经在COCO数据集上训练好的动态图模型转换
# 然后分别运行两个转换脚本

# PaddleSeg模型转换
!python PaddleSeg/export.py \
       --config ppliteseg_stdc1.yml \
       --model_path output_ppliteseg_stdc1/best_model/model.pdparams \ 
       --save_dir ./inference/ppliteseg/ \
       --input_shape 1 3 512 1024

# PaddleDetection模型转换
!python PaddleDetection/tools/export_model.py \
       -c PaddleDetection/configs/ppyolo/ppyolo_tiny_650e_coco.yml \
       --output_dir=./inference/ppyolo/ \
       -o weights=./output_ppyolo/ppyolo_tiny_650e_coco.pdparams

完成转换之后我们能分别得到两套model.pdmodel和model.pdiparams文件以及对应的yaml配置文件。

6.基于FastDeploy开发OpenVINO推理模块

本项目使用的是英特尔® NUC 迷你电脑套件,毫无疑问使用OpenVINO加速是该平台最佳的推理部署解决方案,以往使用OpenVINO需要下载套件、安装和配置,过程比较繁琐,因此我采用了FastDeploy的部署方案,调用其内置的OpenVINO推理后端进行快速开发、部署。

FastDeploy的预编译安装或编译安装教程可参考官方Github文档:
https://github.com/PaddlePaddle/FastDeploy

因为考虑到程序的适用性和多种硬件环境兼容,我首先写了Option配置,根据不同的硬件选择不同的推理后端,在CPU环境中,默认使用OpenVINO作为推理后端。

# 这里我们用预编译包的方式安装FastDeploy
!pip install fastdeploy-python -f https://www.paddlepaddle.org.cn/whl/fastdeploy.html
import cv2
import numpy as np
import fastdeploy as fd
from PIL import Image
from collections import Counter

def FastdeployOption(device=0):
    option = fd.RuntimeOption()
    if device == 0:
        option.use_gpu()
    else:
        # 使用OpenVino推理
        option.use_openvino_backend()
        option.use_cpu()
    return option

然后,将语义分割模型推理代码封装成一个类,方便前端快速调用。在init方法中,直接调用了PaddleSegModel() 函数进行模型初始化(热加载),通过PaddleSegModel.predict() 完成结果的推理,得到推理结果之后执行postprocess()对结果进行解析,提取建筑和绿地的像素数量,统计图像占比,得到环境要素的占比结果。最后调用vis_segmentation()函数对推理结果进行可视化。

class SegModel(object):
    def __init__(self, device=0) -> None:
        self.segModel = fd.vision.segmentation.ppseg.PaddleSegModel(
            model_file = 'inference/ppliteseg/model.pdmodel',
            params_file = 'inference/ppliteseg/model.pdiparams',
            config_file = 'inference/ppliteseg/deploy.yaml',
            runtime_option=FastdeployOption(device)
        )
    
    def predict(self, img):
        segResult = self.segModel.predict(img)
        result = self.postprocess(segResult)
        visImg = fd.vision.vis_segmentation(img, segResult)
        return result, visImg
    
    def postprocess(self, result):
        resultShape = result.shape
        labelmap = result.label_map
        labelmapCount = dict(Counter(labelmap))
        pixelTotal = int(resultShape[0] * resultShape[1])
        # 统计建筑率和绿地率
        buildingRate, greenRate = 0, 0
        if 8 in labelmapCount:
            buildingRate = round(labelmapCount[8] / pixelTotal* 100, 3) 
        if 9 in labelmapCount:
            greenRate = round(labelmapCount[9] / pixelTotal * 100 , 3)
        
        return {"building": buildingRate, "green": greenRate}

同理,直接调用FastDeploy的PPYOLO()类完成模型的推理,经过后处理格式化数据之后调用对应的可视化函数vis_detection()进行可视化。

class DetModel(object):
    def __init__(self, device=0) -> None:
        self.detModel = fd.vision.detection.PPYOLO(
            model_file = 'inference/ppyolo/model.pdmodel',
            params_file = 'inference/ppyolo/model.pdiparams',
            config_file = 'inference/ppyolo/infer_cfg.yml',
            runtime_option=FastdeployOption(device)
        )
        # 阈值设置
        self.threshold = 0.3
    
    def predict(self, img):
        detResult = self.detModel.predict(img.copy())
        result = self.postprocess(detResult)
        visImg = fd.vision.vis_detection(img, detResult, self.threshold, 2)
        return result, visImg

    def postprocess(self, result):
        # 得到结果
        detIds = result.label_ids
        detScores = result.scores
        # 统计数量
        humanNum, CarNum = 0, 0
        for i in range(len(detIds)):
            if detIds[i] == 0 and detScores[i] >= self.threshold:
                humanNum += 1
            if detIds[i] == 2 and detScores[i] >= self.threshold:
                CarNum += 1
        return {"human": humanNum, "car": CarNum}

把PP-Liteseg语义分割模型和PP-YOLO tiny目标检测模型封装成类之后,保存为inferEngine.py文件,以供后续前端代码调用。

6.结合PySide6开发可视化GUI界面

AIStudio平台无法在线预览,请访问本项目的Github仓库并拉取代码后在本地运行。
项目Github地址:https://github.com/JiehangXie/UAV-Monitor

前端开发的整体而言开发难度不大,主要的难点在于三个视频播放的组件同时更新导致的程序卡死或者延时问题。在本项目中应用的解决方法是用多线程,把三个视频播放组件的后端分开三个独立的线程,一个线程(displayThread)把实时视频流原画推送到前端更新,另外两个线程(segThread和detThread)同步完成语义分割和目标检测推理实时视频帧图像并将后处理之后的推理结果图像更新到前端组件上。具体代码如下所示(main.py):

import cv2
import sys
from main_ui import Ui_MainWindow
from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtGui import QImage, QPixmap
from threading import Thread
from inferEngine import SegModel, DetModel
import logging
import time

# 初始化日志
logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

class App(QMainWindow):
    def __init__(self):
        super(App, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.beginButton.clicked.connect(self.playBtn)

    def playBtn(self):
        # 获取配置信息
        self.inputVideoPath = self.ui.filePathEdit.text() # 文件路径
        self.device = self.ui.deviceBox.currentIndex() # 设备信息
        self.frameRate = int(self.ui.frame.text()) # 帧数

        # 初始化模型
        logger.info("Model Init.")

        self.SegObject = SegModel(self.device)
        self.DetObject = DetModel(self.device)

        self.inputvideoCapture = cv2.VideoCapture(self.inputVideoPath)
        self.fps = self.inputvideoCapture.get(cv2.CAP_PROP_FPS)
        inputVideoThread = Thread(target=self.displayThread)
        inputVideoThread.start()

    def displayThread(self):
        i = 0
        while self.inputvideoCapture.isOpened():
            self.ret, self.frame = self.inputvideoCapture.read()
            if self.ret:
                self.frame = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
                image = QImage(self.frame, self.frame.shape[1], self.frame.shape[0], QImage.Format_RGB888)
                self.ui.inputVideo.setPixmap(QPixmap.fromImage(image))
                self.ui.inputVideo.setScaledContents(True)
                cv2.waitKey(1)

                # 每N帧处理分割检测一次,防止使用GPU时显存不足
                if i % self.frameRate == 0:
                    # 语义分割线程
                    self.segVideoThread = Thread(target=self.segThread)
                    self.segVideoThread.start()
                        
                    # 目标检测线程
                    self.detVideoThread = Thread(target=self.detThread)
                    self.detVideoThread.start()
                i += 1
                time.sleep(1 / self.fps)
            else:
                self.inputvideoCapture.release()
                break
    
    def segThread(self):
        # 语义分割线程
        logger.info("Thread Segmentation: start")

        # 处理输入视频帧(临时Fastdeploy对seg的支持问题,下个版本修复)
        frame = self.frame if self.device == 0 else cv2.resize(self.frame, (1024, 512))

        result, segImage = self.SegObject.predict(frame)
        segImage = cv2.cvtColor(segImage, cv2.COLOR_BGR2RGB)
        segImageQT = QImage(segImage, segImage.shape[1], segImage.shape[0], QImage.Format_RGB888)
        self.ui.segVideo.setPixmap(QPixmap.fromImage(segImageQT))
        self.ui.segVideo.setScaledContents(True)
        cv2.waitKey(1)
        self.ui.greenrate.setText("{} %".format(result["green"]))
        self.ui.buildingrate.setText("{} %".format(result["building"]))

        logger.info("Thread Segmentation: finished")

    def detThread(self):
        # 目标检测线程
        logger.info("Thread Detection: start")
        result, detImage = self.DetObject.predict(self.frame)
        # detImage = cv2.cvtColor(detImage, cv2.COLOR_BGR2RGB)
        detImageQT = QImage(detImage, detImage.shape[1], detImage.shape[0], QImage.Format_RGB888)
        self.ui.detVideo.setPixmap(QPixmap.fromImage(detImageQT))
        self.ui.detVideo.setScaledContents(True)
        cv2.waitKey(1)
        self.ui.humanCount.setText("{} 人".format(result["human"]))
        self.ui.carCount.setText("{} 辆".format(result["car"]))

        logger.info("Thread Detection: finished")


app = QApplication([])
window = App()
window.show()

sys.exit(app.exec())

最后执行python main.py运行程序,查看推理效果。

在这里插入图片描述

7.总结

FastDeploy是一个帮助开发者快速部署深度学习模型的套件,内置了包括OpenVINO在内的多个推理后端,并对OpenVINO进行了针对性的优化,很好地兼容了英特尔NUC迷你主机的硬件,无需另外安装OpenVINO套件和配置环境,同时也免去了调优提速的烦恼,降低了开发者的学习成本和部署成本,提高了部署开发效率。

8.参考链接

https://github.com/JiehangXie/UAV-Monitor
https://github.com/PaddlePaddle/FastDeploy
https://github.com/PaddlePaddle/PaddleDetection
https://github.com/PaddlePaddle/PaddleSeg

此文章为搬运
原项目链接

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐