当前位置:   article > 正文

(三) 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 ] [pxpy]

    =[pxpy]
    + [bxby]
    [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}=[ˆXBˆXAˆXBˆYAˆYBˆXAˆYBˆYA]

    =[cosθsinθsinθcosθ]
    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 ] [pxpy]
    = _{B}^{A}\textrm{R}[pxpy]
    =[cosθsinθsinθcosθ]
    [pxpy]
    [pxpy]=BAR[pxpy]=[cosθsinθsinθcosθ][pxpy]
    变换后的坐标都在坐标系{A}中。

  • 3)缩放
    则向量 p → = [ p x p y ] \overrightarrow{p} = [pxpy]

    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'} = [pxpy]
    = [TxpxTypy]
    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 = [10px01py001]
    Tt=100010pxpy1
  • 旋转: T r = [ c o s θ − s i n θ 0 s i n θ c o s θ 0 0 0 1 ] T_r = [cosθsinθ0sinθcosθ0001]
    Tr=cosθsinθ0sinθcosθ0001
  • 缩放: T a = [ T x 0 0 0 T y 0 0 0 1 ] T_a = [Tx000Ty0001]
    Ta=Tx000Ty0001
  • 剪切: T s = [ 1 t a n ϕ 0 t a n ψ 1 0 0 0 1 ] T_s = [1tanϕ0tanψ10001]
    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 = [a00a01a02a10a11a12001]

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 ] [pxpy1]
=[a00a01a02a10a11a12001]
[pxpy1]
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 = [a00a01a02a10a11a12001]

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

在这里插入图片描述

  • 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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

在这里插入图片描述

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 ] [XYZ]

= [a00a01a02a10a11a12a20a211]
[xy1]
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\{x=XZ=a00x+a01y+a02a20x+a21y+1y=YZ=a10x+a11y+a12a20x+a21y+1
\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 ] [a00a01a02a10a11a12a20a211]

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

在这里插入图片描述

当然,这里的四个角点是假设已知的,在实际中可通过轮廓检测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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/100433
推荐阅读
相关标签
  

闽ICP备14008679号