FPGA實現圖像幾何變換:鏡像


  鏡像變換又分為水平鏡像和豎直鏡像。水平鏡像即將圖像左半部分和右半部分以圖像豎直中軸線為中心軸進行對換;而豎直鏡像則是將圖像上半部分和下半部分以圖像水平中軸線為中心軸進行對換,如圖所示。

   水平鏡像的變換關系為:

  對矩陣求逆得到:

  豎直鏡像的變換關系為:

  對矩陣求逆得到:

 

一、MATLAB實現

1、函數法實現 

%--------------------------------------------------------------------------
%                       函數法鏡像
%--------------------------------------------------------------------------
clc;
clear all;
RGB = imread('monkey.jpg'); %讀取圖像 [ROW,COL,N] = size(RGB); tform1 = maketform('affine',[-1 0 0;0 1 0;COL 0 1]); %定義水平鏡像變換矩陣 tform2 = maketform('affine',[1 0 0;0 -1 0;0 ROW 1]); %定義垂直鏡像變換矩陣 tform3 = maketform('affine',[-1 0 0;0 -1 0;COL ROW 1]); %定義水平垂直鏡像變換矩陣 H_mirror = imtransform(RGB,tform1,'nearest'); V_mirror = imtransform(RGB,tform2,'nearest'); HV_mirror = imtransform(RGB,tform3,'nearest'); subplot(2,2,1),imshow(RGB); title('原圖'); subplot(2,2,2),imshow(H_mirror); title('水平鏡像'); subplot(2,2,3),imshow(V_mirror); title('垂直鏡像'); subplot(2,2,4),imshow(HV_mirror);title('水平垂直鏡像');

   參數 transformtype指定了變換的類型,如常見的 ‘affine’ 為二維或多位仿射變換,包括平移、旋轉、比例、拉伸和錯切等。

   點擊運行得到如下結果:

2、公式法實現

  光靠函數法還是不行,我們得自己締造公式來實現。它的原理如下所示:

  Q 為輸出,I 為輸入,X和Y為像素坐標,width 和 height 為圖像寬度和高度。因此鏡像算法就是講輸入坐標和圖像的寬度高度做減法得到輸出坐標,同時由於減法的結果必然小於被減數,固這實際上是單純的無符號數的減法。

  由此我們得到如下 MATLAB 代碼:

%--------------------------------------------------------------------------
%                       公式法鏡像
%--------------------------------------------------------------------------
clc;
clear all;
RGB = imread('monkey.jpg'); %讀取圖像
 
[ROW,COL,N] = size(RGB);

H_mirror  = uint8(zeros(ROW, COL,N)); %Horizontal mirror
V_mirror  = uint8(zeros(ROW, COL,N)); %Vertical mirror
HV_mirror = uint8(zeros(ROW, COL,N)); %H&V miirror

%水平鏡像
for i =1:ROW
    for j=1:COL
        for k=1:N
            x = i;
            y = COL-j+1;
            z = k;
            H_mirror(x,y,z) =RGB(i,j,k);
        end
    end
end

%垂直鏡像
for i =1:ROW
    for j=1:COL
        for k=1:N
            x = ROW-i+1;
            y = j;
            z = k;
            V_mirror(x,y,z) =RGB(i,j,k);
        end
    end
end

%水平垂直鏡像
for i =1:ROW
    for j=1:COL
        for k=1:N
            x = ROW-i+1;
            y = COL-j+1;
            z = k;
            HV_mirror(x,y,z) =RGB(i,j,k);
        end
    end
end

subplot(2,2,1),imshow(RGB);      title('原圖');
subplot(2,2,2),imshow(H_mirror); title('水平鏡像');
subplot(2,2,3),imshow(V_mirror); title('垂直鏡像');
subplot(2,2,4),imshow(HV_mirror);title('水平垂直鏡像');

   點擊運行得到如下結果:

  和函數法的結果一致,表明我們的公式法是可行的。

 

二、FPGA實現

  鏡像需要一整幀的像素進行坐標轉換,因此必須對一幀圖片進行緩存,假設一個緩存器,我們可以通過公式計算到變換后的坐標和數值的對應關系,逐漸的寫入緩存器,而后就可以直接從緩存區順序讀出,最后的結果就是鏡像的了。

  緩存器件比較常用的有FIFO、RAM、SDRAM、DDR2、DDR3等,由於FIFO沒有地址的概念,所以首先排除,而SDRAM和DDR2、DDR3的接口較復雜,因此本次設計采用 RAM 來緩存一幀圖像,RAM容量小,我選擇了緩存 140x140x16bit 的圖像,再增大我的FPGA芯片就支持不住了。雖然圖片小,但足以驗證算法,如果后續遇到實際項目需要大分辨率的圖片,再考慮使用 SDRAM 或 DDR2、DDR3 也是可以的。

1、端口信號

  本次設計輸入是串口,輸出是TFT屏,因此本模塊共有讀寫兩個不同的時鍾。本次設計用到坐標變換,因此采用之前在博客《協議——VGA》中寫好的 TFT 屏驅動程序,將坐標信號引進來,其他則沒什么特別的:

module Mirror
//========================< 端口 >==========================================
(
//system --------------------------------------------
input   wire                rst_n                   , //復位,低電平有效
//uart ----------------------------------------------
input   wire                wr_clk                  , //50m
input   wire    [15:0]      din                     ,
input   wire                din_vld                 ,
//key -----------------------------------------------
input   wire                key_vld                 , //按鍵切換模式
//TFT_driver ----------------------------------------
input   wire                rd_clk                  , //9m
input   wire    [ 9:0]      TFT_x                   , //得到顯示區域橫坐標
input   wire    [ 9:0]      TFT_y                   , //得到顯示區域縱坐標
output  wire    [15:0]      TFT_data                  //輸出圖像數據
);

