18.双线性插值缩放算法的matlab与FPGA实现(二)

MATALB算法转化为FPGA算法的过程

  在图像算法中,可以知道算法本身不算复杂,但是在FPGA中,视频主要用视频流的方式在FPGA中,基本不可能对图像进行任意位置的索引。因此,如何根据视频流来进行插值最难的点在于如何寻找到缩放后的点对应的四个点坐标。
  根据视频流的特点,可以知道视频流的索引式按照左上到右下进行依次索引的。根据这个特点,可以实现只缓存几行数据即可实现图像的索引。
  这里缩放算法对视频流有两种,一种是主动式(act),另一种是被动式(inact),主动式是指算法的视频流输入不受缩放模块控制,视频信息通常以视频流的方式直接进入模块。被动式式指算法的视频流输入受到缩放模块的控制,视频流一般先存储在DDR、SDRAM等存储介质中,等待缩放模块去读取像素信息。
  由双线性插值的直观图一节结合FPGA中的数据流特点,可以大致推算出FPGA的缩放实现原理。
  首先由缩放后的显示信息来依次产生缩放后的坐标信息,然后根据缩放后的坐标信息来推算出缩放前的像素信息,根据缩放前的像素信息来求出加权和得到缩放后的坐标像素。难点是判断点是否到了下一个数据并且找到正确的四个点。

被动式的缩放算法

  被动式的缩放算法模块中像素信息由下游数据流主动索取,一般下游数据流可以为显示模块或者算法模块。首先,我们来看被动式的模块接口。可以看到缩放前的数据由缩放模块主动请求,而缩放后的数据由缩放模块被动发出。

module scaler_inact #(
	parameter SRC_IW = 640 	,
	parameter SRC_IH = 480 	,
	parameter DST_IW = 1280 ,
	parameter DST_IH = 720 
)
(
	input				clk				, 	//输入时钟
	input				rst				, 	//复位
	//上游一般是内存DMA的读出缓存			
	input				pre_ready		, 	//上游数据准备信号
	output				pre_req			, 	//数据请求输入信号
	input		[7:0]	pre_data		, 	//数据输入
	
	//下游一般是VGA/HDMI等数据显示端口
	input				post_clk		, 	//数据输出时钟
	output	reg			post_ready		, 	//数据缓存准备好
	input				post_req		, 	//数据请求输出信号
	output		[7:0]	post_data		, 	//数据输出
	output				post_empty		 	//数据空信号
);

  首先,MATLAB中求出缩放系数: s x s_x sx s y s_y sy

sx = src_w / dst_w;
sy = src_h / dst_h;

  对应的,在FPGA中也要求出缩放系数,由于模块的输入输入比例都是已知的(例化时参数已经定义了),所以这个缩放系数也可以直接求出。这里由于缩放系数一般都是浮点数,所以将数据放大 2 12 次方 2^{12}次方 212次方.

//fix16_12
localparam 	[15:0] 	sx 	= 	SRC_IW*4096/DST_IW 	;
localparam	[15:0] 	sy 	= 	SRC_IH*4096/DST_IH 	;

  计算出了缩放系数后,MATLAB开始进行循环索引了,这里也是FPGA实现的难点之一。首先来看FPGA中的目标图像计数。

