摘要:拍得到和传得回是两回事——下行带宽永远不够。这篇讲判决逻辑为什么必须放进 FPGA、行流水线怎么搭、资源账怎么算,以及三个必踩的坑:跨时钟域、DMA 溢出、位宽截断。
---
0. 先说结论
在轨图像处理的约束不是算力,是数据一旦进了内存,带宽和时间就已经花掉了。
所以判决必须前移:在像素流还在走的时候,就把"留/丢"决定掉。
行流水线的判决延迟 = 判决涉及的行数 × 行周期。这个数字通常比"存一帧再处理"小两三个数量级——这就是为什么星上筛选基本都做在 FPGA 里。
1. 先把账算出来:为什么必须丢
以一台 4096 × 3072、12 bit 的相机为例,单帧原始数据量:
4096 × 3072 × 12 bit / 8 ≈ 18.9 MB
按 10 fps 输出,就是约 189 MB/s。而常见的星地下行链路(S/X 波段)在几十到几百 Mbps 量级,换算下来每秒只能传几 MB 到十几 MB。
也就是说:能传下去的,通常不到能拍到的百分之一。
这个比例决定了系统设计的顺序——先设计"丢什么",再设计"存什么"。反过来做,就会得到一个"能拍很多、传不回去"的系统。
2. 为什么判决必须在 FPGA 上
| 方案 | 判决延迟 | 资源占用 | 能做的算法 |
|---|---|---|---|
| 存整帧 → CPU 判 | 一帧时间 + 搬运时间 | 大(整帧缓存) | 任意复杂 |
| 存整帧 → RT 判 | 一帧时间 | 中 | 中等 |
| FPGA 行流水线 | 几行时间 | 小(行缓存) | 简单但足够 |
关键认知:丢帧判决本身通常不需要复杂算法。
云覆盖、曝光饱和、星点数量、梯度方差——这些全是逐像素/逐行的局部统计,FPGA 做正合适。而真正复杂的算法(目标识别、场景理解)本来也不该在星上做:星上算力有限、功耗受限、算法更新周期长。
把"简单的判决"放在最前面,把"复杂的处理"放在地面,是这类系统唯一能跑通的架构。
3. 判据怎么设计
四个常用判据可以叠加使用:
| 判据 | 计算方式 | 拦掉什么 | FPGA 友好度 |
|---|---|---|---|
| 饱和度 | 统计 ≥ 阈值的像素占比 | 过曝帧 | 高(逐像素比较) |
| 云覆盖 | 亮度均值 + 方差 | 全云/全黑帧 | 高(滑动累加) |
| 清晰度 | 梯度绝对值之和 | 运动模糊、失焦 | 中(需行缓存) |
| 星点/目标数 | 局部极大值计数 | 导航/定位帧优先 | 低(需二维窗口) |
实用的做法是两级串联:
- 一级粗筛:饱和度 + 云覆盖,一行就能判,不需要缓存。
- 二级细筛:清晰度,需要 2–3 行缓存,但能拦住"看得见但没用"的模糊帧。
绝大多数废帧在第一级就被拦掉,二级的压力就小了。这个结构的好处是:一级的阈值可以设得很宽松(宁可放过),把精度压力留给二级。
4. 行流水线怎么搭
核心结构只有三段:
像素流 → [行统计] → [判决] → [写FIFO / 丢弃] ↑ 阈值(可配置)
三个要点:
(1)统计和判决分开。统计用流水线累加器,判决用比较器,两者都不阻塞像素流。任何一个环节插入"等待",整条流水线的吞吐就掉了。
(2)判决结果延迟几行生效。这意味着一帧的开头几行总是先写进缓冲,判完再决定要不要保留。所需缓冲深度 = 判决行数 × 行宽 × 位宽。
(3)丢弃要彻底。判决为丢,FIFO 直接不写。让数据"先进来再删"等于没省——带宽已经花掉了。
一行统计的伪代码(对应 FPGA 上的并行实现):
for each pixel p in line: sum += p sumsq += p*p sat += (p >= SAT_THRESH) ? 1 : 0 grad += abs(p - p_prev) // 需 1 像素延迟 after line: mean = sum / W var = sumsq / W - mean*mean if (sat > SAT_MAX) or (var < VAR_MIN): drop_frame()
两个静默错误的来源:
sumsq的位宽。4352 个 12 bit 像素、每个平方后 24 bit,累加需要 24 + 13 = 37 bit。位宽给少了会溢出,而且溢出后结果看起来"还算正常",最难查。- 除法。除以 W 用移位(W 取 2 的幂),不要真的例化一个除法器——既费资源又拖时序。
5. 资源账:先算再写
以中端 Kintex-7 级别 FPGA 为例,资源大头通常是这两项:
行缓存(BRAM):
BRAM 位数 = 判决行数 × 行宽 × 位宽 = 3 × 4096 × 12 bit ≈ 147 Kbit ≈ 4 块 36Kb BRAM
统计乘法(DSP):逐像素平方是一路 DSP,梯度是减法 + 绝对值(LUT 即可)。流水线并行度决定 DSP 数量,通常一行 2–4 个就够。
| 资源 | 用途 | 估算方法 | 余量要求 |
|---|---|---|---|
| LUT | 统计 + 判决 + 接口 | 综合后看报告 | ≥ 30% |
| DSP | 乘法(平方、梯度) | 并行度 × 2 | ≥ 30% |
| BRAM | 行缓存 + FIFO | 行数 × 行宽 × 位宽 | ≥ 20% |
| 时钟 | 像素时钟 / 处理时钟 | 需跨时钟域 | — |
经验:BRAM 经常比算法本身更吃资源。先算行缓存,再谈算法复杂度。
6. 三个必踩的坑
坑一:跨时钟域。
像素时钟来自相机接口,处理时钟来自板载晶振,两者不同源。直接跨域采样会偶发丢像素——频率越低越难复现,因为它依赖两个时钟的相位关系。
必须用异步 FIFO 或握手同步。这条没有捷径。
坑二:DMA FIFO 溢出。
判决放行了太多帧,主机侧读得慢,FIFO 一满就静默丢数据,现象看起来像"相机丢帧"。
处理办法:要么在 FPGA 侧限流(按最坏下行速率放行),要么把 FIFO 的满标志接进判决逻辑——满了就强制丢弃当前帧,并打一个计数器。这个计数器是排错的关键,没有它你永远不知道数据是在哪一级丢的。
坑三:位宽截断。
12 bit 像素做 8 bit 显示/存储时直接取高 8 位,暗部细节全丢;更麻烦的是统计阈值按 12 bit 定,截断之后再比就全错。
位宽必须在链路上全程标注清楚:哪一段是 12 bit 原始、哪一段是 8 bit 显示、统计用的是哪一段。这一条写进设计文档,比写在脑子里强。
7. 边界
FPGA 适合"确定性强、局部性强、延迟敏感"的判断,不适合"需要全局信息、需要迭代"的算法。
判断标准一句话:这个判决能不能只靠当前行 + 前 N 行做出来?
能,就放 FPGA;不能,就老老实实下传,或者先做降采样再判。
把目标识别硬塞进 FPGA,通常换来的是资源爆掉、精度下降、迭代周期以月计——这三个代价,没有一个能在项目后期补回来。
8. 参考资料
- NI:FlexRIO 与 LabVIEW FPGA 的流水线设计
- NI:FPGA 跨时钟域与异步 FIFO 的使用
- CCSDS 图像数据压缩标准(星上数据处理的接口约定)
---
附:5 分钟自测,先回答这 5 个问题
- 判决是在像素流入内存之前做的,还是存完再判?
- 判决要几行缓存?BRAM 按"行数 × 行宽 × 位宽"算过吗?
sumsq的位宽按最坏情况(满像素、满行宽)算过吗?- DMA FIFO 的满标志接进判决逻辑了吗?有丢帧计数器吗?
- 统计用的位宽和存储用的位宽,在链路图上看得到吗?
第 3 条和第 4 条是"静默错误":不报错、不崩溃,只是结果悄悄不对,或者数据悄悄少了。
---
拍得到和传得回是两回事。判决前移省下的带宽,通常比优化压缩算法更直接——而且它对算法精度没有要求。