一、单点旋转
1.1、旋转
我们以最简单的一个点的旋转为例子,令旋转中心为坐标系中心O(0, 0),假设有一点P0,P0 离旋转中心O的距离为r, OP0与坐标轴x轴的夹角为 , 绕O顺时 针旋转
角后对应的点为P ,如下图所示:
在OpenCV中,旋转是以图像的左上角为原点,且以逆时针为正方向。因此在上面 的例子中, 其实是一个负值。
该矩阵也被称作旋转矩阵。然而我们所要的不仅仅是可以围绕图像左上角进行旋转,而是可以围绕任意点进行 旋转。那么我们可以将其转化成绕原点的旋转,其过程为:
1. 首先将旋转点移到原点
2. 按照上面的旋转矩阵进行旋转得到新的坐标点
3. 再将得到的旋转点移回原来的位置
1.2、平移
在以任意点为旋转中心时,除了要进行旋转之外,还要进行平移操作。那么当点 经过平移 后得到P点时,如下图所示:
1.3、旋转平移
从平移和旋转的矩阵可以看出,3x3矩阵的前2x2部分是和旋转相关的,第三列与平移相关。
如果想要将一个图像围绕某个点旋转,需要执行以下步骤:
1.首先需要将旋转中心平移到原点,就是上面的点 到P的过程。
2.接着构造旋转矩阵,通过旋转矩阵将图像进行旋转。
3.旋转完成后,将图像按照平移的路径原路返回,使其回到原来的位置。
于是我们就可以根据这个矩阵计算出图像中任意一点绕某点旋转后的坐标了,这个矩阵学名叫做仿射变 换矩阵,而仿射变换是一种二维坐标到二维坐标之间的线性变换,也就是只涉及一个平面内二维图形的 线性变换,图像旋转就是仿射变换的一种。它保持了二维图形的两种性质:
1. 平直性:直线经过变换后依然是直线。
2. 平行性:平行线经过变换后依然是平行线。
二、图像旋转
图像旋转就是将图像里的每个像素点都带入仿射变换 矩阵里,从而得到旋转后的新坐标。在OpenCV中,要得到仿射变换矩阵可以使用 cv2.getRotationMatrix2D(),通过这个函数即可直接获取到上面的旋转矩阵,该函数需要接收的参数 为:
Center:表示旋转的中心点,是一个二维的坐标点(x,y)
Angle:表示旋转的角度
Scale:表示缩放比例,可以通过该参数调整图像相对于原始图像的大小变化
注意:
由于三角函数的值是小数,那么其乘积也会是小数,虽然OpenCV中会对其进 行取整操作,但是像素点旋转之后的取整结果也有可能重合,这样就会导致可能会在旋转的过程中丢失 一部分原始的像素信息。并且如果使用了scale参数进行图像的缩放的话,当图像放大时,比如一个 10*10的图像放大成20*20,图像由100个像素点变成400个像素点,那么多余的300个像素点是怎么来 的?而当图像缩小时,比如一个20*20的图像缩小为10*10的图像,需要丢掉300个像素点,这时我们就需要利用其他方法来帮我们计算旋转后的图像中每一个 像素点所对应的像素值,从而保证图像的完整性。
三、插值方法
在图像处理和计算机图形学中,插值(Interpolation)是一种通过已知数据点之间的推断或估计来获取 新数据点的方法。它在图像处理中常用于处理图像的放大、缩小、旋转、变形等操作,以及处理图像中 的像素值。
图像插值算法是为了解决图像缩放或者旋转等操作时,由于像素之间的间隔不一致而导致的信息丢失和 图像质量下降的问题。当我们对图像进行缩放或旋转等操作时,需要在新的像素位置上计算出对应的像 素值,而插值算法的作用就是根据已知的像素值来推测未知位置的像素值。
3.1、最近邻插值
其中,dstX表示目标图像中某点的x坐标,srcWidth表示原图的宽度,dstWidth表示目标图像的宽度; dstY表示目标图像中某点的y坐标,srcHeight表示原图的高度,dstHeight表示目标图像的高度。而srcX 和srcY则表示目标图像中的某点对应的原图中的点的x和y的坐标。
通俗的讲,该公式就是让目标图像中 的每个像素值都能找到对应的原图中的像素值,这样才能根据不同的插值方法来获取新的像素值。
根据该公式,我们就可以得到每一个目标点所对应的原图像的点。
那么根据公式我们就可以计算出放大后的图像(0,0)点对应的原图像中的坐标为:
也就是原图中的(0,0)点,而最近邻插值的原则是:目标像素点的像素值与经过该公式计算出来的对 应的像素点的像素值相同,如出现小数部分需要进行取整。那么放大后图像的(0,0)坐标处的像素值 就是原图像中(0,0)坐标处的像素值,也就是10。
也就是原图中的(0.5,0)点,因此需要对计算出来的坐标值进行取整,取整后的结果为(0,0),也 就是说放大后的图像中的(1,0)坐标处对应的像素值就是原图中(0,0)坐标处的像素值,其他像素 点计算规则与此相同。
3.2、双线性插值
双线性插值算法是一种比较好的图像缩放算法,它充分的利用了原图中虚拟点四周的四个真实存在像素 点的值来共同决定目标图中的一个像素点的值,因此缩放效果比简单的最邻近插值要好很多,缩放后图 像质量高,不会出现值不连续的情况。
然后根据Q11、Q21得到R1的插值,根据Q12、Q22得到R2的插值,然后根据R1、R2得到P的插值即 可,这就是双线性插值。
这样就得到了P点的插值。注意此处如果先在y方向插值、再在x方向插值,其结果与按照上述顺序双线 性插值的结果是一样的。
双线性插值的对应关系看似比较清晰,但还是有2个问题。首先是根据坐标系的不同,产生的结果不同, 这张图是左上角为坐标系原点的情况,我们可以发现最左边x=0的点都会有概率直接复制到目标图像中 (至少原点肯定是这样),而且就算不和原图像中的点重合,也相当于进行了1次单线性插值(带入到权 重公式中会发现结果)。
下面这张图是右上角为坐标系原点的情况,我们可以发现最右面的点都会有概率直接复制到目标图像中 (至少原点肯定是这样),而且就算不和原图像中的点重合,也相当于进行了1次单线性插值。那么当我 们采用不同的坐标系时产生的结果是不一样的,而且无论我们采用什么坐标系,最左侧和最右侧(最上 侧和最下侧)的点是“不公平的”,这是第一个问题。
第二个问题时整体的图像相对位置会发生变化。如下图所示,左侧是原图像(3,3),右侧是目标图像(5, 5),原图像的几何中心点是(1,1),目标图像的几何中心点是(2,2),根据对应关系,目标图像的几何中心 点对应的原图像的位置是(1.2,1.2),那么问题来了,目标图像的原点(0,0)和原始图像的原点是重合的, 但是目标图像的几何中心点相对于原始图像的几何中心点偏右下,那么整体图像的位置会发生偏移,所 以参与计算的点相对都往右下偏移会产生相对的位置信息损失。这是第二个问题。
3.3、像素区域插值
像素区域插值主要分两种情况,缩小图像和放大图像的工作原理并不相同。
当使用像素区域插值方法进行缩小图像时,它就会变成一个均值滤波器(滤波器其实就是一个核,这里 只做简单了解,后面实验中会介绍),其工作原理可以理解为对一个区域内的像素值取平均值。
当使用像素区域插值方法进行放大图像时,如果图像放大的比例是整数倍,那么其工作原理与最近邻插 值类似;如果放大的比例不是整数倍,那么就会调用双线性插值进行放大。
3.4、双三次插值
与双线性插值法相同,该方法也是通过映射,在映射点的邻域内通过加权来得到放大图像中的像素值。 不同的是,双三次插值法需要原图像中近邻的16个点来加权。
假设原图像A大小为m*n,缩放后的目标图像B的大小为M*N。其中A的每一个像素 点是已知的,B是未知的,我们想要求出目标图像B中每一个像素点(X,Y)的值,必须先找出像素 (X,Y)在原图像A中对应的像素(x,y),再根据原图像A距离像素(x,y)最近的16个像素点作为计算目 标图像B(X,Y)处像素值的参数,利用BiCubic基函数求出16个像素点的权重,图B像素(x,y)的值就等 于16个像素点的加权叠加。
将上面的16个点的坐标带入函数中,获取16像素所对应的权重W(x)。然而BiCubic函 数是一维的,所以我们需要将像素点的行与列分开计算,比如a00这个点,我们需要将x=0带入BiCubic 函数中,计算a00点对于P点的x方向的权重,然后将y=0带入BiCubic函数中,计算a00点对于P点的y方 向的权重,其他像素点也是这样的计算过程。
3.5、Lanczos插值
Lanczos插值方法与双三次插值的思想是一样的,不同的就是其需要的原图像周围的像素点的范围变成 了8*8,并且不再使用BiCubic函数来计算权重,而是换了一个公式计算权重。
假设原图像A大小为m*n,缩放后的目标图像B的大小为M*N。其中A的每一个像素 点是已知的,B是未知的,我们想要求出目标图像B中每一个像素点(X,Y)的值,必须先找出像素 (X,Y)在原图像A中对应的像素(x,y),再根据原图像A距离像素(x,y)最近的64个像素点作为计算目 标图像B(X,Y)处像素值的参数,利用权重函数求出64个像素点的权重,图B像素(x,y)的值就等于64 个像素点的加权叠加。
假如下图中的P点就是目标图像B在(X,Y)处根据上述公式计算出的对应于原图像A中的位置,P的坐标 位置会出现小数部分,所以我们假设P点的坐标为(x+u,y+v),其中x、y表示整数部分,u、v表示小数 部分,那么我们就可以得到其周围的最近的64个像素的位置。
与双三次插值一样,也需要将像素点分行和列分别带入计算权重值,其他像素点也是这样的计算过 程。
最近邻插值的计算速度最快,但是可能会导致图像出现锯齿状边缘和失真,效果较差。双线性插值的计 算速度慢一点,但效果有了大幅度的提高,适用于大多数场景。
双三次插值、Lanczos插值的计算速度 都很慢,但是效果都很好。
在OpenCV中,关于插值方法默认选择的都是双线性插值,且一般情况下双线性插值已经能满足大部分 需求。
四、边缘填充方式
如上图所示,左图在逆时针旋转45度之后原图的四个顶点在右图中已经看不到了,同时,右图的四个顶点 区域其实是什么都没有的,因此我们需要对空出来的区域进行一个填充。右图就是对空出来的区域进行 了像素值为(0,0,0)的填充,也就是黑色像素值的填充。
4.1、边界复制(BORDER_REPLICATE)
边界复制会将边界处的像素值进行复制,然后作为边界填充的像素值,如下图所示,可以看到四周的像 素值都一样。
4.2、 边界反射(BORDER_REFLECT)
4.3、边界反射101(BORDER_REFLECT_101)
与边界反射不同的是,不再反射边缘的像素点
4.4、边界常数(BORDER_CONSTANT)
当选择边界常数时,还要指定常数值是多少,默认的填充常数值为0
4.5、边界包裹(BORDER_WRAP)
五、图像旋转
导入模块
import cv2
输入图像
img=cv2.imread('lena.png')
计算 2D 旋转的仿射矩阵
angle=45
scale=1
M=cv2.getRotationMatrix2D((img.shape[0]/2,img.shape[1]/2),angle,scale)
对图像进行旋转
img_rotation=cv2.warpAffine(img,M,img.shape[:2],flags=cv2.INTER_LINEAR,borderMode=cv2.BORDER_REFLECT_101,borderValue=(0,0,0))
输出图像
cv2.imshow('img',img)
cv2.imshow('img_rotation',img_rotation)
cv2.waitKey(0)
完整代码
import cv2
img=cv2.imread('lena.png')
angle=45
scale=1
#getRotationMatrix2D就是旋转矩阵
#第一个参数:旋转中心
#第二个参数:旋转角度
#第三个参数:缩放比例
M=cv2.getRotationMatrix2D((img.shape[0]/2,img.shape[1]/2),angle,scale)
# 有了旋转矩阵之后,就能直接去进行图像旋转了
# cv2.warpAffine:对图像进行旋转
# 第一个参数:要旋转的图像
# 第二个参数:通过cv2.getRotationMatrix2D获取到的旋转矩阵
# 第三个参数:输出图像的大小
img_rotation=cv2.warpAffine(img,M,img.shape[:2],flags=cv2.INTER_LINEAR,borderMode=cv2.BORDER_REFLECT_101,borderValue=(0,0,0))
cv2.imshow('img',img)
cv2.imshow('img_rotation',img_rotation)
cv2.waitKey(0)
六、库函数
6.1、getRotationMatrix2D()
cv.getRotationMatrix2D( center, angle, scale ) -> retval
方法 | 描述 |
---|---|
center | 源图像中的旋转中心。 |
angle | 旋转角度(以度为单位)。正值表示逆时针旋转(假定坐标原点为左上角)。 |
scale | 各向同性比例因子。 |
6.2、warpAffine()
cv.warpAffine( src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]] ) -> dst
方法 | 描述 |
---|---|
src | 输入图像 |
dst | 输出图像 |
M | 2*3变换矩阵 |
dsize | 输出图像的大小。 |
flags | 插值方法(请参阅 InterpolationFlags)和可选标志的组合WARP_INVERSE_MAP这意味着 M 是逆变换 (DST→来源). |
borderMode | 像素外插方法(请参阅 BorderTypes);当 borderMode=BORDER_TRANSPARENT 时,表示目标图片中与源图片中“离群值”对应的像素没有被函数修改。 |
borderValue | 在恒定边界的情况下使用的值;默认情况下,它是 0。 |
flags | 插值方法 |
INTER_NEAREST
Python:cv.INTER_NEAREST
|
最近邻插值 |
INTER_LINEAR
Python:cv.INTER_LINEAR
|
双线性插值 |
INTER_CUBIC
Python:cv.INTER_CUBIC
|
双三次插值 |
INTER_AREA
Python:cv.INTER_AREA
|
使用像素区域关系重新采样。这可能是图像抽取的首选方法,因为它可以获得无摩尔纹的结果。但是当图像缩放时,它类似于 INTER_NEAREST 方法。 |
INTER_LANCZOS4
Python:cv.INTER_LANCZOS4
|
8x8 邻域上的 Lanczos 插值 |
INTER_LINEAR_EXACT
Python:cv.INTER_LINEAR_EXACT
|
位精确双线性插值 |
INTER_NEAREST_EXACT
Python:cv.INTER_NEAREST_EXACT
|
位精确最近邻插值。这将产生与 PIL 、 scikit-image 或 Matlab 中的最近邻方法相同的结果。 |
INTER_MAX
Python:cv.INTER_MAX
|
插值代码的掩码 |
WARP_FILL_OUTLIERS
Python:cv.WARP_FILL_OUTLIERS
|
标志,填充所有目标图像像素。如果其中一些对应于源图像中的异常值,则它们将设置为零 |
WARP_INVERSE_MAP
Python:cv.WARP_INVERSE_MAP
|
标志, 逆变换 例如,linearPolar 或 logPolar 变换:
|
WARP_RELATIVE_MAP
Python:cv.WARP_RELATIVE_MAP
|