引言

这段时间感觉像是大学里面最闲的时候了。
最终答辩已经完成,就只等着最后的上交材料,然后估计要不了多久就被叫去打工,呜呜。🫨
不过还是有点时间,想着把我的毕设整理一下,供大佬们参考。
开发板是 Digilent Basys 3,FPGA芯片型号是 XC7A35T-1CPG236C。

关于代码参考

在我的毕业设计中,不可避免地在互联网中参考了大量的文章,感谢咸鱼IC 在本项目中关于图像卷积方面的系列讲解。

简介

本设计的主要部分是对图像进行卷积处理,而为了能够有图像输入,且受限制于开发板上没有DDR,无法存储高分辨率图像,故后面又增加了一个摄像头。
图像处理部分包含线性滤波、非线性滤波、色彩处理。

摘要

近年来针对FPGA平台的图像处理方案愈受关注。本文设计并实现了一种基于FPGA 的图像预处理算法设计与方案,着重于以底层硬件逻辑的方式来实现与优化算法。以Artix-7系列FPGA为处理芯片,以OV5640为摄像模块采集实时图像,自制了OV5640模块与开发板之间的转接PCB板,用FPGA直接驱动VGA显示器,以System Verilog为开发语言,针对摄像头所采集的实时图像进行处理,分别描述了图像卷积算法、图像滤波算法,以及边缘检测和形态学滤波等算法,处理的结果实时显示在VGA显示器上。
本设计采用同步FIFO代替移位寄存器来优化卷积窗口的实现,改善了数据处理的连续性和准确性,具有一定的创新性,设计结果表明能够实现实时图像的预处理。

关键词:FPGA;摄像模块;边缘检测;图像滤波

实时图像采集的硬件设计

系统设计方案

系统的总体设计如图3.1所示,该设计包括多个模块。在设计中,采用了两种不同频率的时钟,分别为VGA驱动和摄像头采集提供时钟源,以确保摄像头驱动时钟的精确性。OV5640摄像头内部独立含有PLL,能够根据不同视频参数的需求参数相应的时钟。伴随着摄像头的数据传输,其中最为重要的信号就是pclk(像素时钟),后续连接的各种图像处理都需要依赖pclk对图像卷积窗口的形成提供定位。

设计方案
经过对最后图像处理结果以及性能的分析可以看出,本文设计的图像处理系统完全满足了对视频图像实时处理的目标,同时为更高规格的视频输入留下了充足的扩展空间。

图像采集模块

作为机器视觉领域的关键部件,摄像头在各类图像处理环境中有着广泛的应用。摄像头可根据其感应材料分为两类。其中之一是电荷耦合器件,这类器件一般对图像的处理时间较长,而且会更消耗电源。另一类是互补金属氧化物半导体,这类器件适合嵌入式环境,但是成像效果不是非常理想。如今,大多数电子设备所采用的是CMOS 类型的数字摄像头。在这些设备中,CMOS 摄像头因其制造成本较低和功耗较小的优点而受到偏好。特别是在移动设备和高性能计算应用中,CMOS 摄像头的灵活性和集成度高的特性使其更加适用。

OV5640 摄像头

本文所采用的是豪威科技公司的OV5640图像传感器,这是一款1/4英寸的单芯片图像传感器。OV5640以其良好且稳定的成像质量、低功耗和高灵敏度而著称,特别适用于低照度环境,其感光阵列达到26241964分辨率(物理尺寸),最高支持 25921944@15 FPS(QSXGA)、90 FPS VGA(640*480)分辨率的图像采集,适合需求高清晰度和高帧率视频的应用场景。此外,OV5640支持自动曝光控制、白平衡、自动焦距调整等功能,极大地简化了图像处理的前期准备工作,允许更加精准和快速的图像分析与处理。

OV5640

OV5640 图像传感器支持多种数据输出格式,包括 RGB(RGB565/555/444)、RawRGB、YUV(4:2:2)和 YcbCr(4:2:2)8位格式。能够通过串行摄像机控制总线(SCCB)来调整摄像头的分辨率、数据格式、图像质量以及其他传输参数,从而实现高度的定制化和优化操作。
如图3.3所示详细描述了OV5640的内部功能,左下角的部分负责时序和系统逻辑的控制,其接收的信号包括外部时钟信号XVCLK,这是一个至关重要的信号,因为它提供了系统所有时序逻辑的基准时钟;低功耗模式控制信号PWDN,这允许系统在不需要处于高性能模式时降低功耗,从而减少功率;以及复位信号RESET,以确保在出现技术故障或进行系统维护时能够重置整个系统到初始状态,保证系统的稳定运行。
OV5640_sys

在图像传感器的输出方面,OV5640提供了水平参考信号HERF、垂直同步信号VSYNC和内部产生的同步数据时钟PCLK。图像传感器部分的主要任务是将图像数据准确地传递给模拟放大器(AMP)和模数转换器(ADC),确保图像数据的高效处理。
在实际应用中,这个摄像头具有的DVP接口是10位的宽度,能够一次性输出所有位的全部数据。然而,本次设计使用了VGA的模拟显示输出,这种模拟信号接口已经不满足现代的高清影像输出,因此OV5640模组在设计时将舍弃了最低两位D[1:0]的信号,使用高8位已经能满足本次设计的需求,经过两个时钟周期形成了16位的RGB565信号,这样同时关注了信号的实用性与兼容性。
OV5640_top

图像采集模块的总体设计如上图展示。该模块里面主要有SCCB的配置电路, RGB565像素合成部分,图像数据控制模块。配置电路模块通过SCCB协议对摄像头进行初始化。RGB565像素合成部分将SCCB获取的数据组成一个RGB565数据输出。图像数据控制模块等待摄像头完成初始化后(包括配置寄存器设置和丢弃最初几帧不稳定的图像)才输出图像。

