ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA ISP图像处理流水线设计与Vivado实现要点

FPGA ISP图像处理流水线设计与Vivado实现要点 1. 动手之前ISP流水线的整体架构与设计思路FPGA图像处理这几年被越来越多的团队盯上原因很简单实时性、低延迟、可定制这三个词放到一起传统处理器还真的很难同时满足。你拿到一颗CMOS Sensor想让它输出一张能直接看的画面中间隔着黑电平校正、去马赛克、白平衡、Gamma、色彩校正、降噪等一系列操作这一整套链路就是ISPImage Signal Processor。如果你打算用FPGA自己搭一条ISP流水线而且在Xilinx Vivado环境下从零开始这篇内容应该能帮你少踩不少坑。先说清楚这篇博文适合谁你最好有Verilog或VHDL的基础知道什么是时序约束上手过Vivado的基本工程流程。如果你只是听说过“FPGA能做图像处理”但还没实际写过一行业务代码那本文也能帮你建立整体认知但从零到跑通的路径建议先拿一个简单的视频通路练手再回来啃ISP。整个过程假设你手头有一块带视频输入接口MIPI或LVDS的开发板以及一个显示输出接口HDMI或DP否则纯仿真也能验证部分模块但“上板看到图像”才是真正检验设计的方式。很多人一开始会犯一个错误把ISP当成“一堆算法模块的堆叠”以为把网上找的去马赛克代码、白平衡代码拼在一起就能出图。实际上FPGA上的ISP最难的不是算法本身而是数据流的时序组织。像素是一个一个进来的行与行之间有消隐帧与帧之间有间隔任何一个模块如果吞吐跟不上或者缓存没有对齐整条流水线就会花屏、卡顿、色彩全乱。所以我一贯的建议是先画数据流图再定模块边界最后才是写代码。1.1 一个“真”ISP流水线到底包含哪些环节搞清范围比动手更重要。标准的ISP链路按输入到输出的顺序大致包括这几段Sensor原始数据接收解析MIPI/LVDS时序恢复像素数据黑电平校正BLC减掉Sensor暗电流导致的固定偏置坏点校正DPC排除感光单元异常带来的亮点/暗点镜头阴影校正LSC补偿镜头边缘亮度下降去马赛克Demosaic把Bayer格式插值成RGB图白平衡AWB修正色温让白色在不同光源下都呈现白色色彩校正矩阵CCM把Sensor光谱响应校正到标准色彩空间Gamma校正调整亮度曲线适应显示设备的非线性降噪与锐化提升画质的可选环节色彩空间转换RGB转YCbCr等方便压缩、传输或显示一套完整的ISP实现还有一些自动控制层面的东西比如AE自动曝光、AWB算法闭环、AF自动对焦这些通常跑在ARM或软核上FPGA里做的是快速统计与反馈执行。对于从零搭建的实战项目我建议分两个阶段第一阶段先把固定参数的信号链打通也就是上面列出的主链路让画面正确且可看第二阶段再考虑闭环控制。1.2 为什么选择FPGA而不是ARM/DSP先说一句公道话如果你处理的图像分辨率是720p以下、帧率30fps以内用Cortex-A系列处理器加优化过的OpenCV库可能开发效率更高而且最终效果也不会差太多。但一旦需求变成1080p60fps甚至4K60fps每个像素的处理时间只有几个纳秒这时候你要么用专用ISP芯片要么用FPGA。FPGA的优势表现在三个维度第一是确定性延迟。处理器的软件流水线受操作系统调度、缓存命中率影响延迟抖动很大。FPGA的像素流水线一旦时序收敛每个像素的延迟是固定的这在工业检测、医疗内窥、机器人视觉这类场景很关键。第二是并行度。ISP里的很多操作是逐像素或逐窗口的天然适合硬件并行。你可以把去马赛克用的3x3窗口、降噪用的5x5窗口同时计算处理速度不受算法复杂度影响只受时钟频率和像素吞吐的约束。第三是接口灵活性。Sensor输出的MIPI、LVDS、Sub-LVDS信号格式五花八门显示器的HDMI、DP、eDP也各有时序FPGA的IO可配置性让你可以用一套芯片连接不同规格的输入输出。当然代价也很明显开发周期长、调试困难、算法迭代速度慢。所以你只有在确定产品有大批量、低延迟或接口特殊的需求时才真正需要FPGA方案。1.3 基于Xilinx Vivado的整体架构规划在Vivado环境下我习惯把整个工程拆成四层来看第一层是接口层。负责把Sensor数据接收进来把处理完的数据通过HDMI/LVDS发出去。Xilinx提供了MIPI CSI-2 RX、Video Timing Controller等IP核以及Vivado里面向视频应用的Video Pipeline参考设计。这些IP可以解决协议解析的问题但一定要搞清楚它们输出的像素格式和时序信号——比如pixel总线上的数据宽度、hsync/vsync/de信号的极性这些细节决定了下游模块怎么接。第二层是存储层。ISP中大量算法需要行缓存Line Buffer或帧缓存Frame Buffer。Xilinx的方案通常是用AXI4或AXI4-Stream接口把数据搬到DDR再通过Video Framebuffer Read/Write IP读取。如果你的分辨率不高、片上BRAM/URAM容量够也可以完全用片上存储实现帧延迟或几行的缓存省掉DDR带宽的压力。第三层是处理层。也就是ISP各算法模块这是你真正需要动手写RTL的地方。我建议每一个算法模块保持独立的时钟域和AXI-Stream接口模块之间通过简单的Valid/Ready握手传递数据。这样每个模块可以单独仿真、单独优化也能在后期插入或替换算法而不用动其他部分。第四层是控制层。包括寄存器配置、中断处理、I2C配置Sensor、以及运行AE/AWB等统计控制算法。如果只是验证ISP通路可以用Vivado自带的MicroBlaze软核或者直接通过JTAG用Vivado的VIO/ILA调试。如果未来要量产再考虑用Zynq的ARM核。这种分层方式的优势在于当某个模块出现问题你只需要盯住那一层不用在几百个信号里大海捞针。我见过不少团队是在一个顶层文件里把所有算法平铺直叙结果一个信号名改动就能让整个验证环境崩掉后续维护苦不堪言。1.4 像素时钟、行场消隐与带宽估算写代码前先把数学算清楚这比什么都重要。以1080p60fps为例实际显示区域是1920x1080但加上行消隐和帧消隐完整的像素时序一般是2200x1125。这意味着像素时钟大约是2200*1125*60 148.5MHz。这是标准HDMI的像素时钟你的处理逻辑必须在这个频率下稳定跑起来。带宽方面需要认真估算。如果Sensor输出的是RAW10格式也就是每个像素10bit那么一行1920像素就是19200bit一帧约2.4MB60fps就是约144MB/s。这个量级对DDR3/DDR4来说毫无压力。但如果你做多帧降噪或者需要把RAW图也存下来带宽会成倍增长。我的建议是列一张表把每个环节的数据率、存储需求算清楚再决定方案。我还特别提醒一点像素时钟与逻辑时钟未必相等。有些设计里Sensor接口是148.5MHz但内部算法模块用296MHz把像素拆成两个半像素并行处理再通过异步FIFO跨时钟域。这样做的原因是降低单个模块的时序压力。但跨时钟域处理一定要用异步FIFO并且要仔细检查FIFO的空满信号否则数据错位是迟早的事。2. 图像采集与预处理模块的实现2.1 Sensor输入接口与MIPI/LVDS数据接入CMOS Sensor的输出接口常见有MIPI CSI-2、LVDS、HiSPi、并行接口等。以最常见的MIPI CSI-2为例它的物理层是一对或几对差分信号线传输的是高速串行数据必须先经过接收端的PHY恢复出字节流再根据协议解包。在Xilinx平台上最省事的方法是使用Xilinx提供的MIPI CSI-2 RX IP核Vivado的IP Catalog里搜索MIPI CSI-2 RX。这个IP核处理了物理层接收和协议解析会输出AXI4-Stream格式的像素数据以及相关控制信号。你只需要在配置界面里选好通道数、数据类型RAW10/RAW12等、是否使用DPHY等选项就能把Sensor数据接进FPGA。我自己踩过的坑是raw数据在MIPI包里的字节序问题。有些Sensor输出RAW10时每个像素10bit四个像素打包成5个字节。如果IP核配置的像素格式与你Sensor datasheet不一致出来的图像会是斜条纹或整体偏移。排查方法很简单上板后抓一帧数据用ILA看几个像素的十六进制值再对照Bayer图案的排列规律通常很快能发现问题。如果你用的是老式的并行Sensor时序就简单得多基本的clock、hsync、vsync、data信号直接用IO输入然后打几拍同步。需要注意输入信号的时序约束要写对尤其是时钟和数据之间的建立保持关系建议参考Sensor的datasheet计算input delay。2.2 Bayer格式与坏点校正Sensor上每个像素只能感知一种颜色分量通过覆盖在感光单元上的滤色片实现常见的排列是RGGB、BGGR、GBRG等这就是Bayer格式。在预处理模块里你最先要做的事情是确定你手里的Sensor是哪种排列因为这直接决定后续去马赛克的做法。坏点dead pixel是Sensor制造和传输过程中的必然产物表现为固定位置的亮点或暗点。坏点校正的基本思路是检测当前像素与邻域像素的差异如果差异超过阈值就判定为坏点用邻域均值或中值替代。在FPGA里实现坏点检测我的做法是构建一个3x3窗口需要两行缓存计算中心像素与周围8个像素的差值。如果最大差值大于预设阈值则认为中心是坏点用非中心像素的中值替换。这里有个细节阈值不能设得太低否则正常的边缘细节会被误判为坏点造成画面模糊。一般阈值可以设置为Sensor输出动态范围的10%左右比如10bit数据就是100 LSB但具体还要结合Sensor噪点水平调整。坏点校正还有一个变种叫动态坏点校正用于处理受温度影响临时出现的坏点。但从工程角度看先用固定坏点表加阈值检测把通路跑通是最稳妥的策略。2.3 黑电平校正与镜头阴影校正的工程简化黑电平校正的作用是处理Sensor暗电流对信号的影响。即使完全没有光照Sensor也会输出一个不为零的基值这个基值随温度变化。简单做法是从每个像素中减去一个固定值更稳健的方案是检测Sensor光学黑区OB区的均值动态调整减去的数值。镜头阴影校正则复杂一些。因为镜头的光学特性画面边缘的亮度通常会低于中心需要对每个像素乘上一个增益系数。这个系数的分布形式可以用一个径向多项式描述。在FPGA实现时通常用一块RAM存储每个位置的增益系数或者用简化的简化多项式计算实时系数。不过我的建议是第一版工程中先把LSC略过直接设成增益为1。原因很简单它不影响通路是否能跑通而且做不好很容易引入奇怪的偏色噪点。先把主链路跑通、看到清晰图像再回来补LSC也不迟。这和软件开发的“先做垂直切片”思路是一样的。3. 核心ISP算法模块的FPGA实现3.1 去马赛克双线性插值与基于边缘的插值去马赛克是整个ISP里最有代表性的算法模块也是很多FPGA教材的经典案例。它的目标是从Bayer排列的像素恢复出每个像素位置上的RGB三个分量。最简单的实现方法是双线性插值。以RGGB排列为例当前像素是绿色时它的红色分量通过左右两个R像素平均得到蓝色分量通过上下两个B像素平均得到当前像素是红色时绿色分量取上下左右四个G像素平均蓝色分量取四个对角线B像素平均。这种方法实现简单只需两三行缓存和一个加法器树但画面边缘会出现明显的彩色锯齿假彩色。工程中更推荐的是基于边缘方向的插值。基本思路是先计算水平方向和垂直方向的梯度如果水平梯度更小就优先用水平邻域插值反之则用垂直邻域。以G通道恢复为例使用R/B像素位置周围的G像素根据梯度方向选择加权组合。在FPGA里实现边缘导向插值窗口从3x3变成5x5逻辑复杂度会高一些但图像质量提升非常明显。省流量的写法是用小RAM存两行像素加上当前行生成5x5窗口再用流水线方式并行计算水平和垂直梯度。只要把梯度计算和插值计算拆成不同级时钟频率依然能保持较高。值得一提的是Xilinx提供了Video Processing SubsystemIP核里面有现成的去马赛克、色彩空间转换等算法模块可以直接配置使用。如果你的目标是快速看到效果直接用IP核是一个选择。但从学习角度和技术积累来看自己写一遍去马赛克再和IP核对效果收获会更大。3.2 白平衡与色彩校正矩阵的系数处理白平衡的目的是让白色物体在任何光源下都呈现白色。工程上的做法是对RGB三个通道分别乘一个增益系数通常以G通道为基准。也就是说如果当前场景偏蓝就把B通道增益降低R通道增益升高。在FPGA里白平衡模块本质上是三路乘法器。需要留意的是增益系数的小数表示。我建议用定点数比如把1.0表示为2568bit小数位输入像素10bit输出动态范围会增大需要在乘法后做饱和截位。定点运算设计的“底线”是始终知道你的数据在哪个位置有小数点这个信息要写进开发文档否则后续模块很容易混淆数据范围。色彩校正矩阵则是3x3矩阵运算即R_out m00*R m01*G m02*BG和B同理。这个矩阵的作用是修正Sensor的光谱响应偏差让颜色更接近标准色彩空间。实现上需要9个系数和3个乘加器。这里最常出的问题是系数的动态范围很多CCM系数会大于1甚至包含负数数据位宽必须留有符号位和小数位否则输出会溢出或出现负值截断。为了调试方便我会在白平衡和CCM模块里设计一个“旁路开关”通过寄存器控制是否启用该模块。这样上板后第一件事就是关掉所有算法先验证RAW数据通路是否正常然后一个模块一个模块地打开定位问题要容易得多。3.3 Gamma校正查找表法还是硬件计算Gamma校正解决的是显示设备亮度响应非线性的问题。人眼对暗部亮度变化更敏感而显示器尤其是常见的LCD、OLED在低亮度区域的灰度表现并不呈线性关系所以需要对像素值做一次幂函数变换。标准做法是Y_out (Y_in/255)^(1/gamma) * 255一类形式。FPGA实现Gamma有两种常见选择第一种是查找表法把输入的所有可能取值10bit就是1024个点预先算出对应的输出值存到Block RAM里。运行时直接把像素值作为地址读取输出值。这种方法结构简单、时序好修改Gamma曲线只需要更新RAM内容非常适合工程部署。第二种是硬件实时计算用CORDIC或分段线性逼近实现幂函数。这在灵活性上有优势但会消耗更多逻辑资源而且时序收敛难度明显上升。对于大多数ISP项目我强烈建议用查找表法。即使是4K分辨率的流水线Gamma查表器的BRAM占用也只是几块而已性价比很高。有一个容易被忽略的细节Gamma校正通常放在色彩校正之后、色彩空间转换之前。如果顺序反了会导致色彩饱和度和对比度异常白平衡也容易偏差。3.4 降噪与锐化工程上的“克制”原则降噪和锐化在ISP里最容易“过度设计”。很多初学者喜欢堆一堆高级算法比如双边滤波、非局部均值、小波去噪然后发现FPGA资源爆炸时序跑不上100MHz最后不得不推翻重来。我的经验是在FPGA里空间域滤波是最稳妥的。对于RAW域和RGB域的轻量降噪一个3x3或5x5的高斯滤波就足够如果需要保边可以换用双边滤波的实现注意双边滤波的权重计算需要指数函数工程上通常用查表近似。锐化则推荐使用非锐化掩模Unsharp Mask的思路原图像减去模糊后的图像得到高频细节再把高频细节加回原图。在FPGA里实现就是一条并行路径原图经过延迟得到时序对齐的像素同时经过一个低通滤波器得到模糊图两者相减再乘以锐化增益最后与原图像加。这里最关键的是时序对齐三个支路处理同一像素的数据必须同步到达加法器否则图像边缘会出现错位重影。降噪和锐化还有一个隐藏的坑它们会相互放大噪声。如果先做降噪再做锐化要注意锐化增益不要太高如果先锐化后降噪图像会显得很脏。我个人的偏好是先轻度降噪再做边缘保护性去噪最后用很弱的锐化做输出。这也是很多成熟ISP的做法。4. 缓存架构与管线时序设计4.1 行缓存与窗口生成机制ISP里几乎所有空间滤波算法都需要用到邻域像素比如3x3去马赛克、3x3滤波、5x5降噪。FPGA没有随机访问整帧图像的能力最常用的做法是行缓存。行缓存本质上是Shift Register移位寄存器或RAM按行存储输入数据。以3x3窗口为例你需要两行完整行缓存再算上当前正在输入的行就能拼出三行数据。具体做法是像素串行输入先进入第0行缓存同时第0行缓存读出旧像素进入第1行缓存两行缓存输出与当前像素组成3x3矩阵。Xilinx提供了RAM-Based Shift RegisterIP核配置好深度和宽度就能生成行缓存。但在高分辨率场景行缓存会占用不少BRAM。以1080p宽度为例10bit像素值的行缓存深度为1920用一块18Kb BRAM勉强能存三行左右。5x5窗口则需要4条行缓存BRAM占用明显上升。窗口生成模块的时序设计有一个坑当行有效信号de到来时每个时钟周期只进来一个像素但3x3窗口的9个像素需要在同一拍输出。这意味着窗口的右侧像素来自当前输入其余像素来自行缓存的延迟输出必须处理好每一列的延迟差异。常见的做法是把行缓存输出再打几拍延迟对齐后统一输出。建议把所有窗口像素打上数据有效信号避免在消隐期间产生伪像素。4.2 帧缓存策略DDR还是片上RAM去马赛克、白平衡、Gamma这些模块都是逐像素流水线不需要整帧数据。但如果你要做时间域降噪、HDR合成、或者需要把图像转成某个协议发送就得有帧缓存。帧缓存有两种典型策略第一种是用DDR。通过Xilinx的Video Framebuffer Write和Video Framebuffer ReadIP核把AXI4-Stream视频流写入DDR再按需读出。这种方式灵活支持任意大小分辨率但需要经过AXI接口会产生几个像素时钟的延迟。同时如果DDR带宽被其他功能占用比如CPU运行Linux、存储图片可能出现带宽瓶颈。第二种是片上全部集成。用BRAM/URAM组成帧缓存。这种方法延迟低、不依赖外部存储但容量受限。比如1080p60fps的RGB888一帧是1920x1080x3字节约6MB而Zynq-7000系列的最大BRAM只有4.9MB左右一张图都存不下。所以片上帧缓存通常只用于很小的分辨率比如CIF352x288或VGA级别。从工程实践看如果只是搭建一条基础的ISP信号链帧缓存并非必需。很多算法模块只需要几行缓存就能完成。建议第一版设计先做“无帧缓存”的实时通路等验证完整链路效果再按需求加帧缓存。4.3 AXI总线的读写调度与帧同步当你真的需要把视频写入DDR时AXI4总线的效率问题就躲不开了。视频数据是高速连续流如果每次传输只写几个字节效率会非常低还容易占满总线。推荐的做法是使用AXI的Burst模式写入时尽可能攒够一行数据再发起突发写操作减少总线握手开销。在Xilinx平台时序层面处理视频帧同步也很重要。视频流有帧同步信号vsync和行同步信号hsync写入DDR或从DDR读出的模块必须保证帧号对齐。否则画面会出现撕裂tearing——上半帧是老一帧下半帧是新一帧。解决方法是采用双缓冲或者三缓冲机制。写入端永远写当前帧读出端永远读上一帧中间用帧ID和同步信号做防撕裂控制。帧同步问题的调试很折磨人因为它是间歇性出现的——带宽压力大的时候画面偶尔闪一下。我一度以为是Sensor时序不稳定最后才发现是读端和写端帧ID错位。调试时建议把帧计数器和行计数器引出到ILA出现撕裂瞬间抓下来一眼就能看出是哪里开始错位。5. 常见问题与调试技巧实录5.1 图像偏色、斜条纹、边缘锯齿的排查思路上板调试是ISP项目最漫长的阶段。我总结了一套排查顺序每次遇到图像异常都按这个思路查很少绕弯路先查数据通路是否正常。把算法全部旁路输出RAW数据灰度显示。如果画面是正常的灰度梯形图或Sensor测试图说明输入和输出通路是通的。如果出现斜条纹大概率是像素格式或字节序配置错误如果画面是整片绿色或品红色很可能是Bayer排列设置错了。再查时序信号。用ILA观察de信号与像素数据的对应关系。如果de的宽度和位置不对会导致一行像素偏移图像整体错位。还要检查场消隐/行消隐是否被意外裁剪可能导致缓存溢出或花屏。然后查色彩链路。打开白平衡和CCM后如果偏色先关掉CCM只看白平衡效果。若纯白背景出现中心偏色、四周偏色可能是镜头阴影问题而非色彩矩阵问题。把Gamma旁路掉对比开与关的效果判断是亮度曲线问题还是色度问题。边缘锯齿和假彩色的出现往往需要评估去马赛克算法的效果。如果画面边缘出现马赛克状伪影可以考虑把插值算法换成边缘导向版本或者检查去马赛克模块的窗口数据是否对齐——我见过有人把Bayer排列在配置里改错了位置导致绿色通道和红蓝通道错位。5.2 Vivado时序收敛与资源优化经验FPGA图像处理工程时序问题几乎无法避免尤其是148.5MHz的像素时钟跑到整条流水线上时。我的几条经验第一模块边界隔拍。在三个模块之间都插入一级寄存器打一拍虽然会引入几个像素时钟延迟但极大简化了跨模块时序分析。千万不要在模块之间做组合逻辑直连这在复杂工程中一定出问题。第二优化关键路径。乘法器、加法器链是时序瓶颈的重灾区。以CCM为例3x3矩阵乘加如果用组合逻辑一次完成路径延迟可能超过时钟周期。解决方法是插入多级流水线把算R、G、B的三个乘加分成两到三级。代价是额外几个周期的延迟换来的稳定时序是非常划算的。第三合理使用DSP48。Xilinx FPGA中有大量DSP Slice乘法操作应尽量映射到DSP48E上而不是用LUT拼乘法器。Vivado综合时通常能自动映射但如果你用了大量动态移位或带符号乘法建议手动指定综合属性或者直接例化DSP IP核。资源优化方面行缓存尽量用RAM-Based Shift Register而不是普通寄存器链查找表类算法优先用BRAM系数缓存放分布式RAM或BRAM都可以关键看访问频率。另外像素总线位宽和信息编排也要统一建议全局定义参数文件Package / Macro避免不同模块里位宽大小写不一致而浪费资源。5.3 仿真与上板验证的配合方法我对仿真的态度一直是模块级仿真必须做但系统级仿真不要过度迷恋。原因很简单一个完整ISP通路的系统级仿真需要你写一个Bayer测试图生成器、一个像素时钟、模拟行场消隐再把输出写成图片这本身就是一个不小的软件工程。而且仿真跑100帧的时间和实用性都很差通常调试效率不如上板抓ILA快。我的工作流程是每个算法模块单独做UVM或简单testbench仿真输入构造好的梯度图、棋盘图、纯色图验证输出数据是否符合预期。模块级仿真通过后直接上板用ILA观测关键节点的数据。上板发现的问题优先在模块级复现这样既保证效率又能精确定位逻辑错误。仿真中有一个值得注意的细节Bayer测试图的生成最好不是简单的插值图片而是严格按照目标Sensor的Bayer排列生成。否则你验证好的去马赛克算法上板后可能因为排列错位而完全无法工作。另外调试图像用的输出图片格式建议直接保存为PPM或PGM格式这样用Python脚本或者ImageJ打开都很方便。如果能把上板采集的数据导出为文本再转成图像调试流程会顺畅很多。5.4 关于定点精度那些被忽略的“小数位”很多初学者在纯FPGA图像处理里容易忽略定点数的精度控制。软件里用float算一下很简单但FPGA里浮点代价太高绝大多数ISP模块都用定点数。这里最关键的是要一致性。举例来说白平衡增益如果是4.8倍你不能简单乘5否则整体亮度偏高。把增益量化成定点数时比如乘数位宽32bit小数位为16bit那么实际乘法后要右移16位并且注意饱和。如果后面的模块用的是不同的定点格式数据范围就会错乱最终画面要么发暗要么过曝。我在几个模块之间统一用“整数部分12bit 小数部分16bitQ12.16”的定点格式处理乘法结果像素数据本身保持10bit或12bit。模块边界做截位时必须明确舍入方式——推荐用四舍五入或者无偏舍入而不是直接截断。否则在同一帧图像上偏差会累积出可观察到的带状条纹banding。还有一个常见问题是除法。FPGA里除法器资源昂贵通常用乘法加查表实现边界处理比如计算权重时用倒数查表替代除法。所有固定的除法尽量在系数计算阶段离线算好硬件里只做乘法。5.5 抓bug的实战案例一场偏色问题排查记录最后分享一次让我印象深刻的调试经历。某次上板后图像画面能正常显示但整体有一种说不出的偏绿。按照前面的排查思路我先把白平衡和CCM全部旁路图像仍然是偏绿的。于是我开始怀疑Bayer排列配置错了。后来用ILA抓了原始RAW数据把一个3x3窗口的像素值打出来对照Sensor datasheet说明的Bayer图案我那块Sensor是BGGR发现数据流的排列是正确的。那问题出在哪最后查出问题在MIPI CSI-2 RX IP核的配置上。这个IP核支持AXI4-Stream输出像素数据同时可以输出一个数据“用户自定义信号”TUSER其中可以携带帧同步和行同步信息。我配置时选错了行同步信号的位置导致后续模块以为每行的起始位置比实际提前了几个像素整行错位而这种错位在自然图像上看起来就很像偏色加上图像颜色统计自然失衡。这个问题的排查花了我大半天。但教训很有价值IP核的用户自定义信号配置一定要逐个字段核对尤其是它是否输出frame_start、line_start等信息以及这些信号与像素数据的时序关系。IP核的输出时序有时候比算法本身更容易致命。另一个心得是遇到奇怪的图像问题先截原始数据不要急着改算法。当作“图像是出错的证据”用ILA和数据分析去找差异而不是凭感觉猜。这一原则适用于所有FPGA图像处理项目。我现在搭ISP流水线时仍然会先写一个简单的“旁路模式”把Sensor的RAW数据直接灰度输出到显示器上确认采集端OK再逐步打开各处理模块。这个旁路开关就像软件里的“占位桩”会把调试时间至少缩短一半。建议你在设计顶层时就把各模块的旁路寄存器预留好后续会感谢自己这个决定。
返回列表