FPGA 实现任意角度图像旋转原理与代码设计
概述
基于 Cordic 算法获取正余弦数值后,即可进行旋转公式的计算。图像旋转的本质是实现坐标变换公式,通过多级流水线处理完成像素映射。

图像旋转代码设计思路
1. 旋转后的图像尺寸
图像旋转后像素位置发生变化,总像素面积不变但显示范围改变。为确保完整显示,需计算对角线尺寸作为新图像的显示范围。
reg [12:0] row_size; reg [12:0] col_size;
assign Pixel_X = row_size;
assign Pixel_Y = col_size;
wire [31:0] cosout_abs = (cosout[31]) ? -cosout : cosout;
wire [31:0] sinout_abs = (sinout[31]) ? -sinout : sinout;
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) begin
row_size <= 'd0;
col_size <= 'd0;
end else begin
// h --> row
// w --> col
row_size <= (ROW * cosout_abs + COL * sinout_abs) >> 14;
col_size <= (COL * cosout_abs + ROW * sinout_abs) >> 14;
end
end
2. 旋转后图像的有效位置
有效位置可自由设定,此处以屏幕中心为旋转后图像的中点。采用通用 LCD 时序参数(如 480*272)进行对齐。
// parameter define
localparam H_SYNC = 11'd41,
H_BACK = 11'd2,
H_LEFT = 11'd0,
H_VALID = 11'd480,
H_RIGHT = 11'd0,
H_FRONT = 11'd2,
H_TOTAL = 11'd525;
localparam V_SYNC = 11'd10,
V_BACK = 11'd2,
V_TOP = 11'd0,
V_VALID = 11'd272,
V_BOTTOM = 11'd0,
V_FRONT = 11'd2,
V_TOTAL = 11'd286;
// cnt_h:行扫描计数器
// cnt_v:场扫描计数器
// data_req:数据请求信号
wire data_req = (((cnt_h >= (((H_VALID - Pixel_X)>>1) + H_SYNC + H_BACK - 'd5)) &&
(cnt_h < (((H_VALID - Pixel_X)>>1) + Pixel_X + H_SYNC + H_BACK - 'd5))) &&
((cnt_v >= ((V_VALID - Pixel_Y)>>1) + V_SYNC + V_BACK - 'd5) &&
((cnt_v < (((V_VALID - Pixel_Y)>>1) + Pixel_Y + V_SYNC + V_BACK - 'd5)))));
3. 第一级流水线
在图像有效信号有效时进行行场计数。
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) r_rotate_valid <= 1'b0;
else r_rotate_valid <= data_req;
end
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) r_rotate_end <= 'd0;
else if (r_rotate_valid && (vcnt == row_abs - 1) && (hcnt == col_abs - 2)) r_rotate_end <= 1'b1;
else r_rotate_end <= 'd0;
end
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) hcnt <= 'd0;
else if (r_rotate_valid && (r_rotate_end || (hcnt == col_abs - 1))) hcnt <= 'd0;
else if (r_rotate_valid) hcnt <= hcnt + 'd1;
end
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) vcnt <= 'd0;
else if (r_rotate_valid && r_rotate_end) vcnt <= 'd0;
else if (r_rotate_valid && (hcnt == col_abs - 1)) vcnt <= vcnt + 'd1;
end
4. 第二级流水
计算图像旋转公式,以中心点为起始坐标 (0,0),组合公式并右移 14 位。
reg signed [12:0] x_cos;
reg signed [12:0] y_sin;
reg signed [12:0] y_cos;
reg signed [12:0] x_sin;
assign row_abs = row_size;
assign col_abs = col_size;
assign row1 = row_abs >> 1;
assign col1 = col_abs >> 1;
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) x_cos <= 'd0;
else if(r_rotate_valid_1d) x_cos <= ((hcnt - col1) * cosout) >>> 14;
else x_cos <= x_cos;
end
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) y_sin <= 'd0;
else if(r_rotate_valid_1d) y_sin <= ((vcnt - row1) * sinout) >>> 14;
else y_sin <= y_sin;
end
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) y_cos <= 'd0;
else if(r_rotate_valid_1d) y_cos <= ((vcnt - row1) * cosout) >>> 14;
else y_cos <= y_cos;
end
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) x_sin <= 'd0;
else if(r_rotate_valid_1d) x_sin <= ((hcnt - col1) * sinout) >>> 14;
else x_sin <= x_sin;
end
5. 第三级流水
按照公式排列组合得出旋转后图像映射到原始图像的坐标位置。流程:原始屏幕坐标 -> 中心坐标系 -> 应用旋转公式 -> 旋转后的中心坐标 -> 转换回屏幕坐标系。
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) r_rotate_valid_2d <= 'd0;
else r_rotate_valid_2d <= r_rotate_valid_1d;
end
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) hcnt_rotate <= 'd0;
else if(r_rotate_valid_2d == 1'b1) hcnt_rotate <= x_cos - y_sin + (COL >> 1);
else hcnt_rotate <= 'd0;
end
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) vcnt_rotate <= 'd0;
else if(r_rotate_valid_2d == 1'b1) vcnt_rotate <= y_cos + x_sin + (ROW >> 1);
else vcnt_rotate <= 'd0;
end
6. 第四级流水线
判断 hcnt_rotate 和 vcnt_rotate 是否在原图像范围内,结合 data_cnt 计数器限制读取像素总数防止溢出。使用 ROM IP 核存储图像数据,第五级流水用于对齐 ROM 读出延迟。
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) r_rotate_valid_3d <= 'd0;
else r_rotate_valid_3d <= r_rotate_valid_2d;
end
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) begin
rden <= 'd0;
addra <= 'd0;
end else if(r_rotate_valid_3d == 1'b1) begin
if((hcnt_rotate >= 'd0) && (hcnt_rotate < COL) && (vcnt_rotate >= 'd0) && (vcnt_rotate < ROW) && data_cnt < ROW * COL) begin
rden <= 1'b1;
addra <= COL * vcnt_rotate + hcnt_rotate;
end else begin
rden <= 1'b0;
addra <= 'd0;
end
end else begin
rden <= 'd0;
addra <= 'd0;
end
end
always @(posedge clk_i, negedge rstn_i) begin
if (!rstn_i) data_cnt <= 'd0;
else if (data_cnt == ROW * COL - 1) data_cnt <= 'd0;
else if (r_rotate_valid_3d && (hcnt_rotate >= 'd0) && (hcnt_rotate < COL) && (vcnt_rotate >= 'd0) && (vcnt_rotate < ROW)) data_cnt <= data_cnt + 'd1;
end
img_mem_gen img_mem_gen_inst (
.address(addra),
.clock(clk_i),
.rden(rden),
.q(rom_data)
);
仿真结果
支持 30°、-30°、228° 等任意角度逆时针或顺时针旋转,功能正常。






总结
该方案实现了任意角度图像旋转,时序逻辑清晰,适用于 LCD 显示场景。核心在于利用 Cordic 算法生成三角函数值,并通过多级流水线完成坐标映射与数据读取。

