基于FPGA实现图像裁剪并进行modelsim仿真

Fighting_XH 发表于 2022/02/16 16:57:22 2022/02/16
【摘要】 图像处理——基于FPGA实现图像裁剪并进行modelsim仿真


小插曲

假期后的不知道第几天,无聊没事做。但就是不想学习,或者心里想学。可身体支撑不住,因为得了一种在家困来困去的病,想来放假的时候还寄来了两本学习的书,是有些可笑,现在已经是一层灰;在家为了做些事情,买了字帖练字,读张爱玲的故事,做手工,享受北方的大澡堂子等等等,过些日子暖和了才打算出去玩,今天倒是其余的事情玩累了,刚好不想学新知识,便整理一下之前的笔记吧!

1、 FPGA实现图像处理算法的设计流程

  • FPGA算法实现流程的掌握是比较重要的,实现之前首先要知道大概该怎么去做;
  • 分析图像处理算法,通俗来讲,就是通过一连串的操作,将输入图像转变成想要的结果。
  • 硬件实现算法的关键不是移植一个已有的串行算法,而是将算法转化成可用有效的计算架构。
  • 我们一般不会在FPGA上去开发新算法,如果要改变一个算法,那么首先在软件中来进行,确保在软件中是能够正确运行的情况下,再去进行硬件映射。

在这里插入图片描述

2、 单幅图像上的点操作

一个像素的输出只取决于输入图像的相应像素值,与位置无关。
另外点操作也可以并行处理,比如说将图像进行分割,分割成若干部分,每个部分单独进行处理。

  • 公式说明:f为任意函数,因此点操作可表示为一个映射或者转换函数。
    在这里插入图片描述

  • 图片说明: 左图:点操作通过一个映射函数,将输入I映射到输出Q上。
    右图:点操作的流水处理方式(对每个像素依次进行处理)
    在这里插入图片描述

应用场景:对比度增强、图像分割、彩色滤波、变化检测、图像掩模

3、几何裁剪

3.1 序言

包含相同内容的两幅图像可能由于成像角度、透视关系乃至镜头自身原因所造成的几何失真而呈现出截然不同的外观,这就给观测者或是后续的图像识别等带来了困扰。通过适当的几何变换可以最大程度地消除这些几何失真所产生的负面影响,有利于我们在后续的处理和识别工作中将注意力集中于图像中要观察的对象本身,而不是该对象的角度和位置等。

3.2 几何变换简单介绍

几何变换:按照一定的映射关系,对图像的像素位置进行运算,也可以说是将一幅图像中的坐标位置映射到另一幅图像中的新坐标位置,只改变像素的位置关系,不改变像素值的大小。

  • 一个几何变换需要两部分运算:首先是空间变换运算,如平移、旋转和镜像等,需要用它来表示输出图像与输入图像之间的(像素)映射关系;此外,还需要使用灰度插值算法,因为按变换关系进行计算后,可能出现不连续、空白像素空间的现象等(输出图像的像素可能被映射到输入图像的非整数坐标上)因此采用插值算法进行像素增加、弥补。

  • 几何变换常常作为其他图像处理应用的预处理步骤,适当的预处理对于使算法对图像范围内遇到的变化具有好的鲁棒性。另外预处理的主要目标是增强场景中感兴趣的信息或特征,同时抑制不相关的信息。

3.3 裁剪原理

如下为裁剪公式,I代表输入,Q代表输出,[x,y]代表原图像坐标,a r t b代表四个边界,从某种角度来看,它实际上一种非线性滤波器,保留输入坐标的同时变换输出色彩。

因此裁剪简单来说,就是将边界区域确定,然后将该区域的数据输出,从而进行显示即可。

在这里插入图片描述

4、FPGA实现几何裁剪

4.1 裁剪实现的重点

根据裁剪原理可知,实现的重点是边界的确定,以确定显示区域。

4.2 裁剪目标

给定一张120120的图片,裁剪成5050的图片,最终才VGA上进行显示。

4.3 准备工作

  • 首先采用工具将图片生成对应的.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下

4.4 关于像素值的获取

  •  分析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

4.5 裁剪算法实现的RTL图:

在这里插入图片描述

5、裁剪算法的modelsim仿真测试:

  •  下面波形图可以看出:

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)
);
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。