跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
编程语言算法

FPGA 实现任意角度图像旋转:原理与代码设计

在 FPGA 中实现任意角度图像旋转的原理与代码设计。通过 Cordic 算法获取正余弦值,利用坐标变换公式计算像素映射关系。设计了四级流水线处理逻辑,包括尺寸计算、有效区域判定、坐标转换及 ROM 地址生成。仿真结果表明,该方案支持任意角度和方向的图像旋转,功能正常。

CryptoLab发布于 2026/4/5更新于 2026/10/2086 浏览

1. 概述

本文介绍基于 FPGA 的任意角度图像旋转实现。通过 Cordic 算法获取正余弦数值,结合坐标变换公式完成像素映射。

image

2. 图像旋转代码设计思路

2.1 旋转后的图像尺寸

在一副图像经过旋转后,原本像素的位置肯定会发生变化,图像总的面积虽然保持不变但是各别位置的尺寸会改变。比如一副 100x100 像素的图像进行旋转,我们只需要获得它的最长距离也就是对角线的尺寸作为旋转后的图像的显示范围。这样无论怎样旋转都能完整显示图像。

如下代码,Pixel_X 和 Pixel_Y 为旋转后图像的尺寸。ROW 和 COL 为原始图像尺寸,利用勾股定理求出对角线的值即可。

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.2 旋转后图像的有效位置

这个旋转后的有效位置可以自由设定,这里设定为屏幕中心处的位置。具体设计如下:

使用一块 480*272 的 LCD 屏幕,具体时序参数如下:

//parameter define localparam
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)))));

2.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

2.4 第二级流水

这里开始计算图像旋转公式。以中心点为起始坐标,相当于坐标系的 (0,0) 点,这样四个象限的所有坐标点都可以表示出来。并且按照上图的公式组合起来即可,最终右移 14 位。

image

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

2.5 第三级流水

同样的,按照公式进行排列组合得出旋转后图像映射到原始图像的坐标位置。在这里有几个可能不好理解的地方:原始屏幕坐标 (hcnt, vcnt) 转换到中心坐标系(减去 COL/2 和 ROW/2)-> 中心坐标 (hcnt - COL/2, vcnt - ROW/2) -> 应用旋转公式 -> 旋转后的中心坐标 -> 转换回屏幕坐标系(加上 COL/2 和 ROW/2)。

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

2.6 第四级流水线

  • hcnt_rotate 在 0 到 COL-1 之间(在原图像列范围内)
  • vcnt_rotate 在 0 到 ROW-1 之间(在原图像行范围内)
  • 已读取的像素数小于图像总像素数(ROW*COL)
  • 每行有 COL 个像素,所以第 vcnt_rotate 行的起始地址是 COL*vcnt_rotate
  • 再加上该行内的列偏移 hcnt_rotate

data_cnt 计数器用于限制读取的像素总数不超过原图像的总像素数(ROW*COL)。这是为了防止地址溢出或重复读取。最后连接一个 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 )
);

3. 仿真结果

30°旋转

image

图片横着看,逆时钟旋转 30°。

image

-30°

image

image

228°

image

image

问题不大,任意角度,任意方向,其它的我就不列举了。

4. 结语

目前总的来看功能是正常的,时序和代码架构已验证,可供参考。

目录

  1. 1. 概述
  2. 2. 图像旋转代码设计思路
  3. 2.1 旋转后的图像尺寸
  4. 2.2 旋转后图像的有效位置
  5. 2.3 第一级流水线
  6. 2.4 第二级流水
  7. 2.5 第三级流水
  8. 2.6 第四级流水线
  9. 3. 仿真结果
  10. 4. 结语

更多推荐文章

查看全部
  • 基于 Java Swing 的个人所得税计算模拟器
  • Agent Memory 相关文献追踪:异构存储与经验记忆
  • FPGA 任意角度图像旋转原理与实现
  • 波士顿动力机器人技术解析:从 Spot 到 Atlas
  • OSCP 实战笔记:获取并破解 Net-NTLMv2 哈希(下)
  • Neovim + LazyVim 现代化配置指南 (Linux)
  • C++ 模板与 string 类使用指南
  • Python 网站开发框架大全:主流 Web 框架优缺点对比与选型指南
  • C++伸展树介绍与红黑树实现
  • Ubuntu 22.04 中的版本控制系统 Git 实战
  • Vivado 入门实战:D 触发器设计与烧录流程
  • Linux 入门指南:从零开始掌握命令行基础
  • Spring Cloud Gateway 路由、过滤器与限流机制详解
  • Python 属性描述符:原理剖析与 ORM 实战
  • HOG+SVM 目标检测原理与 Python 实现
  • G3M:基于图生成建模的视频预训练机器人操作
  • 常见 AI 模型与编程术语美式发音速查表
  • MySQL Explain 分析 SQL 执行计划
  • AI生成HTML原型导入Axure实操指南
  • 基于 Ant Design 的前端首页 Header 组件设计

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online