图片旋转是图像处理中的一种基本操作,用于将图像按照指定的角度进行旋转。旋转操作在图像编辑、计算机视觉和图形学等领域中非常常见,通常用来调整图像的方向或在图像识别中进行数据增强。
在图像旋转中,背后的数学原理主要涉及二维欧几里得空间中的几何变换。具体来说,图像旋转可以通过二维旋转矩阵来实现。对于一个二维平面上的点 (x, y),绕原点逆时针旋转角度 θ 后的新坐标 (x', y') 可以通过以下旋转矩阵计算得到:

故二维平面下的旋转矩阵为:

三维空间的旋转矩阵有分别绕x轴旋转,绕y轴旋转和绕z轴旋转。

在OpenCV中,你可以使用多种方法来实现图像的旋转。最常用的方法之一是使用cv2.getRotationMatrix2D函数结合cv2.warpAffine函数。下面是如何实现图像旋转的步骤:
方法1:使用cv2.getRotationMatrix2D和cv2.warpAffine
具体步骤如下: - 加载图像:首先,你需要加载你想要旋转的图像。 - 获取旋转矩阵:使用cv2.getRotationMatrix2D函数来创建一个旋转矩阵。 - 应用旋转:使用cv2.warpAffine函数将旋转矩阵应用到图像上。
代码实现:
import cv2
import numpy as np
# 加载图像
image = cv2.imread('./images/png/test01.png')
# 获取图像的中心点
(h, w) = image.shape[:2]
center = (w // 2, h // 2)
# 设置旋转角度(例如:45度)和缩放因子(例如:1.0表示不缩放)
angle = 45
scale = 1.0
# 获取旋转矩阵
M = cv2.getRotationMatrix2D(center, angle, scale)
# 执行旋转操作
rotated = cv2.warpAffine(image, M, (w, h))
# 显示结果
cv2.imshow('Rotated Image', rotated)
cv2.waitKey(0)
cv2.destroyAllWindows()

方法2:使用cv2.rotate(适用于OpenCV 4.0及以上版本) 从OpenCV 4.0开始,你可以使用cv2.rotate函数来直接旋转图像,这提供了一种更简单的方式来实现旋转。但是该函数只提供了90,180和270度的旋转方式。
代码实现:
import cv2
# 加载图像
image = cv2.imread('./images/png/test01.png')
# 旋转90度,逆时针方向,其他选项包括cv2.ROTATE_90_CLOCKWISE, cv2.ROTATE_180等
rotated = cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)
# 显示结果
cv2.imshow('Rotated Image', rotated)
cv2.waitKey(0)
cv2.destroyAllWindows()

图像缩放是通过调整图像的尺寸来改变图像的大小。缩放过程中,图像中的像素会被重新分配到新的位置,从而改变像素之间的间距和像素的个数。缩放可以使图像变大(放大)或变小(缩小),具体的缩放比例由用户指定。图像缩放的本质就是在原图的基础上做插值计算,从而增加或减少像素点的数量。常见的插值方式有最临近插值,双线性插值,双三次插值,兰索斯(Lanczos)插值等。
最邻近插值法是一种简单的图像缩放方法,它的基本思想是:在进行图像缩放时,对于目标图像中的每个像素点,找到原图像中距离最近的像素点,并将该像素点的值赋给目标图像中的像素点。计算它在原图像中对应的坐标 (x,y)的计算公式如下:

假设我们有一张 2×2 的简单图像,如下所示:

现在我们要将这张图像放大到 4×4 的大小,也就是将每个方向上的像素数量翻倍。
由于计算出来的 x(原)和y(原)可能是小数,我们需要找到原图像中距离它最近的整数坐标。这里我们直接取最接近的整数(这里采用向下取整)。

具体计算过程如下表格所示:

根据上述计算,放大后的 4×4 图像如下:

