一、鏡像
鏡像變換分為水平鏡像和豎直鏡像。水平鏡像即將圖像左半部分和右半部分以圖像豎直中軸線為中心軸進行對換;而豎直鏡像則是將圖像上半部分和下半部分以圖像水平中軸線為中心軸進行對換,如圖所示。
具體變換關系參考:
FPGA實現圖像幾何變換:鏡像
二、旋轉
旋轉一般是指將圖像圍繞某一指定點旋轉一定的角度。 旋轉通常也會改變圖像的大小,和圖像平移的處理一樣,可以把轉出顯示區域的圖像截去,也可以改變輸出圖像的大小以擴展顯示范圍。
具體可參考:
FPGA實現圖像幾何變換:旋轉
本次只討論90°整數倍旋轉,對應MATLAB旋轉核為 'loose',因為本次實驗處理的是長寬不等的矩形圖片(與方形圖片有點區別 'crop' ),如圖所示。
三、鏡像&旋轉的FPGA實現
本文是基於上面兩個博客基礎上做出一定更改,因為90度的旋轉對圖像地址操作與鏡像類似,所以將兩者整合起來,外部引入兩個按鍵key分別對鏡像、旋轉模式進行選擇,並將旋轉后的新坐標接入鏡像的老坐標,除此之外對於旋轉90度后的矩形圖片的顯示區域做出適應處理。
本次實驗圖片尺寸:240*136(寬*高),LCD為4.3寸的顯示分辨率480*272。
代碼如下:
1 module mirror( 2 input clk, 3 input rst_n, 4 input [1:0] mirror_mode, //鏡像模式輸入
5 input [1:0] rotate_mode, //旋轉模式輸入
6 input [15:0] data_in, //RGB565數據輸入
7 input [10:0] hcount, //LCD顯示的行計數
8 input [10:0] vcount, //場計數
9 output [15:0] read_addr, //生成的讀RAM地址
10 output [15:0] data_out //顯示的數據
11 ); 12
13 localparam COL = 240; //圖像行像素個數
14 localparam ROW = 136; //圖像場像素個數
15
16 wire display_value //顯示區域有效標志位
17 wire [8:0] cnt_col; //顯示區域圖片的行坐標
18 wire [8:0] cnt_row; 19 reg [8:0] mirror_x; //鏡像后的行坐標
20 reg [8:0] mirror_y; 21 reg [8:0] rotate_x; //旋轉后的行坐標
22 reg [8:0] rotate_y; 23
24 //對於480*272顯示面板,圖片240*136要顯示到正中央,對於90度旋轉,顯示區域的行場坐標需重新計算:
25 assign cnt_col = (rotate_mode==0||rotate_mode==2) ? (hcount >= 120 ? hcount-120 : 0) : (hcount >= 172 ? hcount-172 : 0); 26 assign cnt_row = (rotate_mode==0||rotate_mode==2) ? (vcount >= 68 ? vcount-68 : 0) : (vcount >= 16 ? vcount-16 : 0); 27
28
29 //--------------------------------------------------- 30 //========================================================================== 31 //== 鏡像操作,讀地址重規划 32 //==========================================================================
33 always @(*) begin
34 case(mirror_mode) 35 2'b00 : begin //原圖
36 mirror_x = rotate_x; 37 mirror_y = rotate_y; 38 end
39 2'b01 : begin //水平鏡像
40 mirror_x = (COL-1) - rotate_x; 41 mirror_y = rotate_y; 42 end
43 2'b10 : begin //垂直鏡像
44 mirror_x = rotate_x; 45 mirror_y = (ROW-1) - rotate_y; 46 end
47 2'b11 : begin //水平垂直鏡像
48 mirror_x = (COL-1) - rotate_x; 49 mirror_y = (ROW-1) - rotate_y; 50 end
51 default : begin
52 mirror_x = rotate_x; 53 mirror_y = rotate_y; 54 end
55 endcase
56 end
57
58 //========================================================================== 59 //== 90°旋轉操作 60 //==========================================================================
61 always @(*) begin
62 case(rotate_mode) 63 2'b00 : begin //原圖
64 rotate_x = cnt_col; 65 rotate_y = cnt_row; 66 end
67 2'b01 : begin //右轉90度,
68 rotate_x = cnt_row; 69 rotate_y = (ROW-1) - cnt_col; 70 end
71 2'b10 : begin //旋轉180度,相當於原圖水平、垂直鏡像
72 rotate_x = (COL-1) - cnt_col; 73 rotate_y = (ROW-1) - cnt_row; 74 end
75 2'b11 : begin //左轉90度(右轉270度),右轉90度后水平、垂直鏡像
76 rotate_x = (COL-1) - cnt_row; 77 rotate_y = cnt_col; 78 end
79 default : begin
80 rotate_x = cnt_col; 81 rotate_y = cnt_row; 82 end
83 endcase
84 end
85
86 //rotate_mode==0||rotate_mode==2旋轉時顯示寬高對調,重新定義顯示區域
87 assign display_value = (rotate_mode==0||rotate_mode==2) ? (hcount >= 120 && hcount < 360) && (vcount >= 68 && vcount < 204) 88 : (hcount >= 172 && hcount < 308) && (vcount >= 16 && vcount < 256); 89
90 assign read_addr = mirror_y * COL + mirror_x; //RAM中讀取的像素地址
91 assign data_out = display_value ? data_in : 0; //顯示區域外為黑
92
93 endmodule
代碼中,兩個按鍵key在頂層模塊中進行模式計數將結果作為該模塊輸入(注意該模塊時鍾clk是LCD屏的像素時鍾,在頂層模塊中例化了50MHz的按鍵去抖動模塊,避免在該模塊中重新寫去抖模塊。第一次忘了這茬,在該模塊直接用50MHz去抖模塊,忘了時鍾不同造成按鍵檢測失靈)
注:代碼中hcount由另一個LCD驅動模塊生成(類似經典的color_bar模塊),讀入的data_in是read_addr 地址的,比hcount晚了一個時鍾,在LCD驅動模塊中另外做了行場有效同步處理,本篇文章暫不介紹。
四、實驗現象
https://www.bilibili.com/video/BV1Rh411e7hF?share_source=copy_web
參考資料:[1] OpenS Lee:FPGA開源工作室(公眾號)
[2] 咸魚FPGA : FPGA實現圖像幾何變換:鏡像&旋轉