基于FPGA的双线性插值算法设计与modelsim分析

前言

  • 在视频图像几何校正或图像配准坐标系转换中,往往需要进行缩放、旋转、透视变换等几何变换操作。要想进行缩放旋转操作,就要得到输入输出图像之间的映射关系,也可称之为几何变换关系。

  • 输入和输出像素之间的映射可以用两种不同的方式进行定义,分别是前向映射和逆向映射
    在这里插入图片描述

  • 几何变换的基本结构,包括两种如下左图的前向映射和右图的逆向映射:
    在这里插入图片描述

  • 前向映射适合于处理流输入,例如 来自一个摄像机的输入,其每个输入像素 能被映射到 指定输出图像中的位置。

  • 逆向映射更适合于产生数据流输出,例如图像数据流输出到显示器,因为对于每个输出像素,逆向映射指定了 像素值来自于 输入图像的什么位置

  • 地址的话,一般由行列计数器提供,通过行列计数器获得某点坐标,从而根据二维坐标到一维地址的转换计算得到地址。

  • 但是按照这种映射关系,输出图像的像素可能被映射到输入图像的非整数坐标上,因此需要采用插值技术


前向映射的缺点:通过输入位置和相应映射关系来求输出位置,那么当得到非整数输出像素位置的时候,就需要进行四舍五入等,那么会出现如下的问题:
1、当缩放变换系数大于1(放大图像)的时候,四舍五入导致出现空洞,让映射后的一些输出位置没有像素值
在这里插入图片描述

2、四舍五入导致像素覆盖,映射后的几个输入像素可能被映射到同一个输出像素位置,那么后一个输出像素值会将前一个覆盖
除此之外,还有一个缺点是输出图像某点像素值不能直接得到,需要遍历输入图像的所有像素值,对其进行坐标变换,分配像素值到整数位置,才能得到输出图像各像素点的像素值。


结论:相比前向映射,逆向映射法更直观。对于逆向映射来说,我们知道输出图像上整数点位置(x’,y’)在变换前位于输入图像上的位置(x,y),一般来说这是个非整数点位置,利用其周围整数像素位置以及对应的像素值(双线性插值使用周围四个整数像素位置)进行插值,就得到了该点的像素值。我们遍历输出图像,经过坐标变换、插值两步操作,我们就能将其像素值一个个地计算出来,因此逆向映射又叫图像填充映射。如下图所示:
在这里插入图片描述

1、几种常见插值类型

1.1 最邻近插值

网上有很多解释,可自己查阅

  • 优缺点:最简单但锯齿严重
  • 基本原理:选择距离期望位置最近的像素进行填补(相当于最近像素的复制粘贴)

最近邻采用最常见的坐标系,以图像的左上角为原点(0,0);假如我们已知源图像src大小为3 * 3,且知道3 * 3图像内9个点的像素值,现在将src源图像扩大成4*4的目标图像dst,并将对应的像素值填入,在填入对应像素值的时候有如下公式(1):

> srcX = dstX* (src_Width/dst_Width) 
>                                                                           
> srcY = dstY * (src_Height/dst_Height)

src_Width / ds_tWidth 和 src_Height / dst_Height = 3/4 ,表示两幅图像的长宽边长比
dstX和dstY我们可以根据目标图像得到,代表目标图像中每个点的横纵坐标。
因此根据dstX和dstY、以及边长比,即可算出对应的源图像坐标,将对应的像素值读出进行填补即可。


举例:

目标图像左上角(0,0),带入上述公式,即可找到对应源图像中的坐标(0*(3/4),0*(3/4))=(0,0),因此目标图像(0,0)位置对应的像素值就是源图像(0,0)点对应的像素值2。
同理计算目标图像(0,1)点对应源图像中的(0,3/4)位置,在源图像中像素坐标值都是整数,因此这里的小数3/4需要四舍五入,为(0,3/4)=(0,0.75) = (0,1),因此对应像素值为38。
依次计算,最终可得到4*4图像的全部像素值,此时就实现了图像的最邻近插值放大。
在这里插入图片描述


总结:通过上述插值可知,当遇到浮点数的时候直接四舍五入取值,拿0.75来说,我们直接取1,这样就会出现较大的误差。因此我们采用距离权重的方式来减少这种误差。既然0.75位于0和1直接,我们即可采用0.75到0,1的距离来分配权重,最终通过整数位置的像素值,得到浮点数0.75处的像素值,这也称为线性插值。

1.2 双线性插值