最邻近插值法的优点是计算简单,速度快,但缺点是可能会导致图像出现锯齿状的边缘,因为它是通过简单的取最近点来实现缩放的,没有考虑像素之间的过渡。因此,最邻近插值法适用于对图像质量要求不高的情况,或者在需要快速缩放图像时使用。
代码实现:
import cv2
import numpy as np
# 读取图像
image = cv2.imread('./images/jpg/bird01.jpg')
# 定义目标图像的大小,例如将图像大小调整为原来的50%
height, width = image.shape[:2]
new_height = int(height * 0.5)
new_width = int(width * 0.5)
# 使用cv2.resize进行缩放,INTER_NEAREST表示使用最近点插值
resized_image = cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_NEAREST)
# 显示原始图像和缩放后的图像
cv2.imshow('Original Image', image)
cv2.imshow('Resized Image (Nearest Neighbor)', resized_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

双线性插值(Bilinear Interpolation)是一种在二维空间中常用的插值方法,其核心思想是在两个方向上分别进行一次线性插值。具体来说,对于目标图像中的一个像素点,首先通过反向变换得到该点在源图像中的浮点坐标(包含整数部分和小数部分),然后利用该点周围的四个最邻近像素点(位于源图像中)的灰度值,在水平和垂直两个方向上分别进行线性插值,从而得到目标像素点的灰度值。

代码实现:
import cv2
import numpy as np
# 读取源图像
src_image = cv2.imread('./images/jpg/small_bird01.jpg')
# 指定缩放比例
scale_x, scale_y = 1.5, 1.5
# 计算目标图像大小
dst_width = int(src_image.shape[1] * scale_x)
dst_height = int(src_image.shape[0] * scale_y)
# 使用双线性插值进行图像缩放
dst_image = cv2.resize(src_image, (dst_width, dst_height), interpolation=cv2.INTER_LINEAR)
# 显示源图像和目标图像
cv2.imshow('Source Image', src_image)
cv2.imshow('Resized Image', dst_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

与其他插值方法相比,双线性插值具有以下优势:
小结: 最近邻插值的计算速度最快,但是可能会导致图像出现锯齿状边缘和失真,效果较差。双线性插值的计算速度慢一点,但效果有了大幅度的提高,适用于大多数场景。双三次插值、Lanczos插值的计算速度都很慢,但是效果都很好。
在OpenCV中,关于插值方法默认选择的都是双线性插值,且一般情况下双线性插值已经能满足大部分的需求。
最近邻插值(Nearest-neighbor interpolation,cv2.INTER_NEAREST):
双线性插值(Bilinear interpolation,cv2.INTER_LINEAR):
双三次插值(Bicubic interpolation,cv2.INTER_CUBIC):
区域插值(Area-based resampling,cv2.INTER_AREA):
Lanczos 插值(Lanczos resampling,cv2.INTER_LANCZOS4):
边界填充是一种在图像边缘添加额外像素的操作。这些额外的像素用于处理图像卷积、平滑或其他涉及边界的操作,以避免边缘效应。例如,在进行卷积操作时,如果不进行边界填充,图像边缘的像素将会缺少周围的像素信息,从而导致结果图像边缘模糊或失真。比如:
边缘填充就是给图像或者图形的边缘外补充一些像素或者颜色,让它们看起来更完整、更自然,避免出现一些突兀的边界问题。
OpenCV 提供了多种边界填充方法,主要包括以下几种:
下面是OpenCv的各类边界填充效果代码实现:
import cv2
import numpy as np
# 读取图像
image = cv2.imread('./images/png/traffic_light.png')
# 定义填充的边界大小
top, bottom, left, right = 80, 80, 80, 80 # 上、下、左、右的填充大小
# 执行零填充
padded_image_zero = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=0)
# 执行镜像填充
padded_image_mirror = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_REFLECT)
# 执行常数填充,这里以255为例,对于彩色图像,需要指定每个通道的值
padded_image_constant = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=[255, 255, 255])
# 执行边缘填充
padded_image_edge = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_REPLICATE)
# 执行环绕填充,图像的另一边的像素填充到边界。
padded_image_wrap = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_WRAP)
# 显示结果
cv2.imshow('Zero Padded Image', padded_image_zero)
cv2.imshow('Mirror Padded Image', padded_image_mirror)
cv2.imshow('Constant Padded Image', padded_image_constant)
cv2.imshow('Edge Padded Image', padded_image_edge)
cv2.imshow('Wrap Padded Image', padded_image_wrap)
cv2.waitKey(0)
cv2.destroyAllWindows()
