2、图像处理概述:数字图像在FPGA中的表示与存储格式

各位同学,咱们今天聊聊数字图像在FPGA里到底长什么样。说白了,图像就是一串数字,但怎么组织这些数字,直接决定了你的处理速度和质量。我刚开始用FPGA做图像处理时,就吃过不少亏——比如把像素顺序搞反了,结果整幅图像显示出来是镜像的。嗯,这种坑踩过一次就记住了。

2.1 数字图像的基本概念

一张数字图像,本质上就是一个二维数组。每个位置上的数值,代表该点的亮度或颜色信息。我们管这个最小单元叫像素(Pixel)。

举个例子,一张640×480的灰度图,就是640列、480行,总共307200个像素。每个像素用8位二进制数表示,取值范围0~255。0是纯黑,255是纯白,中间是不同程度的灰。

彩色图像呢?最常见的是RGB格式——每个像素由红、绿、蓝三个分量组成。每个分量通常也是8位,所以一个像素就是24位。你想想看,一张1920×1080的彩色图,光像素数据就有1920×1080×3 ≈ 6.2MB。这还只是一帧。

核心要点:FPGA处理图像时,我们面对的不是“图片”,而是连续不断的像素数据流。每一拍时钟,进来一个像素,出去一个结果。这种流式处理,正是FPGA的强项。

2.2 图像在FPGA中的存储格式

FPGA里没有硬盘,也没有文件系统。图像数据要么存在Block RAM(BRAM)里,要么存在外部DDR SDRAM里。怎么存?这就有讲究了。

2.2.1 逐行扫描存储(Raster Scan)

这是最自然的存储方式。从左上角第一个像素开始,从左到右存完第一行,再存第二行,以此类推。说白了,就是按显示顺序存。

我在项目中遇到过一个问题:摄像头输出的数据就是逐行扫描的,但我要做3×3的卷积滤波。如果只存一行数据,根本凑不齐3行。怎么办?得用行缓存(Line Buffer)。

// 伪代码:3行缓存结构
reg [7:0] line_buf_0 [0:IMG_WIDTH-1];  // 第0行
reg [7:0] line_buf_1 [0:IMG_WIDTH-1];  // 第1行
reg [7:0] line_buf_2 [0:IMG_WIDTH-1];  // 第2行

// 每个时钟周期,数据依次移位
line_buf_2 <= line_buf_1;
line_buf_1 <= line_buf_0;
line_buf_0 <= pixel_in;

这样,任意时刻我都能拿到3行、3列的9个像素,构成一个3×3窗口。嗯,这里要注意:行缓存的深度必须等于图像宽度,否则会串行。

2.2.2 窗口缓存(Window Buffer)

有了行缓存,再配合几个寄存器,就能构建出窗口缓存。窗口大小取决于你的算法——3×3、5×5、甚至7×7。

我个人习惯把窗口缓存做成一个二维寄存器阵列。比如3×3窗口:

reg [7:0] window [0:2][0:2];  // 3行3列

// 每个时钟更新窗口内容
// 新像素从右侧挤入,最左侧像素被丢弃
window[0][2] <= pixel_in;      // 新像素进入第0行最右
window[0][1] <= window[0][2];  // 右移
window[0][0] <= window[0][1];  // 右移
// 第1行、第2行类似,但数据来自行缓存

小技巧:如果你用Xilinx的Vivado,可以直接调用FIFO或Shift Register IP核来实现行缓存。比自己写Verilog省事得多,而且资源利用率更高。

2.3 色彩空间的表示

FPGA里处理彩色图像,最常用的是RGB和YCbCr两种格式。

色彩空间 分量 位宽 特点
RGB R, G, B 各8位,共24位 直接来自摄像头传感器,适合显示
YCbCr 4:4:4 Y, Cb, Cr 各8位,共24位 亮度与色度分离,适合压缩和边缘检测
YCbCr 4:2:2 Y, Cb, Cr Y 8位,Cb/Cr共享8位 节省带宽,常用于视频传输

为什么FPGA里常用YCbCr?因为很多图像处理算法只关心亮度(Y分量),比如边缘检测、直方图均衡。你想想看,如果直接用RGB,你得同时处理三个通道,计算量翻三倍。而转成YCbCr后,只处理Y通道就够了。

我曾经做过一个项目,要在FPGA里做实时人脸检测。一开始直接用RGB做,结果逻辑资源不够用。后来改成YCbCr,只处理Y分量,资源直接省了60%。

注意:RGB转YCbCr需要乘法器和加法器。虽然公式是固定的,但如果你用浮点运算,资源消耗会很大。我建议用定点数近似,比如把系数放大256倍,最后右移8位。这样只用整数乘加,一个时钟周期就能出结果。

2.4 图像数据在DDR中的组织方式

当图像分辨率超过1080p时,BRAM就不够用了。这时候得用外部DDR。但DDR的访问方式和BRAM完全不同——它有行激活、列选通、预充电这些乱七八糟的时序。

为了高效利用DDR带宽,我们通常把图像数据按Burst(突发)方式存取。一次突发传输可以连续读写多个像素,而不是一个像素一个像素地读。

举个例子,DDR3一次突发长度是8。如果每个像素24位(3字节),一次突发可以读8×3=24字节,也就是8个像素。这样效率最高。

// 地址映射示例:假设图像宽度1920,每像素3字节
// 第y行、第x列的像素地址 = 基地址 + (y * 1920 + x) * 3
// 但为了对齐DDR的Burst边界,通常按行对齐到64字节

嗯,这里有个坑:如果你不按行对齐,跨行读取时会浪费带宽。我曾经调试一个1080p60的显示系统,图像老是撕裂。查了两天才发现,是DDR地址没对齐,导致读一行数据要花两倍的时间。

2.5 知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以看到,从像素到行缓存,再到窗口缓存,最后到DDR存储,每一步都有对应的硬件结构。

数字图像在FPGA中的表示与存储 像素 (Pixel) 8位灰度 / 24位RGB 行缓存 (Line Buffer) 深度 = 图像宽度 窗口缓存 3×3 / 5×5 阵列 DDR SDRAM Burst对齐 色彩空间转换 RGB ↔ YCbCr 逐行扫描 / 窗口 核心思路:将二维图像转化为一维数据流,用流水线结构逐级处理 行缓存 + 窗口缓存 = 任意邻域像素的并行访问

这张图其实就说明了一件事:FPGA处理图像,本质上就是把二维的像素矩阵,变成一维的数据流,然后用流水线的方式逐级处理。行缓存和窗口缓存是核心结构,它们让你能在任意时刻拿到邻域像素,这是实现卷积、滤波、边缘检测等算法的基础。

总结一下:

  • 图像在FPGA里是流式数据,不是完整的帧
  • 行缓存是构建窗口的基础,深度等于图像宽度
  • YCbCr比RGB更适合做算法处理,省资源
  • DDR存储要注意Burst对齐,否则带宽浪费严重

好了,这一章的内容就到这里。记住这些基础概念,后面讲流水线设计时,你会发现自己已经站在了坚实的地基上。

可可精品资料库