FPGA实现图像几何变换:缩放
假设图像x轴方向的缩放比率Sx,y轴方向的缩放比率Sy,相应的变换表达式为:
其逆运算如下:
直接根据缩放公式计算得到的目标图像中,某些映射源坐标可能不是整数,从而找不到对应的像素位置。例如,当Sx=Sy=2时,图像放大2倍,放大图像中的像素(0, 1)对应于原图中的像素(0, 0.5),这不是整数坐标位置,自然也就无法提取其灰度值。因此我们必须进行某种近似处理,这里介绍一-种简单的策略即直接将它最邻近的整数坐标位置(0,0)或者(0,1)处的像素灰度值赋给它,这就是所谓的最近邻插值。当然还可以通过其他插值算法来近似处理。
然而,FPGA实现插值算法比较困难,足可以作为一篇论文来讨论了,为了简化操作,本次设计采用简单的像素复制和像素阉割的方式来实现图像的放大和缩小。
一、MATLAB实现
%-------------------------------------------------------------------------- %-- 图像的放大和缩小 %-------------------------------------------------------------------------- clear all close all clc img = imread('monkey.jpg'); %读取输入图片的数据 A = imresize(img,2); imwrite(A,'放大2倍.jpg'); B = imresize(img,0.5); imwrite(B,'缩小2倍.jpg');
MATLAB自带缩放函数,就懒得自己写了。默认采用的是最近邻插值法,也可以选择双线性插值法(bilinear)、双三次插值法(bicubic)。因为MATLAB中的 imshow 会让图片看起来尺寸一样,所以选择另存为图片,用电脑图片查看软件打开:
二、FPGA实现图像放大
1、实现原理
FPGA实现各种插值算法难度较大,我也没这个心情去深究,直接采用像素复制的办法。
假设一张图片如下所示:
现将图片扩大为原先的2倍,则图片变成如下所示:
接下来就用Verilog来实现这一算法。
2、代码设计
这次的代码设计和之前的镜像、旋转类似,关键都在于地址的选择,SDRAM 控制器比较复杂,懒得改,拿一个 RAM 来做缓存和跨时钟域的处理,图片分辨率为 140x140x16bit。
这次同样引入一个外部按键,用于控制放大的倍数,共有 1、2、4、8 四种倍数,如下所示:
always @(posedge rd_clk or negedge rst_n) begin if(!rst_n) begin n <= 2'b00; end else if(key_vld) begin n <= n + 1'b1; end end
接下来我们就可以利用这个 n 来设计放大的地址了,请看代码:
//偏移量公式:+ [side*(n-1)/2],n为放大倍数 //--------------------------------------------------- always @(*) begin case(n) 2'b00 : begin //原图 zoom_x = cnt_col; zoom_y = cnt_row; end 2'b01 : begin //2倍 zoom_x = (cnt_col+70)>>1; zoom_y = (cnt_row+70)>>1; end 2'b10 : begin //4倍 zoom_x = (cnt_col+210)>>2; zoom_y = (cnt_row+210)>>2; end 2'b11 : begin //8倍 zoom_x = (cnt_col+490)>>3; zoom_y = (cnt_row+490)>>3; end default : begin zoom_x = cnt_col; zoom_y = cnt_row; end endcase end
如果没有偏移量,那么图像的放大将从左上角开始,放大后的图像出现偏移,因此引入偏移量,使图片放大后的中间点还是原图片位置的中间点。偏移量公式为:[side * (n-1)/2],n为放大倍数,由按键提供。side为边长,这里我选用的图片是140x140,边长一样都是140。这个偏移公式实际是数学问题,不理解的话对照着上面的示意图写写算算就懂了。
3、上板验证
上板后首先看到的是原图:
放大2倍:
放大4倍:
放大8倍:
视频演示如下:
此次使用FPGA实现放大功能的实验成功。另外说一点的是,本次的设计尽管图像放大了,但是图像的尺寸没有变化,超过尺寸的图像直接舍去了。如果确实需要,我们也可以改成图像尺寸随着放大的尺寸而跟着变化,重点无非一样是显示的坐标设计。
为了避免除法器,改为移位计算,得到1、2、4、8倍放大,如果采用除法,则可以实现任意整数倍放大。
三、FPGA实现图像缩小
1、实现原理
假设一张图片如下所示:
现将图片变为原先的1/2,则图片变成如下所示:
尺寸变成了原先的1/2,此外数据也减少了,显示采用隔行隔列处理,将像素压缩为原先的1/2。(这种图片做的有点问题,但大致就是这个原理。)
2、FPGA实现
这次的代码设计和之前的镜像、旋转类似,关键都在于地址的选择,SDRAM 控制器比较复杂,懒得改,拿一个 RAM 来做缓存和跨时钟域的处理,图片分辨率为 140x140x16bit。
这次同样引入一个外部按键,用于控制缩小的倍数,共有 1、2、4、8 四种倍数,如下所示:
//缩小倍数 //--------------------------------------------------- always @(posedge rd_clk or negedge rst_n) begin if(!rst_n) begin n <= 2'b00; end else if(key_vld) begin n <= n + 1'b1; end end //读地址坐标 //--------------------------------------------------- assign zoom_x = cnt_col << n; assign zoom_y = cnt_row << n;
直接利用 n 来移位,达到缩小倍数的需求。
此外缩小会导致尺寸减小,因此最后的输出显示的尺寸也需要改动一下,如果不改动,那么可能会出现4张缩小的图片同时显示,比较难看。
这是原先的输出范围,行列计数时用过,本来最后输出也是用它的。
//读使能,确定显示位置 //--------------------------------------------------- assign rd_en = (TFT_x >= IMG_x) && (TFT_x < IMG_x + COL) && (TFT_y >= IMG_y) && (TFT_y < IMG_y + ROW) ? 1'b1 : 1'b0;
现在改动一下输出的尺寸:
//只显示左上角第一个缩小图像 //--------------------------------------------------- assign zoom_rd_en = (TFT_x >= IMG_x) && (TFT_x < IMG_x + (COL>>n)) && (TFT_y >= IMG_y) && (TFT_y < IMG_y + (ROW>>n)) ? 1'b1 : 1'b0;
//assign TFT_data = rd_en ? buffer[rd_addr] : 16'h0000; assign TFT_data = zoom_rd_en ? buffer[rd_addr] : 16'h0000;
这样就只显示4张缩小的图片的第1张图片了,好看多了。
3、上板验证
上板后首先看到的是原图:
缩小2倍:
缩小4倍:
缩小8倍:
视频演示如下:
此次使用FPGA实现缩小功能的实验成功。另外说一点的是,本次的设计的显示图像为左上角,没有调到正中间。如果确实需要,可以进一步改进。
为了避免除法器,改为移位计算,得到1、2、4、8倍缩小,如果采用除法,则可以实现任意整数倍缩小。
后记
FPGA实现几何变换的博客到此为止了,一共实现了:裁剪、镜像、旋转、平移和缩放。其中裁剪是最简单的,而后面4个都是利用了图片缓存的地址做文章,镜像一篇重点介绍了图片缓存地址的设置,后面几篇对此提的少,仅列出不同部分,如果看不懂可以回到镜像那篇博客仔细阅读。
很多设计都是小demo,实现的比较粗糙,但也是图像处理的一种,扩展了我们图像处理的思路,提高了Verilog的设计能力。
四、补充更新(二倍放大)
无意间想到一种二倍放大的简易实现方法,故更新一下。
1、原理
如果一副图像的宽度需要变为原先的一半,要怎么做?很简单,降采样就行,假如原先一行像素是1、2、3、4、5、6、7、8,那么降采样后就是 1、3、5、7 或2、4、6、8,行宽就变成一半了。高度变为原先一半也是一样的道理,那么二倍缩小就是行列都降采样就行。那么二倍放大就可以反过来想,每 1/2 行读两次,每一整行再重读一次。直接用 ram 就能实现了,如下所示:
这样一幅 4x2 的图片就二倍放大为 8x4 了,理论存在,实践开始。
2、时序分析
如图所示,设计 VGA_req 信号,使之为原先行的 1/2,列只取奇数部分,偶数的则填充上一行奇数的,但是输出的 de 则是完整的,关键代码如下所示:
assign VGA_req = (cnt_h >= H_SYNC + H_BACK - 3) && (cnt_h < H_SYNC + H_BACK + H_ADDR/2 - 3) && (cnt_v >= V_SYNC + V_BACK ) && (cnt_v < V_SYNC + V_BACK + V_ADDR ) && cnt_v[0];
ram 写如下所示:
//========================================================================== //== ram写 //========================================================================== always @(posedge clk) begin ram_wr_en <= VGA_req; end always @(posedge clk or negedge rst_n) begin if(!rst_n) begin ram_wr_addr <= 10'b0; end else if(ram_wr_addr == H_ADDR/2 -1)begin ram_wr_addr <= 10'b0; end else if(ram_wr_en) begin ram_wr_addr <= ram_wr_addr + 1'b1; end end assign ram_wr_data = VGA_din;
ram读如下所示:
assign ram_rd_en = (cnt_h >= H_SYNC + H_BACK - 1) && (cnt_h < H_SYNC + H_BACK + H_ADDR - 1) && (cnt_v >= V_SYNC + V_BACK ) && (cnt_v < V_SYNC + V_BACK + V_ADDR ); always @(posedge clk or negedge rst_n) begin if(!rst_n) begin addr_cnt <= 10'b0; end else if(addr_cnt == H_ADDR-1)begin addr_cnt <= 10'b0; end else if(ram_rd_en) begin addr_cnt <= addr_cnt + 1'b1; end end assign ram_rd_addr = {1'b0,addr_cnt[9:1]};
3、实现效果
这次就不上板了,用 Matlab 和 Modelsim 联合仿真弄一下吧。
仿真结果表明,处理后的图片比处理前的像素点放大了两倍,图像显示也是正确的。
关于仿真平台可以看我另一篇博客《》
参考资料:
[1] OpenS Lee:FPGA开源工作室(公众号)
[2] 张铮, 王艳平, 薛桂香. 数字图像处理与机器视觉[M]. 人民邮电出版社, 2010.