OpenCV 图像旋转和平移 数学和代码原理详解

news2024/11/15 10:27:31

文章目录

    • 数学原理
      • 旋转矩阵
      • 平移和旋转
      • 合成变换矩阵
      • 应用在OpenCV中的实现
    • 代码关键点解读
    • 完整代码
      • C++代码:
      • Python代码:

在OpenCV中进行图像旋转涉及到一些基本的几何变换和图像处理操作。

数学原理

在图像旋转中,背后的数学原理主要涉及二维欧几里得空间中的几何变换。具体来说,图像旋转可以通过二维旋转矩阵来实现。

旋转矩阵

对于一个二维平面上的点 (x, y),绕原点逆时针旋转角度 θ 后的新坐标 (x', y') 可以通过以下旋转矩阵计算得到:

在这里插入图片描述

平移和旋转

在实际应用中,图像通常不会绕原点旋转,而是绕图像的某个中心点 (cx, cy) 进行旋转。要实现绕任意点旋转,我们需要先将该点平移到原点,进行旋转,然后再平移回原来的位置。

具体步骤如下:

  1. 平移中心点到原点:将中心点 (cx, cy) 平移到原点 (0, 0)
  2. 旋转:在原点进行旋转。
  3. 平移回原位置:将旋转后的点再平移回 (cx, cy)

合成变换矩阵

综合上述步骤,绕任意点 (cx, cy) 逆时针旋转角度 θ 的变换矩阵可以表示为:

M = T ⋅ R ⋅ T − 1 M = T \cdot R \cdot T^{-1} M=TRT1
其中:

  • ( T ) 是平移矩阵,用于将中心点平移到原点。
  • ( R ) 是旋转矩阵,用于在原点进行旋转。
  • ( T^{-1} ) 是逆平移矩阵,用于将旋转后的点平移回原位置。

具体形式为:

T = [ 1 0 − c x 0 1 − c y 0 0 1 ] T = \begin{bmatrix} 1 & 0 & -cx \\ 0 & 1 & -cy \\ 0 & 0 & 1 \end{bmatrix} T= 100010cxcy1

R = [ cos ⁡ θ − sin ⁡ θ 0 sin ⁡ θ cos ⁡ θ 0 0 0 1 ] R = \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix} R= cosθsinθ0sinθcosθ0001

T − 1 = [ 1 0 c x 0 1 c y 0 0 1 ] T^{-1} = \begin{bmatrix} 1 & 0 & cx \\ 0 & 1 & cy \\ 0 & 0 & 1 \end{bmatrix} T1= 100010cxcy1
所以综合后的旋转矩阵 ( M ) 为:

M = [ cos ⁡ θ − sin ⁡ θ c x ( 1 − cos ⁡ θ ) + c y sin ⁡ θ sin ⁡ θ cos ⁡ θ c y ( 1 − cos ⁡ θ ) − c x sin ⁡ θ 0 0 1 ] M = \begin{bmatrix} \cos\theta & -\sin\theta & cx(1-\cos\theta) + cy\sin\theta \\ \sin\theta & \cos\theta & cy(1-\cos\theta) - cx\sin\theta \\ 0 & 0 & 1 \end{bmatrix} M= cosθsinθ0sinθcosθ0cx(1cosθ)+cysinθcy(1cosθ)cxsinθ1
由于图像坐标是二维的,我们只需要前三列中的前两行:

M = [ cos ⁡ θ − sin ⁡ θ c x ( 1 − cos ⁡ θ ) + c y sin ⁡ θ sin ⁡ θ cos ⁡ θ c y ( 1 − cos ⁡ θ ) − c x sin ⁡ θ ] M = \begin{bmatrix} \cos\theta & -\sin\theta & cx(1-\cos\theta) + cy\sin\theta \\ \sin\theta & \cos\theta & cy(1-\cos\theta) - cx\sin\theta \end{bmatrix} M=[cosθsinθsinθcosθcx(1cosθ)+cysinθcy(1cosθ)cxsinθ]

应用在OpenCV中的实现

在OpenCV中,函数 cv::getRotationMatrix2D 就是用来计算这个旋转矩阵的:

cv::Mat cv::getRotationMatrix2D(cv::Point2f center, double angle, double scale);

center 参数表示旋转中心 (cx, cy)angle 表示旋转角度 θ,scale 表示缩放比例。

然后通过 cv::warpAffine 函数应用这个旋转矩阵来实现图像的旋转:

cv::warpAffine(src, dst, M, cv::Size(width, height));

其中 M 就是通过 getRotationMatrix2D 计算得到的旋转矩阵。

总结一下,图像旋转的数学原理是通过平移和旋转组合的方式,利用二维旋转矩阵实现绕任意点的旋转。OpenCV中提供的函数封装了这些数学计算,使得图像旋转操作变得简单直观。

代码关键点解读

以胖虎为例

在这里插入图片描述

C++代码:

void rotate_demo(Mat &image){
    int width = image.cols;
    int height = image.rows;
    //计算旋转中心坐标
    Point2f center(width/2.0,height/2.0);
    double angle =180;
    Mat rotation_matrix = getRotationMatrix2D(center,angle,1.0);
    Mat rotate_image ;
    warpAffine(image,rotate_image,rotation_matrix,Size(width,height));
    imshow("Rotate Image",rotate_image);
}

调用上述代码就会发现一个问题: 图像是旋转了,但是旋转后的图像尺寸不对了, 向右旋转之后,图像宽度不正常。宽度应该是原来的高度,原来的高度应该是宽度才对

在这里插入图片描述

warpAffine(image,rotate_image,rotation_matrix,Size(width,height));中尝试调换一下宽度和高度试试。

warpAffine(image,rotate_image,rotation_matrix,Size(height,width));

尝试运行之后,发现虽然窗口尺寸对了但是图像右上角都是黑边,原来的图像现了缺失

在这里插入图片描述

出现这个原因是我们一开始是按照中心点进行旋转,并不是从左上角开始旋转的,因此会出现图片缺失问题,因此我们需要把图像再平移回去。平移回去的第一大问题就是旋转之后的图像宽度和高度发生了变化,我们需要重新计算旋转后的图像尺寸

OpenCV提供了计算旋转后的的图像边界尺寸的工具,bbox就是以某个角度旋转之后的矩阵,为什么需要这个函数?举个栗子,旋转九十度,图像的宽度是原来图像的高度,图像的高度是原来的宽度,但如果是旋转45度?旋转后的图像并不是原来图像的高度,需要重新计算。

 Rect bbox = RotatedRect(Point2f(), image.size(), angle).boundingRect();

旋转矩阵 rotation_matrix 是一个 2x3 的矩阵,其形式为:
[ a b t x c d t y ] \begin{bmatrix} a & b & tx \\ c & d & ty \end{bmatrix} [acbdtxty]
其中 txty 是平移部分。

因此我们需要调整tx和ty的值

  • 旋转后的图像尺寸 bbox.size() 的中心点坐标为 (bbox.width / 2.0, bbox.height / 2.0)
  • 原始旋转中心点为 center,坐标为 (center.x, center.y)
  • 调整 txty 的目的是将旋转中心点平移到新图像中心,使图像内容在旋转后居中。

调整后的 txty 计算如下:

  • tx:将图像沿 x 轴平移 bbox.width / 2.0 - center.x
  • ty:将图像沿 y 轴平移 bbox.height / 2.0 - center.y

对应的代码为:

rotation_matrix.at<double>(0, 2) += bbox.width / 2.0 - center.x;
rotation_matrix.at<double>(1, 2) += bbox.height / 2.0 - center.y;

at<double>(0, 2) 访问矩阵中的 tx 值,at<double>(1, 2) 访问矩阵中的 ty 值。

再来运行一下代码,可以看到此时图像就正常了。

在这里插入图片描述

试试45度旋转,修改angle的值为45,效果如下,注意图像的尺寸已经发生了改变,重新计算尺寸后不会造成像素丢失的问题

在这里插入图片描述

完整代码

C++代码:

void rotate_demo(Mat &image){
    int width = image.cols;
    int height = image.rows;
    //计算旋转中心坐标
    Point2f center(width/2.0,height/2.0);
    double angle =45;
    Mat rotation_matrix = getRotationMatrix2D(center,angle,1.0);
    //因为涉及到旋转,图像的高度和宽度其实发生了变化
    // 计算旋转后的图像边界尺寸
    Rect bbox = RotatedRect(Point2f(), image.size(), angle).boundingRect();
    rotation_matrix.at<double>(0, 2) += bbox.width / 2.0 - center.x;
    rotation_matrix.at<double>(1, 2) += bbox.height / 2.0 - center.y;

    Mat rotate_image ;
    warpAffine(image,rotate_image,rotation_matrix,bbox.size());
    imshow("Rotate Image",rotate_image);
}

Python代码:

import cv2
import numpy as np