2、參數設計

  本工程的 TFT 屏是 480x272 的,而圖片我選擇的是 140x140,我希望圖片能顯示在 TFT 屏的中間,這樣更好看,當然還有圖片的長度和高度也都用參數化的形式表示。

parameter COL               = 10'd140               ; //圖片長度
parameter ROW               = 10'd140               ; //圖片高度
parameter IMG_x             = 10'd170               ; //圖片起始橫坐標
parameter IMG_y             = 10'd66                ; //圖片起始縱坐標

3、緩存buffer寫

  前面說到緩存器可以用 RAM ,但在 OpenS Lee 的源程序中,我學到了一種新的方法來替代RAM:二維數組。二維數組用的好就和RAM沒有區別,而且比RAM要省事,畢竟RAM還要我們鼠標點擊去生成IP核,又要例化,還是有點麻煩。

  首先是定義:  reg [15:0] buffer[COL*ROW-1:0] ; //類似RAM ,這樣就相當於申請了一塊RAM,不過形式是數組。 

  接着我們要對這個數組進行寫操作,特別注意的是讀寫操作是不同的時鍾。

  寫是直接寫,沒有什么貓膩,寫進去就行,時鍾為寫時鍾。

//==========================================================================
//==    緩存buffer,寫操作
//==========================================================================
//寫數據
//---------------------------------------------------
always @(posedge wr_clk) begin
    buffer[wr_addr] <= din;
end

//寫地址
//---------------------------------------------------
always @(posedge wr_clk or negedge rst_n) begin
    if(!rst_n) begin
        wr_addr <= 'd0;
    end
    else if(din_vld) begin
        wr_addr <= wr_addr + 1'b1;
    end
end

4、讀使能

  本來是沒有讀使能的,這里設置一個讀使能,目的在於確定圖像在TFT屏中的顯示位置,此外給下面的行列規划提供加一條件。

//讀使能,確定顯示位置
//---------------------------------------------------
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;

5、行列規划

//行計數
//---------------------------------------------------
always @(posedge rd_clk or negedge rst_n) begin
    if(!rst_n)
        cnt_col <= 10'd0;
    else if(add_cnt_col) begin
        if(end_cnt_col)
            cnt_col <= 10'd0;
        else
            cnt_col <= cnt_col + 10'd1;
    end
end

assign add_cnt_col = rd_en;
assign end_cnt_col = add_cnt_col && cnt_col== COL-10'd1;

//列計數
//---------------------------------------------------
always @(posedge rd_clk or negedge rst_n) begin
    if(!rst_n)
        cnt_row <= 10'd0;
    else if(add_cnt_row) begin
        if(end_cnt_row)
            cnt_row <= 10'd0;
        else
            cnt_row <= cnt_row + 10'd1;
    end
end

assign add_cnt_row = end_cnt_col;
assign end_cnt_row = add_cnt_row && cnt_row== ROW-10'd1; 

6、鏡像操作

  通過公式計算鏡像后的坐標,這是本篇博客的核心代碼,有4種不同的模式,通過按鍵調節模式變化,采用case語句進行判斷,時鍾是讀時鍾。

//==========================================================================
//==    鏡像操作,讀地址重規划
//==========================================================================
always @(posedge rd_clk or negedge rst_n) begin
    if(!rst_n) begin
        mode <= 2'b00;
    end
    else if(key_vld) begin
        mode <= mode + 1'b1;
    end
end

always @(*) begin
    case(mode)
        2'b00   : begin                                     //原圖
                    mirror_x = cnt_col;
                    mirror_y = cnt_row;
                  end
        2'b01   : begin                                     //水平鏡像
                    mirror_x = (COL-1) - cnt_col;
                    mirror_y = cnt_row;
                  end
        2'b10   : begin                                     //垂直鏡像
                    mirror_x = cnt_col;
                    mirror_y = (ROW-1) - cnt_row;
                  end
        2'b11   : begin                                     //水平垂直鏡像
                    mirror_x = (COL-1) - cnt_col;
                    mirror_y = (ROW-1) - cnt_row;
                  end
        default : begin
                    mirror_x = cnt_col;
                    mirror_y = cnt_row;
                  end
    endcase
end

7、緩存buffer讀

//==========================================================================
//==    緩存buffer,讀操作
//==========================================================================
always @(posedge rd_clk or negedge rst_n) begin
    if(!rst_n)
        rd_addr <= 'd0;
    else
        rd_addr <= mirror_y * COL + mirror_x;
end

always @(posedge rd_clk) begin
        rd_en_r <= rd_en;
end

assign TFT_data = rd_en_r ? buffer[rd_addr] : 16'hffff;

   注意時鍾,全程都要注意。這個buffer同時解決了串口和TFT屏之間跨時鍾域的問題。

 

三、上板驗證

  總共4種模式,模式0、1、2、3 分別得到如下結果:

  把這4副圖也按MATLAB的樣子拼到一塊看看吧:

  和上面的 MATLAB 實驗結果對比,可以看到此次圖像鏡像實驗成功。

   視頻演示如下:

 

參考資料:

[1] OpenS Lee:FPGA開源工作室(公眾號)

[2] 張錚, 王艷平, 薛桂香. 數字圖像處理與機器視覺[M]. 人民郵電出版社, 2010.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM