赞
踩
在图像算法中,可以知道算法本身不算复杂,但是在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循环。
接下来我们看一个状态机。先只看状态机的启动,当模块的写入缓存足够时,状态机启动,由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
剩下的部分就是无脑计算了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。