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的邻域数据。
注意:行缓存需要消耗BRAM资源。分辨率越高,行缓存越大。我曾经在1920x1080的项目里,一个行缓存就吃掉了几十个BRAM。设计前一定要算好资源预算。
4.4 实际应用中的选择
什么时候用行缓存?什么时候用帧缓存?我总结了一个表格:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 3x3滤波(Sobel、高斯) | 行缓存 | 只需要几行数据,BRAM足够 |
| 5x5或更大滤波核 | 行缓存 | BRAM够用的话,行缓存延迟更低 |
| 全局处理(直方图均衡) | 帧缓存 | 需要访问整帧数据 |
| 视频缩放、旋转 | 帧缓存 | 需要随机访问任意像素 |
我的建议:能用行缓存就别用帧缓存。行缓存延迟只有几个时钟周期,帧缓存动辄几十个周期。而且帧缓存要接DDR控制器,设计复杂度高不少。
4.5 避坑指南
最后分享几个我踩过的坑:
- 行缓存初始化:图像刚开始时,行缓存里是空的。前几行数据要特殊处理,比如补零或者复制边界。我一开始没注意,结果图像边缘出现了奇怪的条纹。
- 数据对齐:RGB转Gray时,三个分量的位宽要一致。我曾经混用了8位和10位的数据,结果灰度值算出来全是错的。
- 时序收敛:行缓存用BRAM实现,BRAM的读延迟是固定的。如果你的滤波逻辑太复杂,可能跑不到目标频率。我一般会在行缓存输出后加一级流水线寄存器。
好了,这一章的内容就到这里。图像在FPGA里的表示,说白了就是搞清楚数据怎么流、怎么存、怎么对齐。下一章我们会用这些知识来实现一个真正的Sobel边缘检测器。