文章目录一、项目目标与整体思路二、Block Design 中各模块的作用三、为什么一开始必须先统一位宽和格式四、显示链路调通后为什么还要保留 VDMA五、从稳定显示到叠加帧差算法的过渡六、当前项目已经走到哪一步七、这次搭建视频链路最大的经验做基于 FPGA 的机器视觉项目时很多同学一上来就急着上算法但实际上真正决定项目能不能做下去的往往不是算法本身而是最基础的视频链路能不能先跑通。对我这次 ZYNQ 项目来说也是一样。 我这次做的是一个基于 ZYNQ 的视频处理实验平台前端使用 OV5640 摄像头采集图像中间通过 AXI4-Stream 视频流在各个模块之间传输再借助 AXI VDMA 完成 DDR 帧缓存最后通过 HDMI 输出到显示器。后续我在这个平台上继续叠加了帧间差分运动检测算法因此这条视频链路既是显示系统本身也是后面算法调试的基础。这篇文章先不讲复杂算法重点记录一下整个系统是怎么从“能采到图”一步步走到“能稳定显示”的。一、项目目标与整体思路这个项目的核心目标很明确把摄像头采集到的视频数据经过 ZYNQ PL 端的视频链路处理后稳定输出到 HDMI 显示器。从结构上看整条链路可以分成五部分摄像头数据采集OV5640 输出像素时钟、行同步、场同步和 8 位数据通过自定义采集模块整理成视频接口。视频流标准化使用 Video In to AXI4-Stream把摄像头原始视频信号转换成 AXI4-Stream 格式便于后续模块统一处理。帧缓存使用 AXI VDMA 把视频流写入 DDR再从 DDR 中读出。这样做的好处是可以实现帧级缓存 方便后续多帧算法处理 使输入和输出两端解耦显示链路从 DDR 读出的 AXI4-Stream 视频送入 AXI4-Stream to Video Out再配合 VTC 输出时序信号最终驱动 HDMI/LCD 显示。格式转换与算法接口预留由于系统中部分模块使用 RGB565部分模块使用 RGB888因此在链路中还加入了颜色格式转换模块。同时为后续帧差法、目标检测等算法预留了 AXI4-Stream 接口。这一套结构搭好之后后续加算法就不是“从零开始”而是在一个已经能稳定显示的平台上逐步叠加功能。二、Block Design 中各模块的作用从整个 BD 的结构来看核心模块并不算特别多但每个模块都有明确分工。OV5640_Data 采集模块这个模块是摄像头数据进入 FPGA 的第一站。它负责接收来自 OV5640 的PCLK VSYNC HREF Data[7:0]并在内部整理成适合后续视频处理模块使用的并行视频信号。简单理解这个模块做的是“把摄像头吐出来的原始时序数据变成系统内部能识别的视频像素流”。v_vid_in_axi4s_0这是 Xilinx 官方的 Video In to AXI4-Stream IP。它的作用是把传统视频接口转换成 AXI4-Stream 视频流。这一步非常关键因为后面很多视频处理模块包括 VDMA、算法 IP、Video Out都是基于 AXI4-Stream 工作的。在这次项目中视频流宽度最终采用了 16 位因为整条处理链后来统一到了 RGB565 格式这一点在后续调试中非常重要。rgb888to565 / rgb565to888由于不同模块对数据格式要求不同所以颜色格式转换模块必不可少。rgb888to565把 24 位 RGB888 压缩成 16 位 RGB565rgb565to888把 16 位 RGB565 扩展成 24 位 RGB888在我后面的算法模块中也大量用到了 RGB565 转 RGB888 再转灰度 Y 的处理思路。当前 AXI_VIP_Frame_Difference 模块中输入输出仍然是 16 位 AXI4-Stream而内部会先把 s0_axis_tdata_dly2 和 fifo_q 扩展为 RGB888再送入 RGB888_YCbCr444 做灰度计算。AXI VDMA这个项目里 VDMA 是核心中的核心。VDMA 的作用不是简单“搬数据”而是承担了视频系统里的帧缓存任务S2MM把 AXI4-Stream 视频写入 DDRMM2S把 DDR 中的视频再读出为 AXI4-Stream在我的设计里之所以使用双 VDMA 结构是为了给后续算法做准备。帧差法本质上需要比较两帧图像因此必须依赖帧缓存。5. AXI_VIP_Frame_Difference这是我自己插入系统里的算法 IP。从接口上看它是一个双输入、单输出的 AXI4-Stream 视频处理模块s0_axis_*当前视频流 s1_axis_*另一帧/延迟帧视频流 m_axis_*处理后的输出视频流当前这版模块里已经包含了双路16位 AXI4-Stream 输入 FIFO 缓存 RGB565 到 RGB888 转换 RGB888_YCbCr444 灰度转换 frame_difference_flag 差分判定逻辑不过这篇文章主要讲显示链路本身因此算法部分先点到为止后面会单独开一篇详细写。VTC 与 AXI4-Stream to Video Out视频显示除了像素数据还必须有时序。VTCVideo Timing Controller负责生成分辨率对应的同步时序AXI4-Stream to Video Out 则负责把 AXI 流重新还原成显示接口信号。这两个模块的关系可以理解为VTC告诉系统“现在什么时候是行起始、场起始、有效显示区域” Video Out把 AXI 视频流按照这个节奏吐出去如果这两个模块配置不对最常见的现象就是黑屏 花屏 只显示部分区域 显示器有同步但图像不对三、为什么一开始必须先统一位宽和格式我这次调试过程中一个特别深的体会就是视频系统最怕“链路中每段都差一点”。比如前面是 RGB888后面是 RGB565某个模块按 24 位理解另一个模块按 16 位读SDK 里 hsize 还是按 3 字节算而硬件已经改成 2 字节这种问题单独看都不大但串起来就是黑屏、竖条纹、花屏。所以后来我做了一个非常重要的决定把中间处理链统一为 RGB565也就是 16 位。这样做有几个直接好处VDMA 配置统一AXI VDMA 的 Stream Data Width 统一设为 16 位SDK 配置统一read_hsize 和 write_hsize 都按 hsize * 2 计算算法 IP 接口统一AXI_VIP_Frame_Difference 的输入输出也全部改成 16 位 AXI4-Stream调试思路更清晰只要显示出问题优先怀疑位宽不匹配 stride 不对 格式转换前后不一致这一步虽然看起来只是“改成 16 位”但实际上是后面所有调试能够推进下去的前提。四、显示链路调通后为什么还要保留 VDMA有同学会问既然只是显示图像为什么不直接摄像头 → Video In → Video Out → HDMI中间为什么还要绕一圈 DDR答案很简单因为后面我要做算法。如果只是摄像头直显数据只存在于当前这一时刻你无法方便地拿“前一帧”和“当前帧”做比较。而引入 VDMA DDR 之后当前帧可以写到 DDR 上一帧可以从另一块缓存中读出 算法 IP 可以在双路视频之间做比较这其实就是把一个“显示系统”扩展成了一个“可做视频算法实验的平台”。五、从稳定显示到叠加帧差算法的过渡在系统能稳定显示之后我开始往中间插入 AXI_VIP_Frame_Difference 模块。一开始最容易犯的错误就是“觉得算法模块代码没问题直接全功能接进去”。但实际调试下来发现这样非常容易把问题搞复杂。所以后面我采用了一个更靠谱的方法先做纯透传先让算法模块什么都不做只把 s0_axis 原样输出到 m_axis。只要这一步能显示说明模块接口没问题 上下游连接没问题 AXI4-Stream 协议基本没问题再加 FIFO先把第二路输入和 FIFO 加回来但输出仍然保持透传。再加灰度和差分标志在模块内部计算 frame_difference_flag但输出仍然不改动。最后再尝试运动区域标红和框选这样一层层恢复功能才能真正定位问题到底出在哪一层。这个思路后来证明非常有效。因为如果一开始就把 FIFO、灰度、差分、框选、画框全部塞进去一旦出问题根本不知道是哪里错了。六、当前项目已经走到哪一步到目前为止这个项目已经完成了下面这些内容OV5640 摄像头采集 Video In to AXI4-Stream RGB565 / RGB888 格式转换 双 VDMA 帧缓存 VTC Video Out 显示 AXI4-Stream 自定义算法模块插入 帧差法的基本运动检测与调试从代码上也能看到当前算法模块已经具有完整的 AXI4-Stream 输入输出结构并且内部完成了 FIFO 缓存、RGB 扩展、灰度转换和差分标志计算。也就是说这个系统已经不是一个简单的“摄像头显示实验”而是一个可以继续做视频算法验证的平台。七、这次搭建视频链路最大的经验如果让我总结一句最重要的话那就是先把视频链路跑通再谈算法。因为对于 FPGA 视频项目来说黑屏未必是算法问题 花屏未必是摄像头问题 框不出来也未必是检测逻辑本身的问题很多时候真正的问题可能只是位宽没统一 stride 算错了 VDMA 读写没有对上 AXI4-Stream 控制信号没对齐所以把底层显示平台先搭稳实际上是在给后面算法调试“铺路”。