def rotate_demo(image_path, angle=45):
    # 读取图像
    image = cv2.imread(image_path)
    if image is None:
        print("Could not read the image.")
        return

    height, width = image.shape[:2]

    # 计算旋转中心坐标
    center = (width / 2.0, height / 2.0)

    # 计算旋转矩阵
    rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)

    # 计算旋转后的图像边界尺寸
    corners = np.array([
        [0, 0],
        [width, 0],
        [width, height],
        [0, height]
    ])

    corners = np.hstack((corners, np.ones((4, 1))))
    rotated_corners = rotation_matrix.dot(corners.T).T

    x_coords = rotated_corners[:, 0]
    y_coords = rotated_corners[:, 1]

    bbox_width = int(np.ceil(x_coords.max() - x_coords.min()))
    bbox_height = int(np.ceil(y_coords.max() - y_coords.min()))

    # 调整旋转矩阵的平移部分
    rotation_matrix[0, 2] += bbox_width / 2.0 - center[0]
    rotation_matrix[1, 2] += bbox_height / 2.0 - center[1]

    # 执行旋转操作
    rotate_image = cv2.warpAffine(image, rotation_matrix, (bbox_width, bbox_height))

    # 显示旋转后的图像
    cv2.imshow("Rotate Image", rotate_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

# 调用旋转函数
rotate_demo("path_to_your_image.jpg", 45)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1941156.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式硬件-Xilinx FPGA DDR4 接口配置基础(PG150)

1. 简介 1.1 DDR4 SDRAM 控制器主要特点 支持8到80位接口宽度的组件&#xff08;支持 RDIMM、LRDIMM、UDIMM 和 SODIMM&#xff09; 最大组件限制为9&#xff0c;此限制仅适用于组件&#xff0c;不适用于 DIMM。密度支持 最高支持 32 GB 的组件密度&#xff0c;64 GB 的 LRDI…

步步精慕尼黑上海电子展完美收官,感恩所有相遇,期待下次再会

2024年7月11日至13日&#xff0c;慕尼黑上海电子展圆满落幕&#xff0c;步步精科技&#xff08;以下简称步步精&#xff09;在此次展会上取得了丰硕的成果。作为连接器行业的重要制造商&#xff0c;步步精携带其最新产品和连接器技术方案亮相展会&#xff0c;吸引了大量参观者的…

【HarmonyOS】HarmonyOS NEXT学习日记:六、渲染控制、样式结构重用

【HarmonyOS】HarmonyOS NEXT学习日记&#xff1a;六、渲染控制、样式&结构重用 渲染控制包含了条件渲染和循环渲染&#xff0c;所谓条件渲染&#xff0c;即更具状态不同&#xff0c;选择性的渲染不同的组件。 而循环渲染则是用于列表之内的、多个重复元素组成的结构中。 …

RK3568笔记四十二:OLED 屏幕驱动(模拟I2C)

若该文为原创文章&#xff0c;转载请注明原文出处。 本篇记录使用GPIO模拟I2C驱动OLED屏幕&#xff0c;显示界面效果如下。 主要流程是&#xff0c;修改设备树&#xff0c;使用普通IO口&#xff0c;驱动模拟I2C方式&#xff0c;应用程直接传输数据控制。 1、修改设备 2、编写…

Go语言 Import导入

本文主要介绍Go语言import导入使用时注意事项和功能实现示例。 目录 Import 创建功能文件夹 加法 减法 主函数 优化导入的包名 .引入方法 总结 Import 创建功能文件夹 做一个计算器来演示&#xff0c;首先创建test文件夹。 加法 在test文件夹中创建add文件夹&#xff…

数据预处理在建模中的重要性与常见方法(三):特征工程篇

数据预处理在建模中的重要性与常见方法&#xff08;三&#xff09;&#xff1a;特征工程篇 特征工程是数据预处理中至关重要的一步&#xff0c;通过构建、转换和选择最能代表数据特性的特征&#xff0c;以提高模型的性能和准确性。常见的特征工程方法包括特征选择、特征提取和特…

前端-模拟请求数据mook第三方插件 json-server的使用

大纲 第一步下载第二配置mook的数据源第三配置启动命令第四运行模拟服务第五测试接口如果要进行更复杂的操作 第一步下载 npm install json-server -D"devDependencies": {"json-server": "^1.0.0-beta.1"}第二配置mook的数据源 在项目的根目录…

某指挥调度系统功能展示(下)

照片管理 拍照是普通执勤巡检中很常用的信息记录功能。 通过此功能可以看到设备本地拍摄的照片&#xff0c;此平台分成了两部分&#xff1a; 一部分是设备上的&#xff0c;需要设备在线才可以访问&#xff1b;支持上传到平台&#xff0c;并且在设备端有相应的选择&#xff0…

人、智能、机器人……

在遥远的未来之城&#xff0c;智能时代如同晨曦般照亮了每一个角落&#xff0c;万物互联&#xff0c;机器智能与人类智慧交织成一幅前所未有的图景。这座城市&#xff0c;既是科技的盛宴&#xff0c;也是人性与情感深刻反思的舞台。 寓言&#xff1a;《智光与心影》 在智能之…

Linux性能分析之-CPU篇

开发车载软件app&#xff0c;除了常用Android操作系统外&#xff0c;还可能是基于Linux系统开发。对于web应用基本也都部署在Linux系统上&#xff0c;所以&#xff0c;进行系统性能分析&#xff0c;很大情况下都是对Linux系统进行性能分析。此篇博客将重点介绍如果收集CPU相关指…

GPT-4o mini是什么?

今天&#xff0c;全网都知道 OpenAI 发现货了&#xff01; GPT-4o mini 取代 GPT 3.5&#xff0c;从此坐上正主之位。 从官网信息来看&#xff0c;OpenAI 最新推出的 GPT-4o mini 重新定义了 AI 成本效益的标准&#xff0c;其性能优于前代模型 GPT-3.5 Turbo&#xff0c;且成本…

SpringBoot系列—4.SpringBoot 整合Mybatis、MP(MyBatis-Plus)

SpringBoot系列—1.IDEA搭建SpringBoot框架 SpringBoot系列—2.SpringBoot拦截器篇 SpringBoot系列—3.SpringBoot Redis篇 SpringBoot系列—4.SpringBoot 整合Mybatis、MP&#xff08;MyBatis-Plus&#xff09; SpringBoot系列—5.SpringBoot 整合Mybatis-Plus分页 **1.pom.xm…

设计模式-Git-其他

目录 设计模式&#xff1f; 创建型模式 单例模式&#xff1f; 啥情况需要单例模式 实现单例模式的关键点&#xff1f; 常见的单例模式实现&#xff1f; 01、饿汉式如何实现单例&#xff1f; 02、懒汉式如何实现单例&#xff1f; 03、双重检查锁定如何实现单例&#xff…

【扩散模型(五)】IP-Adapter 源码详解3-推理代码

系列文章目录 【扩散模型&#xff08;一&#xff09;】中介绍了 Stable Diffusion 可以被理解为重建分支&#xff08;reconstruction branch&#xff09;和条件分支&#xff08;condition branch&#xff09;【扩散模型&#xff08;二&#xff09;】IP-Adapter 从条件分支的视…

前端JS特效第48集:terseBanner焦点图轮播插件

terseBanner焦点图轮播插件&#xff0c;先来看看效果&#xff1a; 部分核心的代码如下(全部代码在文章末尾)&#xff1a; <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatibl…

python每日学习:异常处理

python每日学习8&#xff1a;异常处理 Python中的错误可以分为两种&#xff1a;语法错误和异常 语法错误(Syntax errors) &#xff1a;代码编译时的错误&#xff0c;不符合Python语言规则的代码会停止编译并返回 错误信息。 缺少起始符号或结尾符号(括号、引号等)。 缩进错误…

算法篇 滑动窗口 leetCode 30 串联所有单词的子串

串联所有单词的子串 1.题目描述2.题目解释2.1 原理解释2.2 文字分析 3.代码演示 1.题目描述 2.题目解释 2.1 原理解释 2.2 文字分析 3.代码演示

移动硬盘在苹果电脑上使用后在windows中无法读取 Win和Mac的硬盘怎么通用

在日益普及的跨平台工作环境中&#xff0c;苹果电脑与Windows PC之间的数据交换成为日常需求。然而&#xff0c;用户常面临一个困扰&#xff1a;为何苹果电脑的硬盘能在macOS下流畅运行&#xff0c;却在Windows系统中变得“水土不服”&#xff1f;这一问题核心在于硬盘格式的不…

mac docker no space left on device

mac 上 docker 拉取镜像报错 Error response from daemon: write /var/lib/docker/tmp/docker-export-3995807640/b8464f52498789c4ebbc063d508f04e8d2586567fbffa475e3cd9afd3c5a7cf2/layer.tar: no space left on device解决&#xff1a; 增加 docker 虚拟磁盘大小。如下图

Echarts + 低代码 :可视化如何赋能企业的创新之路?

Echarts最新技术资源&#xff08;建议收藏&#xff09; https://gcdn.grapecity.com.cn/forum.php?modviewthread&tid149493&highlightecharts 前言 数据驱动已经成为企业决策和业务优化的关键所在&#xff0c;在数字化时代&#xff0c;高效的数据分析与可视化呈现是…