SCCB

SCCB(串行摄像头控制总线)是一种由豪威科技公司公布的一种串行与自家产品相结合的协议。这种协议主要用于OV系列的摄像头模块,例如本次设计所使用的OV5640等。该总线负责摄像头的很多功能,比如输出视频格式和分辨率等。相应的结构框图图3.5详细展示了各组件之间的连接和交互方式,确保了整个系统的协调工作。

SCCB设备连接

为了减少DVP接口的引脚需求,SCCB使用了两根线的设计。这种设计不仅简化了硬件接口,而且增强了SCCB协议的灵活性和效率,使得设备设计更为紧凑且功能强大。
SCCB引脚描述

OV5640图像传感器也是两线式SCCB接口,包括SIO_C串行时钟输入线和SIO_D串行双向数据线。SIO_C的最短时间周期为10us,相应的最大频率可达100K。在实际应用中,通常可支持的频率范围在100K-400K之间都可以。
寄存器的通信协议是SCCB,这种接口协议与IIC 协议有很大的相似性。这两种协议都是用于在集成电路之间进行低速度、短距离的通信。它们只是在某些细节上有点不同,但它们的工作原理是基本相似的。这使得在许多应用场合中,可以互换使用。

OV5640 转接板

另外,在现代电子设备设计中,组件的直接集成通常会受到实际物理空间和功能需求的限制。本文的实际应用中,由于摄像头本身无法与开发板通过引脚直接相连,因此使用了一个转接板来实现接口转接与电源的管理。这种转接板起到了至关重要的桥梁作用,不仅解决电源与信号传输的需求,同时也为摄像头提供了不同的电压。

转接板3D
转接板Re

转接板的转换电压由输入提供的3.3V直流电源,经过两个专门的低压差线性稳压器(LDO),ME6211C28M5G-N和ME6211C15M5G-N,进行电压调节和降压,最终为摄像头提供所需的高质量稳定电源。这里使用ME6211C28M5G-N稳压器负责输出2.8V的电压,ME6211C15M5G-N输出1.5V的电压,这样的电压组合常适用于对电源敏感的摄像头系统中,因为不同的电路部分需要不同的电压级。
转接板供电1
转接板供电2

转接板上输入端与输出端都独立地接入 10μF 与 0.1μF 电容,主要是为了过滤来自FPGA开发板提供电源的纹波和噪声。因为输入端电源的纹波和噪声往往会干扰LDO的正常工作,导致输出电压的不稳定性或者噪声增大。

系统电气连接

FPGA引脚摄像头引脚FPGA引脚摄像头引脚
B16cmos_sclR18cmos_data[0]
C16cmos_sdaP18cmos_data[1]
A16cmos_pclkP17cmos_data[2]
A17cmos_xclkN17cmos_data[3]
A15cmos_pwdnM19cmos_data[4]
C15cmos_vsyncM18cmos_data[5]
B15cmos_hrefL17cmos_data[6]
K17cmos_data[7]

摄像头经过转接板连接后的实物图如下。

系统连接实物图

图像处理的研究与设计

图像卷积

图像通过卷积进行数学处理,该过程涉及使用一个滤波器(亦称卷积核或掩码)来调整图像中每个像素的值,这是通过将该像素及其周边像素的值与一个预设的3×3矩阵相乘并求和来实现的。对于一个3×3的滤波器,即包含目标像素以及周围的八个像素。

卷积窗口的形成

由于FPGA的内部存储资源受限,本文缓存一帧图像数据存储任务分配给了的BRAM存储器,以应对OV5640图像采集与VGA显示之间时钟不一致的问题。对于数据量相对较小的行缓存,就直接在FPGA内部进行存储。通常,行缓存可以通过FIFO或Shift寄存器来实现。然而,使用Shift寄存器在图像换帧时会出现数据遗留的问题。考虑到FIFO的灵活性,同时为了更高效地利用资源,本文采用FIFO进行缓存。
形成卷积窗口的资源占用

另外,本文使用BRAM缓存一帧完整的图像数据,最终的系统分辨率设定为320*240,每行数据包含320个像素点。因为本次使用的开发板上的VGA视频接口的每个颜色色深只有4位,所以将BRAM的数据位宽也设置为4位以便于节省资源。
缓存一帧的BRAM资源占用

FIFO实现

在FPGA中,使用FIFO的卷积方法通常包括使用寄存器数组或是内置的RAM。为了完成3×3的卷积操作,可以构建一个结构,其中包括两个FIFO队列,它们分别存储图像一行中的像素值。随着图像数据的逐行流入FPGA,相应的像素值将依次进入各自的FIFO队列。当缓存到第三行数据时即可形成3×3窗口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
fifo_10x4096 fifo_10x4096_1(
    .clk(clk),
    .srst(!rst_n),
    .wr_en(wr_en_1),
    .din(din),
    .rd_en(rd_en_1),
    .dout(dout1)
);
fifo_10x4096 fifo_10x4096_2(
    .clk(clk),
    .srst(!rst_n),
    .wr_en(wr_en_2),
    .din(din),
    .rd_en(rd_en_2),
    .dout(dout2)
);

此处实例化两个的FIFO缓冲区模块,每个模块具备4096深度和10位宽度的数据接口。clk是时钟信号,所有数据的读写操作都是同步进行,与这个时钟信号的上升沿对齐。在形成图像卷积窗口时最重要的是定位好每一个像素点,然后根据像素点位置精细控制缓存区内的数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
logic din_vld_pre=0;
always @(posedge clk) begin
++cnt_col;
if(din_vld!=din_vld_pre) begin          //din_vld 发生变化
    if (din_vld_pre==0) begin           //din_vld 上升沿
    if(!rst_n)begin
        cnt_row=0;
    end else begin
        if(cnt_row>res_y-2)begin
            cnt_row=0;
        end else begin
            ++cnt_row;
        end
        cnt_col=0;
    end
        end else begin                   //din_vld 下降沿
    end
    din_vld_pre<=din_vld;
