鏡像變換又分為水平鏡像和豎直鏡像。水平鏡像即將圖像左半部分和右半部分以圖像豎直中軸線為中心軸進行對換;而豎直鏡像則是將圖像上半部分和下半部分以圖像水平中軸線為中心軸進行對換,如圖所示。
水平鏡像的變換關系為:
對矩陣求逆得到:
豎直鏡像的變換關系為:
對矩陣求逆得到:
一、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 為輸入,Xt 和Yt 為像素坐標,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.