上述介绍了最邻近插值,误差较大,我们引入了距离权重。将一个浮点数坐标(i + u ,j + v)根据权重分配到距离其最近的四个整数坐标上,进而通过这四个整数坐标的像素值,来得到该浮点坐标的像素值。

  • 核心思想:分别在xy两个方向上进行一次插值,即可得到浮点坐标。距离越远权重越小。

双线性插值公式(2):
在这里插入图片描述
其中i j 表示浮点数的整数部分,u v表示浮点数的小数部分。 (i+u,j+v) 最近的原图像中的整数坐标为 (i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)。

比如下图所示,已知四个红色点的像素值且为整数,求最终落在非整数位置的P的像素值。我们可以先在x方向上进行插值,得到插值结果R1和R2,然后在y方向上继续进行插值,即可得到插值点P。因此当我们知道四个整数坐标以及它们的像素值时,即可根据双线性插值公式,计算出P点的像素值。
在这里插入图片描述


1.2.1 原点选取的问题

上述我们所讲的原点都是图像的左上角,所以其坐标的计算都由公式(1)得到,这里存在一个问题,当33图像扩大成55,对于5*5图像来说,中心坐标是(2,2),用公式(1)计算出对应的源坐标是(1.2,1.2),但相对于源图像来说,其中心坐标是(1,1),因此我们插值的时候所利用的图像像素整体偏右下,而不是均匀地分布整个图像,为了均匀分布整个图像,我们在取原点的时候,不取左上角,而是取中心对齐的方式来插值,因此源坐标和目标坐标之间的映射关系变成公式(3):

> SrcX = (dstX+0.5)* (src_Width/dst_Width) -0.5  
> 
> SrcY = (dstY+0.5) * (src_Height/dst_Height)-0.5

此时将目标图中心坐标(2,2)带入,得到对应的源图像坐标(1,1),和源图像中心坐标重合,即可均匀的分布与整幅图像。

1.3 双三次插值

  • 效果更平滑,和双线性插值类似,只不过用相邻16个点,复杂且消耗资源多。
  • 一般来说,双线性插值是一个折中选择。

2、FPGA实现双线性插值

2.1 准备工作

  • 准备100*100的mif文件,将其提前存储在RAM中
  • 用双线性插值来实现100 * 100图像到256* 256图像的放大操作。
  • 双线性插值公式

2.2 实现的难点

  • 算法中小数部分到底该如何处理(定点化处理,注意精度问题)
  • 如果求出插值公式中的系数,以及周围四个点的坐标
  • 求出四个点之后,为加快速度,如何将四个点的像素值同时读出

2.3 整体的RTL图

在这里插入图片描述

  • 上图可看到,包含五个模块:坐标转换模块,内存管理模块,双线性插值计算模块,分频模块,VGA显示模块。重点看前三个模块。

2.3.1 坐标转换模块

主要工作:得到浮点数(i + u ,j +v)的整数部分的值 i j ;得到四个插值系数 u , 1-u ,v , 1-v
模块接口:
我这里dst_width直接带入的数值,其实可以引出该信号并赋值,更直观。通过src_width和dst_width来计算源图像x y坐标。
在这里插入图片描述

2.3.1.1 源图像和目标图像坐标之间的映射关系

  • 为达到更好的效果采用优化后的公式(3),也就是以中心为原点插值的方式:
    在这里插入图片描述

2.3.1.2 小数部分的处理

  • 我们采用浮点数定点化处理的方式来处理小数部分,因为要目标图像的长宽为256*256,因此需要扩大512倍(放大到不含小数),才能没有小数部分,所以公式改变:

在这里插入图片描述

2.3.1.3 整数和小数部分的表示以及最近的四个整数点坐标

  • 扩大了512倍相当于右移9位即可去掉小数部分,因此低9位即可表示小数部分,我们定义浮点数为20位,其中整数部分占高10位,那么低10位给小数部分。(多给小数部分一位的原因:10位才可表示最大值512,所以小数部分的第10位需要补0)
 //高10位为整数部分
assign coordinate_x = src_x[19:10];   // i
assign coordinate_y = src_y[19:10];   // j
 //低10位为小数部分,但本来小数部分是9位,因此高位补0
