OpenCV 笔记(25):图像的仿射变换

1. 几何变换

图像的几何变换是指将一幅图像中的坐标位置映射到另一幅图像中的新坐标位置,其实质是改变像素的空间位置,估算新空间位置上的像素值。几何变换不改变图像的像素值,只是在图像平面上进行像素的重新安排。

以下是常用的几种几何变换:

  • 旋转:将图像旋转指定角度。
  • 缩放:按缩放因子调整图像大小,使其变大或变小。
  • 平移:将图像从当前位置移动到新位置。
  • 错切:沿特定轴倾斜图像。
  • 仿射变换:一个更广泛的类别,包括单个变换中的缩放、旋转、错切和平移。
  • 透视变换:此变换模拟 3D 空间中的透视效果,允许进行更复杂的操作,例如校正由摄像机角度引起的扭曲。

几何变换通常使用数学函数和变换矩阵来实现。这些矩阵定义了原始图像中的每个像素如何映射到转换图像中的新位置。

2. 仿射变换

2.1 仿射变换

图像处理中的仿射变换 是指对图像进行一次线性变换和平移,将其映射到另一个图像空间的过程。仿射变换可以保持图像的"平直性",即直线经过仿射变换后依然为直线,平行线经过仿射变换后依然为平行线。

通常,使用 2x3 大小数组 M 来进行仿射变换。数组由两个矩阵 A、B 组成,其中矩阵 A(大小为2x2)用于矩阵乘法,矩阵 B(大小为2x1)用于向量加法。

<math xmlns="http://www.w3.org/1998/Math/MathML"> A = ∣ a 00 a 01 a 10 a 11 ∣ A= \begin{vmatrix} a_{00} & a_{01} \\ a_{10} & a_{11} \end{vmatrix} </math>A= a00a10a01a11

<math xmlns="http://www.w3.org/1998/Math/MathML"> B = ∣ b 00 b 10 ∣ B = \begin{vmatrix} b_{00} \\ b_{10} \end{vmatrix} </math>B= b00b10

<math xmlns="http://www.w3.org/1998/Math/MathML"> M = ∣ A B ∣ = ∣ a 00 a 01 b 00 a 10 a 11 b 10 ∣ M = \begin{vmatrix} A & B \end{vmatrix} = \begin{vmatrix} a_{00} & a_{01} & b_{00} \\ a_{10} & a_{11} & b_{10} \end{vmatrix} </math>M= AB = a00a10a01a11b00b10

原像素点坐标(x,y),经过仿射变换后的点的坐标是(u,v),则矩阵仿射变换基本算法原理:

<math xmlns="http://www.w3.org/1998/Math/MathML"> ∣ u v ∣ = A ∗ ∣ x y ∣ + B \begin{vmatrix} u \\ v \end{vmatrix} = A*\begin{vmatrix} x \\ y \end{vmatrix} + B </math> uv =A∗ xy +B

其数学表达式如下:

<math xmlns="http://www.w3.org/1998/Math/MathML"> { u = a 00 x + a 01 y + b 00 v = a 10 x + a 11 y + b 10 \begin{cases} u = a_{00}x+a_{01}y+b_{00} \\ v = a_{10}x+a_{11}y+b_{10} \end{cases} </math>{u=a00x+a01y+b00v=a10x+a11y+b10

其中:

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> a 00 a_{00} </math>a00 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> a 11 a_{11} </math>a11 代表缩放系数,控制图像在水平和垂直方向上的缩放。
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> a 01 a_{01} </math>a01 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> a 10 a_{10} </math>a10 代表错切系数,控制图像的水平和垂直错切。
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> b 00 b_{00} </math>b00 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> b 10 b_{10} </math>b10 代表平移系数,控制图像在水平和垂直方向上的平移。

由于缩放和旋转是通过矩阵乘法来实现,平移是通过矩阵加法来实现的,将这几个操作都用一个矩阵实现所以构造出上面的 2x3 矩阵 M。

仿射变换是从二维坐标到二维坐标之间的线性变换,且为了保持二维图像的"平直性"和"平行性"。我们需要引入齐次坐标 的概念,最终得到的齐次坐标矩阵表示形式为:

<math xmlns="http://www.w3.org/1998/Math/MathML"> ∣ u v 1 ∣ = ∣ a 00 a 01 b 00 a 10 a 11 b 10 0 0 1 ∣ ∣ x y 1 ∣ \begin{vmatrix} u \\ v \\ 1 \end{vmatrix} = \begin{vmatrix} a_{00} & a_{01} & b_{00} \\ a_{10} & a_{11} & b_{10} \\ 0 & 0 & 1 \end{vmatrix} \begin{vmatrix} x \\ y \\ 1 \end{vmatrix} </math> uv1 = a00a100a01a110b00b101 xy1

2.2 齐次坐标

在数学里,齐次坐标(homogeneous coordinates),或投影坐标(projective coordinates)是指一个用于投影几何里的坐标系统,如同用于欧氏几何里的笛卡儿坐标一般。齐次坐标可让包括无穷远点的点坐标以有限坐标表示。使用齐次坐标的公式通常会比用笛卡儿坐标表示更为简单,且更为对称。

引入齐次坐标的目的是为了更好的表示无限远(infinity)的坐标的概念,在欧式空间中,无限大或者无限小的坐标的并不存在,不能用数值表示。数学家 August Ferdinand Möbius(1) 提出了齐次坐标系,采用 N+1 个量来表示 N 维坐标。

例如,在二维齐次坐标系中,我们引入一个量 w,将一个二维点 (x,y) 表示为 (X,Y,w) 的形式,其转换关系为

<math xmlns="http://www.w3.org/1998/Math/MathML"> x = X w x = \frac{X}{w} </math>x=wX

<math xmlns="http://www.w3.org/1998/Math/MathML"> y = Y w y = \frac{Y}{w} </math>y=wY

其中,w 可以为任意值。

在笛卡尔坐标系中以(1,2)为例,在齐次坐标系中可以用(1,2,1)表示,也可以用(2,4,2)表示,还可以用 (4,8,4),(8,16,8)...表示,即 (k,2k,k),k∈ R 这些点都映射到欧式空间中的一点,即这些点具有 尺度不变性(Scale Invariant) ,是"齐性的"(同族的),所以称之为齐次坐标

"齐次坐标表示是计算机图形学的重要手段之一,它既能够用来明确区分向量和点,同时也更易用于进行仿射(线性)几何变换。"------出自《计算机图形学(OpenGL版)》的作者 F.S. Hill Jr.

通过齐次坐标还可以证明两条平行线可以相交,非常有意思。

3. 仿射变换中常见的变换形式

OpenCV 提供了 warpAffine() 函数实现仿射变换。它可以用于实现各种图像几何变换,例如平移、缩放、旋转、错切等。

ini 复制代码
void warpAffine( InputArray src, OutputArray dst,
                              InputArray M, Size dsize,
                              int flags = INTER_LINEAR,
                              int borderMode = BORDER_CONSTANT,
                              const Scalar& borderValue = Scalar());

第一个参数 src: 输入图像,可以是单通道或多通道图像。

第二个参数 dst: 输出图像,与输入图像同类型和大小。

第三个参数 M: 仿射变换矩阵,2x3 的浮点数矩阵。

第四个参数 dsize: 输出图像的大小。

第五个参数 flags: 插值方式,默认值为 INTER_LINEAR,表示使用双线性插值。

第六个参数 borderMode: 边界模式,默认值为BORDER_CONSTANT,表示使用常量值填充边界。

第七个参数 borderValue: 边界填充值,默认值为0。

3.1 平移

图像平移的公式:

<math xmlns="http://www.w3.org/1998/Math/MathML"> ∣ 1 0 t x 0 1 t y 0 0 1 ∣ ∣ x y 1 ∣ = ∣ x + t x y + t y 1 ∣ \begin{vmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & 1 \end{vmatrix} \begin{vmatrix} x \\ y \\ 1 \end{vmatrix} = \begin{vmatrix} x + t_x \\ y + t_y \\ 1 \end{vmatrix} </math> 100010txty1 xy1 = x+txy+ty1

下面的代码,分别实现了对图像沿着 x 轴、y 轴进行平移

cpp 复制代码
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main() {
    Mat src = imread(".../girl.jpg");
    imshow("src", src);

    int width = src.cols;
    int height = src.rows;

    Mat dst;
    Mat warp_matrix = (cv::Mat_<float>(2, 3) <<1, 0, 400, 0, 1, 0);
    warpAffine(src, dst, warp_matrix, Size(width, height), INTER_LINEAR);
    imshow("Shift along X-axis", dst);

    warp_matrix = (cv::Mat_<float>(2, 3) <<1, 0, 0, 0, 1, 400);
    cv::warpAffine(src, dst, warp_matrix, Size(width, height), INTER_LINEAR);
    cv::imshow("Shift along Y-axis", dst);

    waitKey(0);
    return 0;
}

3.2 缩放

图像缩放的公式:

<math xmlns="http://www.w3.org/1998/Math/MathML"> ∣ s x 0 0 0 s y 0 0 0 1 ∣ ∣ x y 1 ∣ = ∣ x ′ y ′ 1 ∣ \begin{vmatrix} s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & 1 \end{vmatrix} \begin{vmatrix} x \\ y \\ 1 \end{vmatrix} = \begin{vmatrix} x' \\ y' \\ 1 \end{vmatrix} </math> sx000sy0001 xy1 = x′y′1

下面的代码,分别实现了对图像进行0.75和1.25倍的缩放。

cpp 复制代码
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main() {
    Mat src = imread(".../girl.jpg");
    imshow("src", src);

    int width = src.cols;
    int height = src.rows;

    // 设置缩放比例
    float scale = 0.75;

    Mat dst;
    Mat warp_matrix = (cv::Mat_<float>(2, 3) <<scale, 0, 0, 0, scale, 0);
    warpAffine(src, dst, warp_matrix, Size(width, height), INTER_LINEAR);
    imshow("Scale 0.75", dst);

    scale = 1.25;
    warp_matrix = (cv::Mat_<float>(2, 3) <<scale, 0, 0, 0, scale, 0);
    cv::warpAffine(src, dst, warp_matrix, Size(width, height), INTER_LINEAR);
    cv::imshow("Scale 1.25", dst);

    waitKey(0);
    return 0;
}

3.3 旋转

图像旋转的公式:

<math xmlns="http://www.w3.org/1998/Math/MathML"> ∣ c o s ( θ ) − s i n ( θ ) t x s i n ( θ ) c o s ( θ ) t y 0 0 1 ∣ ∣ x y 1 ∣ = ∣ x ′ y ′ 1 ∣ \begin{vmatrix} cos(\theta) & -sin(\theta) & t_x \\ sin(\theta) & cos(\theta) & t_y \\ 0 & 0 & 1 \end{vmatrix} \begin{vmatrix} x \\ y \\ 1 \end{vmatrix} = \begin{vmatrix} x' \\ y' \\ 1 \end{vmatrix} </math> cos(θ)sin(θ)0−sin(θ)cos(θ)0txty1 xy1 = x′y′1

OpenCV 提供了更为简洁的 getRotationMatrix2D() 函数用于生成一个 2x3 的仿射变换矩阵,该矩阵可以用于对图像进行旋转操作。

cpp 复制代码
Mat getRotationMatrix2D(Point2f center, double angle, double scale);

第一个参数 center: 图像旋转中心,以像素为单位。

第二个参数 angle: 旋转角度,以度为单位。逆时针方向为正。

第三个参数 scale: 旋转后的图像缩放比例。

下面的代码,展示了以图像的中心作为旋转中心,并且逆时针方向旋转45度。

cpp 复制代码
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main() {
    Mat src = imread(".../girl.jpg");
    imshow("src", src);

    int width = src.cols;
    int height = src.rows;

    Point center = Point(width / 2, height / 2);
    double angle = 45;
    double scale = 1.0;
    Mat dst;
    Mat warp_matrix = getRotationMatrix2D(center, angle, scale);
    warpAffine(src, dst, warp_matrix, Size(width, height), INTER_LINEAR);
    imshow("Rotate", dst);

    waitKey(0);
    return 0;
}

3.4 错切

图像错切的公式:

<math xmlns="http://www.w3.org/1998/Math/MathML"> ∣ 1 d x 0 d y 1 0 0 0 1 ∣ ∣ x y 1 ∣ = ∣ x ′ y ′ 1 ∣ \begin{vmatrix} 1 & d_x & 0 \\ d_y & 1 & 0 \\ 0 & 0 & 1 \end{vmatrix} \begin{vmatrix} x \\ y \\ 1 \end{vmatrix} = \begin{vmatrix} x' \\ y' \\ 1 \end{vmatrix} </math> 1dy0dx10001 xy1 = x′y′1

其中,

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> d x = t a n ( θ ) d_x = tan(\theta) </math>dx=tan(θ), <math xmlns="http://www.w3.org/1998/Math/MathML"> d y = 0 d_y = 0 </math>dy=0 时,沿 x 方向错切。
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> d x = 0 d_x = 0 </math>dx=0, <math xmlns="http://www.w3.org/1998/Math/MathML"> d y = t a n ( θ ) d_y = tan(\theta) </math>dy=tan(θ) 时,沿 y 方向错切。

下面的例子,展示图像的错切。

cpp 复制代码
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main() {
    Mat src = imread(".../girl.jpg");
    imshow("src", src);

    int width = src.cols;
    int height = src.rows;

    float a = 0.25;
    float b = 0.5;

    Mat dst;
    Mat warp_matrix = (cv::Mat_<float>(2, 3) <<1, a, 0, b, 1, 0);
    warpAffine(src, dst, warp_matrix, Size(width, height), INTER_LINEAR);
    imshow("Shearing", dst);

    waitKey(0);
    return 0;
}

4. 总结

图像仿射变换是一种简单而有效的图像几何变换方法,在图像处理和计算机视觉领域有着广泛的应用。它可以用于图像矫正、增强、配准、合成、目标识别和跟踪等多种任务。

相关推荐
菜狗woc13 小时前
opencv-python的简单练习
人工智能·python·opencv
西猫雷婶15 小时前
python学opencv|读取图像(十四)BGR图像和HSV图像通道拆分
开发语言·python·opencv
云空15 小时前
《QT 5.14.1 搭建 opencv 环境全攻略》
开发语言·qt·opencv
编码小哥15 小时前
opencv中的色彩空间
opencv·计算机视觉
吃个糖糖15 小时前
34 Opencv 自定义角点检测
人工智能·opencv·计算机视觉
花花少年15 小时前
【Windows版】opencv 和opencv_contrib配置
opencv·opencv_contrib
YRr YRr15 小时前
解决Ubuntu 20.04上编译OpenCV 3.2时遇到的stdlib.h缺失错误
linux·opencv·ubuntu
葡萄爱18 小时前
OpenCV图像分割
人工智能·opencv·计算机视觉
编码小哥20 小时前
通过opencv加载、保存视频
人工智能·opencv
发呆小天才O.oᯅ20 小时前
YOLOv8目标检测——详细记录使用OpenCV的DNN模块进行推理部署C++实现
c++·图像处理·人工智能·opencv·yolo·目标检测·dnn