end
在以上代码中,主要是监控和响应输入信号din_vld的变化,以管理行、列的计数器cnt_col、cnt_row。其中din_vld_pre 用于记录上一个时钟周期的din_vld状态。
在每个时钟周期的上升沿,无论din_vld的状态如何,列计数器cnt_col都会递增以实现列计数器增加。然后通过比较din_vld与din_vld_pre,从而推测出din_vld有没有发生变化。下一步就假设din_vld发生变化,然后会进一步分析是上升沿还是下降沿。在上升沿中首先会检查行计数器是否达到预设的最大值(res_y-2)。如果是则将行计数器清零;如果不是则递增行计数器。在每个时钟周期的结束阶段,将din_vld的当前状态赋值给din_vld_pre,为下一个周期的边缘检测做准备。
这种方式下,图像数据按照行的顺序被读取,每次读取一个新的像素时,两个FIFO队列都会向前进一步。队列中最先接收到的像素数据随后会被移除。每次FIFO更新,都会在图像中形成一个包括当前处理像素及其相邻的8个像素的新3×3窗口,伴随FIFO的不断更新,该窗口在图像中滑动,从而不间断地创建新的3×3窗口。

Shift实现

在图像处理中,尤其是执行卷积操作时,边界复制法提供了一种解决图像边缘问题的有效方法。该技术通过在图像的边缘进行像素复制,允许滤波器(即卷积核)覆盖图像的每一个点,包括边缘位置。当滤波器接触到图像边界时,其部分区域会扩展到图像外,这时边界复制法通过复制边缘处的像素值来填补超出部分,确保卷积运算能够覆盖图像的整体。

边缘处理

在图像处理中,尤其是执行卷积操作时,边界复制法提供了一种解决图像边缘问题的有效方法。该技术通过在图像的边缘进行像素复制,允许滤波器(即卷积核)覆盖图像的每一个点,包括边缘位置。当滤波器接触到图像边界时,其部分区域会扩展到图像外,这时边界复制法通过复制边缘处的像素值来填补超出部分,确保卷积运算能够覆盖图像的整体。