always @(posedge clk)
	if(rst)
		dst_hcnt 	<= 	0;
	else if(dst_hcnt == DST_IW - 1)
		dst_hcnt 	<= 	0;
	else if(dst_de == 1'b1)
		dst_hcnt 	<= 	dst_hcnt + 1;
	else 
		dst_hcnt 	<= 	dst_hcnt;

always @(posedge clk)
	if(rst)
		dst_vcnt 	<= 	0;
	else if(dst_hcnt == DST_IW - 1 && dst_vcnt == DST_IH - 1)
		dst_vcnt 	<= 	0;
	else if(dst_hcnt == DST_IW - 1)
		dst_vcnt 	<= 	dst_vcnt + 1;
	else 
		dst_vcnt 	<= 	dst_vcnt;

  这里对应了MATLAB的两层for循环。

先将行数据存入RAM中

  接下来我们看一个状态机。先只看状态机的启动,当模块的写入缓存足够时,状态机启动,由0状态跳转到2状态。

/*
	wr_data_cnt 		模块的数据输入端计数	
*/
always@(posedge clk)
	if(rst)
		begin
			dst_de 		<= 0;
			state  		<= 0;
		end
	else if(pre_ready==1'b1)
		case(state)
			0:	
				if(wr_data_count<DST_IW*buf_line-13)  //缩放数据存储完毕
					begin
						state 	 <= 2;  			//开始判断是往RAM里面读数据还是进行缩放像素计算
					end		
				else
					begin
						state 	 <= state;
					end	

  然后再来看这个一个状态跳转影响了什么。可以看出这个信号当状态由不是2状态跳转到2状态的时候,原坐标的计算使能会拉高一个时钟周期。

//expt_src_vcnt_de 	  原坐标计算使能
always @(posedge clk)
	if(rst)
		expt_src_vcnt_de 	<= 	1'b0;
	else if(state_r != 2 && state == 2)
		expt_src_vcnt_de 	<= 	1'b1;
	else 
		expt_src_vcnt_de 	<= 	1'b0;

  拉高一个时钟周期后,这个信号会进行打拍,后面的这个expt_src_vcntp1_de属于信号流的调度优化,当进入3状态计算的时候也会计算并根据条件来判断是否读取下一行。每打一拍代表了一步计算。

always @(posedge clk)
	if(rst)begin
		expt_src_vcnt_de0 	<= 	0;
		expt_src_vcnt_de1 	<= 	0;
		expt_src_vcnt_de2 	<= 	0;
		expt_src_vcnt_de3 	<= 	0;		
	end
	else begin
		expt_src_vcnt_de0 	<= 	expt_src_vcnt_de || expt_src_vcntp1_de;
		expt_src_vcnt_de1 	<= 	expt_src_vcnt_de0;
		expt_src_vcnt_de2 	<= 	expt_src_vcnt_de1;
		expt_src_vcnt_de3 	<= 	expt_src_vcnt_de2;		
	end

  首先看第一步计算。也就是expt_src_vcnt_de0

/*
	第一个else if对应MATLAB的src_yf = ((i-1)+0.5) * sy - 0.5;%浮点数中的(i-1)+0.5,
	第二个的这个else if暂且不看
*/
//fix16_2 + fix0_2 = fix16_2
always @(posedge clk)
	if(rst)
		expt_src_vcnt0 	<= 	0;
	else if(expt_src_vcnt_de == 1'b1)
		expt_src_vcnt0 	<= 	{dst_vcnt,2'b0} + 2; //0.5*4
	else if(expt_src_vcntp1_de == 1'b1 && dst_vcnt < DST_IH - 1)
		expt_src_vcnt0 	<= 	{dst_vcnt + 1,2'd0} + 2;
	else 
		expt_src_vcnt0 	<= 	'd0;

  然后看第二步计算。也就是expt_src_vcnt_de1

/*
	对应MATLAB的src_yf = ((i-1)+0.5) * sy - 0.5;%浮点数中的((i-1)+0.5)*sy,
*/
//fix16_2 * fix16_12 = fix32_14
always @(posedge clk)
	if(rst)
		expt_src_vcnt1 	<= 	'd0;
	else 
		expt_src_vcnt1 	<= 	expt_src_vcnt0 * sy ;

  然后看第三步计算。也就是expt_src_vcnt_de2

/*
	对应MATLAB的src_yf = ((i-1)+0.5) * sy - 0.5;%浮点数中的((i-1)+0.5)*sy-0.5,
	这里按道理来说应该是fix30_12 - fix12_12 = fix30_12
	但是缩放系数一般没有超过2,
	所以高位基本都是0
*/
//fix26_12 - fix12_12 = fix26_12
always @(posedge clk)
	if(rst)
		expt_src_vcnt2 	<= 	'd0;
	else 
		expt_src_vcnt2 	<= 	expt_src_vcnt1[27:2] - 2048 ;

  然后看第四步计算。也就是expt_src_vcnt_de3

	/*
	这里对应MATLAB的
	   if(src_yf<0)
            src_y0 = 0;
        else 
            src_y0 = floor(src_yf);
        end
        这个边界条件是2到SRC_IH
        这个跟后面的状态机有关
        因为这个2指的是缓存读出的数据行数读到0,1行结束
        看后面状态机就可以明白
	*/
//fix26_12	-> 取整 + 2 = fix14_0 + 2	
always@(posedge clk)
	if(rst)	
		expt_src_vcnt3 <= 'd0;
	else if(expt_src_vcnt2[25]==1'b1)
		expt_src_vcnt3 <= 'd2;
	else if(expt_src_vcnt2[25:12]>SRC_IH-2)
		expt_src_vcnt3 <= SRC_IH;
	else
		expt_src_vcnt3 <= expt_src_vcnt2[25:12] + 2;

  当expt_src_vcnt_de3拉高的时候,看此时的状态机,现在我们处于状态2阶段。于是只看状态2的跳转。

/*
	可以看到当expt_src_vcnt_de3拉高的时候,会判断此时的src_vcnt与expt_src_vcnt3的大小
	状态1是用来读取一整行原像素的
	状态3是用来求目标图像的像素值的
	当原图像目前的行数还在目标图像的行数之内的时候就不用读取一整行像素,直接比较
	当原图像目前的行数不在目标图像的行数之内的时候就需要读取一整行像素,直到在目标行数之内的时候,再比较
	当expt_src_vcnt3为初始值2的时候,src_cnt会连续读取0行和1行的数据
*/
	2:  
		if(src_vcnt>=expt_src_vcnt3&&expt_src_vcnt_de3==1'b1)
			begin
				state 	<= 3;
			end
		else if(src_vcnt<expt_src_vcnt3&&expt_src_vcnt_de3==1'b1)
			begin
				state 	<= 1;
			end
		else 
			begin
				state 	<= state;
			end	

  这里先看状态1,也就是当原图像目前的行数还在目标图像的行数之内的时候就不用读取一整行像素的过程。

//当进入状态1的时候会读取一整行数据
always@(posedge clk)
	if(rst)
		src_de <= 1'b0;
	else if(src_hcnt==SRC_IW-1)
		src_de <= 1'b0;
	else if(state_r!=1&&state==1)
		src_de <= 1'b1;
  	else if(src_vcnt<expt_src_vcnt3&&expt_src_vcnt_de3==1'b1&&state==3)	
		src_de <= 1'b1;
	else
		src_de <= src_de;
//行计数
always@(posedge clk)
	if(rst)		
		src_hcnt <= 'd0;
	else if(src_hcnt==SRC_IW-1)
		src_hcnt <= 'd0;
	else if(src_de==1'b1)
		src_hcnt <= src_hcnt + 1'b1;
	else
		src_hcnt <= src_hcnt;
//列计数
always@(posedge clk)
	if(rst)		
		src_vcnt <= 'd0;
	else if(src_vcnt==SRC_IH&&dst_hcnt==DST_IW-1&&dst_vcnt==DST_IH-1)	
		src_vcnt <= 'd0;
	else if(src_hcnt==SRC_IW-1)
		src_vcnt <= src_vcnt + 1'b1;
	else
		src_vcnt <= src_vcnt;

assign pre_req = src_de;

  以上就是计算原图像行的过程。这里再回来看前面没有分析的部分。

always@(posedge clk)
	if(rst)
		expt_src_vcntp1_de <= 1'b0;
	else if(state_r!=3&&state==3)
		expt_src_vcntp1_de <= 1'b1;	
	else
		expt_src_vcntp1_de <= 1'b0;	
		
	expt_src_vcnt_de0 	<= 	expt_src_vcnt_de || expt_src_vcntp1_de;

	else if(expt_src_vcnt_de == 1'b1)
		expt_src_vcnt0 	<= 	{dst_vcnt,2'b0} + 2; //0.5*4
	else if(expt_src_vcntp1_de == 1'b1 && dst_vcnt < DST_IH - 1)
		expt_src_vcnt0 	<= 	{dst_vcnt + 1,2'd0} + 2;

  这两个是我们上面没有分析的部分。首先观察状态机可知,这个状态机有四个状态,分别是初始状态、判断状态、读取到指定行状态、以及计算列状态。判断、读取和计算是分开的,这样会导致带宽的利用率不高,于是我们在计算列状态的时候也判断行并读取原像素数据。这样到了状态2就可以立即做出判断并将状态跳转到3。防止状态过多的卡在读原像素的状态1。造成带宽的浪费。

计算行坐标

  当读出一行信号后,就可以先将列存储在RAM或者FIFO中了,这里我们选择将列存储在RAM中。看下面这段代码。下面这段代码实际上例化了四个RAM,然后将行数据依次循环存入RAM中。使用RAM的好处是可以对一行数据进行任意位置的索引。

//RAM选择信号   0~3
always@(posedge clk)
	if(rst)		
		wr_addr_sel <= 'd0;
	else if(wr_addr_cnt==SRC_IW-1&&wr_addr_sel==3)
		wr_addr_sel <= 'd0;
	else if(wr_addr_cnt==SRC_IW-1)
		wr_addr_sel <= wr_addr_sel + 1'b1;
	else
		wr_addr_sel <= wr_addr_sel;
//RAM内的数据写入计数
always@(posedge clk)
	if(rst)		
		wr_addr_cnt <= 'd0;
	else if(wr_addr_cnt==SRC_IW-1)
		wr_addr_cnt <= 'd0;
	else if(pre_req==1'b1)
		wr_addr_cnt <= wr_addr_cnt + 1'b1;
	else
		wr_addr_cnt <= wr_addr_cnt;
//例化四个RAM
genvar i;
generate
  for (i=0; i < 4; i=i+1)
  begin: wr_src_data
	//依次拉高写数据使能
	assign wr_addr_de[i] = (pre_req==1'b1&&wr_addr_sel==i);
	
	//对应的RAM地址信号加一与清零
	always@(posedge clk)
		if(rst)			
			pre_wr_addr[i] <= 'd0;
		else if(pre_wr_addr[i]==SRC_IW-1)
			pre_wr_addr[i] <= 'd0;
		else if(wr_addr_de[i]==1'b1)
			pre_wr_addr[i] <= pre_wr_addr[i] + 1'b1;
		else
			pre_wr_addr[i] <= pre_wr_addr[i];
	//对应赋值
	always@(*)
		if(rst)			
			wr_addr[i] = 'd0;
		else if(wr_addr_de[i]==1'b1)
			wr_addr[i] = pre_wr_addr[i];
		else
			wr_addr[i] = rd_addr_w[i];			
		//例化RAM
		tdpram #(
			.AW (12),
			.DW (8 )  
		)
		u1_tdpram 
		(
		  .clka		(clk			),
		  .wea		(wr_addr_de[i]	),
		  .addra	(wr_addr[i]		),
		  .dina		(pre_data		),
		  .douta	(douta[i]		),
		  .clkb		(clk			),
		  .web		(1'b0			),
		  .addrb	(rd_addr[i]		),
		  .dinb		(8'd0			),
		  .doutb	(doutb[i]		) 
		);		
  end
endgenerate

  这里再把前面循环的目标图像代码拿出来。

always @(posedge clk)
	if(rst)
		dst_hcnt 	<= 	0;
	else if(dst_hcnt == DST_IW - 1)
		dst_hcnt 	<= 	0;
	else if(dst_de == 1'b1)
		dst_hcnt 	<= 	dst_hcnt + 1;
	else 
		dst_hcnt 	<= 	dst_hcnt;

always @(posedge clk)
	if(rst)
		dst_vcnt 	<= 	0;
	else if(dst_hcnt == DST_IW - 1 && dst_vcnt == DST_IH - 1)
		dst_vcnt 	<= 	0;
	else if(dst_hcnt == DST_IW - 1)
		dst_vcnt 	<= 	dst_vcnt + 1;
	else 
		dst_vcnt 	<= 	dst_vcnt;

  行数据有了之后就需要列数据,来看看MATLAB怎么计算列数据的。

src_xf = ((j-1)+0.5) * sx - 0.5;%浮点数

  这里对应的FPGA实现有

//这里先计算src_xf1 = (j-1)+0.5   扩大两倍然后+2
//fix16_2 + fix2_2  = fix16_2
always@(posedge clk)
	if(rst)	
		src_xf0 <= 'd0;
	else if(dst_de==1'b1)
		src_xf0 <= {dst_hcnt,2'd0} + 2;
	else
		src_xf0 <= 'd0;
//这里计算 src_xf1 = ((j-1)+0.5) * sx
//fix16_2 * fix16_12  = fix32_14		
always@(posedge clk)
	if(rst)	
		src_xf1 <= 'd0;
	else
		src_xf1 <= src_xf0*sx;
//这里计算 src_xf2 = ((j-1)+0.5) * sx - 0.5
//fix26_12 - fix12_12  = fix26_12 可能为负数
always@(posedge clk)
	if(rst)	
		src_xf2 <= 'd0;
	else
		src_xf2 <= src_xf1[27:2] - 2048;

//纯打排	
always@(posedge clk)
	if(rst)	
		src_xf3 <= 'd0;
	else
		src_xf3 <= src_xf2;		
//x0的坐标
always@(posedge clk)
	if(rst)	
		src_x0 <= 'd0;
	else if(src_xf2[25]==1'b1)
		src_x0 <= 'd0;		
	else
		src_x0 <= src_xf2[25:12];
//x1的坐标
always@(posedge clk)
	if(rst)	
		src_x1 <= 'd0;
	else if(src_xf2[25]==1'b1)
		src_x1 <= 'd1;
	else
		src_x1 <= src_xf2[25:12] + 1'b1;

计算列坐标

  前面已经计算过列坐标了,前面计算一次是为了确定读取的是哪一行,这里再计算一次。
  先看MATLAB计算。

src_yf = ((i-1)+0.5) * sy - 0.5;%浮点数

  再看FPGA实现。

//这里先计算src_yf0 = (j-1)+0.5   扩大两倍然后+2
//fix16_2 + fix2_2  = fix16_2
always@(posedge clk)
	if(rst)	
		src_yf0 <= 'd0;
	else if(dst_de==1'b1)
		src_yf0 <= {dst_vcnt,2'd0} + 2;
	else
		src_yf0 <= 'd0;
//这里先计算src_yf1 = ((j-1)+0.5)*sy   
//fix16_2 * fix16_12  = fix32_14		
always@(posedge clk)
	if(rst)	
		src_yf1 <= 'd0;
	else
		src_yf1 <= src_yf0*sy;
//这里先计算src_yf2 = ((j-1)+0.5)*sy - 0.5  
//fix26_12 - fix12_12  = fix26_12 可能为负数
always@(posedge clk)
	if(rst)	
		src_yf2 <= 'd0;
	else
		src_yf2 <= src_yf1[27:2] - 2048;
//打拍
always@(posedge clk)
	if(rst)	
		src_yf3 <= 'd0;
	else
		src_yf3 <= src_yf2;
//计算y0  注意是在src_yf2的基础上,而不是打拍后的基础上
always@(posedge clk)
	if(rst)	
		src_y0 <= 'd0;
	else if(src_yf2[25]==1'b1)
		src_y0 <= 'd0;
	else
		src_y0 <= src_yf2[25:12];		
//计算y1
always@(posedge clk)
	if(rst)	
		src_y1 <= 'd0;
	else if(src_yf2[25]==1'b1)
		src_y1 <= 'd1;
	else
		src_y1 <= src_yf2[25:12] + 1'b1;

  接下来看有效信号

reg		dst_de0	 	;  //src_yf0   src_xf0
reg		dst_de1  	;  //src_yf1   src_xf1
reg		dst_de2  	;  //src_yf2   src_xf2

reg		src_xy_de   ;  //src_y0   src_x0

always@(posedge clk)
	if(rst)	
		begin	
			dst_de0 <= 1'b0;
			dst_de1 <= 1'b0;
			dst_de2 <= 1'b0;
		end
	else
		begin	
			dst_de0 <= dst_de; 	
			dst_de1 <= dst_de0; //src_yf2
			dst_de2 <= dst_de1; //src_yf3
		end		

always@(posedge clk)
	if(rst)	
		src_xy_de <= 1'b0;
	else
		src_xy_de <= dst_de2;  //src_y0和y1

  此时我们得到了临近的四个点以及目标图像的坐标。此时看MATLAB代码

   %根据四个点坐标以及待求点坐标计算出四个权重
        w11 = (src_x1 - src_xf) * (src_y1 - src_yf);   
        w21 = (src_xf - src_x0) * (src_y1 - src_yf);
        w12 = (src_x1 - src_xf) * (src_yf - src_y0);
        w22 = (src_xf - src_x0) * (src_yf - src_y0);
        %下面的+1是为了对应索引  与上面的+1求相邻坐标不一样
        if(src_y0 >= row - 1 && src_x0 >= col - 1) //最后一个点
            line_data(i,j) =    src_data(src_y0 + 1,src_x0 + 1) * w11;
        elseif(src_y0 >= row - 1)   //最下面一行
            line_data(i,j) =    src_data(src_y0 + 1,src_x0 + 1) * w11 + ...
                                src_data(src_y0 + 1,src_x1 + 1) * w12;
        elseif(src_x0 >= col - 1)    //最右边一行
            line_data(i,j) =    src_data(src_y0 + 1,src_x0 + 1) * w11 + ...
                                src_data(src_y1 + 1,src_x0 + 1) * w21;
        else
            line_data(i,j) =    src_data(src_y0 + 1,src_x0 + 1) * w11 + ...
                                src_data(src_y1 + 1,src_x0 + 1) * w21 + ...
                                src_data(src_y0 + 1,src_x1 + 1) * w12 + ...
                                src_data(src_y1 + 1,src_x1 + 1) * w22;
        end 

  首先,if else的条件判断对应四个边界区域。在FPGA中代码如下:

reg	[2:0]	region_type		;
reg	[2:0]	region_type_r	;
reg	[2:0]	region_type_r1	;
reg	[2:0]	region_type_r2	;
reg	[2:0]	region_type_r3	;
reg	[2:0]	region_type_r4	;
//src_xy_de==1'b1代表四个点全部被算出
always@(posedge clk)
	if(rst)	
		region_type <= 0;
	else if(src_x0>=SRC_IW-1&&src_y0>=SRC_IH-1&&src_xy_de==1'b1)
		region_type <= 1;
	else if(src_y0>=SRC_IH-1&&src_xy_de==1'b1)
		region_type <= 2;
	else if(src_x0>=SRC_IW-1&&src_xy_de==1'b1)	
		region_type <= 3;
	else
		region_type <= 4;

always@(posedge clk)
	if(rst)	
		begin
			region_type_r	<=  'd0;
			region_type_r1  <=  'd0;
			region_type_r2  <=  'd0;
			region_type_r3  <=  'd0;
			region_type_r4  <=  'd0;			
		end
	else
		begin
			region_type_r	<=  region_type		;
			region_type_r1  <=  region_type_r	;
			region_type_r2  <=  region_type_r1	;
			region_type_r3  <=  region_type_r2	;
			region_type_r4  <=  region_type_r3	;
		end		

  剩下的部分就是无脑计算了。

  • 26
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值