系列文章目录
双线性插值缩放算法原理以及matlab与verilog的实现(一)
文章目录
- 系列文章目录
- 前言
- 一、前提回顾
- 二、FPGA实现步骤
- 2.1 找到源图像四个像素点求目标像素点
- 2.2 FPGA实现步骤
- 2.3 总体框架
- 2.4 ROM缓存模块
- 2.5 VGA模块
- 2.6 双线性算法模块
- 三、下板验证
前言
开发平台:vivado2020.1
开发芯片:xc7k410tffv900-2
在上一篇文章,我们学会了双线性缩放插值算法的原理以及matlab的实现,以及VGA时序的简单实现。
本文实现目的:用verilog实现双线性插值算法,将一张112 ×103大小图片,放大两倍至224 ×206,然后通过VGA显示出来
一、前提回顾
双线性插值算法公式如下:
f ( x , y ) = f ( Q 11 ) ( 1 − u ) ( 1 − v ) + f ( Q 21 ) u ( 1 − v ) + f ( Q 12 ) ( 1 − u ) v + f ( Q 22 ) u v f(x,y)={f(Q_{11})}{(1-u)(1-v)}+{f(Q_{21})}{u(1-v)}+{f(Q_{12})}{(1-u)v}+{f(Q_{22})}{uv} f(x,y)=f(Q11)(1−u)(1−v)+f(Q21)u(1−v)+f(Q12)(1−u)v+f(Q22)uv
由于在图像处理中,一般将左上角的像素点定义为坐标原点,因此将上述的图片变换一下坐标,以及命名顺序,就变为了:
f ( x , y ) = f ( Q 11 ) ( 1 − u ) ( 1 − v ) + f ( Q 21 ) u ( 1 − v ) + f ( Q 12 ) ( 1 − u ) v + f ( Q 22 ) u v f(x,y)={f(Q_{11})}{(1-u)(1-v)}+{f(Q_{21})}{u(1-v)}+{f(Q_{12})}{(1-u)v}+{f(Q_{22})}{uv} f(x,y)=f(Q11)(1−u)(1−v)+f(Q21)u(1−v)+f(Q12)(1−u)v+f(Q22)uv
二、FPGA实现步骤
现在我们知道,我们用一张已知各点像素值的图片(源图像),求出一张放大两倍的图片(目标图像),但是目标各点像素值都是未知。双线性插值算法核心就是:用已知的四个像素点的值算出所求目标像素点的值。因此步骤如下:
2.1 找到源图像四个像素点求目标像素点
根据目标像素点位置乘以缩放系数( s r c w i d t h d s t w i d t h , s r c h e i g h t d s t h e i g h t \frac{srcwidth}{dstwidth},\frac{srcheight}{dstheight} dstwidthsrcwidth,dstheightsrcheight)得到一个浮点坐标 ( i + u , j + v ) (i+u,j+v) (i+u,j+v)(其中 i i i, j j j为浮点坐标的整数部分; u u u, v v v为浮点坐标的小数部分),而这个浮点坐标 ( i + u , j + v ) (i+u,j+v) (i+u,j+v)的像素值 f ( i + u , j + v ) f(i+u,j+v) f(i+u,j+v),就是上图中所要求的p点值,所以由公式可知, f ( p ) f(p) f(p)可由 f ( Q 11 f(Q_{11} f(Q11)、 f ( Q 12 f(Q_{12} f(Q12)、 f ( Q 21 f(Q_{21} f(Q21)、 f ( Q 22 f(Q_{22} f(Q22)求出。而 f ( Q 11 f(Q_{11} f(Q11)、 f ( Q 12 f(Q_{12} f(Q12)、 f ( Q 21 f(Q_{21} f(Q21)、 f ( Q 22 f(Q_{22} f(Q22)的坐标分别是 ( i , j ) (i,j) (i,j)、 ( i + 1 , j ) (i+1,j) (i+1,j)、 ( i , j + 1 ) (i,j+1) (i,j+1)、 ( i + 1 , j + 1 ) (i+1,j+1) (i+1,j+1)。
例如:将图像放大2倍
- 缩放系数为(0.5,0.5)
- 假如求目标图像坐标(10,15)的像素值,浮点坐标为(5 + 0,7 + 0.5)其中u = 0,v = 0.5
- 源图像四个点坐标为 ( 5 , 7 ) (5,7) (5,7)、 ( 6 , 7 ) (6,7) (6,7)、 ( 5 , 8 ) (5,8) (5,8)、 ( 6 , 8 ) (6,8) (6,8)。
- 带入双线性插值算法公式就可求出
2.2 FPGA实现步骤
- 求出缩放系数,通常涉及到小数,FPGA处理小数通常是将浮点数转化成定点数来计算(参考FPGA浮点数计算),本文将浮点数放大256倍,例如缩放系数为0.5,则在FPGA中显示为128。再例如,计算 1 - 0.7 = 0.3,在FPGA中就是256 - 179 = 77
- 如何同时取出源图像四个点:因为FPGA中,无论是RAM还是ROM,一个时钟周期只能读出一个数据,所以需要将源图像缓存至四个RAM或者4个ROM中(面积换速度),这样一次性给四个地址到这四个ROM中,就能同时收到四个像素点
- 实现公式:公式都是由加法和乘法组成,为了速度能快,选用加法器和乘法器的IP,而不是直接用 +,×
- 计算完成,数据缓存到RAM里,VGA接口直接读RAM
2.3 总体框架
2.4 ROM缓存模块
- 将图片通过matlab提取像素点,按照RGB顺序,十六进制的格式写入coe文件里,maltab代码如下:
% 读取图像
img = imread('C:\Users\Administrator\Desktop\qq.jpg');% 获取图像尺寸
[height, width, ~] = size(img);% 打开 COE 文件以写入数据
fileID = fopen('C:\Users\Administrator\Desktop\save.coe', 'w');% 写入 COE 文件头部信息
fprintf(fileID, 'memory_initialization_radix=16;\n');
fprintf(fileID, 'memory_initialization_vector=\n');% 遍历图像像素并将像素值写入 COE 文件
for i = 1:heightfor j = 1:width% 将像素值写入 COE 文件fprintf(fileID, '%02X%02X%02X,\n', img(i,j,1), img(i,j,2), img(i,j,3)); end
end% 关闭 COE 文件
fclose(fileID);
disp('COE file generation complete.');
得到coe文件
打开ROM ip,生成一个位宽24位。深度112×103=11536的ROM,初始化文件为此coe文件
2.5 VGA模块
vga模块代码和上篇文章一样,只是加了读ROM的操作,代码如下:
//显示ROM缓存的图片
module vga_ctrl
(input clk ,input [23:0] data_in ,output reg [13:0] rd_rom_addr, output reg vs =1'b0 ,output reg hs =1'b0 ,output reg [23:0] data_out );parameter hcnt_max =1100;//行扫描是对列计数,最大值parameter vcnt_max =2250;//列扫描是对行计数,最大值parameter H_ACTIVE =960; //行数据有效时间parameter H_FRONT_PORCH =44 ; //行前沿时间parameter H_SYNC_TIME =22;//行同步信号时间parameter H_BACK_PORCH =74;//行消隐后肩时间parameter V_ACTIVE =2160;//场数据有效时间parameter V_FRONT_PORCH =8 ; //场前沿时间parameter V_SYNC_TIME =10 ; //场同步信号时间parameter V_BACK_PORCH =72;//场后沿时间reg [10:0] hcnt ='d0 ;reg [11:0] vcnt ='d0 ;reg [10:0] pix_x ='d0 ;//当前显示像素点x坐标reg [11:0] pix_y ='d0 ; //当前显示像素点y坐标//对行扫描进行计数always @(posedge clk)beginif(hcnt == hcnt_max - 1 )hcnt <= 0;elsehcnt <= hcnt +'d1; end//对列扫描进行计数always @(posedge clk)beginif((vcnt == vcnt_max-1)&&(hcnt == hcnt_max -1))vcnt <= 0;else if(hcnt == hcnt_max -1)vcnt <= vcnt +'d1;elsevcnt <= vcnt;endalways@(posedge clk)beginif(hcnt < H_SYNC_TIME)hs <= 1;elsehs <= 0;endalways@(posedge clk)beginif(vcnt < V_SYNC_TIME)vs <= 1;elsevs <= 0;end //整个屏幕有效显示区域always @(posedge clk) beginif(((hcnt >= H_SYNC_TIME +H_BACK_PORCH)&&(hcnt < H_SYNC_TIME +H_BACK_PORCH +H_ACTIVE))&&((vcnt >=V_SYNC_TIME + V_BACK_PORCH)&&(vcnt<V_SYNC_TIME+V_BACK_PORCH+V_ACTIVE)))beginde <= 1'b1;endelse beginde <= 1'b0;endend//当前显示的像素点的位置always@(posedge clk)beginif(de == 1'b1)beginpix_x <= hcnt - H_SYNC_TIME - H_BACK_PORCH;pix_y <= vcnt - V_SYNC_TIME - V_BACK_PORCH;endelse beginpix_x <= 'd0;pix_y <= 'd0;endend
//提前一拍读ROM地址always @(posedge clk) beginif((pix_x >= 278)&&(pix_x <390)&&((pix_y >=1080)&&(pix_y < 1183)))beginif(rd_rom_addr <'d11535)rd_rom_addr <= rd_rom_addr + 1;elserd_rom_addr <= 'd0;endelse beginrd_rom_addr <= rd_rom_addr;endendalways @(posedge clk) beginif((pix_x >= 279)&&(pix_x <391)&&((pix_y >=1080)&&(pix_y < 1183)))data_out <= data_in;else data_out <= 96'h800080_800080_800080_800080;endendmodule
屏幕显示效果:
VGA模块以及ROM数据皆正常,至于为什么图片偏绿,可能是因为显示器YUV和RGB色域转换的问题,后续文章会说明,现在暂时不管。
2.6 双线性算法模块
module bilinear_calculate(input clk ,input rst_n ,input [7:0] cur_u_dec , //输入的uinput [7:0] cur_v_dec , //输出的vinput pix_en,input [7:0] i_j_pix , //i_j_pix=f(i,j)input [7:0] i_j_1_pix , //i_j_1_pix=f(i,j+1)input [7:0] i_1_j_pix , //i_1_j_pix=f(i+1,j)input [7:0] i_1_j_1_pix , //i_1_j_1_pix=f(i+1,j+1)output reg [7:0] post_cal_data,output reg post_cal_data_valid =1'b0);//f(x,y)= f(0,0)*(1-u)(1-v)+f(1,0)*u*(1-v)+f(0,1)*(1-u)*v+f(l,1)*u*vreg [7:0] cur_u_dec_reg=8'd0 ;reg [7:0] cur_v_dec_reg=8'd0 ;reg [8:0] u_1 =9'd0; //1-ureg [8:0] v_1 =9'd0; //1-vreg [8:0] u =9'd0; //ureg [8:0] v =9'd0; //vreg [8:0] u_1_reg =9'd0; reg [8:0] v_1_reg =9'd0; reg [8:0] u_reg =9'd0; reg [8:0] v_reg =9'd0; reg [8:0] u_1_reg_copy =9'd0; reg [8:0] v_1_reg_copy =9'd0; reg [8:0] u_reg_copy =9'd0; reg [8:0] v_reg_copy =9'd0; wire [17:0] u_v ;wire [17:0] u_1_v_1 ;wire [17:0] u_v_1 ;wire [17:0] u_1_v ;reg [17:0] u_v_reg =18'd0;reg [17:0] u_1_v_1_reg =18'd0;reg [17:0] u_v_1_reg =18'd0;reg [17:0] u_1_v_reg =18'd0;reg [7:0] i_j_pix_reg =8'd0; reg [7:0] i_j_1_pix_reg =8'd0; reg [7:0] i_1_j_pix_reg =8'd0; reg [7:0] i_1_j_1_pix_reg =8'd0;wire [25:0] Fi_j_pix0 ; wire [25:0] Fi_j_1_pix0 ; wire [25:0] Fi_1_j_pix0 ;wire [25:0] Fi_1_j_1_pix0 ; reg [25:0] Fi_j_pix0_reg =26'd0; reg [25:0] Fi_j_1_pix0_reg =26'd0; reg [25:0] Fi_1_j_pix0_reg =26'd0;reg [25:0] Fi_1_j_1_pix0_reg =26'd0; reg [25:0] F_i_u_j_v1_pix0 =26'd0;reg [25:0] F_i_u_j_v2_pix0 =26'd0;reg [26:0] F_i_u_j_v_pix0 =27'd0;reg [5:0] pix_en_reg ='d0 ;always@(posedge clk )begincur_u_dec_reg <= cur_u_dec;cur_v_dec_reg <= cur_v_dec;
endalways@(posedge clk )beginu_1 <= {1'b0,~cur_u_dec_reg} + 1;v_1 <= {1'b0,~cur_v_dec_reg} + 1;u <= cur_u_dec_reg;v <= cur_v_dec_reg;
endalways@(posedge clk )beginu_1_reg <= u_1; v_1_reg <= v_1;u_reg <= u; v_reg <= v;
endalways@(posedge clk )beginu_1_reg_copy <= u_1; v_1_reg_copy <= v_1;u_reg_copy <= u; v_reg_copy <= v;
endmult_9_9 mult_u_v (.CLK(clk), .A(u_reg), .B(v_reg), .P(u_v)
);mult_9_9 mult_u_1_v_1 (.CLK(clk), .A(u_1_reg), .B(v_1_reg), .P(u_1_v_1)
);mult_9_9 mult_u_1_v (.CLK(clk), .A(u_1_reg_copy), .B(v_reg_copy), .P(u_1_v)
);mult_9_9 mult_u_v_1 (.CLK(clk), .A(u_reg_copy), .B(v_1_reg_copy), .P(u_v_1)
);always@(posedge clk)beginu_v_reg <= u_v; u_1_v_1_reg <= u_1_v_1; u_v_1_reg <= u_v_1; u_1_v_reg <= u_1_v;
end// pix_cal
always@(posedge clk)begini_j_pix_reg <= i_j_pix; i_j_1_pix_reg <= i_j_1_pix; i_1_j_pix_reg <= i_1_j_pix; i_1_j_1_pix_reg <= i_1_j_1_pix;
endmult_18_8 mult1 (.CLK(clk), .A(u_1_v_1_reg), .B(i_j_pix_reg), .P(Fi_j_pix0)
);mult_18_8 mult2 (.CLK(clk), .A(u_1_v_reg), .B(i_j_1_pix_reg), .P(Fi_j_1_pix0)
);mult_18_8 mult3(.CLK(clk), .A(u_v_1_reg), .B(i_1_j_pix_reg), .P(Fi_1_j_pix0)
);mult_18_8 mult4(.CLK(clk), .A(u_v_reg), .B(i_1_j_1_pix_reg), .P(Fi_1_j_1_pix0)
);always@(posedge clk)beginFi_j_pix0_reg <= Fi_j_pix0; Fi_j_1_pix0_reg <= Fi_j_1_pix0; Fi_1_j_pix0_reg <= Fi_1_j_pix0; Fi_1_j_1_pix0_reg <= Fi_1_j_1_pix0;
endalways@(posedge clk)beginF_i_u_j_v1_pix0 <= Fi_j_pix0_reg + Fi_j_1_pix0_reg;F_i_u_j_v2_pix0 <= Fi_1_j_pix0_reg + Fi_1_j_1_pix0_reg;endalways@(posedge clk)beginF_i_u_j_v_pix0 <= {1'b0,F_i_u_j_v1_pix0} + {1'b0,F_i_u_j_v2_pix0};
endalways@(posedge clk)beginif(F_i_u_j_v_pix0[26 : 24] == 'd0)post_cal_data <= F_i_u_j_v_pix0[23 : 16];elsepost_cal_data <= 8'hff;
endalways @(posedge clk) beginpix_en_reg <= {pix_en_reg[4:0],pix_en};if(pix_en_reg[5] == 1'b1)post_cal_data_valid <= 1'b1;elsepost_cal_data_valid <= 1'b0;
end
endmodule
三、下板验证
再修改一下vga模块,让它显示两部分,左边是原始图片,右边显示放大图片。效果如下: