基于RK3588与YOLOv8的无人机电力巡检边缘AI系统实现
1. 项目概述:当无人机巡检遇上边缘AI
最近在折腾一个挺有意思的项目,核心目标就一个:让搭载RK3588芯片的无人机,在电力线巡检时,能实时、准确地发现隐患,比如绝缘子破损、鸟巢或者导线悬挂异物。听起来像是把好几个热门技术点——边缘计算、目标检测、无人机控制——给揉到了一块儿。确实,这项目本质上是一个轻量化的YOLOv8模型与一套高效的异步视频处理流水线在RK3588这块高性能边缘芯片上的深度整合。
为什么是这几个技术的组合?在电力巡检这个场景里,痛点非常明确。传统的无人机巡检,要么是人眼盯着图传屏幕看,效率低还容易漏检;要么是飞机拍完视频回来再人工分析,时效性太差,真有问题可能已经酿成故障了。我们需要的是在飞行过程中就完成分析,甚至能实时给出预警。这就对机载设备的算力、功耗和算法的速度精度提出了苛刻要求。RK3588作为一款集成了大核CPU和强大NPU的SoC,提供了在端侧进行复杂AI推理的可能;而YOLOv8作为当前最流行的实时检测框架之一,其平衡速度与精度的特性正好匹配;异步处理系统则是为了榨干RK3588的每一分算力,避免因为视频解码、推理、结果绘制这些环节的等待而造成卡顿。
最终我们实现的系统,在输入视频尺寸为640x640的情况下,在RK3588上跑出了平均111 FPS的端到端处理速度。这个“端到端”包含了从RTSP流拉取、解码、预处理、YOLOv8推理、后处理到OSD叠加显示的全流程。这意味着无人机传回的实时视频流,可以被几乎无延迟地分析,为自主避障、重点目标跟踪、即时告警等上层应用提供了坚实的数据基础。这不仅仅是几个开源库的简单调用,而是一套针对嵌入式环境深度优化后的系统工程方案。
2. 核心思路与系统架构设计
2.1 为什么是“异步流水线”?
在资源受限的嵌入式设备上处理连续的视频流,一个最朴素的想法是串行处理:拉流->解码->推理->显示。但这样效率极低,因为每个模块的工作节奏不同。比如,NPU推理一帧可能需要9ms,而视频解码一帧可能只需要3ms。如果串行,解码器在等推理,推理器又在等上一帧显示完成,硬件利用率会非常低。
异步流水线的核心思想是解耦与并行。我们将整个处理流程划分为几个相对独立的阶段(Stage),每个阶段由一个或多个工作线程(Worker)负责,阶段之间通过线程安全的队列(Queue)进行数据(通常是帧或携带元数据的帧)传递。这样,解码线程可以不停地从网络拉流、解码,并把解码后的帧放入队列;推理线程则从队列中取帧进行AI处理,处理完放入另一个结果队列;显示或发送线程再从结果队列取数据做渲染或网络传输。
这样做的好处显而易见:
- 最大化硬件并行度:RK3588的CPU多核、GPU、NPU可以同时忙碌起来。解码可以用CPU或VPU,推理用NPU,后处理和显示用CPU或GPU,各司其职。
- 平滑处理波动:网络抖动导致某几帧解码慢?没关系,推理线程可以消费队列里积压的帧,不会空等。某一帧推理特别耗时?显示线程可以显示稍早的结果,保证显示的流畅性。
- 易于扩展和维护:每个阶段模块化,如果想增加一个算法(如跟踪),或者换一个模型,只需要修改或替换对应的处理阶段,整体架构不受影响。
在我们的系统中,主要设计了四个核心阶段:视频采集与解码阶段、AI推理阶段、结果后处理与融合阶段、输出与显示阶段。它们通过三个核心队列连接:原始帧队列、推理任务队列、结果帧队列。
2.2 硬件选型:RK3588的算力与接口考量
选择RK3588作为硬件平台,是经过仔细权衡的。对于无人机机载计算机,我们需要考虑几个关键指标:AI算力(TOPS)、CPU性能、功耗、接口丰富度和生态支持。
- AI算力:RK3588集成的NPU算力高达6 TOPS(INT8)。这对于运行轻量化后的YOLOv8模型绰绰有余。实测中,我们量化后的YOLOv8-nano模型在NPU上单帧推理时间可稳定在5ms以内,为高帧率处理奠定了基础。
- CPU与内存:四核A76+四核A55的Big.Little架构,既能应对复杂的系统调度和业务逻辑(大核),也能高效处理低负载任务(小核),利于能效比。搭配LPDDR4/5,能满足多线程数据搬运和处理的内存带宽需求。
- 视频编解码能力:内置的VPU支持多路4K视频的硬解码(H.264/H.265/VP9)。这对于处理无人机通常采用的H.264/H.265高清图传流至关重要,能极大降低CPU负载,将算力留给AI。
- 接口与扩展性:丰富的PCIe、USB、MIPI-CSI接口,方便连接无人机飞控(如通过串口或CAN)、图传接收模块、额外传感器等,是作为无人机“大脑”的必备条件。
- 生态与工具链:Rockchip提供了相对完善的NPU开发工具链(RKNN-Toolkit2),支持将PyTorch、TensorFlow等框架训练的模型转换和量化,降低了部署门槛。
注意:RK3588虽然有强大的NPU,但其驱动和工具链版本需要仔细匹配。我们最初使用较旧的驱动,遇到了模型转换精度损失大、推理不稳定等问题。最终锁定在RKNN-Toolkit2 1.5.0 + 特定版本固件的组合上,才达到最佳效果。建议在项目开始时就明确好固件和工具链版本,避免后期兼容性麻烦。
2.3 软件框架选型:GStreamer vs 自定义流水线
构建异步视频处理系统,通常有两种路径:一是基于成熟的媒体框架(如GStreamer),二是完全从零开始用多线程和队列自己搭建。
GStreamer方案:优势是生态成熟,插件丰富,管道(Pipeline)配置灵活,理论上可以快速搭建一个包含解码、推理、显示的流水线。社区也有类似gst-nvinfer(用于NVIDIA)的插件思路。但针对RK3588的RKNN,缺乏官方维护的高质量、高性能的GStreamer插件。虽然可以自己编写一个gst-rknn插件,但需要深入理解GStreamer框架和插件开发,并且要处理好GStreamer内部缓冲与RKNN输入输出Tensor之间的内存拷贝效率问题,初期投入成本高。
自定义流水线方案:即我们采用的方式。使用librockchip_mpp进行硬解码,使用librknn_runtime进行NPU推理,使用OpenCV或DRM/KMS进行显示。各模块间用生产者-消费者模式,通过队列连接。这种方式的好处是:
- 极致控制:每一块内存、每一个线程的调度都可以根据我们的需求进行精细优化,避免框架带来的额外开销。
- 依赖简洁:运行时依赖库少,更适合嵌入式系统裁剪。
- 调试直观:每个环节的数据流清晰,出问题时容易定位。
权衡之下,为了追求极致的性能和可控性,我们选择了自定义流水线的道路。虽然开发量更大,但换来了对系统全链路的深度掌控,这对于优化到111 FPS这样的指标是必要的。
3. 轻量化YOLOv8模型优化实战
3.1 模型选择与训练数据准备
YOLOv8提供了从n(nano)到x(extra large)多个尺度的预训练模型。对于机载边缘设备,必须在精度和速度间取得平衡。我们选择了YOLOv8n(最轻量)和YOLOv8s(小型)作为候选基线。电力巡检目标(绝缘子、防震锤、鸟巢等)通常不是特别密集的小目标,因此轻量模型在足够数据训练下,精度是可以接受的。
数据集构建:
- 数据收集:从历史巡检视频、公开电力数据集以及部分实地拍摄中,收集包含各类电力设备缺陷和正常状态的图片。
- 数据标注:使用LabelImg、CVAT等工具,按照PASCAL VOC或YOLO格式进行标注。关键是要定义清晰的类别,如:
insulator(绝缘子)、broken_insulator(破损绝缘子)、bird_nest(鸟巢)、foreign_object(悬挂异物)等。 - 数据增强:为了提升模型鲁棒性,采用了Mosaic、MixUp、随机旋转、亮度对比度调整、添加云雾模拟等增强手段。特别是针对无人机视角的仿射变换,模拟不同飞行高度和角度的拍摄情况,这对模型泛化能力至关重要。
实操心得:无人机拍摄的图像常常存在镜头畸变、画面抖动、光照变化剧烈等问题。在数据增强阶段,有意识地加入模拟这些情况的变换,比如轻微的径向畸变、高斯模糊模拟抖动、过曝/欠曝,能显著提升模型在真实飞行中的表现。我们甚至用了一段模拟无人机飞行的视频,逐帧截取并标注,作为训练集的一部分。
3.2 模型训练与剪枝量化
我们使用Ultralytics官方框架进行训练。关键训练参数设置如下:
yolo train model=yolov8n.pt data=power_inspection.yaml epochs=300 imgsz=640 batch=16 device=0imgsz=640:与部署时的输入尺寸保持一致。epochs:根据数据集大小和早停(Early Stopping)策略调整,通常100-300轮。data:配置文件,其中定义了类别数和训练/验证集路径。
训练完成后,得到.pt格式的PyTorch模型。但这是浮点模型,无法直接在NPU上高效运行。接下来是关键的两步:剪枝(可选)和量化。
剪枝:为了进一步压缩模型,我们尝试了通道剪枝。使用一些剪枝工具(如Torch-Pruning)对训练好的YOLOv8n进行稀疏化训练和剪枝,移除不重要的通道。实测可以将模型体积减小30%-40%,推理速度提升约20%,但精度会有1-2个百分点的mAP下降。需要根据实际可接受的精度损失来决定是否采用。
量化:这是NPU部署的必经之路。RKNN-Toolkit2支持将FP32模型量化为INT8或UINT8模型,大幅减少模型体积和提升推理速度。量化分为后训练量化(PTQ)和量化感知训练(QAT)。
- PTQ:简单快捷,使用一个代表性的校准数据集(通常从训练集中抽取几百张图)统计激活值分布,确定量化参数。但对于精度要求高的场景,PTQ可能导致较大精度损失。
- QAT:在训练过程中模拟量化过程,让模型适应量化带来的误差,通常能获得比PTQ更好的精度。我们采用了QAT,在训练框架中插入量化节点,重新微调了数十个epoch,使得量化后的INT8模型与原始FP32模型的精度差距控制在0.5% mAP以内。
量化后的模型,从原来的几MB(FP32)缩小到大约2-3MB(INT8),为快速加载和推理创造了条件。
3.3 RKNN模型转换与部署
使用RKNN-Toolkit2进行模型转换。这一步需要准备一个RKNN模型配置文件,定义输入输出节点、预处理参数、量化类型、目标平台等。
一个典型的转换脚本核心部分如下:
from rknn.api import RKNN rknn = RKNN() # 配置 rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform='rk3588') # 加载模型 ret = rknn.load_pytorch(model='yolov8n_qat.pt', input_size_list=[[3, 640, 640]]) # 构建模型 ret = rknn.build(do_quantization=True, dataset='./calib_dataset.txt') # 导出模型 ret = rknn.export_rknn('./yolov8n_power.rknn') rknn.release()mean_values/std_values:需要与模型训练时的归一化方式对应。YOLOv8默认输入是0-255的像素值,所以均值设为0,标准差设为255(即除以255)。dataset:指向校准数据集列表文件,用于PTQ或验证QAT效果。
转换成功后,得到.rknn文件。在C++部署代码中,我们使用librknn_runtime来加载和运行这个模型。
注意事项:RKNN模型在加载时,会根据当前NPU的核心数进行初始化。RK3588的NPU是双核的。在异步流水线中,如果创建多个RKNN上下文(Context)实例,理论上可以尝试并行执行多个推理任务。但需要小心内存和核心争用。我们实践发现,对于YOLOv8n这样的小模型,单实例多线程提交任务,由NPU驱动内部调度,效率已经很高。盲目创建多实例反而可能因资源竞争导致性能下降。
4. 异步视频处理系统的核心实现
4.1 多线程架构与队列设计
整个系统的线程架构是性能的关键。我们设计了以下主要线程:
- 主线程:负责系统初始化、参数解析、线程创建与协调、用户交互(如有)和优雅退出。
- 视频采集线程(1个或多个):负责从RTSP流(无人机图传)或CSI摄像头拉取数据。使用
librockchip_mpp的MPP_DEC接口进行硬解码。解码后的帧(通常是IMGFMT_NV12格式)被放入原始帧队列(RawFrameQueue)。这个队列需要设定一个最大长度(如10帧),防止内存无限增长。 - AI推理线程(1个或多个):从
RawFrameQueue中取出帧,进行预处理(如resize到640x640,颜色空间转换YUV到RGB,归一化等),然后调用RKNN推理接口。推理结果(包含类别、置信度、坐标)被封装成一个结构体,和原始帧索引或指针一起,放入推理结果队列(ResultQueue)。 - 后处理与渲染线程(1个):从
ResultQueue中取出结果,进行非极大值抑制(NMS)过滤掉重叠框,然后将检测框、类别标签、置信度绘制到对应的原始帧上(即OSD叠加)。处理好的帧放入显示帧队列(DisplayFrameQueue)。 - 显示/输出线程(1个):从
DisplayFrameQueue中取帧,通过DRM/KMS接口直接输出到屏幕,或者通过RTP/UDP推流到地面站,也可以保存为视频文件。
我们使用C++标准库的std::queue配合std::mutex和std::condition_variable来实现线程安全的队列。condition_variable用于在队列空时让消费者线程等待,队列满时让生产者线程等待,高效协调生产消费节奏。
4.2 内存管理与零拷贝优化
在视频处理中,频繁的内存拷贝是性能杀手。我们采用了以下策略进行优化:
- 内存池(Memory Pool):系统启动时,预先分配一批固定大小的视频帧缓冲区(
VideoFrame对象)。每个缓冲区包含图像数据内存和必要的元数据(时间戳、宽高、格式等)。所有线程都从内存池中申请和归还缓冲区,避免运行时频繁的malloc/free操作,减少内存碎片和分配开销。 - 共享指针与引用计数:每个
VideoFrame对象使用类似std::shared_ptr的引用计数智能指针进行管理。当一帧数据从解码器出来,被放入RawFrameQueue时,其引用计数增加。推理线程、渲染线程在处理完这帧数据,并不再需要它时,引用计数减少。当引用计数归零,该帧缓冲区被自动回收到内存池。这避免了复杂的手动内存管理,也确保了数据在多个处理阶段间安全传递。 - NPU零拷贝输入:RKNN推理的输入数据需要放在NPU能直接访问的内存中(通常是CMA内存)。我们通过
rknn_create_mem_from_fd或rknn_create_mem_from_phys接口,将内存池中预先分配的、物理连续的内存块注册为RKNN张量内存。这样,预处理后的图像数据可以直接写入这块内存,NPU推理时无需内部拷贝,显著减少了数据搬运开销。 - 解码器输出直连:配置MPP解码器,将其输出缓冲区直接设置为我们内存池中申请好的NV12缓冲区。这样,解码完成的图像数据直接落在后续处理可用的内存中,省去了从解码器输出缓冲区拷贝到应用缓冲区的一步。
通过这些优化,数据流在解码、推理、渲染之间流动时,尽可能避免了冗余的内存拷贝,将宝贵的CPU时间和内存带宽留给了真正的计算任务。
4.3 流水线压力测试与调优
系统搭建好后,需要像调试发动机一样进行压力测试和精细调优。我们使用一个高帧率的本地视频文件模拟RTSP流,进行长时间的压力测试。
关键监控指标:
- 各队列长度:持续观察
RawFrameQueue,ResultQueue,DisplayFrameQueue的长度变化。理想状态是它们在一个较小的范围内波动(如0-3)。如果RawFrameQueue持续增长,说明解码太快或推理太慢;如果DisplayFrameQueue持续增长,说明渲染/输出是瓶颈。 - 线程CPU占用率:使用
top或htop命令查看各线程的CPU使用情况。推理线程的CPU占用通常不高(因为主要工作在NPU),但解码和渲染线程可能CPU占用较高。需要确保没有某个线程长期100%占用一个核心,造成流水线阻塞。 - 帧处理延迟:在帧数据中打入时间戳,记录一帧从解码开始到显示结束的总耗时。这个延迟需要稳定且低于视频帧间隔(例如,对于30FPS输入,处理延迟应稳定低于33ms),否则会导致队列积压和实时性丧失。
常见调优手段:
- 调整队列容量:队列太小容易引起线程频繁等待,太大则增加内存占用和延迟。根据处理速度动态调整,或设置为一个经验值(如5-10帧)。
- 线程优先级设置:在Linux下,可以使用
pthread_setschedparam给关键线程(如显示线程)设置更高的调度优先级(SCHED_FIFO),确保即使系统繁忙,显示也能及时进行,避免卡顿感。 - 推理批处理(Batching):虽然我们追求低延迟,但对于一些非绝对实时的分析任务,可以将队列中的2-4帧组合成一个Batch送入NPU推理。NPU对Batch推理通常有更高的计算效率,能提升整体吞吐量。但这会增加单次推理的延迟,需要权衡。
- 预处理与后处理加速:图像resize、颜色转换等预处理,以及NMS、画框等后处理,可以尝试使用Neon指令集优化,或者利用RK3588的RGA(2D图形加速器)硬件模块来加速,进一步释放CPU。
经过多轮调优,我们的系统最终达到了稳定的111 FPS端到端处理能力,各队列保持平衡,延迟控制在10ms以内,为无人机实时巡检提供了流畅的体验。
5. 系统集成与无人机平台适配
5.1 与飞控系统的通信集成
无人机自主巡检,意味着边缘AI系统不能只“看”,还要能“控”。这就需要将检测结果实时反馈给飞控系统(如PX4, ArduPilot),实现诸如“发现缺陷后悬停拍照”、“跟踪特定设备飞行”、“避让障碍物”等功能。
通信链路通常采用串口(UART)或机载网络(UDP/TCP)。我们选择了MAVLink协议,它是无人机领域事实上的标准通信协议。
- MAVLink集成:在RK3588的程序中,集成MAVLink C库。创建一个独立的通信线程,它监听
ResultQueue(或一个专门的通知队列)。当后处理线程识别到高置信度的缺陷目标(如置信度>0.8的broken_insulator)时,除了在画面上标注,还会生成一个包含目标类型、图像像素坐标、GPS位置(可从飞控获取)等信息的MAVLink消息(例如自定义的COMMAND_LONG消息或VISION_POSITION_DELTA消息)。 - 消息发送:通信线程通过串口(如
/dev/ttyTHS1)或UDP端口,将MAVLink消息发送给飞控。飞控端的飞行栈(Flight Stack)需要预先配置,能够接收并解析这些自定义消息,并触发相应的自动驾驶仪行为,比如切换到“悬停(Hold)”模式,或者执行一个预设的“拍照”动作。 - 心跳与状态同步:通信线程还需要定期向飞控发送心跳包,维持链路连接,并接收飞控的状态信息(如GPS坐标、高度、姿态),用于将图像中的目标位置映射到地理坐标系中。
实操心得:串口通信的稳定性至关重要。需要设置正确的波特率(如921600),并实现可靠的重连和错误处理机制。发送MAVLink消息的频率不宜过高,避免堵塞串口。我们通常只在检测到关键事件时才发送,或者以固定的较低频率(如5Hz)发送目标位置信息。另外,飞控和RK3588系统的时间同步(NTP或PPS)对于数据融合也非常重要。
5.2 功耗、散热与稳定性考量
无人机对重量和功耗极其敏感。RK3588在全速运行,特别是NPU和CPU多核满载时,发热和功耗不容忽视。
- 动态频率调节(DVFS):Linux内核的CPUFreq和DevFreq驱动可以根据负载动态调节CPU和NPU的频率。我们可以编写策略,在无人机巡航、AI分析任务不重时,降低运行频率以省电;在进入巡检区域需要密集分析时,再提升频率。但需要注意,频率切换本身有延迟和开销,过于频繁的调节可能适得其反。
- 温度监控与降频:在RK3588核心板附近布置温度传感器,实时监控温度。当温度超过阈值(如85°C)时,主动降低CPU/NPU频率,甚至暂停部分非关键任务,以防止过热重启。这可以通过编写一个简单的守护进程,读取
/sys/class/thermal/thermal_zone*/temp文件,并调用相关接口调整频率来实现。 - 散热设计:在结构设计上,必须为RK3588核心板配备足够的散热措施,如散热片、导热硅胶,甚至小型风扇或金属机壳辅助散热。良好的散热是系统长时间稳定运行的基础。
- 看门狗与异常恢复:系统需要具备自我恢复能力。启用硬件看门狗(WDT),在程序主循环中定期“喂狗”。如果程序因未知原因卡死,看门狗超时会导致系统重启。此外,关键线程(如解码、推理线程)如果崩溃,应该有监控机制尝试重启该线程,而不是让整个系统瘫痪。
5.3 地面站交互与数据回传
完整的巡检系统还需要一个地面站进行监控和指挥。我们开发了一个简单的Qt或Web界面的地面站软件。
- 视频流显示:RK3588处理后的视频流(叠加了检测框),通过H.264/H.265编码后,经由无人机的数传链路或4G/5G网络,以RTMP或RTP/UDP流的形式推送到地面站。地面站软件使用FFmpeg或GStreamer解码并实时显示。这提供了第一视角的巡检画面。
- 检测结果与元数据同步:除了视频流,检测结果(目标类型、位置、置信度、截图)以及无人机状态信息,通过另一条数据链路(如MAVLink的
DATA_STREAM或自定义的UDP/TCP通道)同步发送到地面站。地面站软件解析后,可以在地图上标注缺陷点,生成巡检报告,并保存带标注的图片或视频片段作为证据。 - 指令下发:地面站操作员可以查看实时分析结果,并手动下发指令给RK3588端,例如:标记当前帧为误报、调整AI检测的置信度阈值、切换检测模型(如从常规巡检模型切换到针对特定缺陷的精细模型)、或命令无人机飞往指定坐标进行复查。
这套“端-边-云”协同的架构,使得无人机电力巡检从单纯的“飞行拍照”升级为“实时感知-智能决策-精准行动”的闭环系统。
6. 性能实测、问题排查与优化记录
6.1 性能测试指标与方法
我们建立了一套标准的性能测试流程,以确保系统在不同工况下的表现符合预期。
测试环境:
- 硬件:Rockchip RK3588开发板(8GB内存),连接USB摄像头模拟无人机图传,或播放本地高清视频文件。
- 软件:基于Buildroot定制的Linux系统,内核版本5.10,关闭非必要后台服务。
- 模型:INT8量化的YOLOv8n模型,输入尺寸640x640,类别数6。
测试指标:
- 端到端帧率(FPS):从收到一帧原始数据开始,到输出带OSD结果帧为止的平均频率。这是衡量系统实时性的核心指标。使用高精度时钟(如
std::chrono::high_resolution_clock)在流水线入口和出口打点计算。 - 单模块耗时:使用性能分析工具(如
perf,gprof, 或简单的打点)测量解码、预处理、推理、后处理、渲染各阶段的平均耗时。 - CPU/NPU利用率:使用
top,mpstat和Rockchip提供的npu_top工具,监控各核心的负载情况。 - 内存占用:使用
free和smem命令监控系统总内存和各个进程的内存使用情况,确保无内存泄漏。 - 检测精度(mAP):在预留的测试集上,运行完整的处理流水线,统计模型的平均精度(mAP@0.5),确保优化没有显著损害精度。
测试结果示例:
| 模块 | 平均耗时(ms) | 备注 |
|---|---|---|
| 视频解码(H.264 1080P) | 2.1 | 使用MPP硬解码,CPU占用低 |
| 图像预处理(Resize+ColorConvert) | 1.5 | 使用OpenCV优化代码,可考虑RGA加速 |
| NPU推理(YOLOv8n INT8) | 4.8 | 核心耗时,已优化至5ms内 |
| 后处理(NMS+坐标映射) | 0.8 | 纯CPU计算 |
| OSD渲染与显示 | 1.0 | 使用DRM直接显示,无额外拷贝 |
| 单帧总耗时 | ~9.0 | 理论最大FPS ≈ 111 |
| 实测稳定FPS | 105-111 | 受调度波动、输入源影响 |
6.2 典型问题排查实录
在开发过程中,我们遇到了形形色色的问题,以下是几个典型案例:
问题一:推理结果随机错误或NPU崩溃
- 现象:系统运行一段时间后,YOLOv8的检测框会乱飞,或者直接报错“RKNN_ERR_NPU_TIMEOUT”。
- 排查:
- 首先检查温度,排除过热降频。
- 检查内存,使用
valgrind排查是否有内存越界。发现某一处预处理代码在极端情况下存在数组访问越界的风险。 - 检查多线程同步。发现推理线程在向RKNN运行时提交输入数据后,立即释放了该内存(被其他线程复用),而此时NPU可能还在异步处理该数据,导致读取了错误的内存内容。
- 解决:确保输入输出Tensor的内存生命周期必须覆盖从
rknn_inputs_set到rknn_outputs_get的完整过程。使用引用计数管理Tensor内存,只有确认该帧推理结果已被后处理线程取走后,才释放相关内存。
问题二:帧率不稳定,偶尔卡顿
- 现象:平均FPS能达到100,但观察视频输出有肉眼可见的间歇性卡顿。
- 排查:
- 检查各队列长度监控,发现
DisplayFrameQueue偶尔会清空,导致显示线程无帧可显,造成卡顿。 - 使用
perf记录并生成火焰图,发现卡顿发生时,系统有大量的malloc/free调用,来自某个日志库频繁分配小内存。 - 进一步追踪,发现后处理线程在画框时,频繁创建和销毁
cv::Point和cv::Scalar对象。
- 检查各队列长度监控,发现
- 解决:将日志输出改为异步或静态缓冲区。在后处理线程中,复用画图用的几何对象,避免在热路径上进行频繁的堆内存分配。优化后,卡顿现象消失。
问题三:检测框在屏幕上位置偏移
- 现象:AI检测框的坐标,映射到1080P显示画面上时,发生偏移,不贴合目标。
- 排查:
- 确认模型输入是640x640,而解码输出是1920x1080(NV12)。
- 检查预处理resize逻辑:是从1080P裁剪到640x640,还是等比例缩放后填充?我们采用的是等比例缩放+灰边填充(Letterbox)的方式,以保持目标不变形。
- 问题出在后处理的坐标反变换。推理输出的坐标是相对于640x640输入图像的,需要先减去灰边偏移,再按缩放比例映射回原始1080P图像的坐标。代码中在计算缩放因子时,误用了目标的宽高而非图像的宽高比。
- 解决:修正坐标反变换公式。确保缩放因子
scale = min(640/1080.0, 640/1920.0),偏移dx = (640 - 1080 * scale) / 2,dy = (640 - 1920 * scale) / 2。将推理框坐标[x1, y1, x2, y2]反算为[(x1 - dx)/scale, (y1 - dy)/scale, ...]。
6.3 持续优化方向
达到111 FPS只是一个里程碑,系统仍有优化空间:
- 模型层面:
- 知识蒸馏:用更大的YOLOv8x模型作为教师网络,蒸馏训练更小的学生网络(如自定义的纳米级网络),在精度损失极小的情况下进一步压缩模型。
- 神经网络架构搜索(NAS):针对电力巡检特定的目标分布,搜索更高效的网络结构。
- 系统层面:
- 异构计算:将预处理(如resize)和后处理(如NMS)中可并行的部分,尝试用RK3588的GPU(Mali-G610)或RGA来加速,进一步减轻CPU负担。
- 流水线深度优化:探索将NMS等后处理也放到NPU上执行的可能性(如果RKNN工具链支持自定义算子),减少CPU与NPU之间的数据往返。
- 动态分辨率:根据无人机与目标的距离,动态调整推理输入分辨率。远距离时用低分辨率快速扫描,检测到潜在目标后,切换高分辨率进行精细识别,平衡整体速度和局部精度。
- 功能层面:
- 多模型协同:除了目标检测,可以并行运行一个轻量化的分割模型(如PP-LiteSeg),用于识别绝缘子串的完整性和破损区域,提供更精细的分析。
- 时序分析与跟踪:引入目标跟踪算法(如ByteTrack),对连续帧中的同一设备进行跟踪,可以更稳定地判断其状态,并过滤掉瞬时误检。
这个项目让我深刻体会到,边缘AI落地不是一个简单的模型部署问题,而是一个涉及算法、软件工程、硬件特性和系统调优的综合性工程。从111 FPS这个数字背后,是无数个对细节的打磨和对瓶颈的突破。希望这些实践中的经验和踩过的坑,能给正在探索边缘智能应用的你带来一些启发。
