• FPGA实现图像几何变换:裁剪


    序章

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

      图像几何变换又称为图像空间变换,它将一幅图像中的坐标位置映射到另一幅图像中的新坐标位置。我们学习几何变换的关键就是要确定这种空间映射关系,以及映射过程中的变换参数。几何变换不改变图像的像素值,只是在图像平面上进行像素的重新安排。一个几何变换需要两部分运算:首先是空间变换所需的运算,如平移、旋转和镜像等,需要用它来表示输出图像与输入图像之间的(像素)映射关系;此外,还需要使用灰度插值算法,因为按照这种变换关系进行计算,输出图像的像素可能被映射到输入图像的非整数坐标上。

      设原图像 f(x0,y0) 经过几何变换产生的目标图像为 g(x1 ,y1), 则该空间变换(映射)关系可表示为:

    x1 = s(x0,y0)
    y1 = t(x0,y0)

      其中,s(x0,y0) 和 t(x0,y0) 为由 f(x0,y0) 到 g(x1 ,y1) 的坐标变换函数。例如,当 x= s(x0,y0) = 2x0,y1= t(x0, y0) = 2y时,变换后的图像 g(x1,y1) 只是简单地在 x 和 y 两个空间方向上将 f(x0,y0) 的尺寸放大一倍。 由此可知,只要掌握了有关变换函数 s(x0, y0) 和 t(x0, y0) 的情况,就可以实现几何变换。

       而对于FPGA来说,输出像素通常来说并不是来自同一个输入像素位置。这就意味着需要一些形式的缓存来处理由几何形状改变引起的延迟。最简单的方法是将输入图像或输出图像(或两者)保存在一个帧缓存中。大部分的几何变换不太容易用数据流同时实现输入和输出。

    一、FPGA几何裁剪

      本篇博客整理一下 FPGA 实现几何变换中最简单的一个:几何裁剪。前向映射将原图像的像素坐标作为自变量,以某个变换函数得出目标图像的像素坐标,裁剪变换的变换函数如下,Q为输出,I为输入,x和y为原图像坐标,t、b、l、r为四个边界,从某种角度来看,它实际上一种非线性滤波器,保留输入坐标的同时变换输出色彩。

     

      所以,实现一个裁剪模块实际上是要通过给定的边界信息来确定可以输出的一个区域,然后根据是否在这个区域内来确定输出。

    二、MATLAB实现

      我们的输入是一张 480x272 的图片,准备裁剪成一张 200x200 的图片。代码如下所示:

     1 %--------------------------------------------------------------------------
     2 %                       裁剪
     3 %--------------------------------------------------------------------------
     4 clc;
     5 clear all;
     6 RGB = imread('Naruto.jpg'); %读取图像
     7 
     8 cut = RGB(37:236,141:340,:);%图像裁剪
     9 
    10 figure;imshow(RGB); title('原图');
    11 figure;imshow(cut); title('裁剪图');

      代码令人大跌眼镜,但是它的确有效,RGB的原始三维数据应该是(1:272,1:480,3),我们直接将它的横纵坐标切割,剩下的就是裁剪后的图像了。

      点击运行,得到如下结果:

      从结果来看,本次的 MATLAB 实现没有问题,成功的将一张原本 480x272 的图片裁剪为了 200x =200 的图片。而如此短的 MATLAB 代码也告诉我们,图像的几何裁剪,就是这么的简单。

    三、FPGA实现

      在本人之前的博客《协议——VGA》中,我设计的 VGA 和 TFT 屏驱动程序就提供了坐标点功能,当时我还没有学会 SDRAM 的操作,因此用 RAM 传输了一张 140x140x16bit 的图片,最后图片显示在了 480x272 分辨率的 TFT 屏正中央。那时其实就是运用到了图像的几何裁剪,将显示区域由 480x272 裁剪为位于正中央的 140x140。这次为了这一系列图像处理博客的连续性,我无意直接拿当时的程序来改造,而是重新来设计图像裁剪的代码。

      本次设计不需要用到 TFT 驱动程序的坐标信号,而是直接由输入的图像数据使能信号重新设计行列规划,然后设计边界点,当像素处于边界内时,输出原数据,当像素处于边界外时,输出为0。

    1、参数设置

      首先还是参数设置,我先列出来,后面的程序才容易懂。这里要提一点,MATLAB 程序中的计数是从1开始,而Verilog是从0开始,因此数值稍有不同。本次裁剪设计总共需要用到以下参数:

    parameter COL               = 11'd480               ; //图片长度
    parameter ROW               = 11'd272               ; //图片高度
    //---------------------------------------------------
    parameter TOP               = 11'd36                ; //边界:上
    parameter BOTTOM            = 11'd236               ; //边界:下
    parameter LEFT              = 11'd140               ; //边界:左
    parameter RIGHT             = 11'd340               ; //边界:右

    2、行列规划

      代码最开始是行列规划,用像素的使能信号来完成:

    //==========================================================================
    //==    行列划分
    //==========================================================================
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            cnt_col <= 11'd0;
        else if(add_cnt_col) begin
            if(end_cnt_col)
                cnt_col <= 11'd0;
            else
                cnt_col <= cnt_col + 11'd1;
        end
    end
    
    assign add_cnt_col = RGB_de;
    assign end_cnt_col = add_cnt_col && cnt_col== COL-11'd1;
    
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            cnt_row <= 11'd0;
        else if(add_cnt_row) begin
            if(end_cnt_row)
                cnt_row <= 11'd0;
            else
                cnt_row <= cnt_row + 11'd1;
        end
    end
    
    assign add_cnt_row = end_cnt_col;
    assign end_cnt_row = add_cnt_row && cnt_row== ROW-11'd1;

    3、裁剪算法

      然后就是本篇博客的核心代码——裁剪,用行列计数器和设定的参数进行比较,选择输出为原数据还是0即可。

    //==========================================================================
    //==    裁剪
    //==========================================================================
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cut_data <= 'd0;
        end
        else if((cnt_row >= TOP) && (cnt_row < BOTTOM) &&(cnt_col >= LEFT) && (cnt_col < RIGHT)) begin
            cut_data <= RGB_data;
        end
        else begin
            cut_data <= 'd0;
        end
    end

    4、信号同步

      最后是信号同步,这个切记不能忘,本次设计比较简单,只是在裁剪时耗费了一个时钟周期,因此打一拍输出即可:

    //==========================================================================
    //==    信号同步
    //==========================================================================
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cut_de    <= 1'd0;
            cut_vsync <= 1'd0;
            cut_hsync <= 1'd0;
        end
        else begin
            cut_de    <= RGB_de;
            cut_vsync <= RGB_vsync;
            cut_hsync <= RGB_hsync;
        end
    end

      OK,检查一下代码和工程,编译上板看看吧。

    三、上板验证

      点击编译生成 sof 文件,下载到开发板,打开串口助手将波特率调到1562500,用MATLAB提前生成好的图片数据用串口助手发送出去,图片就显示出来了:

      之所以选择动漫图片,其实是因为我的FPGA板子出现了问题,灰度数据会显示为红色。在工程中我设计了一个按键,按下后就会切换为裁剪处理后的图片,如下所示:

      和上面 MATLAB 对比可以看到,实验成功。

    后记

      本次设计非常简单,但是作为一名学生,再简单的试卷你也得做,若不是经历过 MATLAB 到 FPGA 上板,你又怎能知道它简单呢。其次里面包含的参数妙用、坐标思想、时序对齐等,其实也是一种不错的练习。

      后续我会继续整理其他的几何变换,或许就没那么简单了。

    参考资料:

    [1] OpenS Lee:FPGA开源工作室(公众号)

    [2] 张铮, 王艳平, 薛桂香. 数字图像处理与机器视觉[M]. 人民邮电出版社, 2010.

  • 相关阅读:
    python面试题解析(python基础篇80题)
    python面试题
    网络IO模型 主要 IO多路复用
    线程队列 线程池 协程
    线程的Thread模块 同步控制:锁,事件,信号量,条件,定时器
    进程池,线程的理论,Threading.Thread来创建线程
    进程之间的通信(IPC),对列,管道,数据共享.进程池初识
    Process 进程之间的数据隔离问题,守护进程,锁,信号量,事件
    js get the local domain and path fast
    github jekyll blog
  • 原文地址:https://www.cnblogs.com/xianyufpga/p/12498840.html
Copyright © 2020-2023  润新知