曂沅仴駦 发表于 2024-6-11 09:06:54

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                        pre_data                ,         //数据输入
       
        //下游一般是VGA/HDMI等数据显示端口
        input                                post_clk                ,         //数据输出时钟
        output        reg                        post_ready                ,         //数据缓存准备好
        input                                post_req                ,         //数据请求输出信号
        output                        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                 sx         =         SRC_IW*4096/DST_IW         ;
localparam                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 - 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==1'b1)
                expt_src_vcnt3 <= 'd2;
        else if(expt_src_vcnt2>SRC_IH-2)
                expt_src_vcnt3 <= SRC_IH;
        else
                expt_src_vcnt3 <= expt_src_vcnt2 + 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 = (pre_req==1'b1&&wr_addr_sel==i);
       
        //对应的RAM地址信号加一与清零
        always@(posedge clk)
                if(rst)                       
                        pre_wr_addr <= 'd0;
                else if(pre_wr_addr==SRC_IW-1)
                        pre_wr_addr <= 'd0;
                else if(wr_addr_de==1'b1)
                        pre_wr_addr <= pre_wr_addr + 1'b1;
                else
                        pre_wr_addr <= pre_wr_addr;
        //对应赋值
        always@(*)
                if(rst)                       
                        wr_addr = 'd0;
                else if(wr_addr_de==1'b1)
                        wr_addr = pre_wr_addr;
                else
                        wr_addr = rd_addr_w;                       
                //例化RAM
                tdpram #(
                        .AW (12),
                        .DW (8 )
                )
                u1_tdpram
                (
                  .clka                (clk                        ),
                  .wea                (wr_addr_de        ),
                  .addra        (wr_addr                ),
                  .dina                (pre_data                ),
                  .douta        (douta                ),
                  .clkb                (clk                        ),
                  .web                (1'b0                        ),
                  .addrb        (rd_addr                ),
                  .dinb                (8'd0                        ),
                  .doutb        (doutb                )
                );               
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 - 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==1'b1)
                src_x0 <= 'd0;               
        else
                src_x0 <= src_xf2;
//x1的坐标
always@(posedge clk)
        if(rst)       
                src_x1 <= 'd0;
        else if(src_xf2==1'b1)
                src_x1 <= 'd1;
        else
                src_x1 <= src_xf2 + 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 - 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==1'b1)
                src_y0 <= 'd0;
        else
                src_y0 <= src_yf2;               
//计算y1
always@(posedge clk)
        if(rst)       
                src_y1 <= 'd0;
        else if(src_yf2==1'b1)
                src_y1 <= 'd1;
        else
                src_y1 <= src_yf2 + 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                region_type                ;
reg                region_type_r        ;
reg                region_type_r1        ;
reg                region_type_r2        ;
reg                region_type_r3        ;
reg                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               
  剩下的部门就是无脑盘算了。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 18.双线性插值缩放算法的matlab与FPGA实现(二)