assign coordinate_xx = {1'b0,src_x[8:0]};  //u
assign coordinate_yy = {1'b0,src_y[8:0]};  //v

2.3.1.4 插值公式系数的表示

  • 系数包含,u,1-u,v,1-v。因为扩大了512倍数,所以1-u,变成了512-扩大后的小数部分的u
 assign coefficient2 = {1'b0,src_x[8:0]};  //u

assign coefficient1 = 'd512 - coefficient2;   // 1-u

assign coefficient4 = {1'b0,src_y[8:0]}; //v

assign coefficient3 = 'd512 - coefficient4; //1-v

2.3.2 内存管理模块

  • 采用4个RAM来解决同时读取四个点对应像素的问题
  • 调用IP核的方法,将之前的mif文件存储进去即可
    在这里插入图片描述
2.3.2.1 获得距离浮点坐标最近的四个整数坐标
(i,j)=  (srcX[19:10] ,srcY[19:10];

(i+1,j) = (srcX[19:0] + 'd1,srcY[19:0]);

(i,j+1) = (srcX[19:0],srcY[19:10] + 'd1);

(i+1,j+1) = (srcX[19:0] + 'd1 ,srcY[19:10] + 'd1); 

有了四个整数坐标,即可将二维坐标转换成一维地址,分别从四个存储里面读对应的像素值:

assign address_bx  = (coordinate_x == width)?coordinate_x + coordinate_y*src_width - 'd1:coordinate_x + coordinate_y*src_width;
assign address_bx1 = (coordinate_x == width)?address_bx:address_bx + 'd1;
assign address_by  = (coordinate_y==width)?address_bx:coordinate_x + (coordinate_y+'d1)*src_width;
assign address_by1 = (coordinate_x == width)?address_by:address_by + 'd1;

2.3.3 插值公式代入计算模块

目前是插值系数,以及四个整数坐标对应的像素值都已经有了,因此
代入公式:

assign data_1 = coefficient1*coefficient3*doutbx;  //(1-u)*(1-v)*f(i,j)

assign data_2 = coefficient2*coefficient3*doutbx1;//u*(1-v)*f(i+1,j)

assign data_3 = coefficient1*coefficient4*doutby;//(1-u)*v*f(i,j+1)

assign data_4 = coefficient2*coefficient4*doutby1;//u*1-v*f(i+1,j+1)
 

将四个data进行相加:

assign data_a = data_1 + data_2;
assign data_b = data_3 + data_4;
assign data_oq = data_aq + data_bq;

因为扩大了512倍数,因此还要将结果缩小512倍。所以最终的结果我们要取高8位。

 data_o <= data_oq[25:18];

3、仿真波形

该设计计算量大,不便于仿真数值的查看,因此我们将计算出的数据以txt文本的方式,导出,然后用matlab读取txt文档,直接进行图像的显示。

导出方式:在tb文件中添加如下代码


`timescale 10 ns/ 10 ps

module bilinear_tb();

		reg[7:0] save_data[0:256*256-1];  //表示有65536个8位的图像数据
		reg clk;

		reg start; 
																	
		wire [7:0]  VGA_RGB;
		wire VGA_BLK;
		wire hsync;
		wire vsync;
		wire en_o;


//实例化顶层文件
bilinear i1 (
	
			.clk     (clk),
			.start   (start),
			.VGA_RGB   (VGA_RGB),
			.VGA_BLK (VGA_BLK),
			.hsync   (hsync),
			.vsync   (vsync),
			.en_o(en_o)

		);

initial                                                
begin                                                                        
        clk     =1;
       start    =0;
       #10;
       start    = 1;		 
end    
//产生时钟激励
always #1 clk  =~clk;  

//打开post_img.txt文件
//---------------------------------------------------
integer post_img_txt;

initial begin
    post_img_txt = $fopen("post_img.txt");
end

//像素写入到txt中
//---------------------------------------------------
reg [20:0] pixel_cnt; 

always @(posedge clk) begin
    if(!start) begin
        pixel_cnt <= 0;
    end
    else if(en_o) begin
        pixel_cnt = pixel_cnt + 1;
        $fdisplay(post_img_txt,"%h",VGA_RGB);
		
        if(pixel_cnt == 65536)
            $stop;
    end
end
                                         
endmodule

txt文档用matlab处理,以生成相应图片:

fid0=fopen('savedata.txt','r');    %读入所需要的txt文件
[a,count]=fscanf(fid0,'%x');  %a为data.txt文件数据读入的矩阵,以16进制形式,count为该矩阵元素个数
b=reshape(a,256,256);       % 构建成100*100的矩阵形式     

c=b';           %   需要再转置一次方为图片行列方向的矩阵  

imshow(c,[]);    %显示图片  
title('verilog插值后的图像')  

在这里插入图片描述


当加入VGA模块的时候,使用上述方法出现问题,因此tb中的计数器是顺序的,因此像素也是按顺序逐脉冲的写入,而对于VGA显示来说,它有行场消隐时间,因此采用逐脉冲顺序写入的方式,会出现无用的像素点,搞清楚原因后没有继续修改,因此如果加上了VGA模块,我们只需要调用锁相环,提供25Mhz时钟,然后上板验证即可。
代码

参考

  • 8
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting_FPGA

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值