基于XC7Z100+OV5640(DSP接口)YOLO人脸识别前向推理过程(部分3)

发布时间 2023-06-10 20:08:53作者: 李白的白

基于ZYNQ的摄像头显示系统

  • 本文介绍了如何使用ZYNQ开发板、OV5640摄像头和HDMI显示器搭建一个摄像头显示系统
  • 本文的内容主要分为以下几个部分:
    • 硬件介绍
    • Vivado工程创建
    • Vitis工程创建
    • 实验结果展示
硬件介绍
  • ZYNQ开发板
    • 使用的是ZINC 7100芯片,具有双核ARM Cortex-A9处理器(PS)和可编程逻辑(PL)
    • 使用的是HDMI接口作为显示输出,支持720P分辨率
    • 使用的是DDR3内存,用于存储摄像头采集的图像数据
  • OV5640摄像头
    • 使用的是DVP接口作为图像输入,支持多种分辨率,本实验中使用720P
    • 使用的是SCCB接口作为配置接口,通过PS端的EMIO连接
    • 使用的是两个外部时钟源,一个是50MHz,一个是24MHz,用于驱动摄像头工作
Vivado工程创建
  • 在Vivado中创建一个新的工程,选择ZYNQ芯片型号为XC7Z100FFG900-2

  • 添加ZYNQ IP核,并对其进行配置,主要包括以下几个方面:

    • PS端配置
      • 勾选HP接口和DDR3接口,用于连接VDMA IP核
      • 勾选EMIO接口,用于连接SCCB IP核
      • 勾选FCLK_CLK0,设置为200MHz,用于提供时钟给HDMI接口
    • PL端配置
      • 设置时钟输入为50MHz,来自PR端的晶振
      • 设置DDR3型号为MT41J128M16HA-125,位宽为16位
  • 添加摄像头OV5640配置模块(OV5640_config),并连接 DVP 接口和时钟信号,用于通过SCCB接口对摄像头进行初始化和设置

  • 添加摄像头数据转换Video In to AXI4-Stream IP模块(video_in_to_axis),用于将DVP接口的数据转换为AXI Stream接口的数据

  • 添加VDMA IP核,并对其进行配置

    添加 VDMA IP,并配置读写通道,将 AXI Stream 接口转换为 AXI Full 接口,并通过 AXI Interconnect 连接到 ZYNQ IP
    
    • 主要包括以下几个方面:
    • General配置
      • 设置Number of Channels为1,表示只使用一帧缓冲区
      • 设置Stream Data Width为16,表示每个像素占用16位
      • 设置Memory Map Data Width为32,表示与DDR3的数据位宽一致
    • Write Channel配置
      • 设置Fixed Frame Store Addresses为true,表示使用固定的内存地址作为帧缓冲区
      • 设置Base Address为0x10000000,表示帧缓冲区的起始地址
      • 设置Horizontal Size为1280*2,表示一行的字节数(注意要乘以2,因为每个像素占用2个字节)
      • 设置Vertical Size为720,表示一帧的行数
      • 设置Stride为1280*2,表示两行之间的字节数(注意要乘以2,因为每个像素占用2个字节)
    • Read Channel配置
      • 设置Fixed Frame Store Addresses为true,表示使用固定的内存地址作为帧缓冲区
      • 设置Base Address为0x10000000,表示帧缓冲区的起始地址(与写通道一致)
      • 设置Horizontal Size为1280*2,表示一行的字节数(注意要乘以2,因为每个像素占用2个字节)
      • 设置Vertical Size为720,表示一帧的行数
      • 设置Stride为1280*2,表示两行之间的字节数(注意要乘以2,因为每个像素占用2个字节)
  • 添加视频时序控制模块(VTC),用于生成720P的视频时序信号给HDMI接口

    添加 VTC IP,并配置为 720P 分辨率,生成视频时序信号
    
  • 添加视频数据转换模块(axis_to_video_out),用于将AXI Stream接口的数据转换为DVP接口的数据(类似于VGA接口)

    添加 AXI4-Stream to Video Out IP,将 AXI Stream 接口转换为 DVP 接口
    
  • 添加HDMI输出模块(hdmi_out),用于将DVP接口的数据转换为HDMI接口的数据,并输出到显示器上

    添加 HDMI Top 和 HDMI Out 模块,将 DVP 接口转换为 HDMI 接口,并连接到板子上的 HDMI 显示器
    
  • 添加颜色转换模块(rgb565_to_rgb888),用于将RGB565格式的数据转换为RGB888格式的数据(因为HDMI输出需要RGB888格式)

  • 连接各个模块之间的信号线,并设置相应的时钟、复位、使能等信号

  • 运行Connection Automation命令,自动连接ZYNQ IP核和VDMA IP核之间的AXI总线信号

  • 验证设计是否有错误或警告,并生成比特流文件

