FPGA图像处理——缩放&上采样(分辨率提高)
一、缩放原理
图像几何变换又称为图像空间变换,它将一副图像中的坐标位置映射到另一幅图像中的新坐标位置。即一种空间映射关系,需要注意映射过程中的变化参数。图像的几何变换改变了像素的空间位置,建立一种原图像像素与变换后图像像素之间的映射关系。映射关系根据变换方向分为“向前映射”和“向后映射”。
- 向前映射:只要给出原图像上的任意像素坐标,能通过对应的映射关系获得到该像素在变换后图像的坐标位置。
- 向后映射:知道任意变换后图像上的像素坐标,计算其在原图像的像素坐标。
然而,在使用向前映射处理几何变换时通常会产生两个问题:映射不完全,映射重叠。本文只针对向前映射的一种情况“缩放”进行讨论。
图像缩放指的是将图像的尺寸变小或变大的过程,也就是减少或增加原图像数据的像素个数。简单来说,就是通过增加或删除像素点来改变图像的尺寸。当图像缩小时,图像会变得更加清晰,当图像放大时,图像的质量会有所下降,因此需要进行插值处理。
设水平缩放系数为sx,垂直缩放系数为sy,(x0,y0)为缩放前坐标,(x,y)为缩放后坐标,其缩放的坐标映射关系:
矩阵表示的形式为:
这是向前映射,在缩放的过程改变了图像的大小,使用向前映射会出现映射重叠和映射不完全的问题,所以这里更关心的是向后映射,也就是输出图像通过向后映射关系找到其在原图像中对应的像素。
向后映射关系:
二、FPGA实现图像缩放
(1)图片局部放大
在显示区域大小不变的情况下进行图片放大相当于对图片进行局部放大查看,如下图2倍放大后只显示黄色区域:
假设一张图片如下所示:
现将图片扩大为原先的2倍,则图片变成如下所示:
注:通常放大应采用邻近插值的方法填充像素,本次实验采用邻近复制的方法只进行列地址操作,不进行加除法操作,道理类似于广告牌,复制虽造成像素过渡细节较差,但从远处看可达到不错效果。
(2)图片缩小(下采样、降采样)
假设一张图片如下所示:
现将图片变为原先的1/2,则图片变成如下所示:
尺寸变成了原先的1/2,此外数据也减少了,显示采用隔行隔列处理,将像素压缩为原先的1/2。(这种图片做的有点问题,但大致就是这个原理。)
(3)FPGA实现
对于小分辨率图片可以用片内 RAM 来做缓存和跨时钟域的处理,对于大分辨率图片或视频帧则要用到片外SDRAM进行缓存。本实验只进行小分辨率图像实验,图像分辨率为 240x136x16bit(RGB565)。
采用RAM IP 或者直接申请一个数组用于存放图片数据,如下:
localparam COL = 240; //图像行像素个数
localparam ROW = 136; //图像场像素个数
reg [15:0] buffer [COL*ROW-1:0] ; //类似RAM ,这样就相当于申请了一块RAM,不过形式是数组。
形式上看似寄存器搭建的存储器数组,但经过综合后其实还是用的 RAMB16BWERs ,只是避免了RAM IP的设置。
为了看到不同倍数的缩放效果可以引入两个外部按键key,分别用于控制放大倍数Zoom_In和缩小倍数Zoom_Out。
对于从图片中心进行放大则需要设置偏移量,如果没有偏移量,图像的放大将从左上角开始,放大后的图像出现偏移,因此引入偏移量,使图片放大后的中间点还是原图片位置的中间点。偏移量公式为:[side * (n-1)/2],n为放大倍数,side为图片的尺寸;对于图片缩小,进行分辨率降采样后,显示区域应相应变小。
缩放代码如下所示:
1 module zoom(
2 input clk,
3 input rst_n,
4 input [1:0] Zoom_In, //放大倍数
5 input [1:0] Zoom_Out, //缩小倍数
6 input [15:0] data_in, //图片RGB565数据输入,晚于读地址1clk
7 input [9:0] hcount, //LCD显示驱动的行计数
8 input [9:0] vcount, //LCD显示驱动的场计数
9 output [15:0] read_addr, //读地址
10 output [15:0] data_out //缩放后的数据输出,在其它模块信号同步打拍
11 );
12
13 localparam COL = 240; //图像行像素个数
14 localparam ROW = 136; //图像场像素个数
15
16 parameter IMG_x=120; //显示起始行坐标
17 parameter IMG_y=68;
18
19 wire [8:0] cnt_col; //图片显示区域的行计数
20 wire [8:0] cnt_row; //图片显示区域的场计数
21 reg [8:0] zoom_In_x; //放大后的坐标映射
22 reg [8:0] zoom_In_y;
23 wire [8:0] zoom_x; //最终缩放后坐标映射
24 wire [8:0] zoom_y;
25 wire display_value; //图像有效显示区域
26
27 assign cnt_col = hcount >= IMG_x ? hcount-IMG_x : 0;
28 assign cnt_row = vcount >= IMG_y ? vcount-IMG_y : 0;
29
30 //=======================放大坐标映射==============================
31 //偏移量公式:+ [side*(n-1)/2],n为放大倍数 side为图像的宽、高
32 always @(*) begin
33 case(zoom_n)
34 2'b00 : begin //原图
35 zoom_In_x = cnt_col;
36 zoom_In_y = cnt_row;
37 end
38 2'b01 : begin //2倍
39 zoom_In_x = (cnt_col+120)>>1;
40 zoom_In_y = (cnt_row+68)>>1;
41 end
42 2'b10 : begin //4倍
43 zoom_In_x = (cnt_col+360)>>2;
44 zoom_In_y = (cnt_row+204)>>2;
45 end
46 2'b11 : begin //8倍
47 zoom_In_x = (cnt_col+840)>>3;
48 zoom_In_y = (cnt_row+476)>>3;
49 end
50 default : begin
51 zoom_In_x = cnt_col;
52 zoom_In_y = cnt_row;
53 end
54 endcase
55 end
56
57
58 //-------------------缩小坐标映射--------------------------------
59 //直接利用移位来达到缩小倍数的需求。串接在放大映射坐标后,可以在实现分辨率下采样(减小)的局部放大
60 assign zoom_x = zoom_In_x << Zoom_Out;
61 assign zoom_y = zoom_In_y << Zoom_Out;
62
63 //---------------------------------------------------
64 //由于缩小会使分辨率减小,原显示区域会出现缩小倍数^2的图像阵列,所以只显示左上角第一个缩小图像
65 assign display_value = (hcount >= IMG_x && hcount < IMG_x+(COL>>Zoom_Out)) && (vcount >= IMG_y && vcount < IMG_y+(ROW>>Zoom_Out));
66
67 assign read_addr = zoom_y * COL + zoom_x; //缩放映射后的 RAM 地址
68 assign data_out = display_value ? data_in : 0; //有效显示区域输出图像,无效背景为黑
69
70 endmodule
注意:由于将缩小倍率接入放大后坐标后,缩小倍率控制显示区域的大小,放大倍数控制局部放大,可在缩小的区域进行局部放大。
实验效果:
若缩小时显示区域不处理,会得到奇怪的图像阵列,图像若为方形则是整齐的阵列,效果如下:
2倍缩小
4倍缩小
8倍缩小
整个缩放实验效果:
FPGA图像处理——缩放
三、FPGA实现图像上采样(分辨率提高)
由于我所用的开发板的片内RAM资源有限,刚好能存放240*136_16bit的数据量,而我的LCD显示屏分辨率是480*272,想到利用利用邻近地址数据读两次的方法使图片能充满整个显示屏。对于二倍放大,每 1/2 行读两次,每一整行再重读一次。直接用 ram 就能实现了,如下所示:
localparam H_N = 240; //图像行像素个数
localparam V_N = 136; //图像场像素个数
localparam IMAGE_N = H_N*V_N; //图像像素个数
//====================RAM读地址计数器图片240_136显示===================================================
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_addr <= 0;
else if(add_cnt_addr)begin
if(end_cnt_addr)
cnt_addr <= 0;
else
cnt_addr <= cnt_addr +1; //地址计数
end
end
assign add_cnt_addr = data_vld && (hcount >= 120 && hcount < 360) && (vcount >= 68 && vcount < 204);
assign end_cnt_addr = add_cnt_addr && cnt_addr == IMAGE_N - 1;
2倍上采样的读地址:
1 //===================RAM读地址计数器图片240_136邻近复制2倍显示480_272==================================== 2 3 wire h_cnt2; 4 wire v_cnt2; 5 always @(posedge clk or negedge rst_n)begin 6 if(!rst_n) 7 cnt_addr <= 0; 8 else if(add_cnt_addr)begin 9 if(end_cnt_addr) 10 cnt_addr <= 0; 11 else if(!v_cnt2 && hcount == 479) //行计数TFT显示屏宽度-1 12 cnt_addr <= cnt_addr - (240-1); //要显示的图像宽度-1, cnt_addr <= cnt_addr - (240-1); 13 else if(h_cnt2) //!h_cnt2, 14 cnt_addr <= cnt_addr +1; 15 else 16 cnt_addr <= cnt_addr; 17 end 18 end 19 20 assign h_cnt2 = hcount[0]; //最低位每两次为一周期变化,2倍放大可用作标志位,同理次低位为4倍放大标志位 21 assign v_cnt2 = vcount[0]; 22 assign add_cnt_addr = data_vld; 23 assign end_cnt_addr = add_cnt_addr && vcount == 271 && hcount == 479; //最后一行复制
实验效果
FPGA图像处理——上采样(分辨率提高)
参考资料:
[1] OpenS Lee:FPGA开源工作室(公众号)
[2] 咸鱼FPGA :FPGA实现图像几何变换:缩放
[3] noticeable :几何变换的基本概念