(三) OpenCV仿射变换与透射变换(Affine and Perspective Transform)

图像最基本的变换即仿射变换(Affine Transform)和透射变换(Perspective Transform)。仿射变换是对一个向量空间进行一次线性变换并接上一次平移。透射变换是中心投影的射影变换。

1.仿射变换

仿射变换是线性变换与平移的组合。

1.1原理描述

首先,线性变换是什么?线性变换是满足以下两条性质的变换:1)直线在变换后仍然为直线,不能有所弯曲。2)原点必须保持固定。常见的线性有绕原点的旋转以原点为中心的缩放原点不变的错切/推移/剪切(shear)。平移(translation)是将物件的每点向同一方向移动相同距离。图像变换中涉及宽度和高度两个方向,以二维为例。

  • 1)平移
    在这里插入图片描述

    原点O(0,0)沿 p → \overrightarrow{p} p 平移后到达p点,平移的关系可表示为:
    [ p x ′ p y ′ ] = [ p x p y ] + [ b x b y ] \begin{bmatrix} p'_{x}\\ p'_{y}\\ \end{bmatrix}=\begin{bmatrix} p_{x}\\ p_{y}\\ \end{bmatrix} + \begin{bmatrix} b_{x}\\ b_{y}\\ \end{bmatrix} [pxpy]=[pxpy]+[bxby] p ′ = p + b p'=p+b p=p+b

  • 2)旋转
    在这里插入图片描述

    如上图,坐标系{A}中的向量 p p p逆时针旋转 θ \theta θ p ′ p' p位置。求 p ′ p' p在坐标系{A}中的坐标。
    假设坐标系{A}也逆时针旋转 θ \theta θ{B},则 p ′ p' p{B}中的坐标为 ( p x , p y ) (p_x, p_y) (px,py),{B} 相对于{A}的变换可通过{B}中的基在{A}的基上投影求嘚。
    x ^ A = ( 1 , 0 ) \hat{x}_A=(1,0) x^A=(1,0)
    y ^ A = ( 0 , 1 ) \hat{y}_A=(0,1) y^A=(0,1)
    x ^ B = ( c o s θ , s i n θ ) \hat{x}_B=(cos\theta,sin\theta) x^B=(cosθ,sinθ)
    y ^ B = ( − s i n θ , c o s θ ) \hat{y}_B=(-sin\theta,cos\theta) y^B=(sinθ,cosθ)

    B A R = [ X ^ B X ^ A X ^ B Y ^ A Y ^ B X ^ A Y ^ B Y ^ A ] = [ c o s θ − s i n θ s i n θ c o s θ ] _{B}^{A}\textrm{R}=\begin{bmatrix} \hat{X}_B\hat{X}_A & \hat{X}_B \hat{Y}_A \\ \hat{Y}_B \hat{X}_A & \hat{Y}_B \hat{Y}_A \end{bmatrix}=\begin{bmatrix} cos\theta & -sin\theta \\ sin\theta & cos\theta \end{bmatrix} BAR=[X^BX^AY^BX^AX^BY^AY^BY^A]=[cosθsinθsinθcosθ]

    [ p x ′ p y ′ ] = B A R [ p x p y ] = [ c o s θ − s i n θ s i n θ c o s θ ] [ p x p y ] \begin{bmatrix} p'_x\\ p'_y \end{bmatrix} = _{B}^{A}\textrm{R}\begin{bmatrix} p_x\\ p_y \end{bmatrix}=\begin{bmatrix} cos\theta & -sin\theta \\ sin\theta & cos\theta \end{bmatrix}\begin{bmatrix} p_x\\ p_y \end{bmatrix} [pxpy]=BAR[pxpy]=[cosθsinθsinθcosθ][pxpy]变换后的坐标都在坐标系{A}中。

  • 3)缩放
    则向量 p → = [ p x p y ] \overrightarrow{p} = \begin{bmatrix} p_x\\ p_y \end{bmatrix} p =[pxpy]沿 X , Y X,Y X,Y方向分别缩放 T x , T y T_x,T_y Tx,Ty得向量 p ‘ → \overrightarrow{p‘} p ,其坐标为 p ′ → = [ p x ′ p y ′ ] = [ T x p x T y p y ] \overrightarrow{p'} = \begin{bmatrix} p'_x\\ p'_y \end{bmatrix} = \begin{bmatrix} T_xp_x\\ T_yp_y \end{bmatrix} p =[pxpy]=[TxpxTypy]

  • 4)剪切

    图片参考自[1]在这里插入图片描述

    如上图是 Y Y Y轴不变, X X X轴逆时针旋转 ψ \psi ψ发生的错切,以 B B B点为例,错切后得 B ′ B' B点, B B B点为 ( x , y ) (x, y) (x,y),则 B ′ B' B点为 ( x , y + x t a n ψ ) (x, y+xtan\psi) (x,y+xtanψ)

