4、图像基础与数字图像处理:图像在FPGA中的表示

各位同学,今天我们聊聊图像在FPGA里到底长什么样。说实话,很多初学者一上来就写滤波算法,结果发现数据流根本对不上——问题就出在没搞懂图像在硬件里的表示方式。

4.1 像素、分辨率与灰度——FPGA眼中的图像

图像在FPGA里,说白了就是一串串数字流。没有颜色,没有形状,只有0和1。

像素是图像的最小单元。每个像素对应一个数值,这个数值代表亮度。比如8位灰度图,0是纯黑,255是纯白,中间是各种灰。

分辨率就是图像有多少行、多少列。比如640x480,就是480行,每行640个像素。FPGA处理时,你得知道什么时候换行,什么时候换帧。

我刚开始做项目时,犯过一个低级错误:把分辨率搞反了。结果图像显示出来是斜的,查了两天才发现是行场同步信号对不上。嗯,从那以后我再也不敢小看分辨率参数了。

核心概念:

  • 像素:图像的最小数据单元
  • 分辨率:图像的行数×列数
  • 灰度:每个像素的亮度值(0~255)

4.2 色彩空间——RGB转Gray

彩色图像在FPGA里通常用RGB格式。每个像素三个分量:R(红)、G(绿)、B(蓝)。每个分量8位,一个像素就是24位。

但很多算法只需要亮度信息,比如边缘检测。这时候就要把RGB转成灰度图。

转换公式很简单:

Gray = 0.299 * R + 0.587 * G + 0.114 * B

为什么权重不一样?因为人眼对绿色最敏感,对蓝色最不敏感。这个公式是ITU-R BT.601标准里规定的。

在FPGA里实现时,我建议用整数运算,避免浮点。比如:

Gray = (77 * R + 150 * G + 29 * B) >> 8

这样只需要乘法和移位,资源消耗小很多。我在项目中用过这个方案,跑在150MHz完全没问题。

个人经验:如果你用Xilinx的IP核,可以直接用RGB2Gray的IP。但自己写的话,注意数据位宽。我习惯把中间结果扩展到16位,防止溢出。

4.3 行缓存与帧缓存——FPGA的“记忆”

图像处理算法往往需要同时访问多个像素。比如3x3的滤波核,需要当前像素和它周围的8个邻居。但FPGA没有随机访问内存的能力——数据是一行一行流进来的。

怎么办?用缓存。

行缓存:存几行数据。比如3x3滤波,需要3行数据同时可用。那就用3个行缓存,每个缓存存一整行。

帧缓存:存整帧图像。一般用外部DDR3/DDR4实现。帧缓存的好处是可以随机访问任意像素,但延迟大、带宽有限。

我画了一张图,帮你理解行缓存的工作原理:

行缓存工作原理(3x3滤波示例) 像素数据流 行缓存 1(第N-1行) 行缓存 2(第N行) 行缓存 3(第N+1行) 3x3 窗口 输出滤波结果 每个行缓存存储一整行像素数据 3个行缓存同时输出3行数据,形成3x3窗口

你看,数据从左往右流。每来一个新像素,行缓存就更新一次。三个行缓存同时输出,就得到了3x3的邻域数据。

注意:行缓存需要消耗BRAM资源。分辨率越高,行缓存越大。我曾经在1920x1080的项目里,一个行缓存就吃掉了几十个BRAM。设计前一定要算好资源预算。

4.4 实际应用中的选择

什么时候用行缓存?什么时候用帧缓存?我总结了一个表格:

场景 推荐方案 原因
3x3滤波(Sobel、高斯) 行缓存 只需要几行数据,BRAM足够
5x5或更大滤波核 行缓存 BRAM够用的话,行缓存延迟更低
全局处理(直方图均衡) 帧缓存 需要访问整帧数据
视频缩放、旋转 帧缓存 需要随机访问任意像素

我的建议:能用行缓存就别用帧缓存。行缓存延迟只有几个时钟周期,帧缓存动辄几十个周期。而且帧缓存要接DDR控制器,设计复杂度高不少。

4.5 避坑指南

最后分享几个我踩过的坑:

  • 行缓存初始化:图像刚开始时,行缓存里是空的。前几行数据要特殊处理,比如补零或者复制边界。我一开始没注意,结果图像边缘出现了奇怪的条纹。
  • 数据对齐:RGB转Gray时,三个分量的位宽要一致。我曾经混用了8位和10位的数据,结果灰度值算出来全是错的。
  • 时序收敛:行缓存用BRAM实现,BRAM的读延迟是固定的。如果你的滤波逻辑太复杂,可能跑不到目标频率。我一般会在行缓存输出后加一级流水线寄存器。

好了,这一章的内容就到这里。图像在FPGA里的表示,说白了就是搞清楚数据怎么流、怎么存、怎么对齐。下一章我们会用这些知识来实现一个真正的Sobel边缘检测器。

可可精品资料库