一、縮放原理
圖像幾何變換又稱為圖像空間變換,它將一副圖像中的坐標位置映射到另一幅圖像中的新坐標位置。即一種空間映射關系,需要注意映射過程中的變化參數。圖像的幾何變換改變了像素的空間位置,建立一種原圖像像素與變換后圖像像素之間的映射關系。映射關系根據變換方向分為“向前映射”和“向后映射”。
- 向前映射:只要給出原圖像上的任意像素坐標,能通過對應的映射關系獲得到該像素在變換后圖像的坐標位置。
- 向后映射:知道任意變換后圖像上的像素坐標,計算其在原圖像的像素坐標。
然而,在使用向前映射處理幾何變換時通常會產生兩個問題:映射不完全,映射重疊。本文只針對向前映射的一種情況“縮放”進行討論。
圖像縮放指的是將圖像的尺寸變小或變大的過程,也就是減少或增加原圖像數據的像素個數。簡單來說,就是通過增加或刪除像素點來改變圖像的尺寸。當圖像縮小時,圖像會變得更加清晰,當圖像放大時,圖像的質量會有所下降,因此需要進行插值處理。
設水平縮放系數為sx,垂直縮放系數為sy,(x0,y0)為縮放前坐標,(x,y)為縮放后坐標,其縮放的坐標映射關系:
矩陣表示的形式為:
這是向前映射,在縮放的過程改變了圖像的大小,使用向前映射會出現映射重疊和映射不完全的問題,所以這里更關心的是向后映射,也就是輸出圖像通過向后映射關系找到其在原圖像中對應的像素。
向后映射關系:
二、FPGA實現圖像縮放
(1)圖片局部放大
在顯示區域大小不變的情況下進行圖片放大相當於對圖片進行局部放大查看,如下圖2倍放大后只顯示黃色區域:
假設一張圖片如下所示:
現將圖片擴大為原先的2倍,則圖片變成如下所示:
注:通常放大應采用鄰近插值的方法填充像素,本次實驗采用鄰近復制的方法只進行列地址操作,不進行加除法操作,道理類似於廣告牌,復制雖造成像素過渡細節較差,但從遠處看可達到不錯效果。
(2)圖片縮小(下采樣、降采樣)
假設一張圖片如下所示:
現將圖片變為原先的1/2,則圖片變成如下所示:
尺寸變成了原先的1/2,此外數據也減少了,顯示采用隔行隔列處理,將像素壓縮為原先的1/2。(這種圖片做的有點問題,但大致就是這個原理。)
(3)FPGA實現
對於小分辨率圖片可以用片內 RAM 來做緩存和跨時鍾域的處理,對於大分辨率圖片或視頻幀則要用到片外SDRAM進行緩存。本實驗只進行小分辨率圖像實驗,圖像分辨率為 240x136x16bit(RGB565)。
采用RAM IP 或者直接申請一個數組用於存放圖片數據,如下:
localparam COL = 240; //圖像行像素個數
localparam ROW = 136; //圖像場像素個數
reg [15:0] buffer [COL*ROW-1:0] ; //類似RAM ,這樣就相當於申請了一塊RAM,不過形式是數組。
形式上看似寄存器搭建的存儲器數組,但經過綜合后其實還是用的 RAMB16BWERs ,只是避免了RAM IP的設置。
為了看到不同倍數的縮放效果可以引入兩個外部按鍵key,分別用於控制放大倍數Zoom_In和縮小倍數Zoom_Out。
對於從圖片中心進行放大則需要設置偏移量,如果沒有偏移量,圖像的放大將從左上角開始,放大后的圖像出現偏移,因此引入偏移量,使圖片放大后的中間點還是原圖片位置的中間點。偏移量公式為:[side * (n-1)/2],n為放大倍數,side為圖片的尺寸;對於圖片縮小,進行分辨率降采樣后,顯示區域應相應變小。
縮放代碼如下所示:
1 module zoom( 2 input clk, 3 input rst_n, 4 input [1:0] Zoom_In, //放大倍數
5 input [1:0] Zoom_Out, //縮小倍數
6 input [15:0] data_in, //圖片RGB565數據輸入,晚於讀地址1clk
7 input [9:0] hcount, //LCD顯示驅動的行計數
8 input [9:0] vcount, //LCD顯示驅動的場計數
9 output [15:0] read_addr, //讀地址
10 output [15:0] data_out //縮放后的數據輸出,在其它模塊信號同步打拍
11 ); 12
13 localparam COL = 240; //圖像行像素個數
14 localparam ROW = 136; //圖像場像素個數
15
16 parameter IMG_x=120; //顯示起始行坐標
17 parameter IMG_y=68; 18
19 wire [8:0] cnt_col; //圖片顯示區域的行計數
20 wire [8:0] cnt_row; //圖片顯示區域的場計數
21 reg [8:0] zoom_In_x; //放大后的坐標映射
22 reg [8:0] zoom_In_y; 23 wire [8:0] zoom_x; //最終縮放后坐標映射
24 wire [8:0] zoom_y; 25 wire display_value; //圖像有效顯示區域
26
27 assign cnt_col = hcount >= IMG_x ? hcount-IMG_x : 0; 28 assign cnt_row = vcount >= IMG_y ? vcount-IMG_y : 0; 29
30 //=======================放大坐標映射============================== 31 //偏移量公式:+ [side*(n-1)/2],n為放大倍數 side為圖像的寬、高
32 always @(*) begin
33 case(zoom_n) 34 2'b00 : begin //原圖
35 zoom_In_x = cnt_col; 36 zoom_In_y = cnt_row; 37 end
38 2'b01 : begin //2倍
39 zoom_In_x = (cnt_col+120)>>1; 40 zoom_In_y = (cnt_row+68)>>1; 41 end
42 2'b10 : begin //4倍
43 zoom_In_x = (cnt_col+360)>>2; 44 zoom_In_y = (cnt_row+204)>>2; 45 end
46 2'b11 : begin //8倍
47 zoom_In_x = (cnt_col+840)>>3; 48 zoom_In_y = (cnt_row+476)>>3; 49 end
50 default : begin
51 zoom_In_x = cnt_col; 52 zoom_In_y = cnt_row; 53 end
54 endcase
55 end
56
57
58 //-------------------縮小坐標映射-------------------------------- 59 //直接利用移位來達到縮小倍數的需求。串接在放大映射坐標后,可以在實現分辨率下采樣(減小)的局部放大
60 assign zoom_x = zoom_In_x << Zoom_Out; 61 assign zoom_y = zoom_In_y << Zoom_Out; 62
63 //--------------------------------------------------- 64 //由於縮小會使分辨率減小,原顯示區域會出現縮小倍數^2的圖像陣列,所以只顯示左上角第一個縮小圖像
65 assign display_value = (hcount >= IMG_x && hcount < IMG_x+(COL>>Zoom_Out)) && (vcount >= IMG_y && vcount < IMG_y+(ROW>>Zoom_Out)); 66
67 assign read_addr = zoom_y * COL + zoom_x; //縮放映射后的 RAM 地址
68 assign data_out = display_value ? data_in : 0; //有效顯示區域輸出圖像,無效背景為黑
69
70 endmodule
注意:由於將縮小倍率接入放大后坐標后,縮小倍率控制顯示區域的大小,放大倍數控制局部放大,可在縮小的區域進行局部放大。
實驗效果:
若縮小時顯示區域不處理,會得到奇怪的圖像陣列,圖像若為方形則是整齊的陣列,效果如下:
2倍縮小
4倍縮小
8倍縮小
整個縮放實驗效果:
FPGA圖像處理——縮放
三、FPGA實現圖像上采樣(分辨率提高)
由於我所用的開發板的片內RAM資源有限,剛好能存放240*136_16bit的數據量,而我的LCD顯示屏分辨率是480*272,想到利用利用鄰近地址數據讀兩次的方法使圖片能充滿整個顯示屏。對於二倍放大,每 1/2 行讀兩次,每一整行再重讀一次。直接用 ram 就能實現了,如下所示:
localparam H_N = 240; //圖像行像素個數
localparam V_N = 136; //圖像場像素個數
localparam IMAGE_N = H_N*V_N; //圖像像素個數 //====================RAM讀地址計數器圖片240_136顯示===================================================
always @(posedge clk or negedge rst_n)begin
if(!rst_n) cnt_addr <= 0; else if(add_cnt_addr)begin
if(end_cnt_addr) cnt_addr <= 0; else cnt_addr <= cnt_addr +1; //地址計數
end
end
assign add_cnt_addr = data_vld && (hcount >= 120 && hcount < 360) && (vcount >= 68 && vcount < 204); assign end_cnt_addr = add_cnt_addr && cnt_addr == IMAGE_N - 1;
2倍上采樣的讀地址:
1 //===================RAM讀地址計數器圖片240_136鄰近復制2倍顯示480_272==================================== 2 3 wire h_cnt2; 4 wire v_cnt2; 5 always @(posedge clk or negedge rst_n)begin 6 if(!rst_n) 7 cnt_addr <= 0; 8 else if(add_cnt_addr)begin 9 if(end_cnt_addr) 10 cnt_addr <= 0; 11 else if(!v_cnt2 && hcount == 479) //行計數TFT顯示屏寬度-1 12 cnt_addr <= cnt_addr - (240-1); //要顯示的圖像寬度-1, cnt_addr <= cnt_addr - (240-1); 13 else if(h_cnt2) //!h_cnt2, 14 cnt_addr <= cnt_addr +1; 15 else 16 cnt_addr <= cnt_addr; 17 end 18 end 19 20 assign h_cnt2 = hcount[0]; //最低位每兩次為一周期變化,2倍放大可用作標志位,同理次低位為4倍放大標志位 21 assign v_cnt2 = vcount[0]; 22 assign add_cnt_addr = data_vld; 23 assign end_cnt_addr = add_cnt_addr && vcount == 271 && hcount == 479; //最后一行復制
實驗效果
FPGA圖像處理——上采樣(分辨率提高)
參考資料:
[1] OpenS Lee:FPGA開源工作室(公眾號)
[2] 咸魚FPGA :FPGA實現圖像幾何變換:縮放
[3] noticeable :幾何變換的基本概念