【FPGA/图像处理】板上验证

少女dtysky

世界Skill

时刻2015.05.31

要证明模块的可靠性,需要对所有的设计进行板上验证,本次毕设课题与Xilinx的XUP(Xilinx University Program,Xilinx大学计划)合作,使用了XUP提供的Zybo开发板,Zybo是一个ZYNQ平台的开发板,片上搭载了一个双核的ARM,并且具有VGA等接口作为视屏输出,输入采用XUP提供的摄像头以及配套的驱动程序,测试硬件如图4-1所示。同时考虑到资源的压力,摄像头被配置为320x240的模式。本章将会说明如何设计一个测试框架,并对所有已实现的模块放入框架进行测试。
测试需要所有的模块,测试工程在这里: TestOnBoard


4 板上验证

要证明模块的可靠性,需要对所有的设计进行板上验证,本次毕设课题与Xilinx的XUP(Xilinx University Program,Xilinx大学计划)合作,使用了XUP提供的Zybo开发板,Zybo是一个ZYNQ平台的开发板,片上搭载了一个双核的ARM,并且具有VGA等接口作为视屏输出,输入采用XUP提供的摄像头以及配套的驱动程序,测试硬件平台如图4-1所示。同时考虑到资源的压力,摄像头被配置为320x240的模式。本章将会说明如何设计一个测试框架,并对所有已实现的模块放入框架进行测试。

图4-1 测试硬件平台
图4-1 测试硬件平台

4.1 测试框架

测试框架可以分为输入、处理和输出三个部分,如图4-2所示。其中输入采用摄像头模块,在其后放置一个帧缓存来存储输入数据,但考虑到Zybo的片上RAM资源并不多,同时灰度图像已经足以满足测试,所以首先将摄像头输出的数据进行灰度化再存入帧缓存,这样可以节省一半的RAM资源。在帧缓存后使用读取模式帧控制器来读取数据,进入处理部分,在处理部分中,每一个处理模块对数据进行并行处理,并按照实际的需求对先后顺序做出一些调整,从而并行输出各自的处理结果。处理部分之后是输出部分,输出采用一个VGA控制器进行,由于VGA无法和处理模块同步,所以中间需要加一个帧缓存,帧缓存使用一个写入模式的帧控制器进行控制,由于需要接受多个处理模块的输入数据,所以必须有一个多路选通器来选择将那一个结果送给帧控制器,多路选通器接受外部控制,以此间接决定VGA的最终输出。
一部分处理模块需要外部进行配置参数的输入,比如TH核就需要输入阈值化模式和阈值,所以要考虑如何对其进行配置,此处利用ZYNQ的ARM平台和AXI协议,构建一个AXI总线的配置模块用于各个模块参数的配置,同时提供系统复位和多路选通器的输入,即用软件的方法配置硬件,可以大大提高灵活性和降低调试成本。

图4-2 测试框架
图4-2 测试框架

4.2 AXI总线模块的构建

4.2.1 AXI总线模块的结构和原理

基于AXI总线的模块是一种特殊的模块,可以通过它利用AXI协议使得ARM平台(PS端)和可编程硬件平台(PL端)进行通信[28],Vivado中提供了一种便捷的方式来进行这种模块的创建[29],创建的时候有三种模式进行选择,分别是"AXI-Lite","AXI-Full"和"AXI-Stream",三种模式各有各的用处,同时还可以选择有几个从模块,考虑到本次需要建立的模块实现的功能比较简单,所以采用了Lite模式,只有一个从模块。创建完成后可以看到在被创建的模块目录下有一些文件夹和文件,如图4-3所示,我们需要关心的只有这几个文件,其中XX为模块名:

  1. hdl/XXV1_0.v: 这是此模块的顶层HDL文件,用于管理所有从模块。
  2. hdl/XXV1_0_S00_AXI.v: 从模块的HDL文件,用于功能的实现,如果有若各从模块,将有多个这样的文件。
  3. drivers/XXV1_0/src/XX.c: ARM端驱动程序,用于定义上位机部分的功能。
  4. drivers/XXV1_0/src/XX.c: ARM端驱动程序的头文件。

对于Lite模式,Xilinx已经预先生成了模块的AXI协议部分模板,用户只需要在指定的地方编写自己需要的逻辑即可。

