假設圖像x軸方向的縮放比率Sx,y軸方向的縮放比率Sy,相應的變換表達式為:
其逆運算如下:
直接根據縮放公式計算得到的目標圖像中,某些映射源坐標可能不是整數,從而找不到對應的像素位置。例如,當Sx=Sy=2時,圖像放大2倍,放大圖像中的像素(0, 1)對應於原圖中的像素(0, 0.5),這不是整數坐標位置,自然也就無法提取其灰度值。因此我們必須進行某種近似處理,這里介紹一-種簡單的策略即直接將它最鄰近的整數坐標位置(0,0)或者(0,1)處的像素灰度值賦給它,這就是所謂的最近鄰插值。當然還可以通過其他插值算法來近似處理。
然而,FPGA實現插值算法比較困難,足可以作為一篇論文來討論了,為了簡化操作,本次設計采用簡單的像素復制和像素閹割的方式來實現圖像的放大和縮小。
一、MATLAB實現
%-------------------------------------------------------------------------- %-- 圖像的放大和縮小 %-------------------------------------------------------------------------- clear all close all clc img = imread('monkey.jpg'); %讀取輸入圖片的數據 A = imresize(img,2); imwrite(A,'放大2倍.jpg'); B = imresize(img,0.5); imwrite(B,'縮小2倍.jpg');
MATLAB自帶縮放函數,就懶得自己寫了。默認采用的是最近鄰插值法,也可以選擇雙線性插值法(bilinear)、雙三次插值法(bicubic)。因為MATLAB中的 imshow 會讓圖片看起來尺寸一樣,所以選擇另存為圖片,用電腦圖片查看軟件打開:
二、FPGA實現圖像放大
1、實現原理
FPGA實現各種插值算法難度較大,我也沒這個心情去深究,直接采用像素復制的辦法。
假設一張圖片如下所示:
現將圖片擴大為原先的2倍,則圖片變成如下所示:
接下來就用Verilog來實現這一算法。
2、代碼設計
這次的代碼設計和之前的鏡像、旋轉類似,關鍵都在於地址的選擇,SDRAM 控制器比較復雜,懶得改,拿一個 RAM 來做緩存和跨時鍾域的處理,圖片分辨率為 140x140x16bit。
這次同樣引入一個外部按鍵,用於控制放大的倍數,共有 1、2、4、8 四種倍數,如下所示:
always @(posedge rd_clk or negedge rst_n) begin if(!rst_n) begin n <= 2'b00; end else if(key_vld) begin n <= n + 1'b1; end end
接下來我們就可以利用這個 n 來設計放大的地址了,請看代碼:
//偏移量公式:+ [side*(n-1)/2],n為放大倍數 //--------------------------------------------------- always @(*) begin case(n) 2'b00 : begin //原圖 zoom_x = cnt_col; zoom_y = cnt_row; end 2'b01 : begin //2倍 zoom_x = (cnt_col+70)>>1; zoom_y = (cnt_row+70)>>1; end 2'b10 : begin //4倍 zoom_x = (cnt_col+210)>>2; zoom_y = (cnt_row+210)>>2; end 2'b11 : begin //8倍 zoom_x = (cnt_col+490)>>3; zoom_y = (cnt_row+490)>>3; end default : begin zoom_x = cnt_col; zoom_y = cnt_row; end endcase end
如果沒有偏移量,那么圖像的放大將從左上角開始,放大后的圖像出現偏移,因此引入偏移量,使圖片放大后的中間點還是原圖片位置的中間點。偏移量公式為:[side * (n-1)/2],n為放大倍數,由按鍵提供。side為邊長,這里我選用的圖片是140x140,邊長一樣都是140。這個偏移公式實際是數學問題,不理解的話對照着上面的示意圖寫寫算算就懂了。
3、上板驗證
上板后首先看到的是原圖:
放大2倍:
放大4倍:
放大8倍:
視頻演示如下:
此次使用FPGA實現放大功能的實驗成功。另外說一點的是,本次的設計盡管圖像放大了,但是圖像的尺寸沒有變化,超過尺寸的圖像直接舍去了。如果確實需要,我們也可以改成圖像尺寸隨着放大的尺寸而跟着變化,重點無非一樣是顯示的坐標設計。
為了避免除法器,改為移位計算,得到1、2、4、8倍放大,如果采用除法,則可以實現任意整數倍放大。
三、FPGA實現圖像縮小
1、實現原理
假設一張圖片如下所示:
現將圖片變為原先的1/2,則圖片變成如下所示:
尺寸變成了原先的1/2,此外數據也減少了,顯示采用隔行隔列處理,將像素壓縮為原先的1/2。(這種圖片做的有點問題,但大致就是這個原理。)
2、FPGA實現
這次的代碼設計和之前的鏡像、旋轉類似,關鍵都在於地址的選擇,SDRAM 控制器比較復雜,懶得改,拿一個 RAM 來做緩存和跨時鍾域的處理,圖片分辨率為 140x140x16bit。
這次同樣引入一個外部按鍵,用於控制縮小的倍數,共有 1、2、4、8 四種倍數,如下所示:
//縮小倍數 //--------------------------------------------------- always @(posedge rd_clk or negedge rst_n) begin if(!rst_n) begin n <= 2'b00; end else if(key_vld) begin n <= n + 1'b1; end end //讀地址坐標 //--------------------------------------------------- assign zoom_x = cnt_col << n; assign zoom_y = cnt_row << n;
直接利用 n 來移位,達到縮小倍數的需求。
此外縮小會導致尺寸減小,因此最后的輸出顯示的尺寸也需要改動一下,如果不改動,那么可能會出現4張縮小的圖片同時顯示,比較難看。
這是原先的輸出范圍,行列計數時用過,本來最后輸出也是用它的。
//讀使能,確定顯示位置 //--------------------------------------------------- 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;
現在改動一下輸出的尺寸:
//只顯示左上角第一個縮小圖像 //--------------------------------------------------- assign zoom_rd_en = (TFT_x >= IMG_x) && (TFT_x < IMG_x + (COL>>n)) && (TFT_y >= IMG_y) && (TFT_y < IMG_y + (ROW>>n)) ? 1'b1 : 1'b0;
//assign TFT_data = rd_en ? buffer[rd_addr] : 16'h0000; assign TFT_data = zoom_rd_en ? buffer[rd_addr] : 16'h0000;
這樣就只顯示4張縮小的圖片的第1張圖片了,好看多了。
3、上板驗證
上板后首先看到的是原圖:
縮小2倍:
縮小4倍:
縮小8倍:
視頻演示如下:
此次使用FPGA實現縮小功能的實驗成功。另外說一點的是,本次的設計的顯示圖像為左上角,沒有調到正中間。如果確實需要,可以進一步改進。
為了避免除法器,改為移位計算,得到1、2、4、8倍縮小,如果采用除法,則可以實現任意整數倍縮小。
后記
FPGA實現幾何變換的博客到此為止了,一共實現了:裁剪、鏡像、旋轉、平移和縮放。其中裁剪是最簡單的,而后面4個都是利用了圖片緩存的地址做文章,鏡像一篇重點介紹了圖片緩存地址的設置,后面幾篇對此提的少,僅列出不同部分,如果看不懂可以回到鏡像那篇博客仔細閱讀。
很多設計都是小demo,實現的比較粗糙,但也是圖像處理的一種,擴展了我們圖像處理的思路,提高了Verilog的設計能力。
四、補充更新(二倍放大)
無意間想到一種二倍放大的簡易實現方法,故更新一下。
1、原理
如果一副圖像的寬度需要變為原先的一半,要怎么做?很簡單,降采樣就行,假如原先一行像素是1、2、3、4、5、6、7、8,那么降采樣后就是 1、3、5、7 或2、4、6、8,行寬就變成一半了。高度變為原先一半也是一樣的道理,那么二倍縮小就是行列都降采樣就行。那么二倍放大就可以反過來想,每 1/2 行讀兩次,每一整行再重讀一次。直接用 ram 就能實現了,如下所示:
這樣一幅 4x2 的圖片就二倍放大為 8x4 了,理論存在,實踐開始。
2、時序分析
如圖所示,設計 VGA_req 信號,使之為原先行的 1/2,列只取奇數部分,偶數的則填充上一行奇數的,但是輸出的 de 則是完整的,關鍵代碼如下所示:
assign VGA_req = (cnt_h >= H_SYNC + H_BACK - 3) && (cnt_h < H_SYNC + H_BACK + H_ADDR/2 - 3) && (cnt_v >= V_SYNC + V_BACK ) && (cnt_v < V_SYNC + V_BACK + V_ADDR ) && cnt_v[0];
ram 寫如下所示:
//========================================================================== //== ram寫 //========================================================================== always @(posedge clk) begin ram_wr_en <= VGA_req; end always @(posedge clk or negedge rst_n) begin if(!rst_n) begin ram_wr_addr <= 10'b0; end else if(ram_wr_addr == H_ADDR/2 -1)begin ram_wr_addr <= 10'b0; end else if(ram_wr_en) begin ram_wr_addr <= ram_wr_addr + 1'b1; end end assign ram_wr_data = VGA_din;
ram讀如下所示:
assign ram_rd_en = (cnt_h >= H_SYNC + H_BACK - 1) && (cnt_h < H_SYNC + H_BACK + H_ADDR - 1) && (cnt_v >= V_SYNC + V_BACK ) && (cnt_v < V_SYNC + V_BACK + V_ADDR ); always @(posedge clk or negedge rst_n) begin if(!rst_n) begin addr_cnt <= 10'b0; end else if(addr_cnt == H_ADDR-1)begin addr_cnt <= 10'b0; end else if(ram_rd_en) begin addr_cnt <= addr_cnt + 1'b1; end end assign ram_rd_addr = {1'b0,addr_cnt[9:1]};
3、實現效果
這次就不上板了,用 Matlab 和 Modelsim 聯合仿真弄一下吧。
仿真結果表明,處理后的圖片比處理前的像素點放大了兩倍,圖像顯示也是正確的。
關於仿真平台可以看我另一篇博客《》
參考資料:
[1] OpenS Lee:FPGA開源工作室(公眾號)
[2] 張錚, 王艷平, 薛桂香. 數字圖像處理與機器視覺[M]. 人民郵電出版社, 2010.