这一过程首先要确定图像边缘所需填充的像素位置,然后直接由最外侧像素复制到新的填充区域中形成。例如,一个 3×3 大小的卷积核,一般在图像边上添加两行两列的填充。这种方式使得卷积核能够在完整地在图像的每一个像素上进行卷积计算。虽然边界复制法通常能够有效工作,但在边缘像素与周围像素差异大的情况下效果较差。边界复制法逻辑简单,但代码较为冗长。
其主要思想为将涉及到的边缘卷积窗口挑出来单独进行处理。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
    always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        {matrix_11, matrix_12, matrix_13} <= {16'd0, 16'd0, 16'd0};
        {matrix_21, matrix_22, matrix_23} <= {16'd0, 16'd0, 16'd0};
        {matrix_31, matrix_32, matrix_33} <= {16'd0, 16'd0, 16'd0};
    end
    //------------------------------------------------------------------------- 第1排矩阵
    else if(cnt_row == 0)begin
        if(cnt_col == 1) begin        //矩阵 0 0
            {matrix_11, matrix_12, matrix_13} <= {row_3, row_3, row_3};
            {matrix_21, matrix_22, matrix_23} <= {row_3, row_3, row_3};
            {matrix_31, matrix_32, matrix_33} <= {row_3, row_3, row_3};
        end
        else begin                    //矩阵 0 *
            {matrix_11, matrix_12, matrix_13} <= {matrix_12, matrix_13, row_3};
            {matrix_21, matrix_22, matrix_23} <= {matrix_22, matrix_23, row_3};
            {matrix_31, matrix_32, matrix_33} <= {matrix_32, matrix_33, row_3};
        end
    end
    //------------------------------------------------------------------------- 第2排矩阵
    else if(cnt_row == 1)begin
        if(cnt_col == 1) begin        //矩阵 1 0
            {matrix_11, matrix_12, matrix_13} <= {row_2, row_2, row_2};
            {matrix_21, matrix_22, matrix_23} <= {row_2, row_2, row_2};
            {matrix_31, matrix_32, matrix_33} <= {row_3, row_3, row_3};
        end
        else begin                    //矩阵 1 *
            {matrix_11, matrix_12, matrix_13} <= {matrix_12, matrix_13, row_2};
            {matrix_21, matrix_22, matrix_23} <= {matrix_22, matrix_23, row_2};
            {matrix_31, matrix_32, matrix_33} <= {matrix_32, matrix_33, row_3};
        end
    end
    //------------------------------------------------------------------------- 剩余矩阵
    else begin
        if(cnt_col == 1) begin        //矩阵 * 0
            {matrix_11, matrix_12, matrix_13} <= {row_1, row_1, row_1};
            {matrix_21, matrix_22, matrix_23} <= {row_2, row_2, row_2};
            {matrix_31, matrix_32, matrix_33} <= {row_3, row_3, row_3};
        end
        else begin                    //剩余矩阵
            {matrix_11, matrix_12, matrix_13} <= {matrix_12, matrix_13, row_1};
            {matrix_21, matrix_22, matrix_23} <= {matrix_22, matrix_23, row_2};
            {matrix_31, matrix_32, matrix_33} <= {matrix_32, matrix_33, row_3};
        end
    end

图像滤波

在数字图像处理的数据采集和传输过程中,由于外界环境和系统本身的因素,往往会引入各种噪声,这些噪声干扰了获取的数据,从而使得图像质量降低,而且图像中的特征信息变少。噪声根据其与图像信号的联系,可分为两类:与图像信号强度不相关,其结果直接等于数据加上噪声的加性噪声,以及结果等于乘上了噪声的一个系数所导致的乘性噪声。为了确保获取到的数据保持其原有特征,同时又要减少因为噪声所形成的新的假特征,这就需要对获取到的数据滤波。因此本文的图像预处理系统需要设计一个图像滤波模块,是因为了増强图像的质量。

高斯滤波

$$ G(x,\sigma)=\frac {1}{\sqrt2\pi \sigma} e^{-\frac{x^2}{2\sigma^2}} $$$$ G(x,y,\sigma)=\frac {1}{2\pi\sigma^2} e^{-\frac{x^2+y^2}{2\sigma^2}} $$$$ \begin{pmatrix}a & b\\ c & d\end{pmatrix} $$

高斯滤波作用于图像时,其平滑效果依赖于标准差的大小。该方法通过计算相邻像素的加权平均实现图像的平滑处理,其中心附近的像素权重更大。相较于均值滤波,高斯滤波能更温和地平滑图像,同时更有效地保留边缘信息。
在本次设计中,采用了以下的三级计算方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
always @ (posedge clk or negedge rst_n)begin
		Grow1 <= matrix_11     + matrix_12 * 2 + matrix_13;
		Grow2 <= matrix_21 * 2 + matrix_22 * 4 + matrix_23 * 2;
		Grow3 <= matrix_31     + matrix_32 * 2 + matrix_33;
	end
	always @ (posedge clk or negedge rst_n)begin
		Gsum <= Grow1 + Grow2 + Grow3;
	end
	always @ (posedge clk or negedge rst_n)begin
		dout <= Gsum[11:4];
	end

在第一个时钟周期内计算三行的像素加权和,Grow1, Grow2, 和 Grow3 分别表示3×3邻域内上行、中间行和底行的加权和。
在第二个时钟周期内计算了为三个行的总和(Grow1 + Grow2 + Grow3),这个总和(Gsum)表示经过高斯滤波后的中心像素值。
在第三个时钟周期内进行归一化,将Gsum的计算结果除以16,在计算中可以将数据右移4位来表示,也可以表示为将 dout赋值为Gsum[11:4]。这种方法舍弃了低于4位的数据,会造成少量影响。最后两个时钟周期内计算量不大,但是三时钟的设计使得设计更容易收敛,能够运行在更高的时钟频率并且保持数据同步。
在高斯滤波器的模块模拟测试中,激励信号为10段数据,每段数据中每个时钟周期数据递增,也就是一共1到100的数据。在形成卷积窗口时默认采用了边缘复制。

边缘复制仿真

图像数据输入后一个时钟周期形成了卷积窗口,再经过三个时钟周期输出卷积结果。仿真输出的结果为实际高斯滤波器向下取整后的数据,即仿真符合实际。

中值滤波

$$ f(i,j)=\sum_{m,n} I(i+m,j+n)A(m,n) $$$$ f(x,y) = \underset{(s,t)\in I_{xy}}{median} (f(s,t)) $$

中值滤波作为一种全局像素处理技术,需要多次对卷积窗口中的数据排序,从而导致巨大的计算量,耗费大量机选资源,所以会明显增加系统的延迟。为了优化这一过程,本系统采用了FPGA硬件的并行数据处理能力,实施了数据分组及比较。通过采用三级流水线技术,本系统不仅提升了运算速度,还降低了硬件资源与功率的消耗。

三级流水线
该方法采用类似于冒泡排序的算法,拿3×3的中值滤波卷积核来说进行中值滤波的计算,首先,需要将窗口内各行的像素进行大小排序,此步骤耗时一个时钟周期。紧接着,系统对三个最大值、三个中间值以及三个最小值重新进行大小排序,同样消耗一个时钟周期。最终,在第二次排序的基础上,选择最大值中的最小者、最小值中的最大者以及中间值中的中间者进行最后一次大小排序,这一过程也需一个时钟周期。所得结果即为所处理窗口的中值。
中值滤波器在程序设计中体现为调用了7次排序模块。其中的部分模块首尾相连,形成了三级。每个时钟周期对一级进行排序,也就形成了流水线结构。
在中值滤波器的模块模拟测试中,激励信号同样为10段递增的数据,在边缘处进行了复制处理。
中值滤波器仿真

仿真输出的结果为卷积窗口的中值,即仿真符合实际。

边缘检测

边缘检测理论

$$ G\left(x,y\right)=\nabla f\left(x,y\right)=\left[\begin{matrix}G_x\\G_y\\\end{matrix}\right]=\left\lfloor\begin{matrix}\frac{\partial f\left(x,y\right)}{\partial x}\\\frac{\partial f\left(x,y\right)}{\partial y}\\\end{matrix}\right\rfloor $$$$ \left|\nabla f\left(x,y\right)\right|=\sqrt{G_x^2+G_y^2} $$$$ \theta=\arctan{\left(\frac{G_X}{G_y}\right)} $$$$ G_x\left(x,y\right)=\frac{\partial f\left(x,y\right)}{\partial x}=lim∆i→0fi+∆i,j-fi,j∆i≈fi+1,j-fi,j $$$$ G_y\left(x,y\right)=\frac{\partial f\left(x,y\right)}{\partial y}=lim∆j→0fi,j+∆j-fi,j∆i≈fi,j+1-fi,j $$$$ G\left(x,y\right)=\max{\left(\left|G_x\right|,\left|G_y\right|\right)} $$$$ G\left(x,y\right)=\left|G_x\right|+\left|G_y\right| $$$$ A\left(x,y\right)\ast f\left(x,y\right)=\sum_{\tau=1}^{n}f\left(\tau\right)A\left(n-\tau\right) $$

Roberts

$$ G\left(i,j\right)=\left\{\left[f\left(i+1,j+1\right)-f\left(i,j\right)\right]^2+\left[f\left(i+1,j\right)-f\left(i,j+1\right)\right]^2\right\}^\frac{1}{2} $$$$ A_x=\left\lfloor\begin{matrix}1&0\\0&-1\\\end{matrix}\right\rfloor A_y=\left\lfloor\begin{matrix}0&-1\\1&0\\\end{matrix}\right\rfloor$$

Roberts 算子在图像处理中有一个显著的优点,那就是它对垂直边缘的检测效果出色。与之伴随的是,对噪声的灵敏度增强。显然 Roberts 算子无法抑制噪声的影响,因此提取到的边缘效果可能会差,边缘位置也不太精准。

Prewitt

$$ A_x=\left\lfloor\begin{matrix}-1&0&1\\-1&0&1\\-1&0&1\\\end{matrix}\right\rfloor A_y=\left\lfloor\begin{matrix}1&1&1\\0&0&0\\-1&-1&-1\\\end{matrix}\right\rfloor $$

从实际的过滤结果来看,由于 Prewitt 算子综合了差分运算与领域平均的方法,它的边缘检测更加精确,图像边缘变宽,对噪声有平滑作用。由此看来此算子更进一步提高了抗噪声性能。

Sobel

$$A_x=\left\lfloor\begin{matrix}-1&0&+1\\-2&0&+2\\-1&0&+1\\\end{matrix}\right\rfloor A_y=\left\lfloor\begin{matrix}-1&+2&+1\\0&0&0\\-1&-2&-1\\\end{matrix}\right\rfloor$$

Sobel 算子的模板通常没有严格按照位置来加权,如果将其中的加权系数从 $2$ 替换为 $\sqrt2$,就能得到各向同性的 Sobel 算子。 在本次设计中,采用了以下的三级计算方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
always @ (posedge clk or negedge rst_n)begin
    Sx1 <= matrix_11 + (matrix_21 << 1) + matrix_31;
    Sx3 <= matrix_13 + (matrix_23 << 1) + matrix_33;
    Sy1 <= matrix_11 + (matrix_12 << 1) + matrix_13;
    Sy3 <= matrix_31 + (matrix_32 << 1) + matrix_33;
end
always @ (posedge clk or negedge rst_n)begin
    Sx <= (Sx1 > Sx3) ? (Sx1 - Sx3) : (Sx3 - Sx1);
    Sy <= (Sy1 > Sy3) ? (Sy1 - Sy3) : (Sy3 - Sy1);
end
always @ (posedge clk or negedge rst_n)begin
    Ssum = Sx + Sy;
    dout = (Ssum > 16) ? 10'd0 : 10'd1023;
end
在第一个时钟周期内计算水平和垂直的梯度,Sx1 和 Sx3 计算了3×3邻域内左右列的像素值的加权和。Sy1 和 Sy3 计算了同一3×3邻域内上下行的像素值的加权和。然后通过移位操作« 1实现了乘以2的操作。 在第二个时钟周期内计算梯度差的绝对值,使用条件表达式来计算Sx1和Sx3之间以及Sy1和Sy3之间的绝对差值,结果存储在Sx和Sy中。这一步骤是为了获取在每个方向上的边缘强度。 在第三个时钟周期内进行结果合成,将Sx和Sy的和赋给Ssum,即总的边缘强度。 输出dout基于Ssum的值确定输出。如果Ssum大于阈值16,认为不是边缘,输出为0;否则输出为最大值1023,表示是。

Laplacian

$$\nabla^2f=\frac{\partial^2f}{\partial x^2}+\frac{\partial^2f}{\partial y^2}$$$$G\left(i,j\right)=4f\left(i,j\right)-f\left(i+1,j\right)-f\left(i-1,j\right)-f\left(i,j+1\right)-f\left(i,j-1\right)$$$$A_1=\left\lfloor\begin{matrix}0&-1&0\\-1&4&-1\\0&-1&0\\\end{matrix}\right\rfloor A_2=\left\lfloor\begin{matrix}-1&0&-1\\0&4&0\\-1&0&-1\\\end{matrix}\right\rfloor$$

通过Laplacian 算子的卷积核可以看出,当卷积窗口中所有的数据相同时,它的计算结果是零。假设中心像素的数据大于周围像素的平均数据,则计算结果是正数;相反,假设中心像素的数据小于周围像素的平均数据,则计算结果是负数。而且,调整卷积窗口中心的系数可以有效增强图像的锐化效果。这种方法通过对比中心与邻近像素的灰度差异,实现图像边缘的增强。
值得注意的是,使用二阶导数的过零点来检测边缘会造成噪声的急剧增强,因此,Laplacian算子更适合于在噪声较低的环境中进行图像边缘检测,或者在使用高斯函数对图像进行预处理之后再处理。

Laplacian of Gaussian

$$G\left(x,y,\sigma\right)=\frac{1}{2\pi\sigma^2}e^{-\frac{x^2+y^2}{2\sigma^2}}$$$$\nabla^2f=\frac{\partial^2f}{\partial x^2}+\frac{\partial^2f}{\partial y^2}$$$$\nabla^2G\left(x,y,\sigma\right)={\frac{x^2+y^2-2\sigma^2}{\sigma^4}e}^{-\frac{x^2+y^2}{2\sigma^2}}$$$$A=\left\lfloor\begin{matrix}-1&-1&-1\\-1&8&-1\\-1&-1&-1\\\end{matrix}\right\rfloor$$

在本次设计中,采用了以下的三级计算方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
always @ (posedge clk or negedge rst_n)begin
    L1 <= matrix_11 + matrix_12 + matrix_13 + matrix_21;
    L2 <= matrix_31 + matrix_32 + matrix_33 + matrix_23;
    L3 <= matrix_22 * 8;
end
always @ (posedge clk or negedge rst_n)begin
    Lsum <= (L3 > L1 + L2) ? (L3 - L1 - L2) : (L1 + L2 - L3);
end
always @ (posedge clk or negedge rst_n)begin
    dout <= Lsum[7:0];
end
在第一个时钟周期内计算不同部分的像素加权和,L1 和 L2 分别计算边缘区域的像素值之和。L3 作为中心像素的权重是8。
在第二个时钟周期内进行加权和的计算,使用条件表达式为Lsum 计算为核心像素加权(L3)与周围像素加权和(L1,L2)的绝对差值。此处反映了LoG算子的特点,即在边缘处产生零交叉。
在第三个时钟周期内调整数据位宽以匹配输出格式。

Canny

Canny 算子被认为是目前效果最好的的边缘检测方法之一,结合了一阶,二阶导数,非极值抑制与双阈值递归寻找图像边缘点。这些算法实现了较低的错误率,边缘辨识能力提高,同时大限度地抑制了噪声的影响。下面简略说明 Canny 算子实现步骤。

Canny

由于边缘检测算法对直接从摄像头获取到的原始图像数据容易受到影响,因此第一步通常是使用高斯滤波器对图像进行处理,以过滤掉高频噪点。这样的目的是增加图像的平滑程度,避免引起差分算子对噪点的强烈响应。然后通过 Sobel 算子计算出图像数据的梯度及其方向。再使用非极大值抑制的方式使边缘变得更加清晰。最后确定一个阈值来进行判断是否为图像边界,Canny 算子使用了滞后阈值来跟踪图像中的边缘线条,最终实现了最优的边缘检测算法,即好的检测,好的定位与最小响应。
为了提高边缘检测的精确性并减少噪声的影响,我们已经看到了许多新的图像边缘检测方法的出现,这些方法基于数学形态学和分形理论、小波,以及近些年来的神经网络、遗传算法和亚像素等技术。然而,边缘检测并没有通用的理论,因此需要对其算法、方法、实现以及在特定领域的应用进行更深入的研究。

形态学滤波

形态学,也就是数学形态学,是一门建立在格论和拓扑学基础之上的图像分析学科,是数学形态学图像处理的基本理论,在图像处理中占有相当重要的地位。
其核心形态学滤波涵盖了许多运算,其中最基本的两个形态学操作是腐蚀和膨胀。其他的高级形态学操作都是基于这两个基本的形态学操作进行的,比如开运算、闭运算、形态学梯度、顶帽、黑帽等。主要用于图像中成形的有意义的部分,这成为了后续的图像辨识任务的基础与核心。同时,像精细化、像素化和剪除毛刺等技术也经常应用于图像的预处理和后处理阶段,补充了图像处理技术的空缺。

腐蚀

腐蚀(Erode)是一种取卷积窗口最小值的滤波,也就是用卷积核中的最小值作为中心像素的像素值,对于黑色背景,白色线条的图像经过腐蚀后的效果简单来说就是线条会变细。以本次设计的 3×3 卷积核,图示如下:

腐蚀

膨胀

膨胀(Dilate)是一种取卷积窗口最大值的滤波,也就是用卷积核中的最大值作为中间像素的像素值,对于黑色背景,白色线条的图像经过腐蚀后的效果简单来说就是线条会变粗。以本次设计的 3×3 卷积核为例,图示如下:

膨胀

灰度电路设计

$$Y=0.299R+0.587G+0.114B$$$$Cb=-0.1687R-0.3313G+0.5B+128$$$$Cr=0.5R-0.4187G-0.0813B+128$$

根据算数表达式,写出三级流水线设计为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
always @ (posedge clk or negedge rst_n)begin
    {R1,G1,B1} <= { {R0 * 16'd77},  {G0 * 16'd150}, {B0 * 16'd29 } };
    {R2,G2,B2} <= { {R0 * 16'd43},  {G0 * 16'd85},  {B0 * 16'd128} };
    {R3,G3,B3} <= { {R0 * 16'd128}, {G0 * 16'd107}, {B0 * 16'd21 } };
end
always @ (posedge clk or negedge rst_n)begin
    Y1  <= R1 + G1 + B1;
    Cb1 <= B2 - R2 - G2 + 16'd32768; 
    Cr1 <= R3 - G3 - B3 + 16'd32768;
end
always @ (posedge clk or negedge rst_n)begin
    Y2  <= Y1[15:8];  
    Cb2 <= Cb1[15:8];
    Cr2 <= Cr1[15:8];
end
这些计算分为三个时钟周期完成,首先计算每个颜色分量的缩放值,然后求和,最后将结果归一化到8位。为了保证数据的同步性,RGB的有效信号和同步信号也经过三级寄存器延迟处理,以确保与处理后的数据同步输出。

VGA 显示模块

VGA以其高分辨率、快速的显示率和丰富的色彩表现,在模拟彩色显示器领域获得了广泛应用。
在本研究中,我们设计并实现了一个多分辨率显示控制模块。该模块主要用于处理不同分辨率的视频输出,并能够在不同分辨率之间动态切换。核心功能包括像素时钟生成、分辨率切换、视频同步信号处理及颜色数据的处理。此模块应用于视频图像的显示,支持VGA接口输出。

RGB565

RGB565是一种16位的颜色深度格式,用于在有限的存储空间中表示丰富的颜色信息。在这种格式中,红色(R)、绿色(G)、蓝色(B)三种颜色分别被分配不同的位宽,即红色5位、绿色6位、蓝色5位。这种分配方式是基于人眼对绿色光敏感度较高的特性,通过为绿色提供多一位的位宽,能够在不增加存储需求的同时,提高图像的视觉质量。

VGA显示模块设计实现

VGA显示模块

1.模块输入输出定义
模块接受100 MHz的时钟信号clk_100m和三个控制按钮btnC, btnU, btnD,分别用于复位和分辨率向上及向下的切换。模块输出包括视频图像的坐标sx, sy,像素时钟clk_pix,水平同步信号Hsync和垂直同步信号Vsync,以及红绿蓝颜色信号(各4位)。
2.像素坐标的生成
1
2
3
4
5
6
7
8
clk_480p_720p_1080p clock_pix_inst (
    .clk_100m,
    .reset(btnC),
    .clk_480p(clk_pix1),
    .clk_720p(clk_pix2),
    .clk_1080p(clk_pix3),
.locked(clk_pix_locked)
);
上述代码通过调用Vivado的时钟管理IP核实例化clk_480p_720p_1080p模块,定义三个像素时钟clk_pix1、clk_pix2、clk_pix3,对应480p、720p和1080p的像素时钟频率。该模块输出三种不同频率的时钟信号clk_pix1, clk_pix2, clk_pix3,并通过逻辑运算与分辨率索引相结合,动态选择相应的像素时钟输出到clk_pix。
在每个clk_pix时钟的上升沿,sx坐标递增。当sx达到当前行的末端(例如320像素宽的显示行),sx重置为0,同时sy递增,表示移动到下一行。当一行像素处理完成后,sy坐标递增,开始处理下一行。当所有行都处理完成后(例如在240行高的显示情况下),sy也会重置,从而开始一个新的图像帧的处理。
为了确保像素数据正确显示,对像素坐标需要适当的进行边界检查。当sx或sy坐标超过屏幕分辨率设置的界限时,例如sx > 320或sy > 240,坐标将被重置或调整。这样做是为了防止数据写入非显示区域,保证显示数据的完整性和正确性。以上代码较为冗长,不在此处展示。
3. 坐标与内存地址映射
处理好像素坐标后就能够通过相应的地址从BRAM中读取需要显示的像素值。
1
2
3
4
5
6
always @(*)begin
    (sx>320||sy>240) begin
        addr_r=0;
    end else 
        addr_r=sx+sy*320;
end
内存读取地址通过公式addr_r = sx + sy * width计算,其中width是图像的宽度。在当前的系统中,宽度设置为320像素,那么每递增一行,地址增加320。
4. 显示分辨率切换
该部分能够根据用户输入动态切换显示分辨率,支持480p、720p和1080p三种分辨率。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
always @(*) begin
    if(btnU ^ btnD)begin
        if(chingingRes==0)begin
            chingingRes = 1;
            if(btnU)begin
                if(indexOfRes<2)            //上边界
                    ++indexOfRes;
                end else if (indexOfRes>0)      //下边界
            --indexOfRes;
        end
    end else chingingRes=0;
end
按钮btnU(向上)和btnD(向下)用于控制indexOfRes的值。当btnU按下且当前分辨率不是最大时,indexOfRes增加;当btnD按下且当前分辨率不是最小时,indexOfRes减小。chingingRes这个变量被用作一个锁存器或标志,在未进行切换时为0,在检测到按钮单独按下时变为1。保证了在按钮持续按下的情况下不会反复触发分辨率切换。
在最终的设计中取消了这一设计,因为视频信号的带宽与延时需求极大,为了避免对本文核心的图像处理功能资源争夺。经过实际测试,会造成图像质量下降,或者时钟不收敛等情况,故将分辨率固定为480P。

电路设计分析与总结

烧录测试结果

显示器
输出到显示器的分辨率为320240,从摄像头获取到的分辨率也就是显示模块(display)的输出分辨率为480320,对其他区域填充黄色像素点RGB:FA0。由实际的显示器的输出没有黑边可以看出,这个显示器对FPGA输出的图像信号进行了拉伸或填充处理。
来自摄像头的图象之所以被放到了显示器的左上角对齐,是因为不同的VGA显示器(特别是受分辨率比例的影响)会针对输入的分辨率进行适配处理,也就是说即使针对某一个显示器进行了图像的居中放置,这在其他显示器上并非同样如此,所以我认为对图像居中放置实属不必。
下面展示图像处理结果,其中处理所需时钟周期计算了数据从形成RGB到达BRAM的时钟周期与从BRAM到VGA输出的时间,因为驱动时钟不同,造成了BRAM的跨域访问,故以相加的形式展示。具体每级时钟延迟请参照图5.14与图5.15。
原画

处理所需时钟周期:3 + 1 clk
图像原画不经过任何处理。
反相

处理所需时钟周期:3 + 1 clk
图像反相经过一次色彩反相处理,明显图中黑白色阶交换了
增加亮度

处理时钟周期:3 + 1 clk
增加亮度经过一次色彩增亮处理,但是其中的噪点更加明显。
降低亮度

处理时钟周期:3 + 1 clk
降低亮度经过一次色彩减亮处理,左上部分因为色深不足,丢失了其中信息。
高斯滤波

处理时钟周期:7 + 1 clk
高斯滤波经过一次线性滤波处理,图中的噪点明显减少,图像更加平滑。
图像压花
处理时钟周期:7 + 1 clk
图像压花经过一次线性滤波处理,图像呈现出压花的立体感,噪点体积变大。
Sobel边缘检测
处理时钟周期:7 + 1 clk
Sobel边缘检测经过一次线性滤波处理。
因噪声过多,图像边缘勉强能够辨识。
LoG边缘检测
处理时钟周期:7 + 1 clk
LoG边缘检测经过一次线性滤波处理。
因包含高斯滤波,效果比Sobel好一些。
中值滤波
处理时钟周期:7 + 1 clk
中值滤波经过一次非线性滤波处理,去除了几乎所有的椒盐噪声。
LoG_腐蚀
处理时钟周期:11 + 1 clk
LoG + 图像腐蚀经过一次线性滤波与一次非线性滤波处理,图像边缘变细。
LoG_膨胀
处理时钟周期:11 + 1 clk
LoG + 图像膨胀经过一次线性滤波与一次非线性滤波处理,图像边缘变粗。
LoG_膨胀_反相
处理时钟周期:11 + 1 clk
比上一种效果增加了色彩反相处理,增加了部分的可辨识度。

整体系统分析

在深入了解了图像处理之后,将进一步讨论 FPGA 的其他部分设计以构建出完整的图像处理系统。
在本文的研究中,设计的初衷是对RGB数据进行深入的处理,以便更好地理解和利用图像数据。RGB数据的三个通道(红色、绿色和蓝色)会分别进行卷积处理。这是因为每个通道都包含了图像的不同信息,单独处理可以更好地保留这些信息。这种方法允许我对每个颜色通道进行独立的优化,从而提高整体的图像质量,但是这样会占用较多的计算资源,在最后的 FPGA 资源统计中 BRAM 资源达到了 93%使用率。
有一个值得提及的点是,系统被设计为在卷积处理之后才进行颜色空间的转换。从RGB565格式转换为YCbCr格式,我选择了Y通道的数据进行后续的处理。这是因为在YCbCr格式中,Y通道代表的是图像亮度,它包含了图像的大部分视觉信息。通过这种方式,我们的设计能够有效地减少数据量,同时保留了图像的主要特征。

数据流分析

本节将详细介绍CMOS传感器的RGB信号通过各种滤波器和转换的数据处理路径。该过程涉及线性和非线性滤波,然后将RGB信号转换为YCbCr格式。

从CMOS到BRAM的数据流

图像数据首先从OV 5640的COMS图像传感器存储到其内部的FIFO寄存器中, 然后通过DVP接口读取出来。根据数据手册中RGB 565的输出时序图,每一帧能够输出8 bit的数据,因此,RGB 565 就需要两帧才能够表示一个完整像素点,需要手动的对这个数据进行拼接。
在拼接RGB 565数据的时候,他的数据输出顺序也是可以通过设置摄像头的寄存器进行改变。同时,对于数据的输出我一般会附加一个数据有效信号,这样方便后续信号的处理,比如可以通过这个有效信号的上升沿来触发一些信号的处理。至此,我们就形成了的一个像素完整的RGB 565数据。
随后,图像数据的三个RGB通道分别连接到了一个选择器与一个线性图像滤波。另外,为了保证数据能够不经过再次处理而根据现有信息直接输出的显示器上,对于图像滤波这一条支路,将从摄像头传来的水平同步信号与垂直同步信号进行了延时处理,延迟时间与信号处理时间相同,为四个时钟周期。这两个分支的数据最后都到达了一个数据选择器,这个数据选择器对上一级的数据进行选择作为下一级的数据的输入。正是因为这个数据选择器的存在,使得我们可以形成这样的流处理,根据实际需要在每一级中对它进行处理。
再次,图像数据的三个RGB通道分别连接到了一个选择器与一个非线性图像滤波器,在滤波器这个分支对图像数据进行了处理和对同步信号的延迟。最后汇集到一个数据选择器上根据需要,选择输出不同的处理过程。
经过两极的图像滤波之后就需要对图像进行一个存储与显示,考虑到FPGA中BRAM的存储空间十分有限和本次所使用的FPGA开发板图像输出色深为4位,我们对图像处理之前先由RGB的色彩空间转换到了YCbCr,提取出其中的外通道作为灰度值,图像数据由先前的16位,压缩到了4位。
从BRAM到VGA的数据流

在BRAM到VGA Monitor的数据流中。BRAM作为存储元素,贮藏每个像素的4位数据。display模块通过生成addr_r即在BRAM中根据像素点坐标生成的读取地址访问BRAM。读取出来的数据流向Change Color模块,该模块有三个功能,分别是反相,增加亮度和减少亮度;进行该操作需要花费不到一个时钟周期,因为BRAM的数据被链接到了触发器,每当这个数据变化时,会直接将数据处理好并且送给display模块。最后,将处理好的图像数据按照VGA的信号时序的通过VGA接口输出到显示器中。VGA显示器根据水平与垂直同步信号来确定像素的位置。图像处理系统中这一连串协同工作的各模块确保了最终输出图像的质量与一致性。
经Vivado软件上的模拟、综合与实现,本文设计的图像处理模块在Basys 3的FPGA芯片上均能完全运行,并且布局布线的设计能够完全收敛。烧录到开发板上验证结果,能完全符合模块的模拟结果。

硬件信息分析

资源使用率

图中显示了当前项目的资源利用情况报告。
其中值得注意的是,BRAM的利用率高达93%,在存储资源方面已接近其上限,限制了拓展更多逻辑电路设计的可能性。I/O和MMCM的利用率因为需要对图像进行实时处理,所以造成较高的资源占用。DSP单元的利用率仅为2.22%说明在当前设计中并没有充分利用FPGA在高速数学运算方面的优势,这是因为并没有对图像进行过于复杂的处理。
电源功耗

图中显示了当前项目的功耗分析摘要。
动态功耗:总共0.332瓦特,占比82%,主要集中在以下模块:时钟管理器(MMCM):最高功耗为0.219瓦特,占动态功耗的66%。I/O:0.048瓦特,占14%。信号:0.022瓦特,占7%。逻辑:0.013瓦特,占4%。块RAM (BRAM):0.022瓦特,占6%。DSP:0.002瓦特,占1%。时钟:0.005瓦特,占2%。
静态功耗:总共0.075瓦特,占比18%。
根据以上功耗占比情况可以看出,时钟管理器(MMCM)是动态功耗中的最大的模块,这主要是由于display所生成的高频像素时钟所引起的。I/O模块的功耗次之,这可能是开发板与OV5640摄像头通信以及输出VGA信号造成的。而BRAM、DSP和逻辑单元的低功率则体现出了FPGA低功耗的优点。