基于FPGA实现图像裁剪并进行modelsim仿真
文章目录
假期后的不知道第几天,无聊没事做。但就是不想学习,或者心里想学。可身体支撑不住,因为得了一种在家困来困去的病,想来放假的时候还寄来了两本学习的书,是有些可笑,现在已经是一层灰;在家为了做些事情,买了字帖练字,读张爱玲的故事,做手工,享受北方的大澡堂子等等等,过些日子暖和了才打算出去玩,今天倒是其余的事情玩累了,刚好不想学新知识,便整理一下之前的笔记吧!
- FPGA算法实现流程的掌握是比较重要的,实现之前首先要知道大概该怎么去做;
- 分析图像处理算法,通俗来讲,就是通过一连串的操作,将输入图像转变成想要的结果。
- 硬件实现算法的关键不是移植一个已有的串行算法,而是将算法转化成可用有效的计算架构。
- 我们一般不会在FPGA上去开发新算法,如果要改变一个算法,那么首先在软件中来进行,确保在软件中是能够正确运行的情况下,再去进行硬件映射。
一个像素的输出只取决于输入图像的相应像素值,与位置无关。
另外点操作也可以并行处理,比如说将图像进行分割,分割成若干部分,每个部分单独进行处理。
-
公式说明:f为任意函数,因此点操作可表示为一个映射或者转换函数。
-
图片说明: 左图:点操作通过一个映射函数,将输入I映射到输出Q上。
右图:点操作的流水处理方式(对每个像素依次进行处理)
应用场景:对比度增强、图像分割、彩色滤波、变化检测、图像掩模
包含相同内容的两幅图像可能由于成像角度、透视关系乃至镜头自身原因所造成的几何失真而呈现出截然不同的外观,这就给观测者或是后续的图像识别等带来了困扰。通过适当的几何变换可以最大程度地消除这些几何失真所产生的负面影响,有利于我们在后续的处理和识别工作中将注意力集中于图像中要观察的对象本身,而不是该对象的角度和位置等。
几何变换:按照一定的映射关系,对图像的像素位置进行运算,也可以说是将一幅图像中的坐标位置映射到另一幅图像中的新坐标位置,只改变像素的位置关系,不改变像素值的大小。
-
一个几何变换需要两部分运算:首先是空间变换运算,如平移、旋转和镜像等,需要用它来表示输出图像与输入图像之间的(像素)映射关系;此外,还需要使用灰度插值算法,因为按变换关系进行计算后,可能出现不连续、空白像素空间的现象等(输出图像的像素可能被映射到输入图像的非整数坐标上)因此采用插值算法进行像素增加、弥补。
-
几何变换常常作为其他图像处理应用的预处理步骤,适当的预处理对于使算法对图像范围内遇到的变化具有好的鲁棒性。另外预处理的主要目标是增强场景中感兴趣的信息或特征,同时抑制不相关的信息。
如下为裁剪公式,I代表输入,Q代表输出,[x,y]代表原图像坐标,a r t b代表四个边界,从某种角度来看,它实际上一种非线性滤波器,保留输入坐标的同时变换输出色彩。
因此裁剪简单来说,就是将边界区域确定,然后将该区域的数据输出,从而进行显示即可。
根据裁剪原理可知,实现的重点是边界的确定,以确定显示区域。
给定一张120120的图片,裁剪成5050的图片,最终才VGA上进行显示。
-
首先采用工具将图片生成对应的.mif文件,然后将此文件存储在ROM中。——注意ROM的空间大小
-
掌握VGA的时序控制。让图片最终在分辨率为640*480的屏幕上显示。
-
计算边界的坐标:(+141 和 +32 是为了让图片以中心进行裁剪)
左:320-25+141=436
右:436+50=486 (左边界+图片长度)
上:240-25+32=247
下:247+50=297 (左边界+图片宽度)
//裁剪后图片边界的坐标
parameter X0 = ((320-25) +141 ) ;//436左
parameter X1 = ((320+25) +141);//486右
parameter Y0 = ((240-25) +32); //247上
parameter Y1 = ((240+25) +32); //297下
- 分析120*120图片中像素的分布,以采用寻址的方式来获取图像的像素值。
- 一个小方格代表120*120图片的一个像素,根据.mif文件可知道对应地址下像素的值。根据行列计数器和边界点可得到方格点坐标值。
分析:
第二行第一个小方格的坐标(436,247),对应第1个像素点
第二行第一个小方格的坐标(436,248),对应第121个像素
…
- 显示区域的设计:
always @(*) begin
rom_area = (h_cnt >= X0 && h_cnt <= X1 )&&( v_cnt >= Y0 && v_cnt <= Y1);
end
- 存储器中,地址是一维的,因此进行二维坐标到一维地址的转换:
always @(*) begin
rom_addr = (h_cnt-X0) +( 120*(v_cnt-Y0)); //裁剪后的坐标地址
end
- .mif文件的数据的部分查看
根据上面有效区域的分析,以及上述mif文件的数值可知:
有效区域第一行的第一个小方格的坐标是(436,247)且对应像素9dd0
有效区域第一行的第二个小方格的坐标是(437,247)且对应像素9daf
- 下面波形图可以看出:
rom地址从第一个坐标处递增,且坐标(436,247)时,对应rom像素是9dd0,下一个像素是坐标(436,247)时,为9daf,直到486第一行结束rom_area变为低电平,因此再从下一行进行像素的读取
同时,第一行有50个像素,因此是0—49,所以第一行最后坐标(486,247)对应rom中第50个像素的值,为9dce,看波形图验证正确
下一行的话,就要找v_cnt计数器加1,也就是v_cnt = 248的位置:
因此从(436,248)的位置开始分析第二行第一个方格像素:
- 地址像素的验证:坐标(436,248)时,对应的rom_addr= (h_cnt-X0) +(
120*(v_cnt-Y0))=120,因此rom_addr = 120,也就是对应的mif文件中第129个像素的值958e
以此类推:坐标(486,297)时,对应的rom_addr= (h_cnt-X0) +( 120*(v_cnt-Y0))=6050,因此rom_addr = 6050,也就是对应的mif文件中的6049数值ff3e,此时对应rom存储的数据均正确。
但注意观察可以看出,lcd_rgb在坐标为297的时候全部都是0,分析可得到,原因是rom_area在v_cnt = 297的时候变为了低电平。
- 为什么是低电平——符号问题 之前这一块,h_cnt < X1 、v_cnt < Y1 ,所以错误,因此要注意这里的符号。
修改后重新打开modelsim仿真,可看到lcd_rgb有了数据。
至此就是简单的仿真验证。
- 疑问:lcd_rgb数据的接收总是延迟一个周期,因此要让信号同步,信号到底如果同步还没整明白,放假在家这块应该会被搁置了…
裁剪效果图:
延神:
图像缩放:根据前面的分析,同样计算缩放后的边界坐标、显示区域和缩放后的坐标地址
//计算放大2倍后的图片显示区域的坐标
parameter X0 = ((320-120) + 141); //401
parameter X1 = ((320+120) + 141); //521
parameter Y0 = ((240-120) + 32); //212
parameter Y1 = ((240+120) + 32); //331
//显示图片区域
always @(*)begin
rom_area = h_cnt >=X0 && h_cnt < X1 && v_cnt >= Y0 && v_cnt < Y1;
end
always @(*)begin
rom_addr = ((h_cnt-X0)>>1) +( 120*((v_cnt-Y0)>>1)); //放大2倍后的坐标地址
end
图像镜像:
//四种图像状态,根据行列计数器,来进行镜像后的坐标计算
always @(*) begin
case(mode)
2'b00 : begin //原图
mirror_x = cnt_col;
mirror_y = cnt_row;
end
2'b01 : begin //水平镜像
mirror_x = (IMG_WIDTH-1) - cnt_col;
mirror_y = cnt_row;
end
2'b10 : begin //垂直镜像
mirror_x = cnt_col;
mirror_y = (IMG_HEIGHT-1) - cnt_row;
end
2'b11 : begin //水平垂直镜像
mirror_x = (IMG_WIDTH-1) - cnt_col;
mirror_y = (IMG_HEIGHT-1) - cnt_row;
end
default : begin
mirror_x = cnt_col;
mirror_y = cnt_row;
end
endcase
end
//二维坐标转为一维地址
always @(*) begin
rd_addr = (mirror_y*IMG_WIDTH ) + mirror_x;
end
图像旋转:
//旋转的几种情况
always @(*) begin
case(mode)
2'b00 : begin //原图
rotate_x = cnt_col;
rotate_y = cnt_row;
end
2'b01 : begin //右旋转90度
rotate_x = cnt_row;
rotate_y = (IMG_HEIGHT-1) - cnt_col;
end
2'b10 : begin //旋转180
rotate_x = (IMG_HEIGHT-1) - cnt_col;
rotate_y = (IMG_WIDTH-1) - cnt_row;;
end
2'b11 : begin //旋转270
rotate_x = (IMG_WIDTH-1) - cnt_row;
rotate_y = cnt_col;
end
default : begin
rotate_x = cnt_col;
rotate_y = cnt_row;
end
endcase
end
//坐标与地址的转换
always @(*) begin
rd_addr = (rotate_y*IMG_WIDTH ) + rotate_x;
end
补充:上面出现case的几种情况,均可引入按键模块来进行显示,避免来回修改的麻烦。
key_trig key_trig1 (
.clk(clk),
.rst_n(rst_n),
.key_trig(key_trig),
.trig_key_trig(key)
);
- 点赞
- 收藏
- 关注作者
评论(0)