将平移,旋转,缩放和错切写成齐次形式,可写成如下矩阵形式:

  • 平移: T t = [ 1 0 p x 0 1 p y 0 0 1 ] T_t = \begin{bmatrix} 1 & 0 &p_x \\ 0&1 &p_y \\ 0&0 &1 \end{bmatrix} Tt=100010pxpy1
  • 旋转: T r = [ c o s θ − s i n θ 0 s i n θ c o s θ 0 0 0 1 ] T_r = \begin{bmatrix} cos\theta & -sin\theta &0 \\ sin\theta&cos\theta &0 \\ 0&0 &1 \end{bmatrix} Tr=cosθsinθ0sinθcosθ0001
  • 缩放: T a = [ T x 0 0 0 T y 0 0 0 1 ] T_a = \begin{bmatrix} T_x & 0& 0 \\ 0 &T_y & 0\\ 0&0 &1 \end{bmatrix} Ta=Tx000Ty0001
  • 剪切: T s = [ 1 t a n ϕ 0 t a n ψ 1 0 0 0 1 ] T_s = \begin{bmatrix} 1 & tan\phi & 0 \\ tan\psi &1 & 0\\ 0&0 &1 \end{bmatrix} Ts=1tanψ0tanϕ10001

仿射变换是以上四种变换的组合,可写成:

T = T s T a T r T t = [ a 00 a 01 a 02 a 10 a 11 a 12 0 0 1 ] T = T_sT_aT_rT_t = \begin{bmatrix} a_{00} & a_{01} & a_{02} \\ a_{10} &a_{11} & a_{12}\\ 0&0 &1 \end{bmatrix} T=TsTaTrTt=a00a100a01a110a02a121

P ′ = T P P'=TP P=TP,即:

[ p x ′ p y ′ 1 ] = [ a 00 a 01 a 02 a 10 a 11 a 12 0 0 1 ] [ p x p y 1 ] \begin{bmatrix} p'_x \\ p'_y\\ 1 \end{bmatrix}=\begin{bmatrix} a_{00} & a_{01} & a_{02} \\ a_{10} &a_{11} & a_{12}\\ 0&0 &1 \end{bmatrix}\begin{bmatrix} p_x \\ p_y\\ 1 \end{bmatrix} pxpy1=a00a100a01a110a02a121pxpy1
上式是矩阵的乘法,其可看作三维空间的线性变换,而放射变换是在 z = 1 z=1 z=1上图形发生的变化。

图片参考自here
在这里插入图片描述

1.2 OpenCV API

  • getAffineTransform/warpAffine

从1.1中可知,要进行仿射变换需先求出变换矩阵,而变换矩阵 T = [ a 00 a 01 a 02 a 10 a 11 a 12 0 0 1 ] T = \begin{bmatrix} a_{00} & a_{01} & a_{02} \\ a_{10} &a_{11} & a_{12}\\ 0&0 &1 \end{bmatrix} T=a00a100a01a110a02a121中有6个未知数,因此需要至少3对不共线的点来求。
OpenCV中给出变换前后对应的变换后的3对点可使用getAffineTransform方法得到变换矩阵, warpAffine对源图像变换得到变换图像。

#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

int main(int argv, char **argc)
{
    cv::Mat image = cv::imread("imgs/test.jpeg");
    int width = image.cols;
    int height = image.rows;
    cv::Point2f src[3];
    cv::Point2f dst[3];
    // 实现图像逆时针旋转90度的仿射变换
    // 1. src:左上角点 -> dst:左下角点
    src[0] = cv::Point2f(0., 0);
    dst[0] = cv::Point2f(0., width);
    // 2. src:上边点 -> dst:左边点
    src[1] = cv::Point2f(1, 0);
    dst[1] = cv::Point2f(0, width-1);
    // 3. src:左边点 -> dst:下边点
    src[2] = cv::Point2f(0, 1.);
    dst[2] = cv::Point2f(1., width);
    cv::Mat transform_mat = cv::getAffineTransform(src, dst);
    std::cout << "Affine Transform Matrix: " << transform_mat << std::endl;
    cv::Mat affine_img;
    cv::warpAffine(image, affine_img, transform_mat, cv::Size(height, width));
    cv::imwrite("affine_img.png", affine_img); 
    return 0;
}

在这里插入图片描述

  • getRotationMatrix2D

    对于旋转这一仿射变换的特例,OpenCV中定义了更为简单的API来获取变换矩阵,那就是getRotationMatrix2D。通过此方法仅通过指定旋转点和旋转角度,即可得到旋转矩阵,该方法还接受1个Scale参数,若仅旋转图像不缩放,最后1个参数Scale可设置为1。如下以绕图像中心为例旋转图像。

    值得注意的时有时候我们旋转图像后不希望丢失原图像信息,因此需要计算旋转后包围图像的最小矩形的宽高。一种是使用OpenCV自带的方法计算,一种是手动计算。

在这里插入图片描述

#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>


