图像的仿射变换
转自:https://zhuanlan.zhihu.com/p/80852438
https://blog.csdn.net/hty1053240123/article/details/51992398
一,概述
图像的几何变换主要包括:平移、缩放、旋转、仿射、透视等等。图像变换是建立在矩阵运算基础上的,通过矩阵运算可以很快的找到不同图像的对应关系。理解变换的原理需要理解变换的构造方法以及矩阵的运算方法。
图像的几何变换主要分为三类:刚性变换、仿射变换和透视变换,如下图:
仿射变换是从一个二维坐标系变换到另一个二维坐标系,属于线性变换。通过已知3对坐标点可以求得变换矩阵。
透视变换是从一个二维坐标系变换到一个三维坐标系,属于非线性变换。通过已知4对坐标点可以求得变换矩阵。
二,图像基本变换
图像的几何变换包含很多变换,其中有一些基本变换,而仿射变换和透视变换就是对这些基本变换进行组合实现的。
基本变换具体包括:平移(Translation)、缩放(Scale)、旋转(Rotation)、翻转(Flip)和错切(Shear)。
a. 平移
b. 缩放
c. 旋转
d. 翻转
e. 错切:错切亦称为剪切或错位变换,包含水平错切和垂直错切,常用于产生弹性物体的变形处理。
下面这张图可能更形象:
三,仿射变换
3.1 原理
对于二维坐标系的一个坐标点(x, y),可以使用一个2*2矩阵来调整x, y的值,而通过调整x, y可以实现二维形状的线性变换(旋转,缩放),所以整个转换过程的就是对(x, y)调整的过程。
仿射变换(Affine Transformation)是指在向量空间中进行一次线性变换(乘以一个矩阵)和一次平移(加上一个向量),变换到另一个向量空间的过程。
仿射变换代表的是两幅图之间的映射关系,仿射变换矩阵为2*3的军阵,如下图中的矩阵M, 其中的B起着平移的作用,而A中的对角线决定缩放,反对角线决定旋转或错切。所以仿射变换可以由一个矩阵A和一个向量B给出:
原像素点坐标(x, y),经过仿射变换后的点的坐标是T,则矩阵仿射变换基本算法原理:
所以仿射变换是一种二维坐标(x, y)到二维坐标(u, v)的线性变换,其数学表达式如下:
其实到这里还没完,我们知道缩放和旋转通过矩阵乘法实现,而平移是通过矩阵加法来实现的,为了将这几个操作都通过一个矩阵来实现,所以构造出了上面那个2*3的矩阵。
但是这个会改变图像的尺寸,比如一个2*2的图像,乘以2*3的矩阵,会得到2*3的图像,所以为了解决这个问题,我们就增加一个维度,也就是构造齐次坐标矩阵:
【齐次坐标矩阵:】
首先给出简短的定义:仿射变换是线性变换(旋转和缩放)加平移变换,齐次坐标就是用高一维的空间坐标表示低一维空间的坐标。线性变换可以用矩阵表示,旋转和缩放都是线性变换,用矩阵表示如下:
仿射变换也就是上面三个变换的叠加,在上面三个变换中平移变换时没办法使用矩阵相乘的方式来获取的。但是如果将坐标在高一维空间进行表示的时候,也就是采用齐次坐标的时候,平移变换可以用矩阵乘法来进行表示。假设p=(x, y, 1)是齐次坐标下二维点p(x,y)的坐标表示,具体表示如下:
这样就以上三个进行统一,便得到了仿射变换的矩阵表示,其定义也更容易表达,仿射变换也就是下面的变换:
【\齐次坐标矩阵】
最终得到齐次坐标矩阵表达形式为
仿射变换保持了二维图像的“平直性”和“平行性”:
平直性:
- 直线经昂设变换后还是直线
- 圆弧经仿射变换后还是圆弧
平行性:
- 直线之间的相对位置关系保持变
- 平行线经仿射变换后依然为平行线
- 直线上点的位置顺序不会发生变换
- 向量间夹角可能会发生变化
3.2 python实现
通过放射变换将图片中的每个像素点按照一定的规律映射到新的位置,仿射变化需要一个转换矩阵,但是由于仿射变换比较复杂,一般很难直接找到这个矩阵,opencv提供了根据原图像和目标图像上三个对应的点来自动创建变换矩阵,矩阵维度为2*3。
两个图像中非共线的三对对应点确定唯一的一个仿射变换。经仿射变换后,图像中的三个关键点依然构成三角形,但三角形形状已经发生变化。这个函数是cv2.getAffineTransform(pos1, pos2),其中两个位置就是变换前后的对应位置关系。输出的就是仿射矩阵M,最后这个矩阵会被传给函数cv2.warpAffine()来实现仿射变换。
原图为: 仿射变换后的图:
import cv2 import numpy as np img = cv2.imread('image0.jpg', 1) height, width = img.shape[:2] # 405x413 # 在原图像和目标图像上各选择三个点 matSrc = np.float32([[0, 0],[0, height-1],[width-1, 0]]) matDst = np.float32([[0, 0],[30, height-30],[width-30, 30]]) # 得到变换矩阵 matAffine = cv2.getAffineTransform(matSrc, matDst) # 进行仿射变换 dst = cv2.warpAffine(img, matAffine, (width,height))
变换矩阵的数据类型是np.float32, 函数cv2.waroAffine()的第三个参数是输出图像的尺寸(宽,高)。
因为平移和缩放的矩阵比较简单,我们可以直接手动指定:
# 图像平移 # 移位矩阵,水平方向移动100个像素,竖直方向移动200个像素 matShift = np.float32([[1,0,100],[0,1,200]]) # 2行3列 dst = cv2.warpAffine(img, matShift, (width,height)) # 图像缩放 # 缩放矩阵,长宽各缩放一半 matScale = np.float32([[0.5,0,0],[0,0.5,0]]) dst = cv2.warpAffine(img, matScale, (int(width/2),int(height/2)))
向右下平移: 缩小50%
要实现图像旋转,需要通过cv2.getRotationMatrix2D来得到二维旋转变换矩阵(2行3列)。cv2.getRotationMatrix2D三个参数分别为:1,旋转中心;2,旋转角度;3,缩放比例。角度为证,则图像逆时针旋转,旋转后图像可能会超出边界。
matRotate = cv2.getRotationMatrix2D((width*0.5, height*0.5), 45, 1.0) dst = cv2.warpAffine(img, matRotate, (width,height))