01
图像处理算子概述
FPGA最大的优势体现在其低功耗和并行计算的特性。数字图像包含大量数据。使用FPGA可以有效提高图像算法的实时性,同时保证低功耗计算。
图像处理算子/卷积核是最常用的并行运算手段。 ——个图像处理算子/卷积核对图像矩阵从左到右、从上到下进行循环线性运算。循环过程中的各个操作都是相互独立的,没有先后顺序。依靠。
掌握基于FPGA的图像处理算子/卷积核实现方法对于提高图像算法和神经网络运算的实时性具有重要意义。
1.1 图像处理算子的概念
图像处理算子是处理图像时使用的算子。根据计算机视觉(Computer Vision)图像内容表示方法的不同,可以分为基于全局特征的图像内容表示方法下的全局特征描述算子和基于局部特征的图像内容表示方法下的局部特征描述算子。虽然根据图像的不同特征设计了不同的图像算法,但全局特征描述算子和局部特征描述算子在本质上并没有明显的区别。
与卷积神经网络类似,当算法设计采用多级/层运算时,每级/层使用的图像处理算子/卷积核的尺寸越小,越有利于运算深度的扩展。因此,本文主要介绍局部特征描述算子。
图像处理算子设计通常要求具有可重复性、可判别性、局部不变性、信息丰富、定量描述、准确高效等特性。根据图像处理算子的应用功能,可以分为微分算子、基于矩的描述算子、基于滤波器的描述算子和基于分布统计的描述算子。因此,图像处理算子在图像增强领域得到广泛应用。
1.2 图像处理算子函数
图像处理算子/卷积核设计尺寸通常采用奇数:33、55、77……这样通过中心点(中心像素)来确定像素位置信息,方便算子的实现基于中心点。滑动操作。
图像处理算子/卷积核设计尺寸在某些情况下也会使用偶数:基于分布统计的描述算子——SIFT描述算子使用448维度来描述图像的尺度不变特征。
由于奇数尺寸图像处理算子/卷积核应用广泛,这里以一阶微分算子为例,简单介绍一下图像处理算子的工作原理:
对图像进行上述一阶微分运算:
相应的一阶微分算子G满足:
使用一阶微分算子G对数字图像进行循环,得到一阶微分处理后的图像结果:
微分运算常用于研究相邻像素之间的差异,主要应用于图像边缘提取操作。具体原理在微分算子中有详细介绍。
02
图像处理算子原理
图像处理算子根据应用功能的不同主要可以分为四类:微分算子、基于矩的描述算子、基于滤波器的描述算子和基于分布统计的描述算子。本节以微分算子为例介绍图像处理算子的设计和原理。
2.1 离散数据差分运算
微分算子用于对图像进行微分运算,通常用于实现边缘检测、图像二值化等功能。连续函数的微分运算通常定义为:
相应地,二元函数的偏微分运算定义为:
数字图像像素是一组二维离散数据,h不可能趋近无穷小,因此其导数以微分方差的形式近似:
微分方差可以分为不同的形式:
不同的微分形式在解决数据微分运算时可能会存在一定的误差。通常,根据不同的应用场景灵活选择差分形式。以一维离散数据为例,利用差分方差求解一阶微分和二阶微分的结果可以表示为:
2.2 微分算子的设计原则
基于离散数据的微分运算方程,以33算子为例,讲解数字图像微分算子的设计原理。为了便于后续解释原理,约定对于以(x,y)为中心像素的33数字图像区域,每个像素根据其坐标位置命名:
微分算子通过矩阵乘法对数字图像进行微分运算:
因此,可以根据不同的微分方程构造不同的图像微分算子。以下是三种典型的微分算子设计:
1、一阶微分算子设计:
2、二阶微分算子的设计:
3.拉普拉斯算子设计:
2.3 微分算子实现效果
以拉普拉斯算子为例,利用拉普拉斯算子对原始拍摄图像进行差分处理后,得到边缘提取后的图像。提取的边缘被添加到原始图像中以锐化原始图像。操作。
03
图像处理算子实现
并行计算是FPGA图像处理的主要优势。通过图像处理算子对图像区域进行并行处理,可以有效提高算法运行速度,提高系统的实时性。
以33区域为例,由于图像数据通常是按照从上到下、从左到右的顺序逐像素扫描的,因此无法直接读取中心周围33范围内的所有数据。像素。因此,本节采用FIFO缓存的方式,直接从中心像素中提取相邻33区域中的像素值,为后续图像处理算法提供基础模板。
3.1 图像处理算子实现策略
由于数字图像数据是逐行、逐列、逐像素读取的,考虑到读取33区域中的数据需要缓存辅助实现。因此,使用两个FIFO来存储当前读取像素位置的前两行数据,如下图所示。此时,在读取像素的同时,可以根据坐标关系从FIFO中读出33区域内的所有其他像素值。需要注意的是,该方法读取的区域并不是以当前读取的像素为中心,而是以I(x-1,y-1)为中心像素的33区域。
读取过程中值得关注的一个问题是边界像素的处理。例如,扫描第一个数据时,相邻的33区域像素需要包含图片灰色部分的五个像素值,而这五个像素值实际上并不存在。如果边界像素处理不好,图像处理效果可能会大大降低:以微分运算为例,当边缘像素处理不当时,图像周围可能会出现明显的边缘特征,严重影响实际的边缘提取操作。
对于边缘像素的处理,通常根据不同的应用场景采用不同的处理方法,以提高算法的性能。最常用的处理方法有两种:
丢弃边缘像素值,保证算子不超出图像区域;
扩展边缘(图中灰色部分)被赋予初始值0或255(8位数据)。
3.2 图像处理算子Verilog代码
在Verilog中编写图像算子生成代码cx_operator.v,并导入FIFO Generator IP核来缓存前两行图像数据。使用sim_tb.v作为仿真文件输出波形来验证算子的正确性。
各模块代码如下:
1.算子生成模块cx_operator.v:
`时间刻度1ns/1ps
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Company: Cascatrix
//工程师:卡森
//
//创建Date: 2023/04/02
//设计名称: Image_Processing_Operator
//模块名称: cx_operator
//工具版本: v1.0
//描述: 生成图像处理算子
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
模块cx_operator(
输入线时钟,
输入wirerst_n,
输入线,
输入线[7:0]数据,
输出寄存器[7:0]operator_11,
输出寄存器[7:0]operator_12,
输出寄存器[7:0]operator_13,
输出寄存器[7:0]operator_21,
输出寄存器[7:0]operator_22,
输出寄存器[7:0]operator_23,
输出寄存器[7:0]operator_31,
输出寄存器[7:0]operator_32,
输出寄存器[7:0]operator_33
);
参数H_ACTIVE=640;
参数V_ACTIVE=480;
reg [10:0]h_cnt;
reg[10:0]v_cnt;
reg fifo_1_wr_en;
reg fifo_1_rd_en;
电线[7:0]fifo_1_in;
电线[7:0]fifo_1_out;
reg fifo_2_wr_en;
reg fifo_2_rd_en;
电线[7:0]fifo_2_in;
电线[7:0]fifo_2_out;
//水平像素数
总是@(posege clk或negedge rst_n)
开始
如果(!rst_n)
h_cnt=11'b0;
否则如果(zh)
开始
if(h_cnt==H_ACTIVE - 1)
h_cnt=11'b0;
别的
h_cnt=h_cnt + 1'b1;
结尾
结尾
//垂直像素数
总是@(posege clk或negedge rst_n)
开始
如果(!rst_n)
v_cnt=11'b0;
否则if(h_cnt==H_ACTIVE - 1)
开始
if(v_cnt==V_ACTIVE - 1)
v_cnt=11'b0;
别的
v_cnt=v_cnt + 1'b1;
结尾
结尾
//第一个FIFO 的写使能信号
总是@(posege clk或negedge rst_n)
开始
如果(!rst_n)
fifo_1_wr_en=1'b0;
否则if(v_cnt V_ACTIVE - 1)
fifo_1_wr_en=en;
别的
fifo_1_wr_en=1'b0;
结尾
//第二个FIFO 的写使能信号
总是@(posege clk或negedge rst_n)
开始
如果(!rst_n)
fifo_2_wr_en=1'b0;
否则如果(v_cnt 0)
fifo_2_wr_en=en;
别的
fifo_2_wr_en=1'b0;
结尾
//读取第一个FIFO 的使能信号
总是@(posege clk或negedge rst_n)
开始
如果(!rst_n)
fifo_1_rd_en=1'b0;
否则如果(v_cnt 0)
fifo_1_rd_en=en;
别的
fifo_1_rd_en=1'b0;
结尾
//读取第二个FIFO 的使能信号
总是@(posege clk或negedge rst_n)
开始
如果(!rst_n)
fifo_2_rd_en=1'b0;
否则如果(v_cnt 1)
fifo_2_rd_en=en;
别的
fifo_2_rd_en=1'b0;
结尾
//先进先出数据输入
分配fifo_1_in=数据;
分配fifo_2_in=fifo_1_out;
//第一个FIFO的实例
cx_fifo inst_row_1(
.时钟(时钟),
.srst(!rst_n),
.din (fifo_1_in),
.wr_en(fifo_1_wr_en),
.rd_en(fifo_1_rd_en),
.dout(fifo_1_out)。满的()。空的()
);
//第二个FIFO的实例
cx_fifo inst_row_2(
.时钟(时钟),
.srst(!rst_n),
.din (fifo_2_in),
.wr_en(fifo_2_wr_en),
.rd_en(fifo_2_rd_en),
.dout(fifo_2_out)。满的()。空的()
);
//33算子生成
总是@(negedge clk或negedge rst_n)
开始
如果(!rst_n)
开始
运算符_11=8'd0;
运算符_12=8'd0;
运算符_13=8'd0;
运算符_21=8'd0;
运算符_22=8'd0;
运算符_23=8'd0;
运算符_31=8'd0;
运算符_32=8'd0;
运算符_33=8'd0;
结尾
别的
开始
运算符_11=运算符_12;
运算符_12=运算符_13;
运算符_13=fifo_2_out;
运算符_21=运算符_22;
运算符_22=运算符_23;
运算符_23=fifo_1_out;
运算符_31=运算符_32;
运算符_32=运算符_33;
运算符_33=数据;
结尾
结尾
终端模块
2.仿真模块sim_tb.v:
`时间刻度1ns/1ps
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Company: Cascatrix
//工程师:卡森
//
//创建Date: 2023/03/02
//设计名称: Image_Histogram_Statistic
//模块名称: sim_tb
//工具版本: v1.0
//描述:图像输出模拟
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
模块sim_tb(
);
寄存器时钟;
reg rst_n;
reg [31:0] Pixel_cnt;
线HSYN;
线VSYNC;
电线德;
电线[7:0]gray_data;
整数图像_txt;
参数PIXEL_TOTAL=1920*1080;
//参数PIXEL_TOTAL=1680*1050;
//参数PIXEL_TOTAL=1280*1024;
//参数PIXEL_TOTAL=1280*720;
//参数PIXEL_TOTAL=1024*768;
//参数PIXEL_TOTAL=800*600;
//参数PIXEL_TOTAL=640*480;
cx_top inst_cx_top
(
.clk(时钟),
.en (德),
.hsyn(hsyn),
.vsyn (vsyn),
.gray_data(灰色数据)
);
总是#1 clk=~clk;
最初的
开始
时钟=1;
rst_n=0;
#100
rst_n=1;
结尾
最初的
开始
image_txt=$fopen('D:/FPGA_Document/CX_
文档/CX_Image/03_Image_histogram_statistic
/image_src/image_out.txt');
结尾
总是@(posege clk或negedge rst_n)
开始
如果(!rst_n)
开始
像素_cnt=0;
结尾
否则如果(德)
开始
像素_cnt=像素_cnt + 1;
$fwrite(image_txt,'%h',gray_data);
结尾
结尾
总是@(posege clk)
开始
if(pixel_cnt==PIXEL_TOTAL ~vsyn)
开始
$display('CX: image_out.txt 输出成功完成!%t', $realtime, 'ps');
$fclose(image_txt);
$停止;
结尾
结尾
终端模块
3. FIFO 发生器IP 配置:
Basic 将FIFO 配置为Native 接口类型:
Native Ports 将FIFO 位宽配置为8,深度配置为2048,并启用Reset Pin:
状态标志不需要配置:
数据计数不需要配置:
3.3 从测试数据生成Matlab代码
为了方便算子的仿真测试,利用Matlab生成大小为640480的测试数据data_640_480.txt。数据内容从0到160循环,每行循环640/160=4次,每列重复。当仿真波形中各算子行对应列的数据相同时,即可验证算子时序正确。
Matlab测试数据生成代码:
%******************************************************** ***** *********************
% ------------------------------------------------- - ------------------
% Company: 卡斯卡特里克斯
% 工程师: 卡森
%
% 创建日期: 2023/04/02
% 设计名称: data_generate
% 模块名称: data_generate
% 工具版本: v1.0
% 描述: 为算子生成循环数据
%------------------------------------------------- ------------------
%******************************************************** ***** **********************/
清除;清除全部;clc;
% 数据大小
行=480;
列=640;
%数据初始化
数据=0;
% 创建.txt 文件
文件名=['data_640_480','.txt'];
% 打开数据文件
FileData=fopen(文件名,'w');
% 将数据写入文件
对于x=1: 行
对于y=1:col
fprintf(FileData,'%s',dec2hex(数据));
如果数据159
数据=数据+1;
别的
数据=0;
结尾
结尾
结尾
% 关闭数据文件
fclose(文件数据);
3.4 仿真结果分析
仿真波形验证结果如下:
1、写入第一行数据之前,将所有算子像素值设置为0,直接从operator3_读取第一行数据;
2、写入第二行数据时,第一行数据存入FIFO1并从operator2_读取,第二行数据直接从operator3_读取;
3、写入第三行数据时,第一行数据存入FIFO2并从operator1_读取,第二行数据存入FIFO1并从operator2_读取,第三行数据直接从operator3_读取;
4、按上述方式循环遍历数据,波形算子输出始终满足operator1x=operator2x=operator3x,即测试数据逐行对齐,验证33算子生成的时序正确,并实现从一个像素读取相邻像素值。功能。
审稿人:刘庆