图像简单旋转
图像围绕某一点进行旋转,也就是图像中的所有像素围绕该点进行旋转,旋转图像不变。
那么,像素是如何进行旋转的呢?首先我们以最简单的一个点的旋转为例子,且以最简单的情况举例:
单点旋转
我们就可以根据仿射变换矩阵计算出图像中任意一点绕某点旋转后的坐标了,而仿射变换是一种二维坐标到二维坐标之间的线性变换,也就是只涉及一个平面内二维图形的线性变换,图像旋转就是仿射变换的一种。它保持了二维图形的两种性质:
- 平直性:直线经过变换后依然是直线。
- 平行性:平行线经过变换后依然是平行线。
仿射变换矩阵
这个矩阵是如何得到的呢?who care?我们并不需要关心,我们只需要知道如何调用这个接口即可
在OpenCV中,要得到仿射变换矩阵可以使用cv2.getRotationMatrix2D()
功能:通过这个函数即可直接获取到上面的旋转矩阵,该函数需要接收的参数为:
- Center:表示旋转的中心点,是一个二维的坐标点(x,y)
- Angle:表示旋转的角度
- Scale:表示缩放比例,可以通过该参数调整图像相对于原始图像的大小变化
如果你实在想知道仿射变换矩阵是如何得到的,链接如下:
图片旋转
通过cv2.getRotationMatrix2D()得到仿射变换矩阵,利用该矩阵可得到旋转过后的像素点。
图像旋转就是将图像里的每个像素点都带入仿射变换矩阵里,从而得到旋转后的新坐标,即可完成旋转。细心的 只因们 已经发现上面cv2.getRotationMatrix2D()介绍中有一个Scale参数表示缩放比例。
但是这里会有一个问题,由于三角函数的值是小数,那么其乘积也会是小数,那么:
- 虽然OpenCV中会对其进行取整操作,但是像素点旋转之后的取整结果也有可能重合,这样就会导致可能会在旋转的过程中丢失一部分原始的像素信息。
- 并且如果使用了scale参数进行图像的缩放的话,当图像放大时,比如一个10*10的图像放大成20*20,图像由100个像素点变成400个像素点,那么多余的300个像素点是怎么来的?
- 而当图像缩小时,比如一个20*20的图像缩小为10*10的图像,需要丢掉300个像素点,那到底要怎么丢才能保证图像还能是一个正常的图像?
因此我们需要一种方法来帮我们计算旋转后的图像中每一个像素点所对应的像素值,从而保证图像的完整性,这种方法就叫做插值法。
图像插值算法
在图像处理和计算机图形学中,插值(Interpolation)是一种通过已知数据点之间的推断或估计来获取新数据点的方法。它在图像处理中常用于处理图像的放大、缩小、旋转、变形等操作,以及处理图像中的像素值。
首先给出目标点与原图像点之间坐标的计算公式:
其中,
dstX表示目标图像中某点的x坐标,srcWidth表示原图的宽度,dstWidth表示目标图像的宽度;dstY表示目标图像中某点的y坐标,srcHeight表示原图的高度,dstHeight表示目标图像的高度。
srcX、srcY则表示目标图像中的某点对应的原图中的点的x和y的坐标。
通俗的讲,该公式就是让目标图像中的每个像素值都能找到对应的原图中的像素值,从而来获取新的像素值。然后在对其余多余像素,用图像插值算法进行处理:
图像插值算法是为了解决图像缩放或者旋转等操作时,由于像素之间的间隔不一致而导致的信息丢失和图像质量下降的问题。当我们对图像进行缩放或旋转等操作时,需要在新的像素位置上计算出对应的像素值,而插值算法的作用就是根据已知的像素值来推测未知位置的像素值。本实验提供了五种常见的插值算法。了解即可,我们直接调用。
1. 最近邻插值
2. 双线性插值
3. 像素区域插值
4. 双三次插值
5. Lanczos插值
6. 小结
最近邻插值的计算速度最快,但是可能会导致图像出现锯齿状边缘和失真,效果较差。双线性插值的计算速度慢一点,但效果有了大幅度的提高,适用于大多数场景。双三次插值、Lanczos插值的计算速度都很慢,但是效果都很好。
在OpenCV中,关于插值方法默认选择的都是双线性插值,且一般情况下双线性插值已经能满足大部分需求。
边缘填充方式
为什么要填充边缘呢?我们已下图为例。
可以看到,左图在逆时针旋转45度之后原图的四个顶点在右图中已经看不到了,同时,右图的四个顶点区域其实是什么都没有的,因此我们需要对空出来的区域进行一个填充。右图就是对空出来的区域进行了像素值为(0,0,0)的填充,也就是黑色像素值的填充。除此之外,也有其他的方式进行填充,这里介绍五个常用的边缘填充方法。
1. 边界复制(BORDER_REPLICATE)
边界复制会将边界处的像素值进行复制,然后作为边界填充的像素值,如下图所示,可以看到四周的像素值都一样。
2. 边界反射(BORDER_REFLECT)
3. 边界反射101(BORDER_REFLECT_101)
与边界反射不同的是,不再反射边缘的像素点,如下图所示。
4. 边界常数(BORDER_CONSTANT)
当选择边界常数时,还要指定常数值是多少,默认的填充常数值为0(黑色),如下图所示。
5. 边界包裹(BORDER_WRAP)
直接上图,自己看吧
实现函数
cv2.getRotationMatrix2D()函数
功能:用于计算二维旋转矩阵的函数
参数:
- center: 旋转的中心点,通常是一个二元元组 (x, y),表示旋转中心的坐标。
- angle: 旋转的角度,以度为单位。正值表示逆时针旋转,负值表示顺时针旋转。
- scale: 缩放因子。默认情况下,这个值是 1.0,表示不缩放。如果你想要同时旋转和缩放图像,可以通过调整这个参数来实现。
cv2.warpAffine()函数
功能:用于对图像进行仿射变换(Affine Transformation)的函数,仿射变换包括平移、旋转、缩放以及剪切等操作。
参数:
- src: 输入图像。
- M: 变换矩阵,一个 2x3 的数组。这个矩阵是通过其他函数(如 cv2.getRotationMatrix2D())计算得到的,用于描述仿射变换。
- dsize: 输出图像的大小,以 (width, height) 的形式表示。这个参数决定了变换后图像的尺寸。
- dst: 输出图像,与输入图像有相同的大小和类型。这是一个可选参数,如果提供,则变换的结果会存储在这个图像中;如果未提供,则会创建一个新的图像来存储结果。
- flags: 插值方法。常用的插值方法包括 cv2.INTER_LINEAR(线性插值)、cv2.INTER_NEAREST(最近邻插值)、cv2.INTER_CUBIC(三次样条插值)等。这是一个可选参数,如果未提供,则默认使用线性插值。
- borderMode: 边缘填充方法。常用的方法包括 cv2.BORDER_CONSTANT(常量填充)、cv2.BORDER_REFLECT(反射)、cv2.BORDER_REFLECT_101(反射101)等。这是一个可选参数,如果未提供,则默认使用常量填充。
- borderValue: 边界颜色,当 borderMode 为 cv2.BORDER_CONSTANT 时使用。这个参数是一个表示颜色的元组或数组,如 (255, 255, 255) 表示白色。这是一个可选参数,如果未提供,则默认使用黑色 (0, 0, 0)。
代码演示
mport cv2
# 读取一张图片
img = cv2.imread("./lena.png")
# 使用cv2.getRotationMatrix2D(center, angle, scale)获取变换矩阵
# 参数1:center 旋转的中心点(x, y), 一般选择图片的中心 (宽度的一半,高度的一半)
# 参数2:angle 旋转的角度
# 参数3:scale 缩放比例
M = cv2.getRotationMatrix2D((img.shape[1]/2, img.shape[0]/2), 45, 0.5)
# 使用cv2.warpAffine(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None)
# 对图像进行放射变换
img_warp = cv2.warpAffine(img, # 要旋转的图像
M, # 旋转矩阵
(310, 600), # 输出图像的大小 自己指定即可 可和原图不一样大小
flags=cv2.INTER_LINEAR, # 插值方式
borderMode=cv2.BORDER_WRAP # 边缘填充方式,默认是常数填充显示为黑色
)
cv2.imshow('image', img)
cv2.imshow('image_warp', img_warp)
cv2.waitKey(0)
图像镜面旋转
图像的旋转是围绕一个特定点进行的,而图像的镜像旋转则是围绕坐标轴进行的。图像的镜像旋转分为水平翻转、垂直翻转、水平垂直翻转三种。
水平翻转:像素点沿y轴翻转,具体到像素点来说就是令其坐标从(x,y)翻转为(-x,y)。
垂直翻转:像素点沿x轴翻转,具体到像素点来说就是其坐标从(x,y)翻转为(x,-y)。
水平垂直翻转:水平翻转+垂直翻转,具体到像素点来说就是其坐标从(x,y)翻转为(-x,-y)。
cv2.flip()函数
功能:用于翻转图像的函数
参数:
src: 输入图像,即你想要翻转的图像。
flipCode: 翻转的标志,决定了翻转的方式。它可以是以下三个值之一:
- 0:表示沿 x 轴翻转(垂直翻转)。
- 1:表示沿 y 轴翻转(水平翻转)。这是最常用的翻转方式,用于创建镜像效果。
- -1:表示同时沿 x 轴和 y 轴翻转(水平和垂直都翻转,相当于旋转180度)。
dst: 输出图像,与输入图像有相同的大小和类型。这是一个可选参数,如果提供,则翻转的结果会存储在这个图像中;如果未提供,则会创建一个新的图像来存储结果。
代码演示
# 对图像进行翻转操作
import cv2
# 1. 读取图片
image_np = cv2.imread('./image/kabuto.jpg')
# 2. 使用flip函数去对图像进行镜像的翻转
# cv2.flip: 对图像进行镜像翻转
# 第一个参数:要翻转的原始图像
# 第二个参数:标志位, 0:表示绕x轴进行上下翻转, >0:表示绕y轴进行左右翻转 <0:表示绕x轴和y轴各进行一次翻转
image_flip = cv2.flip(image_np, 0)
# 3. 输出,显示
cv2.imshow('image_np', image_np)
cv2.imshow('image_flip', image_flip)
cv2.waitKey(0)
效果图