Vitis工程创建
  • 在Vitis中创建一个新的工程,并导入Vivado生成的比特流文件和硬件描述文件(如果没有自动导入,则手动添加)
  • 添加main.c文件,并编写相应的代码,主要包括以下几个方面:
    • 初始化PS端和PL端的硬件设备,并获取相应的设备句柄和内存映射地址
    • 配置ZYNQ IP核的寄存器,使能HP接口和DDR3接口,并设置FCLK_CLK0为200MHz
    • 配置VDMA IP核的寄存器,使能读写通道,并设置相应的帧缓冲区地址和大小等参数
    • 配置VTC IP核的寄存器,使能视频时序生成,并设置相应的水平和垂直参数等参数
    • 调用摄像头配置模块提供的函数,通过SCCB接口对摄像头进行初始化和设置,并选择720P分辨率和RGB565格式等参数
    • 启动VDMA IP核和VTC IP核,并等待视频显示正常
实验结果展示
  • 将ZYNQ开发板、OV5640摄像头和HDMI显示器连接好,并上电启动开发板
  • 将Vitis工程下载到开发板上,并运行程序
  • 观察HDMI显示器上是否有摄像头采集到的图像正常显示

ZYNQ实现YOLOv3-Tiny的加速方案介绍

  • 加速方案的总体原则:

    加速方案参考了之前手写数字识别的CNN项目,采用了类似的数据预处理、参数传输、卷积计算、激活查找表等模块
    加速方案需要考虑数据尺寸、存储资源、传输带宽、计算延迟等因素,进行设计空间探索,找到最优的设计点
    
    • 加速方案的总体原则是利用ZYNQ的PS和PL分工合作,实现YOLOv3-Tiny的前向计算

    • 在ZYNQ的PL端实现网络结构中的卷积、激活、池化等计算密集型的操作,利用硬件加速计算,使用Verilog代码进行硬件描述

    • 在ZYNQ的PS端实现数据调度、解析yolo层输出结果、后处理、显示输出结果、非极大值抑制等控制逻辑型的操作,使用C/C++代码进行软件编程

    • 在ZYNQ的DDR3中存储网络参数、输入图像数据、中间结果等信息,利用外部存储资源

      • DDR3作为外部存储器,缓存网络参数、输入图像和中间结果
    • 在ZYNQ的HDMI接口上显示检测结果,利用视频输出设备

