FPGA视频、图像缩放算法介绍
视频缩放算法介绍:视频缩放算法包括最邻近插值,双线性插值,双线性三次插值,Lanczos插值算法等。算法的效率是最邻近算法>双线性插值算法>双线性三次插值>Lanczos算法,而算法的效果则恰恰相反
######双线性插值
在FPGA实现图像双线性插值,比较复杂。结果验证和 FPGA自带的效果差不多。占的资源比较少。
双线性插值的框架图:
双线性插值的运算单元:
这样就可以实现视频的缩放。
#####图像缩小的算法
实现了一个Avalon-ST总线的简单图像缩放模块,将640*480的图像输入,变成320*240大小的图像输出。主要思想是,得到图像的行列计数值,根据要缩放的比例,在相应的行列值使能valid信号,比如我要将640*480图像缩小成320*240,那么只需控制将其隔行、隔列使能valid就行了。其实就是一个降采样。代码直接贴出
- </pre><pre name="code" class="cpp">module ST_RESIZE(
- // global clock & reset
- input clk,
- input reset_n,
- // stream sink
- input [23:0] sink_data,
- input sink_valid,
- output sink_ready,
- input sink_sop,
- input sink_eop,
- // stream source
- output [23:0] source_data,
- output source_valid,
- input source_ready,
- output source_sop,
- output source_eop
- );
- localparam CTRL_PKT_NUM = 3;
- assign sink_ready = source_ready;
- assign source_sop = sink_sop;
- assign source_eop = sink_eop;
- //assign source_valid = sink_valid;
- //assign source_data = sink_data;
- parameter WIDTH = 640;
- parameter HEIGHT = 480;
- parameter w_LOG_WIDTH = 320;
- parameter w_LOG_HEIGHT = 240;
- reg state;
- localparam STATE_CTRL = 0;
- localparam STATE_DATA = 1;
- [email protected](posedge clk)begin
- if(~reset_n)
- state <= STATE_CTRL;
- else if( process_en && sink_sop&& sink_data == 24'd15)
- state <= STATE_CTRL;
- else if( process_en && sink_sop&& sink_data == 24'd0)
- state <= STATE_DATA;
- end
- reg [10:0] h_count;
- reg [10:0] v_count;
- wire process_en;
- assign process_en = source_ready & sink_valid;
- [email protected](posedge clk)begin
- if(~reset_n)
- h_count <= 11'b0;
- else if( process_en && sink_sop && sink_data == 24'd0)
- h_count <= 11'b0;
- else if( process_en && h_count< WIDTH-1 && state == STATE_DATA)
- h_count <= h_count + 1'b1;
- else if( process_en && h_count== WIDTH-1 && state == STATE_DATA)
- h_count <= 11'b0;
- end
- [email protected](posedge clk)begin
- if(~reset_n)
- v_count <= 11'b0;
- else if( process_en && sink_sop&& sink_data == 24'd0)
- v_count <= 11'b0;
- else if( process_en && h_count == WIDTH-1 && state == STATE_DATA)
- v_count <= v_count + 1'b1;
- end
- assign source_valid =(sink_valid & sink_sop & sink_data == 24'd15) |
- (sink_valid & state == STATE_CTRL )|
- (sink_valid & sink_sop & sink_data == 24'd00) |
- ( sink_valid & state == STATE_DATA & h_count[0]==0 & v_count[0]==0) |
- ( sink_valid & sink_eop )
- ;
- //assign source_data = sink_data;
- always @(*) begin
- case (state )
- STATE_CTRL :
- if(source_ready & sink_valid)begin
- if(dot_cnt==0)source_data = { 4'b0, w_LOG_WIDTH[ 7: 4], 4'b0, w_LOG_WIDTH[11: 8], 4'b0, w_LOG_WIDTH[15:12] };
- if(dot_cnt==1)source_data = { 4'b0, w_LOG_HEIGHT[11: 8], 4'b0, w_LOG_HEIGHT[15:12], 4'b0, w_LOG_WIDTH[ 3: 0] };
- if(dot_cnt==2)source_data = { 4'b0, 4'b0, 4'b0, w_LOG_HEIGHT[ 3: 0], 4'b0, w_LOG_HEIGHT[ 7: 4] };
- end
- STATE_DATA : source_data = sink_data;
- default : source_data = sink_data;
- endcase
- end
- reg [11:0] dot_cnt;
- always @(posedge clk) begin
- if (!reset_n) begin
- dot_cnt <= 0;
- end
- else begin
- if (dout_ready & source_ready)begin
- if ((pkt_state == STATE_CTRL) )begin // control packet
- if ( dot_cnt < (CTRL_PKT_NUM-1) )
- dot_cnt <= dot_cnt + 11'd1;
- else
- dot_cnt <= 0;
- end
- end
- end
- endmodule
- </pre><pre name="code" class="cpp">module ST_RESIZE(
- // global clock & reset
- input clk,
- input reset_n,
- // stream sink
- input [23:0] sink_data,
- input sink_valid,
- output sink_ready,
- input sink_sop,
- input sink_eop,
- // stream source
- output [23:0] source_data,
- output source_valid,
- input source_ready,
- output source_sop,
- output source_eop
- );
- localparam CTRL_PKT_NUM = 3;
- assign sink_ready = source_ready;
- assign source_sop = sink_sop;
- assign source_eop = sink_eop;
- //assign source_valid = sink_valid;
- //assign source_data = sink_data;
- parameter WIDTH = 640;
- parameter HEIGHT = 480;
- parameter w_LOG_WIDTH = 320;
- parameter w_LOG_HEIGHT = 240;
- reg state;
- localparam STATE_CTRL = 0;
- localparam STATE_DATA = 1;
- [email protected](posedge clk)begin
- if(~reset_n)
- state <= STATE_CTRL;
- else if( process_en && sink_sop&& sink_data == 24'd15)
- state <= STATE_CTRL;
- else if( process_en && sink_sop&& sink_data == 24'd0)
- state <= STATE_DATA;
- end
- reg [10:0] h_count;
- reg [10:0] v_count;
- wire process_en;
- assign process_en = source_ready & sink_valid;
- [email protected](posedge clk)begin
- if(~reset_n)
- h_count <= 11'b0;
- else if( process_en && sink_sop && sink_data == 24'd0)
- h_count <= 11'b0;
- else if( process_en && h_count< WIDTH-1 && state == STATE_DATA)
- h_count <= h_count + 1'b1;
- else if( process_en && h_count== WIDTH-1 && state == STATE_DATA)
- h_count <= 11'b0;
- end
- [email protected](posedge clk)begin
- if(~reset_n)
- v_count <= 11'b0;
- else if( process_en && sink_sop&& sink_data == 24'd0)
- v_count <= 11'b0;
- else if( process_en && h_count == WIDTH-1 && state == STATE_DATA)
- v_count <= v_count + 1'b1;
- end
- assign source_valid =(sink_valid & sink_sop & sink_data == 24'd15) |
- (sink_valid & state == STATE_CTRL )|
- (sink_valid & sink_sop & sink_data == 24'd00) |
- ( sink_valid & state == STATE_DATA & h_count[0]==0 & v_count[0]==0) |
- ( sink_valid & sink_eop )
- ;
- //assign source_data = sink_data;
- always @(*) begin
- case (state )
- STATE_CTRL :
- if(source_ready & sink_valid)begin
- if(dot_cnt==0)source_data = { 4'b0, w_LOG_WIDTH[ 7: 4], 4'b0, w_LOG_WIDTH[11: 8], 4'b0, w_LOG_WIDTH[15:12] };
- if(dot_cnt==1)source_data = { 4'b0, w_LOG_HEIGHT[11: 8], 4'b0, w_LOG_HEIGHT[15:12], 4'b0, w_LOG_WIDTH[ 3: 0] };
- if(dot_cnt==2)source_data = { 4'b0, 4'b0, 4'b0, w_LOG_HEIGHT[ 3: 0], 4'b0, w_LOG_HEIGHT[ 7: 4] };
- end
- STATE_DATA : source_data = sink_data;
- default : source_data = sink_data;
- endcase
- end
- reg [11:0] dot_cnt;
- always @(posedge clk) begin
- if (!reset_n) begin
- dot_cnt <= 0;
- end
- else begin
- if (dout_ready & source_ready)begin
- if ((pkt_state == STATE_CTRL) )begin // control packet
- if ( dot_cnt < (CTRL_PKT_NUM-1) )
- dot_cnt <= dot_cnt + 11'd1;
- else
- dot_cnt <= 0;
- end
- end
- end
- endmodule
另外,假如对于一幅图像(1920*1080),我想把它缩小到任意的大小,比如320*240大小:
对于水平的320像素,因为1920/320=6,因此只需设置一个列循环计数值,范围0-5,在等于5的时候,输出像素valid为1;
对于竖直方向的240,因为1080除以240不是整数,而是9/2。也就是说,需要每隔4.5行,就应该输出一个valid。为了使其整数化,可以等同为每隔9行输出两个valid。因此可以设置一个行循环计数值,范围0-8,在等于3或者等于8的时候,输出valid为1。代码如下:
- module ST_RESIZE(
- // global clock & reset
- input clk,
- input reset_n,
- // stream sink
- input [15:0] sink_data,
- input sink_valid,
- output sink_ready,
- input sink_sop,
- input sink_eop,
- // stream source
- output [15:0] source_data,
- output source_valid,
- input source_ready,
- output source_sop,
- output source_eop
- );
- assign sink_ready = source_ready;
- assign source_sop = sink_sop;
- assign source_eop = sink_eop;
- parameter WIDTH = 1920;
- parameter HEIGHT = 1080;
- reg state;
- localparam STATE_CTRL = 0;
- localparam STATE_DATA = 1;
- [email protected](posedge clk)begin
- if(~reset_n)
- state <= STATE_CTRL;
- else if( process_en && sink_sop&& sink_data == 16'd15)
- state <= STATE_CTRL;
- else if( process_en && sink_sop&& sink_data == 16'd0)
- state <= STATE_DATA;
- end
- reg [10:0] h_count;
- reg [10:0] v_count;
- wire process_en;
- assign process_en = source_ready & sink_valid;
- reg [3:0] h_step_count;
- reg [3:0] v_step_count;
- [email protected](posedge clk)begin
- if(~reset_n)begin
- h_count <= 11'b0;
- h_step_count <= 4'b0;
- end
- else if( process_en && sink_sop && sink_data == 16'd0)begin
- h_count <= 11'b0;
- h_step_count <= 4'b0;
- end
- else if( process_en && h_count< WIDTH-1 && state == STATE_DATA)begin
- h_count <= h_count + 1'b1;
- if(h_step_count >= 4'd5)
- h_step_count <= 4'b0;
- else
- h_step_count <= h_step_count+1;
- end
- else if( process_en && h_count== WIDTH-1 && state == STATE_DATA)begin
- h_count <= 11'b0;
- h_step_count <= 4'b0;
- end
- end
- [email protected](posedge clk)begin
- if(~reset_n)begin
- v_count <= 11'b0;
- v_step_count <= 4'b0;
- end
- else if( process_en && sink_sop&& sink_data == 16'd0)begin
- v_count <= 11'b0;
- v_step_count <= 4'b0;
- end
- else if( process_en && h_count == WIDTH-1 && state == STATE_DATA)begin
- v_count <= v_count + 1'b1;
- if(v_step_count >= 4'd8)
- v_step_count <= 4'b0;
- else
- v_step_count <= v_step_count+1;
- end
- end
- assign source_valid =(sink_valid & sink_sop & sink_data == 16'd15) | //
- (sink_valid & state == STATE_CTRL )|
- (sink_valid & sink_sop & sink_data == 16'd00) |
- ( sink_valid & state == STATE_DATA & h_step_count==4'd5 &( v_step_count==4'd3 | v_step_count==4'd8)) |
- ( sink_valid & sink_eop )
- ;
- assign source_data = sink_data;
- endmodule
2.2. WLI算法
WLI算法应用奇偶分解的思想,将一维缩放中相关的四个点进行奇偶分解(奇部和偶部的相关点值分解后的关系示例见图1)。从定义上分析,奇部向量在频域图像数据处理中是一个高通滤波结构,相比于偶部向量,它具有更强的噪声。而噪声和高频信号对该部分的影响往往会掩盖该部分对正确缩放的像素点取值的贡献,所以为尽量避免奇部向量中携带的噪声等参量对缩放质量造成损害,对它进行简单的线性化操作,得出公式(1)的处理方案。
因为偶部向量对称的特性,其对缩放点的最终取值具有很大的影响度。直接的线性拟合虽然具有运算简单的特点,但正如图2所示,这会使得图片点值的变化太快,影响视觉效果。为使该部分的取值具有缓慢变化的特征,基于平滑曲线的原型方程是一个很好的选择,但曲线的复杂运算带来的资源耗用往往使算法缩放得不偿失。因此,引文对此部分的曲线公式进行分析讨论,巧妙的引入w参数完成了运算方法的降次和近似,如图2所示,最终得出同样是一次方程的拟合曲线图像插值计算公式(2)。
最终由图3表示出了WLI算法基本单元的信号流处理流程,通过串行移位得到四个相关点,利用加/减操作和移位操作分别得到奇部向量和偶部向量。在s参量的调节下,取w = 0.5时,仅通过两个乘法器便可实现最核心的操作,最后加和奇部和偶部,便得出最终的缩放结果。该结构仅包括6次加法和6次移位操作,并伴有3次延时和两个乘法操作,总体硬件资源耗用较少,延时也较低,具备硬件化条件。
2.3. 算法的MATLAB仿真及评估
运用MATLAB语言,编写程序实现缩放算法。将常用的几种算法 [3] 进行了缩放耗时 [4] 、PSNR (Peak Signal to Noise Ratio)比较以及边沿模糊度、脉冲噪声两种无参考图像评测,并对结果进行了比较分析。
PSNR [5] 也叫峰值信噪比,它是最普遍、最广泛使用的评鉴画质的客观测量方法,意指到达噪音比率的顶点信号,是衡量经过处理后的影像品质的客观方法,用MATLAB实现的计算公式如(3)所示。
其中,MSE是原图像与处理图像之间均方误差。
边缘模糊度 [6] 是指阶跃边缘点占总边缘点的个数的比例值,其值越小说明图片边缘更清晰,图像质量越好。一般边缘点的检测是利用robel算子得到的,而在图像中根据相关函数也可以判断并统计出阶跃边缘点,由此便可计算出各个图像的比例值,使用不同算法对多个图片放大两倍后计算出的边缘模糊度结果如表1。
类似的,脉冲噪声 [6] 是指图像中噪声像素点占总像素点的个数比例,图像噪声点是指根据局部图片判断出的本不该出现的点值,即该区域有一定的取值范围,当超过该范围后即认为该点是噪声点。本文采用最小二乘法的预测模型进行容限计算,同时判断和统计总的噪声点个数,进而计算出不同图片的脉冲噪声值结果变化如图4所示。
图1. 奇偶分解相关点示意图
图2. WLI算法偶部线性拟合示意图
图3. WLI算法基本结构数据流处理结构
图4. 多种算法放大2倍的图片脉冲噪声对比
表1. 多种算法放大2倍的图片边缘模糊度
根据MATLAB的相关仿真数据,得出WLI算法不仅具有高PSNR值,低耗时等特征,从表1和图4中的对比结果亦见,其边缘模糊度和脉冲噪声也相对较低,充分体现了该算法的优异性。
3. 硬件系统平台的搭建
针对Xilinx可编程器件开发板Zedboard [7] ,进行算法的硬件实现和IP生成,同时对软硬协同验证系统平台的设计和功能进行软件和硬件划分。如图5所示,上位机中计算机(PC)负责把新的图片数据传送到开发板,或者从开发板接收缩放后的图片数据,并打印各种处理信息和状态值。显示器显示不同算法的缩放图片直观效果。而下位机(Zedboard)则主要完成控制信息编码输入,软件或硬件图像缩放算法实现,以及内部模块间的图片数据传输等功能。其中,输入检测和控制信息生成、执行,以及对比软件缩放算法的实现等部分是分配给ARM处理器通过软件完成的。
3.1. ZEDBOARD的控制输入IP
该IP设计主要完成对5个按键以及2个开关状态的检测及编码,使产生8位编码数据,用以传送给XPS构建的子系统的8位GPIO,以axi_lite相关协议映射编码数据,从而将编码输入传送到了PS (Processing System: ARM处理器)部分,用以软件编程检测和处理。
该设计的实现原理如图6所示,通过计数模块Count实现按键状态的去抖检测,两个按键用于缩放算法选择,并用三个Led的二进制实时显示,另两个按键用于选择缩放倍数,一个按键用于录入状态与完成录入状态间的切换;两个开关则分别控制放大/缩小、显示使能。
3.2. WLI算法IP
3.2.1. Axi4接口
Axi4 [8] 是由Xilinx和ARM合作提出的便于全可编程器件内部ARM和FPGA之间数据的高速通信的总线标准。Zedboard内部使用Axi4,可细分为Axi_lite、Axi4、Axi_stream三大类,包含地址、数据和反馈通道。能实现ARM和FPGA内部的高速并行数据通信,并支持DMA (direct memory access)通信。
3.2.2. 符合Axi4接口的WLI算法IP
Vivado HLS是Xilinx针对其全可编程器件而推出的高级综合组件,该软件可以实现对C语言编写的程序的直接硬件化,并能很好的综合出符合Axi标准的IP [9] - [12] 。本文的设计采用这种设计方法,借助OpenCV的MAT的相关内容、要求,编写可综合算法程序,图7是WLI算法的实现流程。
映射策略确定新图中的一行在原图的行位置,通过每次缩放一行新图数据的基本策略,用四个数组(Bram存储)缓存与映射行相关的四行原图点值,从而实现算法的二维缩放。WLI子模块实现对一维4个相关点的算法缩放,通过先行后列的策略实现个相关点的图像缩放目的。
图5. 缩放算法硬件系统架构
图6. 控制输入IP结构框图
图7. WLI算法流程
3.3. 硬件系统搭建
HLS综合出算法的硬件IP后,通过Planahead环境完成整个硬件系统的搭建。图8为系统结构示意图,该系统包括PS子系统、内存间以HP通道(DDR内存和FPGA间的高宽带、高速数据通道)直接进行数据通信的VDMA、GPIO等IP,以及自主设计的WLI算法IP和VGA显示IP [13] 。
4. 软硬协同验证实验
4.1. 软件原理
在硬件系统搭建完成以后,借助于Xilinx的SDK集成环境进行软件设计。图9是整个软件设计运行的流程图,主要分为初始化、原始图片选择接收存储、GPIO初始化、缩放倍数检测统计、按键状态检测判断和相应算法处理及对比显示等。
软件设计中,通过接收PC的选择信号,选择使用预定义的图片,或接收从PC传来的新图片。然后进入循环检测和缩放处理过程,便于演示。同时,通过显示使能控制端,可以再次更新PC的显示数据。而缩放显示模式不仅决定缩放的算法选择(软件或硬件),还决定显示模式(单一还是对比显示)。
当检测到GPIO为倍数录入状态时,应当进入倍数的检测和相应操作模式,由此可实现0.1精度的任意倍数图像缩放效果,并能够对基于不同缩放算法得到的图片进行对比显示。
4.2. 软硬件协同验证
通过SDK将编译好的bootloader程序、FPGA配置bit文件和裸机程序封装成boot.bin文件,以SD卡实现Zedboard的脱机演示系统。其验证实验的系统如图10所示,右图是双三次插值算法软件缩放结果,而左图则是WLI算法硬件缩放图。
各缩放算法在ARM裸机上缩放图片所耗用时间 [14] 的对比结果如图11所示。其中最近邻(Near)、双线性(Biline)、双三次(Bcubic)和CCI等为软件缩放算法,WLI算法为硬件实现算法。
尽管软件缩放算法在约667 MHz的Cortex A9处理器上运行,而通过FPGA硬件化的WLI算法的运行时钟仅为100 MHz,但图11的结果表明,其缩放耗时仍同最近邻算法相当(Near几乎被WLI覆盖了),可见在相同时钟条件下,其计算效率将会大幅提高,体现硬件实现的并行特征。
图8. 硬件系统结构
图9. 软件处理流程图
相关推荐
- FPGA视频图像处理前——视频数据的捕获(产生shift_ram时钟使能信号)
- 视频透雾原理加视频增强Retinex算法介绍
- 图像算法可以稳定处理视频了!港科大开源通用算法,解决视频处理时域不稳定问题|NeurIPS 2020...
- 图像算法处理视频不稳定?港科大团队提出一种通用算法解决视频处理时域不稳定问题
- 图像算法可以稳定处理视频了!港科大开源通用算法,解决视频处理时域不稳定问题|NeurIPS 2020...
- 深度学习图像视频压缩算法——TNG
- 视频算法分析介绍PSNR、NIQE、VMAF、MS-SSIM, SSIM and DMOS、JND
- 图像增强相关算法介绍 ------ 1
- FPGA视频、图像缩放算法介绍
- KMP算法简单介绍以及实现步骤-附视频
- 010_linuxC++之_运算符重载
- Java中数组复制的几种方式以及数组合并