图4-3 AXI总线模块的结构
图4-3 AXI总线模块的结构

AXI总线模块的工作原理如图4-4,PS端运行的是ARM环境下的C程序,它用于执行用户的软件指令,当需要和PL端通信时,它先通过Xilinx封装的IO函数,利用AXI总线将数据传输到PL端的寄存器中,随后PL端再通过寄存器中的数据来执行相应的操作,随后将需要输出的数据输出给其他PL端的模块。

图4-4 AXI总线模块的原理
图4-4 AXI总线模块的原理

4.2.2 配置模块的构建

配置模块有三类输出——配置参数信号、复位信号和选择信号,但本质上它们都是同样的信号,每一个信号都由PS端提供数据,并由一个单独的端口进行输出,端口的输出来自于PS端送入PL端中寄存器的数据。同时,配置模块还需要能够接受外界的硬件复位和PLL的锁定信号,这两个信号和AXI的复位信号、PS端的复位指令综合产生一个复位输出。配置模块的GUI如图4-5所示。

图4-5 配置模块的GUI
图4-5 配置模块的GUI

配置模块的驱动部分需要考虑两点,即地址管理和函数设计。将配置模块命名为"BOARDINIT_AXI",则模块创建成功时会提供一系列已经定义好的宏,如下:

#define BOARDINIT_AXI_S00_AXI_SLV_REG0_OFFSET 0
#define BOARDINIT_AXI_S00_AXI_SLV_REG1_OFFSET 4
......

这些宏定义了PL端寄存器的地址偏移量,为了便于管理,可以在上面再加一层宏定义:

#define BOARDINIT_RstN BOARDINIT_AXI_S00_AXI_SLV_REG0_OFFSET
#define BOARDINIT_Sels BOARDINIT_AXI_S00_AXI_SLV_REG1_OFFSET
......

同时,在用到配置模块的工程生成完硬件数据流后,可以在SDK建立的项目工程中的BSP中找到"xparameters.h"文件,里面有此模块基地址的偏移量XPAR_BOARDINIT_AXI_0_S00_AXI_BASEADDR:

#define XPAR_BOARDINIT_AXI_0_DEVICE_ID 0
#define XPAR_BOARDINIT_AXI_0_S00_AXI_BASEADDR 0x43C00000
#define XPAR_BOARDINIT_AXI_0_S00_AXI_HIGHADDR 0x43C0FFFF

有了基地址偏移量和寄存器地址偏移量,便可以管理所有的输出地址了,通常将所有的地址放在一个结构体中备用:

typedef struct
{
    u32 BaseAddress;
    u32 RstN;
    ......
    u32 Sels;
} BOARDINIT;

函数设计要考虑到初始化、IO和数据转换。其中初始化用于将结构体中所有的地址初始化,数据转换则是将用户输入的数据转换为PL端可用的数据,比如对于一些模块,其参数要求指定位数的浮点数,但AXI协议传输的是无符号整数,所以不可以直接进行IO,必须进行转换。初始化部分可以使用下面的函数完成:

void BOARDINIT_Init(BOARDINIT *bdit, u32 BaseAddress){
    bdit->BaseAddress = BaseAddress;
    bdit->RstN = BaseAddress + BOARDINIT_RstN;
    ......
    bdit->Sels = BaseAddress + BOARDINIT_Sels;}

IO部分,所有的IO都要依赖与Xilinx提供的IO函数:

#define BOARDINIT_Set(address, data) Xil_In32(address)
#define BOARDINIT_Get(address) Xil_Out32(address, data)

为了便于使用,在上面加上一层封装,这个函数的目的是保证输出的数据确实被写入了寄存器内。

void BOARDINIT_SetWithCheck(u32 Address, u32 data){
    while(BOARDINIT_Get(Address) != data){BOARDINIT_Set(Address, data);}
    return;}

一些参数需要被进行浮点数到指定位数的定点数的转换,以下函数完成了这个功能,其中rel_width为整数部分位数,dec_width为小数部分位数。