int main(int argv, char **argc)
{
    cv::Mat image = cv::imread("../imgs/test.jpeg");
    double angle = 30;
    int width = image.cols;
    int height = image.rows;
    cv::Mat rot_matrix = cv::getRotationMatrix2D(cv::Point2f(0.5 * width, 0.5 * height), angle, 1.0);
    // 2.1
    double sin_angle = sin(abs(angle) / 180 * M_PI);
    double cos_angle = cos(abs(angle) / 180 * M_PI);
    int new_heiht = width * sin_angle + height * cos_angle;
    int new_width = width * cos_angle + height * sin_angle;
    rot_matrix.at<double>(0, 2) += (new_width - width) / 2;
    rot_matrix.at<double>(1, 2) += (new_heiht - height) / 2;
    // 2.2
    cv::Rect2f bbox = cv::RotatedRect(cv::Point2f(width / 2, height / 2), image.size(), angle).boundingRect2f();
    cv::Mat rot_img;
    cv::warpAffine(image, rot_img, rot_matrix, bbox.size());
    cv::imwrite("rot_img.png", rot_img);
}

在这里插入图片描述

2.透射变换

2.1原理描述

1中的仿射变换是在平面上的线性变换加平移,根据其性质可知变换后平行四边形依然是平行四边形,不改变直线的平行关系。透射变换即中心投影变换,利用透视中心、像点、目标点三点共线的条件,按透视旋转定律使承影面(透视面)绕迹线(透视轴)旋转某一角度,破坏原有的投影光线束,仍能保持承影面上投影几何图形不变的变换3
在这里插入图片描述

透视变换公式:
[ X Y Z ] = [ a 00 a 01 a 02 a 10 a 11 a 12 a 20 a 21 1 ] [ x y 1 ] \begin{bmatrix} X \\ Y\\ Z \end{bmatrix} = \begin{bmatrix} a_{00} & a_{01} & a_{02} \\ a_{10} &a_{11} & a_{12}\\ a_{20} &a_{21} & 1 \end{bmatrix}\begin{bmatrix} x \\ y\\ 1 \end{bmatrix} XYZ=a00a10a20a01a11a21a02a121xy1
从变换公式可知,仿射变换是透射变换的特例。
再通过除以Z轴转换成二维坐标:

{ x ′ = X Z = a 00 x + a 01 y + a 02 a 20 x + a 21 y + 1 y ′ = Y Z = a 10 x + a 11 y + a 12 a 20 x + a 21 y + 1 \left\{\begin{matrix} x'=\frac{X}{Z}=\frac{a_{00}x+a_{01}y+a_{02}}{a_{20}x+a_{21}y+1}\\ y'=\frac{Y}{Z}=\frac{a_{10}x+a_{11}y+a_{12}}{a_{20}x+a_{21}y+1} \end{matrix}\right. {x=ZX=a20x+a21y+1a00x+a01y+a02y=ZY=a20x+a21y+1a10x+a11y+a12

透视变换(Perspective Transformation)是将二维的图片投影到一个三维视平面上,然后再转换到二维坐标下,所以也称为投影映射(Projective Mapping)。

移动投影中心和承影面,可得到各种形状的变换。

图片来自于3
在这里插入图片描述

2.2 OpenCV API

透射变换矩阵 [ a 00 a 01 a 02 a 10 a 11 a 12 a 20 a 21 1 ] \begin{bmatrix} a_{00} & a_{01} & a_{02} \\ a_{10} &a_{11} & a_{12}\\ a_{20} &a_{21} & 1 \end{bmatrix} a00a10a20a01a11a21a02a121中共有8个未知数,因此至少需要4对不共线的点来求解变换矩阵。OpenCV中提供了getPerspectiveTransform方法来计算变换矩阵,提供了warpPerspective方法来对图像进行变换。

#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>

int main(int argv, char **argc)
{
    cv::Mat image = cv::imread("imgs/paper.jpg");
    float w = 420, h = 596;
    cv::Point2f per_src[4];
    per_src[0] = cv::Point2f(380, 444);
    per_src[1] = cv::Point2f(895 , 520);
    per_src[2] = cv::Point2f(88, 1126);
    per_src[3] = cv::Point2f(921, 1272);
    cv::Point2f per_dst[4] = {{0.0f, 0.0f}, {w, 0.0f}, {0.0f, h}, {w, h}};

    cv::Mat per_mat = cv::getPerspectiveTransform(per_src, per_dst);
    cv::Mat perspective_img;
    cv::warpPerspective(image, perspective_img, per_mat, cv::Size(w, h));
    cv::imwrite("perspective_img.png", perspective_img);
    return 0;
}

在这里插入图片描述

当然,这里的四个角点是假设已知的,在实际中可通过轮廓检测findContour来获取,后面会把这个补上,已经补上了见,update 2022.11.13


参考:

1.https://cloud.tencent.com/developer/article/1638969
2.https://baike.baidu.com/item/%E9%80%8F%E8%A7%86%E5%8F%98%E6%8D%A2/8746342
3.https://www.jianshu.com/p/1fd77aa1e69e
4.https://www.computervision.zone/my-account/?password-reset=true

  • 9
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值