graph LR A[ZYNQ实现YOLOv3-Tiny的加速方案介绍] --> B(视频显示方案) A --> C(摄像头数据缩放方案) A --> D(网络加速方案) B --> E[摄像头数据转换为stream接口] B --> F[VDMA读写DDR3] B --> G[AXI4-Stream转换为视频接口] B --> H[HDMI驱动显示器] C --> I[裁剪中心区域] C --> J[缩放整幅图像] D --> K(PS端和PL端分工合作) D --> L(数据预处理) D --> M(卷积计算) D --> N(激活计算) D --> O(池化计算) D --> P(数据解析)
  • 加速方案的具体步骤:

    • 参数传输:将Pytorch训练得到的权重、偏置、缩放因子等参数存储在SD卡中,通过HIDMA从PS端传输到PL端,并缓存在BRAM中
    • 卷积计算:从DDR3中读取输入图像数据,并根据需要进行填充操作,然后与缓存的权重参数进行卷积计算,并将结果写回DDR3中
    • 激活查找表:根据卷积计算的结果,在PL端使用查找表进行激活操作,查找表由Pytorch生成并存储在SD卡中
    • 池化操作:在卷积计算后直接进行最大池化操作,减少数据量,并将结果写回DDR3中
    • 上采样操作:在PL端使用双线性插值法进行上采样操作,增加特征图的尺寸,并将结果写回DDR3中
    • 连接操作:在PL端将两个不同尺度的特征图进行连接操作,形成一个大的特征图,并将结果写回DDR3中
    • 解析操作:在PS端对最终的两个特征图进行解析操作,得到目标的类别、置信度和边界框坐标
    • 非极大值抑制:在PS端对解析得到的多个边界框进行非极大值抑制,去除重叠度高的冗余框,保留最终的检测结果
    • 显示操作:在HDMI驱动模块上将检测结果绘制在原始图像上,并显示在屏幕上
    • 数据预处理

      • 采用了全局缩放的方法,而不是局部裁剪的方法,以便对整幅图像进行检测
      • 将摄像头输出的720P图像(1280x720)通过一个resize模块缩放为416x416,以适应网络的输入尺寸,作为YOLOv3-Tiny的输入。Resize模块使用双线性插值算法进行缩放,保证整幅图像都能被检测。这个resize模块可以利用FPGA的并行性来加速图像缩放的过程
      • 使用resize模块实现缩放操作,并将缩放后的图像数据通过VDMA(Video Direct Memory Access)模块写入DDR3中。VDMA模块可以实现高速的视频数据传输,同时减少处理器的负担。
      • 缩放后的图像数据与显示用的图像数据分开存储,避免冲突
    • PL端(FPGA)计算

      在PL端(可编程逻辑端)负责卷积,激活,池化和上采样等计算密集型的操作

      加速器还对原始的YOLO算法进行了一些优化,包括减少位精度、融合归一化层和卷积层、添加一个新的DLQ层等,以减少内存空间和带宽需求,保持精度。

      • PL端通过AXI-DMA接口从DDR3读取所需的数据,包括输入图像和网络参数,并通过Stream_rx模块接收到FPGA内部。AXI-DMA是一种高带宽,高效率的数据传输接口。

      • PL端根据PS端发送的控制信号,在FPGA内部进行相应层的计算,实现所有的YOLO层,包括卷积、激活、池化、上采样等操作。对于卷积层,PL端先对输入图像进行填充(如果需要),然后使用通用矩阵乘法GEMM(General Matrix Multiplication)原理,设计了一个基于systolic array的GEMM处理器。进行卷积运算,并使用查找表实现激活函数。对于池化层和上采样层,PL端使用简单的逻辑电路进行实现。对于YOLO层,PL端直接将输出结果写入DDR3。

        • 对于激活层,PL端使用了一个查找表文件来实现leaky ReLU函数。查找表文件是在Pytorch中生成的,包含了不同输入值对应的输出值。
        • 对于池化层和上采样层,PL端使用了简单的硬件逻辑来实现最大值池化和双线性插值上采样。
        • 对于YOLO层和路由层,PL端不进行任何计算,只是将输入数据原样输出。
      • PL端将每一层的计算结果通过VDMA写入DDR3的相应位置,作为下一层的输入或最终的输出。同时,PL端通过AXI-LITE模块向PS端发送信号,告知PS端该层的计算已经完成。

    • PS端(ARM处理器)调度

      在PS端(处理器端)负责数据的预处理,调度,解析和显示

      • PS端从摄像头获取720P的图像数据,通过VDMA(Video Direct Memory Access)将其写入DDR3内存,并通过Video In to AXI Stream IP将其转换为AXI Stream接口的数据。

      • PS端从DDR3读取图像数据,并通过Resize模块将其缩放为416*416的尺寸,然后再通过VDMA将其写入DDR3的另一部分。这样就得到了YOLOv3-Tiny网络的输入图像。

      • PS端从SD卡(存储设备)读取Pytorch训练得到的网络参数(包括权重、偏置、缩放因子等)文件(bin文件),包括卷积核的权重和偏置,以及激活层的scale,zero point等参数,并通过VDMA将其写入DDR3的另一部分。并通过SD卡和AXI-DMA将其传输到PL端(Programmable Logic)。

      • PS端通过AXI-Lite接口向PL端发送控制信号,告知PL端要进行哪一层的计算,以及需要哪些数据。AXI-Lite是一种低延迟,低开销的通信接口。

        • 对于YOLO层,PS端从DDR3内存中读取输出结果,并进行解析,得到检测出来的物体类别和边界框坐标。
        • 对于路由层,PS端从DDR3内存中读取两个输入特征图,并进行拼接,得到一个输出特征图。然后将输出特征图写回DDR3内存中,以供下一层使用。
        • 对于其他层,PS端不进行任何处理,只是准备下一层的指令。
      • PS端从DDR3读取YOLOv3-Tiny网络的输出结果,并对其进行解析,得到物体类别,置信度和边界框坐标等信息。然后PS端通过HDMI驱动显示器进行显示,并在原始图像上绘制检测到的物体和边界框。

  • Layer计算过程

    Layer 是神经网络中的一个基本单元,它由多个神经元组成,每个神经元都有一个激活函数,用于对输入数据进行非线性变换。Layer 的作用是提取输入数据的特征,并将其传递给下一层或输出层。

    Layer计算过程是指在深度学习中,使用神经网络对输入数据进行一系列的变换和操作,从而得到输出数据的过程。每一层都有自己的参数和激活函数,可以实现不同的功能,如卷积、池化、全连接、归一化等。不同的层可以组合成不同的网络结构,如CNN、RNN、Transformer等,来解决不同的任务,如图像识别、语音识别、自然语言处理等。

    • 输入数据:Layer接收上一层的输出数据或者原始数据作为输入,输入数据通常是一个多维的张量(tensor),例如图片、文本、音频等。

    • 数据预处理:对输入数据进行一些必要的处理,例如填充、缩放、归一化、编码等。

    • 权重参数:Layer有一组可调整的模型参数,通常是一个或多个矩阵(matrix),用来存储Layer学习到的特征和规律。例如卷积层(convolutional layer)的权重参数就是卷积核(kernel),全连接层(fully connected layer)的权重参数就是连接矩阵(connection matrix)等。

    • 运算操作:Layer对输入数据和权重参数进行一定的数学运算,从而得到输出数据。不同类型的Layer有不同的运算操作,例如卷积层的运算操作就是卷积(convolution),激活层(activation layer)的运算操作就是激活函数(activation function),池化层(pooling layer)的运算操作就是池化(pooling)等。

    • 输出数据:Layer将运算操作得到的输出数据传递给下一层或者作为最终结果。输出数据通常也是一个多维的张量,例如特征图(feature map),分类概率(classification probability)等。

    • 本项目中Layer 0、Layer 1和Layer 2的计算步骤:

      • Layer 0:这是一个卷积层,输入通道数为3,输出通道数为16,卷积核大小为3x3,步长为1,填充为1。它的作用是对输入图像进行特征提取。

        • 首先,PS端从SD卡中读取Layer 0的权重参数(包括卷积核和偏置),并通过HIDMA模块发送到PL端的Weight Buffer中。同时,PS端通过HLITE模块告知PL端当前传输的是权重参数。
        • 然后,PS端从摄像头获取720P的图像数据,并通过Resize模块将其缩放为416x416的大小。然后通过VDMA模块将图像数据写入DDR3中,并通过HIDMA模块发送到PL端的Padding模块中。同时,PS端通过HLITE模块告知PL端当前传输的是图像数据。
        • 接着,PL端的Padding模块对图像数据进行填充操作,将其扩展为418x418的大小,并将填充后的数据送入Convolution模块中。
        • 然后,PL端的Convolution模块根据Weight Buffer中存储的权重参数对填充后的数据进行卷积计算,并将结果送入Activation模块中。
        • 最后,PL端的Activation模块根据Shift、Matter和Zero Point等参数对卷积结果进行激活函数计算,并将结果送入Pooling模块中。Pooling模块对激活结果进行最大值池化操作,并将结果写入DDR3中作为Layer 1的输入。
      • Layer 1:这是一个卷积层,输入通道数为16,输出通道数为32,卷积核大小为3x3,步长为1,填充为1。它的作用是对上一层的特征进行进一步的提取。

        • 首先,PS端从SD卡中读取Layer 1的权重参数(包括卷积核和偏置),并通过HIDMA模块发送到PL端的Weight Buffer中。同时,PS端通过HLITE模块告知PL端当前传输的是权重参数。
        • 然后,PS端从DDR3中读取Layer 0的输出结果,并通过HIDMA模块发送到PL端的Padding模块中。同时,PS端通过HLITE模块告知PL端当前传输的是图像数据。
        • 接着,PL端的Padding模块对图像数据进行填充操作,将其扩展为210x210的大小,并将填充后的数据送入Convolution模块中。
        • 然后,PL端的Convolution模块根据Weight Buffer中存储的权重参数对填充后的数据进行卷积计算,并将结果送入Activation模块中。
        • 最后,PL端的Activation模块根据Shift、Matter和Zero Point等参数对卷积结果进行激活函数计算,并将结果送入Pooling模块中。Pooling模块对激活结果进行最大值池化操作,并将结果写入DDR3中作为Layer 2的输入。
      • Layer 2:这是一个卷积层,输入通道数为32,输出通道数为64,卷积核大小为3x3,步长为1,填充为1。它的作用是对上一层的特征进行进一步的提取。

        • 首先,PS端从SD卡中读取Layer 2的权重参数(包括卷积核和偏置),并通过HIDMA模块发送到PL端的Weight Buffer中。同时,PS端通过HLITE模块告知PL端当前传输的是权重参数。
        • 然后,PS端从DDR3中读取Layer 1的输出结果,并通过HIDMA模块发送到PL端的Padding模块中。同时,PS端通过HLITE模块告知PL端当前传输的是图像数据。
        • 接着,PL端的Padding模块对图像数据进行填充操作,将其扩展为106x106的大小,并将填充后的数据送入Convolution模块中。
        • 然后,PL端的Convolution模块根据Weight Buffer中存储的权重参数对填充后的数据进行卷积计算,并将结果送入Activation模块中。
        • 最后,PL端的Activation模块根据Shift、Matter和Zero Point等参数对卷积结果进行激活函数计算,并将结果送入Pooling模块中。Pooling模块对激活结果进行最大值池化操作,并将结果写入DDR3中作为Layer 3的输入。