u32 Format2Fixed(float num, int rel_width, int dec_width){
    u32 s = 0;
    u32 r = 0;
    float dtmp = 0;
    if (num < 0){
        s = 1 << (rel_width + dec_width);
        r = (int)(-num);
        dtmp = (int)num - num;}
    else{
        r = (int)num;
        dtmp = num - r;}
    r = r << dec_width;
    u32 d = 0;
    int i =0;
    for (i = 0; i < dec_width; i++){
        dtmp = dtmp * 2;
        if (dtmp >= 1){
            d += 1 << (dec_width - 1 - i);
            dtmp--;}}
    if (s){return s + s - (r + d);}
    return s + r + d;}

至此,配置模块的驱动部分完成。

4.3 测试-点操作

点操作的Board工程如图4-6,原始图像和灰度图像如图4-7,阈值化的结果如图4-8,对比度变换的结果如图4-9,亮度变换的结果如图4-10,色彩反转的结果如图4-11。

图4-6 点操作Board
图4-6 点操作Board

图4-7 原始图像和灰度图像
图4-7 原始图像和灰度图像,上侧为原始图像,下侧为灰度图像

图4-8 阈值化
图4-8 阈值化,上侧为一般全局阈值化,阈值为150,下侧为等高线阈值化,阈值为130和170

图4-9 对比度变换
图4-9 对比度变换,上侧系数为1.8,下侧系数为0.3

图4-10 亮度变换
图4-10 亮度变换,上侧系数为50,下侧系数为-50

图4-11 色彩反转
图4-11 色彩反转

4.4 测试-局部滤波器

局部滤波器的Board工程如图4-12,原始图像和灰度图像如图4-13,均值滤波器的结果如图4-14,排序滤波器的结果如图4-15,局部阈值化的结果如图4-16,腐蚀膨胀的结果如图4-17。

图4-12 局部滤波器Board
图4-12 局部滤波器Board

图4-13 原始图像和灰度图像
图4-13 原始图像和灰度图像,上侧为原始图像,下侧为灰度图像

图4-14 均值滤波器
图4-14 均值滤波器

图4-15 排序滤波器
图4-15 排序滤波器,上侧为中值滤波器,中间为最大值滤波器,下侧为最小值滤波器

图4-16 局部阈值化
图4-16 局部阈值化,上侧的前置滤波器为均值滤波器,下侧为中值滤波器

图4-17 腐蚀膨胀
图4-17 腐蚀膨胀,上侧为腐蚀,源为中值滤波后的局部阈值化图像,模板为000011010,下侧为膨胀,源为均值滤波后的局部阈值化图像,模板为000011011

4.5 测试-几何变换

几何变换的Board工程如图4-18,原始图像和灰度图像如图4-19,裁剪的结果如图4-20,平移的结果如图4-21,镜像的结果如图4-22,缩放的结果如图4-23,错切的结果如图4-24,旋转的结果如图4-25。

图4-18 几何变换Board
图4-18 几何变换Board

图4-19 原始图像和灰度图像
图4-19 原始图像和灰度图像,上侧为原始图像,下侧为灰度图像

图4-20 裁剪
图4-20 裁剪,上边界为40,下边界为240,左边界为0,右边界为200

图4-21 平移
图4-21 平移,上侧横向偏移100,纵向100,下侧横向偏移-100,纵向-100

图4-22 镜像
图4-22 镜像,上侧为横向,中间为纵向,下侧为全部

图4-23 缩放
图4-23 缩放,上侧横向比例为1.3,纵向为0.6,下侧横向为0.6,纵向为1.3

图4-24 错切
图4-24 错切,上侧横向比例为0.5,纵向为0.5,下侧横向为-1.671,纵向为0.539

图4-25 旋转
图4-25 旋转,上侧角度为90度,下侧角度为225度

4.6 结论

经过实际测试,所有的模块都可以正常工作,设计完全成功。


参考文献

[28] Xilinx, AXI Reference Guide, UG761 v14.3[EB/OL], November 15, 2012
[29] Xilinx, Packaging Custom AXI IP for Vivado IP Integrator, XAPP1168 (v1.0)[EB/OL], June 01, 2013


感谢

硬件支持:
Xilinx上海

测试图像来源:
初心社-世界旅行 - 荷兰/阿姆斯特丹,英国/伦敦,希腊/爱琴海

如果不是自己的创作,少女是会标识出来的,所以要告诉别人是少女写的哦。