1. 三块“芯”的分工逻辑:为什么这么组队
接到这个需求的第一反应,其实不是“选哪颗芯片”,而是“单靠 RK3588 到底行不行”。RK3588 这颗处理器本身已经很强:8 核 CPU、Mali-G610 GPU、6 TOPS 算力的 NPU,还有双 MIPI CSI 口和 8K 解码能力。但一遇到超高清图像处理里的“原始数据入口”,问题就暴露出来了。工业相机、医疗内镜、车载前视这类场景,输出往往是 8K30 甚至 4K120 的 RAW Bayer 流,一秒钟就是几十 Gbps 的数据量。如果把这些原始数据直接灌给 RK3588,内存带宽和 ISP 都会被瞬间打满,NPU 还没有看到图,系统已经先卡死了。所以这个项目最终采用了“FPGA 做入口预处理 + RK3588 做调度与 AI 推理 + NPU 做核心分析”的三核协同架构,FPGA 挡在第一道数据洪流前面,RK3588 只负责处理已经梳洗过的“干净数据”,AI 则跑在最适合它的算力单元上。
1.1 单芯方案的真实瓶颈:数据流而不是算力
很多人会先入为主地认为,视频分析的瓶颈是“算力不足”,但实际做下来会发现,最大的问题是数据流。以 8K30 的 12-bit RAW 为例,理论带宽是 7680×4320×30×12 bit,换算过来大约是 1.49 GB/s,这还不考虑行场消隐、协议开销和格式转换时的额外带宽。RK3588 的内存带宽虽然不低,但 CPU、GPU、NPU、VPU 全在一起抢带宽,一旦 RAW 数据直接进内存,很快就会遇到延迟抖动和帧率不稳。
FPGA 在这里解决的问题不是替代 ISP,而是给 RK3588 减负。它在最前端完成去马赛克、降噪、坏点校正、自动白平衡、裁剪缩放这些“脏活累活”,最终输出一路标准的 YUV 或 RGB 视频给 RK3588。这样 RK3588 的 ISP 和 VPU 就不需要处理 Bayer 插值,RGA 也不需要做超大尺寸的缩放,整条链路从入口就干净了。
1.2 三颗算力单元各自的“最舒服区间”
我在这类方案里喜欢画一张分工表,把每一级的任务边界定死,避免出现“谁都能干、谁都干不彻底”的情况。
| 单元 | 擅长的工作 | 不擅长的工作 | 对应本项目的任务 |
|---|---|---|---|
| FPGA | 低延迟像素级流水线、位宽可控、时序并行 | 复杂算法迭代、AI 模型灵活性 | RAW 输入、去马赛克、降噪、缩放、MIPI 输出 |
| RK3588 CPU | 控制流、调度、协议栈、管理 | 高强度重复计算 | 驱动控制、任务分发、结果后处理、显示输出 |
| RK3588 NPU/GPU | 大数据量矩阵运算、CNN 推理 | 高吞吐像素级预处理 | YOLOv8 目标检测、图像分类、分割等 AI 任务 |
FPGA 属于“专用加速器”,它的优势是确定性和并行性。RK3588 的 NPU 则是“可编程加速器”,它跑模型比 FPGA 灵活得多,哪怕模型从 YOLOv8 换成另一个结构,也只是转换一次 ONNX 的问题,而 FPGA 如果要改网络结构,通常意味着重新做综合布线。把这两者的边界划清楚,整个项目的节奏就会顺很多。
2. 从 FPGA 到 RK3588 的图像通路:MIPI、设备树与驱动调试
硬件连通是整个项目的第一个关键节点。FPGA 与 RK3588 之间最常见的视频接口,不是 PCIe,而是 MIPI CSI-2。原因很实际:RK3588 原生支持 MIPI CSI,Linux 下有完整的 V4L2 驱动生态,而且 MIPI 数据流简单,延迟低,特别适合 FPGA 这种“主动吐数据”的角色。
2.1 MIPI 链路的设计参数与板级连接
RK3588 的 MIPI CSI 接口支持 D-PHY,数据通路可以灵活配置为双 4-lane 或单 8-lane。以我做的 4K60 预处理方案为例,用的是 4-lane MIPI,每条 lane 跑 1.5 Gbps,总带宽 6 Gbps,足够承载 4K60 的 YUV422 数据流。
如果要用 8K30,一条 4-lane 链路就不够了,通常会把 FPGA 输出的数据切分为两条 MIPI 流,分别送到 RK3588 的两个 CSI 口,然后在驱动层做拼接。这里最容易踩的坑是两个口的时钟同步问题:FPGA 必须保证两路 MIPI 的像素时钟来自同一个 PLL,否则 RK3588 收到两路视频后会因为行场相位不一致而出现画面撕裂。
实际连线时,FPGA 要复用一个虚拟 sensor 的角色,也就是把 FPGA 的 MIPI TX 接口接到 RK3588 的 MIPI RX,并通过 I2C 提供模拟的 sensor 寄存器读写能力。RK3588 端的驱动并不会关心对面是真正的摄像头还是 FPGA,它只认 I2C 地址和寄存器的交互行为。这样做的最大好处是,我们不用改动内核里面复杂的 sensor 驱动框架,只需要写一个“虚拟 sensor 驱动”,把 FPGA 的命令通道映射成普通的寄存器读写即可。
2.2 设备树与 v4l2 子设备拓扑的调试经验
设备树这一层的坑,往往比驱动本身多。RK3588 的摄像头通路一般会抽象成“Sensor -> MIPI DPHY -> CSI2 Host -> ISP/Receiver”几层,每一层都要在设备树里正确连接。
我调试时通常先用下面几步把链路拉通:
- 先用
media-ctl -d /dev/media0 -p查看当前 media 拓扑,确认 FPGA 虚拟 sensor 节点是否出现在链路里。 - 再用
v4l2-ctl --list-devices查看对应的 video 节点是否创建成功。 - 接着做一次抓帧测试,用
v4l2-ctl --stream-mmap --stream-count=1 --stream-to=/tmp/frame.yuv抓一帧 YUV 数据,确认数据真的进来了。
如果抓不到帧,优先怀疑寄存器地址是否匹配,其次是 MIPI lane 数和时钟频率是否与 FPGA 实际输出一致。RK3588 的 DPHY 驱动里一般有 lane 数配置,FPGA 端如果用了 4-lane,而设备树里配置成 2-lane,驱动不会立刻报错,但抓帧一定是黑屏或者花屏。这种问题非常隐蔽,因为 log 看起来一切正常,数据通路却没有信号。我建议在 FPGA 端保留一个调试用的测试图生成模块,平时可以切到彩条测试图,这样排查起来能快速确定是链路问题还是后续 ISP 处理问题。
根文件系统层面,如果是用 RK3588 板卡调试,有时候不方便接显示器,我一般直接用 adb 连接板子,既可以用adb push/pull传文件,也可以进 shell 看内核日志。这个习惯在项目前期帮了大忙,因为 FPGA 和 RK3588 之间的握手过程经常要反复改固件,开着 adb 能省去来回插拔 SD 卡的麻烦。
3. FPGA 内部的实际工程:去马赛克、定点数与稳定运行细节
三核协同里,FPGA 是唯一需要写 RTL 的部分,也是整个项目里工程量最大、最不可控的一环。很多人对 FPGA 图像处理有个误解,觉得就是流水线堆数据,写完就能跑。实际上,像素级的定点数设计、复位处理和时钟域交叉,才是真正决定项目成败的地方。
3.1 ISP 预处理流水线:从 RAW 到 YUV 的取舍
典型的 FPGA ISP 流水线大概是这样:
- 黑电平校正,把 sensor 的暗电流偏移减掉;
- 坏点校正,用邻域像素替代异常点;
- 去马赛克,把 Bayer 格式插值成 RGB;
- 降噪,做一定程度的空域滤波;
- 白平衡和色彩校正矩阵;
- Gamma 校正,得到 8-bit 数据;
- 缩放和裁切,输出到 MIPI TX。
这里必须做一个所谓“功能取舍”,不是所有步骤都要放在 FPGA 里。比如自动曝光、自动白平衡的统计计算,我之前在 FPGA 里做了不少统计模块,后来发现不如把统计值通过中断上报给 CPU,由 RK3588 的算力来完成,FPGA 只负责执行最终参数。因为 RK3588 的 Linux 里跑算法太方便了,没必要用 RTL 去折腾复杂的迭代逻辑,而且 FPGA 里的动态迭代一旦 bug 就要重新综合布线,调试周期太长。
3.2 定点数位宽选型:精度、资源与数值溢出的平衡
FPGA 没有浮点单元,所有系数和像素都必须转成定点数。这里最容易出问题的就是位宽选择。
以去马赛克后的 RGB 数据为例,如果 sensor 输出的是 12-bit RAW,经过增益和矩阵乘法之后,中间数据位宽需要预留足够的余量,不然会把高光细节削掉。我常用的做法是:
- 12-bit 输入,黑电平校正后保持 12-bit;
- 增益乘法用 Q4.12 格式,也就是 4 位整数位、12 位小数位,系数范围从 0 到 15.999,精度 1/4096,这个精度对增益调校完全够用;
- 去马赛克后的 RGB 各自扩到 14-bit,给重叠的加法运算留出两个 bit 的余量;
- 色彩校正矩阵用 Q0.16 或 Q1.15 格式,输出前做截断和饱和处理;
- 最后的 Gamma 查表输出 8-bit,完成位宽收敛。
这里需要特别提醒的是“截断和饱和”。如果用简单的截断而不做饱和,任何超过 8-bit 的数都会直接回卷成一个小数,画面上会出现严重的异常伪彩。正确做法是:先判断是否超出最大值,超出则强制设为最大值,低于最小值则设为 0,这段代码在 RTL 里只占几行,但很多初学者会漏掉。
3.3 复位亚稳态与时钟管理:FPGA 稳定性最容易忽略的部分
复位信号亚稳态,是 FPGA 温控、逻辑稳定之外最容易被低估的问题。你从热词里也能看到不少人搜“fpga 复位信号亚稳态”,这说明实际项目中确实经常翻车。FPGA 的复位信号如果来自板载按键、I2C 配置芯片或者外部设备,往往没有和 FPGA 内部时钟同步。当复位信号的有效沿刚好落在时钟沿附近时,触发器输出端就会出现亚稳态,导致部分寄存器复位成功、部分没有复位成功,逻辑行为变得无法复现。
我现在的做法一律是“异步复位、同步释放”,也就是先把外部复位源经过两级 D 触发器打拍,消除亚稳态之后再作为内部全局复位使用。打完拍之后的复位信号再去驱动各个模块的复位端口。这样既保留了异步复位的快速响应,又能保证所有模块在同一时钟周期内完成复位。
时钟方面,FPGA 端通常有多个时钟域,比如 sensor 输入像素时钟、滤波模块工作时钟、MIPI TX 串行时钟。只要跨时钟域,就必须用异步 FIFO 来转接数据。这个环节我见过太多“偶尔卡一帧”的怪象,最后查下来都是直接拿一个时钟域的使能信号去采另一个时钟域的数据。换句话说,异步 FIFO 的钱不能省。
3.4 功耗与散热:温控风扇不能靠感觉
FPGA 跑 8K 图像流水线时,发热量比想象中大得多。尤其是大规模去马赛克和降噪滤波器,内部翻转率很高,片上温度轻松超过 70 摄氏度。原厂的开发板一般只做散热片,被动散热在持续满负荷下压不住,必须上主动风扇。我之前有一版设计直接给 FPGA 散热片上贴了风扇,但风扇接的是常电,转速恒定,低温时噪声大,高温时又不一定够。后来改成了温度反馈控制:利用 FPGA 内部的 XADC 或者板上的温度传感器,读取 die 温度后输出 PWM 控制风扇转速。
这个控制逻辑用状态机或者简单的 PID 都能实现,核心结论是:把温控阈值设成两档,低于 60 度跑 30% 转速,超过 70 度直接拉满,中间用线性插值过渡。这套机制在长时间运行测试里非常管用,温度能稳定在 85 度以内,而风扇噪声也不是特别明显。关键是不要在 FPGA 逻辑里引入大延迟,温度采样周期有个 100ms 左右就够了,毕竟热惯性很小。
4. RK3588 侧的软件基础:Ubuntu 移植、MPP/RGA 和 VPU 管控
RK3588 端的系统搭建,是整个项目里“资料最杂、坑也最多”的一环。热门词里能看到一堆人在搜“rk3588 移植 ubuntu 26”“rk3588 ubuntu”“ubuntu rockchip 社区项目”,说明大家都是在 Linux 环境下做开发,但真正把系统跑稳、把多媒体链路调通的人并不多。
4.1 Ubuntu 移植过程中的分区与打包问题
从官方 SDK 编译得到的内核和根文件系统,通常需要自己整合进启动镜像里。移植 Ubuntu 时,我建议直接基于 Rockchip 官方维护的 Linux SDK,而不是从网上下载一个别人做好的镜像来魔改,因为不同板卡的设备树、DDR 配置和 PMIC 驱动差异太大,拿到手往往不知道里面改了什么。
分区设计上,现在主流 .img 打包都采用 A/B 分区方案,也就是把根文件系统复制两份,启动时挂在当前 active 分区,升级时写入 inactive 分区,最后切换引导标志位。这种方案的优势是升级失败还能回滚,适合需要长期运行的工业设备。但 A/B 分区也意味着根文件系统的空间会翻倍,如果你的 eMMC 只有 32GB,那么一个根分区可能只有 12GB 左右,跑大模型或者装很多依赖就得精打细算。
打包流程我自己习惯写成脚本,每次改完内核或根文件系统就自动执行,省去手工操作。重点是把 boot.img、dtb、rootfs.img 之间的版本匹配关系固定下来。很多时候 RK3588 起不来,并不是内核有问题,而是 DTB 和内核版本不匹配,或者是 uboot 里的 fdt 地址没有对齐。移植最烦的不是编译,而是这些“看运气”的启动细节。
4.2 MPP 和 RGA:视频编解码与格式转换的硬件加速
数据从 FPGA 到 RK3588 之后,还要经过一道或多道格式转换。RK3588 的 VPU 由一个强力的 MPP(多媒体处理平台)驱动管理,负责视频编解码。也就是说,解码好一帧 4K 视频后,需要转成 YUV420SP 并缩放到 640×640 再送给 NPU,这些活如果直接用 CPU,不但慢,还会拖垮整个系统。
我的经验是:格式转换、旋转、镜像、缩放全部交给 RGA,不要自己写循环。RGA 是 Rockchip 的 2D 图形加速硬件,RK3588 上的 RGA 性能足够做 4K 到 1080p 的实时缩放,而且调用方式非常简单,构造一个rga_info结构体,传入 src 和 dst 的 fd 与宽高,再调用ioctl就能完成。比起用自己的 C 代码在 CPU 上像素级搬运,速度快了几个数量级。
MPP 则主要用于视频流编码。如果整个系统要对处理后的画面做本地存储或者推流,就用 MPP 的 VENC 接口做 H.264/H.265 编码。使用 VENC 时,要注意它的帧缓冲必须是从mpp_buffer_group_get_internal获取的内存,直接 malloc 出来的内存会导致编码器无法访问。
5. AI 推理环节怎么提速:YOLOv8 在 RKNN 上的部署与流水线
三核协同的最终目的,是把 AI 分析引擎跑得又快又稳。RK3588 的 NPU 支持 RKNN 格式模型,我们最常见的部署流程就是把训练好的 YOLOv8 导出为 ONNX,再通过 RKNN-Toolkit2 转换成 RKNN 格式。这个过程非常成熟,真正决定性能的是如何设计流水线,而不是如何转换模型。
5.1 模型转换的注意事项
先把训练好的 PyTorch 模型导出为 ONNX,导出时要注意两个点:
- 固定输入尺寸,YOLOv8 默认用了多尺度训练,导出时固定为 640×640 或 1280×1280,否则 RKNN 转换器可能会因为动态维度而报错;
- 确定输出节点名,方便后面解析模型输出。
然后使用 RKNN-Toolkit2 加载 ONNX,配置量化数据集。RK3588 的 NPU 对 INT8 量化非常擅长,如果不做量化,模型就跑不了,因为 NPU 本身是以 INT8 为主要数据类型的。量化数据集最好从实际采集的图像里抽取,不要用网上下载的通用数据集。我见过不少项目在量化时用了 ImageNet 的图片,结果部署到自己的工业场景后,检测精度掉得一塌糊涂,重新用现场数据量化就好了。
5.2 三核协同下的推理流水线:从 MIPI 到结果的几个并行阶段
整个系统的实时性取决于流水线设计。数据链路可以简单地分成四段:
- FPGA 采集并预处理图像;
- RK3588 的 MIPI 驱动把帧送到内存;
- RGA 把帧缩放并转换成 NPU 需要的格式;
- NPU 跑模型,把结果交给 CPU 处理业务逻辑。
如果把这四段串行执行,帧率会很低。更好的做法是用三缓冲或四缓冲机制,让 MIPI 的写指针、RGA 的处理指针和 NPU 的读指针各自向前滚动。也就是说,FPGA 在输出第 N+2 帧时,RGA 正在处理第 N+1 帧,而 NPU 刚好在推理第 N 帧。这样的流水线可以把端到端延迟控制在 2 帧以内,吞吐量却能接近单级处理的最快速度。
5.3 RKNN 接口的实际调用方式
在代码层面,RKNN 的推理接口主要就几个:rknn_init创建上下文,rknn_query查询输入或输出属性,rknn_inputs_set设置输入数据,rknn_run执行推理,rknn_outputs_get获取输出结果。
实际使用时,要特别注意输入的buf必须是 NPU 可访问的内存。最简单的方式是先把 RGA 处理完的 buffer 映射到连续物理内存,再传给rknn_inputs_set。如果直接用普通malloc的内存,NPU 读写时会因为缺页而出现间歇性卡顿,帧率不稳定,排查起来还特别恼火。
我把推理结果做后处理时,通常是在 CPU 线程里进行,比如解析 YOLOv8 输出的 84 维张量(4 个框坐标 + 80 个类别),做 NMS 去掉重叠框,再对检测结果做业务上的过滤。70 00 多个候选框里挑出几十个目标的 NMS 计算量并不大,CPU 完全可以轻松处理。
6. 实测中绕不开的坑和我的经验尺度
最后这部分,聊几个真正值得反复提醒的工程细节。这些细节如果没人告诉你,你可能要花一两周才能定位到根因。
6.1 帧同步与 FIFO 溢出:为什么偶尔会丢帧
FPGA 输出 MIPI 流是“只管发送”的,如果 RK3588 端没有及时消费前一帧,FPGA 里面的 FIFO 就会溢出。最直接的表现是画面每隔几秒卡顿一帧,CPU 使用率不高,内存也充足,但就是掉帧。
解决办法是两个方向同时做。一是让 FPGA 支持背压,也就是通过 I2C 寄存器通知 RK3588 端当前 FIFO 水位,如果水位过高就跳帧;二是把 RK3588 端的 V4L2 queue 设置成足够深的缓冲区,我一般设 8 个 buffer,比默认的 4 个稳妥得多。
6.2 不要在调试阶段过分相信“看起来正常的画面”
去马赛克和降噪这类算法模块,在测试图模式下看起来毫无问题,一换真实场景就会出现摩尔纹、伪彩或者边缘锯齿。原因是测试图是静态的、频率单一,而真实场景的纹理和噪点非常复杂。我的建议是准备三组测试素材:一是标准彩条图,验证链路时序;二是高细节图像,验证去马赛克和锐化效果;三是真实动态视频,验证整体流水线的实时性和稳定性。三组都通过,才敢提交给后端的 AI 团队。
6.3 板卡和固件版本要保持“快照”
这个项目里最影响效率的,是不同版本的板卡硬件、Ubuntu 根文件系统、RKNN Toolkit、FPGA bitfile 之间的组合太多了,经常因为版本不一致导致问题无法复现。我后来给项目建了一个“版本基线表”,把 FPGA 工程的 git commit ID、RKNN Toolkit 版本、Ubuntu 内核版本、分区表版本全部打成一个字段串,每次测试记录都写上这个字段串。排查问题时可以快速确定是哪一层发生了变化,而不是重新从设备树开始查。
7. 三核协同方案的扩展方向与我的维护心得
这个架构不只是一个项目的“一次性拼装”,它在一定程度上可以复用到很多类似需求上。如果你已经解决了 FPGA 到 RK3588 的 MIPI 通路,后面换不同型号的 sensor、换不同的 AI 模型,改动量其实都集中在比较小的范围内。
7.1 从图像检测扩展到视频结构化分析
当 FPGA 的预处理管线稳定之后,可以把 AI 分析从简单的目标检测扩展到视频结构化。比如在 FPGA 端划分兴趣区域,只在 ROI 内做去马赛克和裁剪输出,这样 RGA 和 NPU 的计算量都能进一步降低。如果业务需要识别人脸、车牌或者其他目标,RK3588 的 NPU 可以同时挂载多个模型,只要在调度层做好分时或分核分配即可。
7.2 让我最有收获的一点维护经验
如果要在经验层面只提炼一句话,我会说:三核协同的项目,最大的风险不在某一颗芯片上,而在“级间握手”上。MIPI 时序、中断上报、buffer 管理、NPU 输入内存,这些跨模块的接口才是问题的重灾区。我在后续维护时,会把大量精力放在把每个级间的握手协议先用简单版本跑通,再逐步完善功能,而不是一上来就并行开发 FPGA 全功能管线和 RKNN 推理流水线,否则一旦出问题,定位周期会非常长。
另外,我强烈建议在 FPGA 端保留一个彩条测试模式,在 RK3588 端保留一个纯 CPU 的 YUV 转 JPEG 打印工具。这样在系统联调时,所有团队都可以快速判断“这帧数据到底是格式错了、内容错了,还是颜色错了”,用最短链路把问题定位到具体的层级。这套方法论看着简单,但在超高清图像处理加 AI 分析的项目里,真的能省下大把时间。