图像处理基础:像素与色彩空间、分辨率与帧率
各位同学,欢迎来到第二章。这一章我们不讲代码,也不谈时序。咱们先把图像处理里最基础的概念理清楚。你想想看,FPGA 处理图像,说白了就是处理一堆数字。那这堆数字从哪来?长什么样?怎么在芯片里跑?这就是今天要聊的事。
我个人习惯,在开始任何加速设计之前,先把“数据长什么样”画在纸上。这一步省了,后面大概率要返工。
2.1 像素与色彩空间
图像的最小单位是像素。每个像素就是一个采样点,记录着该位置的颜色信息。那颜色怎么用数字表示?这就引出了色彩空间。
2.1.1 RGB 色彩空间
最常见的是 RGB。红、绿、蓝三个分量,每个分量通常用 8 bit 表示,范围 0~255。一个像素就是 24 bit。显示器、摄像头传感器,基本都是这个路子。
| 色彩空间 | 分量 | 位宽(常见) | 典型用途 |
|---|---|---|---|
| RGB | R, G, B | 8+8+8 = 24 bit | 显示、采集 |
| YUV/YCbCr | Y, U, V | 8+8+8 = 24 bit | 视频压缩、传输 |
| 灰度 | Y | 8 bit | 边缘检测、二值化 |
2.1.2 色彩空间转换
摄像头出来通常是 RGB,但很多算法(比如边缘检测)只关心亮度。那就得转成灰度或 YUV。公式其实不复杂:
Y = 0.299 * R + 0.587 * G + 0.114 * B
U = -0.147 * R - 0.289 * G + 0.436 * B
V = 0.615 * R - 0.515 * G - 0.100 * B
在 FPGA 里做这个,浮点乘法太奢侈。我一般用定点数加移位来实现。比如乘以 0.299,可以近似成 (R * 77) >> 8。精度够用,资源省一大截。
2.2 图像分辨率与帧率
这两个参数直接决定了你的 FPGA 设计要处理多少数据。咱们算一笔账。
2.2.1 分辨率
分辨率就是图像宽 x 高。比如 1920x1080,总共 2,073,600 个像素。每个像素 24 bit,一帧就是 49.76 Mbit,约 6.22 MB。
2.2.2 帧率
帧率是每秒显示多少帧。常见的 60 fps,意味着每秒要处理 60 帧数据。
那总数据带宽是多少?
带宽 = 分辨率 × 每像素位数 × 帧率
= 1920 × 1080 × 24 × 60
= 2,985,984,000 bit/s
≈ 2.98 Gbit/s
3 Gbps 的原始数据流。这还没算消隐区。你想想看,FPGA 要在这么高的速率下完成滤波、边缘检测、目标跟踪,对架构设计的要求有多高。
| 分辨率 | 像素/帧 | @30 fps 带宽 | @60 fps 带宽 |
|---|---|---|---|
| 640x480 (VGA) | 307,200 | 221 Mbps | 442 Mbps |
| 1280x720 (HD) | 921,600 | 663 Mbps | 1.33 Gbps |
| 1920x1080 (FHD) | 2,073,600 | 1.49 Gbps | 2.98 Gbps |
| 3840x2160 (4K) | 8,294,400 | 5.97 Gbps | 11.94 Gbps |
2.3 数字图像在 FPGA 中的表示
图像在 FPGA 里就是一堆寄存器、BRAM 或者 DDR 里的数据。但怎么组织这些数据,决定了你的处理效率。
2.3.1 像素流
最常见的表示方式是像素流。每个时钟周期进来一个像素,按行扫描顺序排列。比如 1920x1080 的图像,就是先传第 0 行 1920 个像素,再传第 1 行,以此类推。
在 Verilog 里,我习惯这样定义:
// 像素数据接口
input wire clk; // 像素时钟
input wire vsync; // 帧同步
input wire hsync; // 行同步
input wire de; // 数据有效
input wire [23:0] pixel_data; // RGB 888
de 信号是关键。只有 de 为高时,pixel_data 才是有效像素。vsync 和 hsync 用来对齐帧和行的边界。
2.3.2 数据位宽的选择
每个像素用多少 bit,直接影响 BRAM 和 DSP 的资源消耗。我做过一个项目,客户要求 10 bit 色深,结果 BRAM 用量比 8 bit 多了 25%。后来跟客户沟通,确认最终输出设备只支持 8 bit,果断砍回 8 bit。
2.4 行缓存与帧缓存概念
这是 FPGA 图像处理里最核心的存储结构。很多算法(比如 3x3 滤波、中值滤波)都需要同时访问相邻像素。那怎么在串行的像素流里拿到上下行的数据?答案就是行缓存和帧缓存。
2.4.1 行缓存(Line Buffer)
行缓存就是存一行像素的 FIFO 或 BRAM。比如你要做 3x3 的 Sobel 滤波,需要同时知道当前像素、上一行同列像素、下一行同列像素。那你就得缓存两行数据。
结构很简单:
- Line Buffer 0:存第 N-1 行
- Line Buffer 1:存第 N 行
- 当前输入:第 N+1 行
每来一个新像素,三个行缓存同时输出对应列的数据,组成 3x3 窗口。
2.4.2 帧缓存(Frame Buffer)
帧缓存就是存一帧完整图像。通常用外部 DDR 实现。什么时候需要帧缓存?
- 需要跨帧处理(比如运动检测、背景建模)
- 需要随机访问任意像素(比如缩放、旋转)
- 需要帧率转换(比如输入 60 fps,输出 30 fps)
但帧缓存的代价很高。DDR 的读写带宽有限,而且有延迟。我一般的原则是:能用行缓存解决的,绝不用帧缓存。
2.4.3 行缓存 vs 帧缓存对比
| 特性 | 行缓存 | 帧缓存 |
|---|---|---|
| 存储介质 | BRAM / FIFO | DDR / SRAM |
| 容量 | 几 KB ~ 几十 KB | 几 MB ~ 几十 MB |
| 延迟 | 几个时钟周期 | 几十 ~ 几百个时钟周期 |
| 适用场景 | 窗口滤波、边缘检测 | 运动估计、帧率转换 |
| 资源消耗 | 低 | 高(需要 DDR 控制器) |
2.5 本章知识体系
下面这张图把本章的核心概念串起来了。你可以看到,从像素到色彩空间,再到分辨率和帧率,最终落到 FPGA 里的行缓存和帧缓存。每一步都环环相扣。