OpenCV 图像旋转和平移 数学和代码原理详解
OpenCV 图像旋转和平移 数学和代码原理详解
在OpenCV中进行图像旋转涉及到一些基本的几何变换和图像处理操作。本文将从数学原理出发,详细讲解图像旋转和平移的实现方法,并给出完整的C++和Python代码示例。
数学原理
在图像旋转中,背后的数学原理主要涉及二维欧几里得空间中的几何变换。具体来说,图像旋转可以通过二维旋转矩阵来实现。
旋转矩阵
对于一个二维平面上的点 ((x, y)),绕原点逆时针旋转角度 (\theta) 后的新坐标 ((x', y')) 可以通过以下旋转矩阵计算得到:
[
\begin{bmatrix}
x' \
y'
\end{bmatrix}
\begin{bmatrix}
\cos\theta & -\sin\theta \
\sin\theta & \cos\theta
\end{bmatrix}
\begin{bmatrix}
x \
y
\end{bmatrix}
]
平移和旋转
在实际应用中,图像通常不会绕原点旋转,而是绕图像的某个中心点 ((c_x, c_y)) 进行旋转。要实现绕任意点旋转,我们需要先将该点平移到原点,进行旋转,然后再平移回原来的位置。
具体步骤如下:
- 平移中心点到原点:将中心点 ((c_x, c_y)) 平移到原点 ((0, 0))。
- 旋转:在原点进行旋转。
- 平移回原位置:将旋转后的点再平移回 ((c_x, c_y))。
合成变换矩阵
综合上述步骤,绕任意点 ((c_x, c_y)) 逆时针旋转角度 (\theta) 的变换矩阵可以表示为:
[
M = T \cdot R \cdot T^{-1}
]
其中:
- (T) 是平移矩阵,用于将中心点平移到原点。
- (R) 是旋转矩阵,用于在原点进行旋转。
- (T^{-1}) 是逆平移矩阵,用于将旋转后的点平移回原位置。
具体形式为:
[
T =
\begin{bmatrix}
1 & 0 & -c_x \
0 & 1 & -c_y \
0 & 0 & 1
\end{bmatrix}
]
[
R =
\begin{bmatrix}
\cos\theta & -\sin\theta & 0 \
\sin\theta & \cos\theta & 0 \
0 & 0 & 1
\end{bmatrix}
]
[
T^{-1} =
\begin{bmatrix}
1 & 0 & c_x \
0 & 1 & c_y \
0 & 0 & 1
\end{bmatrix}
]
所以综合后的旋转矩阵 (M) 为:
[
M =
\begin{bmatrix}
\cos\theta & -\sin\theta & c_x(1-\cos\theta) + c_y\sin\theta \
\sin\theta & \cos\theta & c_y(1-\cos\theta) - c_x\sin\theta \
0 & 0 & 1
\end{bmatrix}
]
由于图像坐标是二维的,我们只需要前三列中的前两行:
[
M =
\begin{bmatrix}
\cos\theta & -\sin\theta & c_x(1-\cos\theta) + c_y\sin\theta \
\sin\theta & \cos\theta & c_y(1-\cos\theta) - c_x\sin\theta
\end{bmatrix}
]
应用在OpenCV中的实现
在OpenCV中,函数 cv::getRotationMatrix2D
就是用来计算这个旋转矩阵的:
cv::Mat cv::getRotationMatrix2D(cv::Point2f center, double angle, double scale);
center
参数表示旋转中心 ((c_x, c_y)),angle
表示旋转角度 (\theta),scale
表示缩放比例。
然后通过 cv::warpAffine
函数应用这个旋转矩阵来实现图像的旋转:
cv::warpAffine(src, dst, M, cv::Size(width, height));
其中 M
就是通过 getRotationMatrix2D
计算得到的旋转矩阵。
总结一下,图像旋转的数学原理是通过平移和旋转组合的方式,利用二维旋转矩阵实现绕任意点的旋转。OpenCV中提供的函数封装了这些数学计算,使得图像旋转操作变得简单直观。
代码关键点解读
以胖虎为例
C++代码:
void rotate_demo(Mat &image){
int width = image.cols;
int height = image.rows;
//计算旋转中心坐标
Point2f center(width/2.0,height/2.0);
double angle =180;
Mat rotation_matrix = getRotationMatrix2D(center,angle,1.0);
Mat rotate_image ;
warpAffine(image,rotate_image,rotation_matrix,Size(width,height));
imshow("Rotate Image",rotate_image);
}
调用上述代码就会发现一个问题:图像是旋转了,但是旋转后的图像尺寸不对了, 向右旋转之后,图像宽度不正常。宽度应该是原来的高度,原来的高度应该是宽度才对。
在 warpAffine(image,rotate_image,rotation_matrix,Size(width,height));
中尝试调换一下宽度和高度试试。
warpAffine(image,rotate_image,rotation_matrix,Size(height,width));
尝试运行之后,发现虽然窗口尺寸对了但是图像右上角都是黑边,原来的图像现了缺失
出现这个原因是我们一开始是按照中心点进行旋转,并不是从左上角开始旋转的,因此会出现图片缺失问题,因此我们需要把图像再平移回去。平移回去的第一大问题就是旋转之后的图像宽度和高度发生了变化,我们需要重新计算旋转后的图像尺寸
OpenCV提供了计算旋转后的的图像边界尺寸的工具,bbox就是以某个角度旋转之后的矩阵,为什么需要这个函数?举个栗子,旋转九十度,图像的宽度是原来图像的高度,图像的高度是原来的宽度,但如果是旋转45度?旋转后的图像并不是原来图像的高度,需要重新计算。
Rect bbox = RotatedRect(Point2f(), image.size(), angle).boundingRect();
旋转矩阵 rotation_matrix
是一个 2x3 的矩阵,其形式为:
[
\begin{bmatrix}
a & b & t_x \
c & d & t_y
\end{bmatrix}
]
其中 (t_x) 和 (t_y) 是平移部分。
因此我们需要调整 (t_x) 和 (t_y) 的值
- 旋转后的图像尺寸
bbox.size()
的中心点坐标为 ((bbox.width / 2.0, bbox.height / 2.0))。 - 原始旋转中心点为
center
,坐标为 ((center.x, center.y))。 - 调整 (t_x) 和 (t_y) 的目的是将旋转中心点平移到新图像中心,使图像内容在旋转后居中。
- 调整后的 (t_x) 和 (t_y) 计算如下:
- (t_x):将图像沿 x 轴平移 (bbox.width / 2.0 - center.x)。
- (t_y):将图像沿 y 轴平移 (bbox.height / 2.0 - center.y)。
对应的代码为:
rotation_matrix.at<double>(0, 2) += bbox.width / 2.0 - center.x;
rotation_matrix.at<double>(1, 2) += bbox.height / 2.0 - center.y;
at<double>(0, 2)
访问矩阵中的 (t_x) 值, at<double>(1, 2)
访问矩阵中的 (t_y) 值。
再来运行一下代码,可以看到此时图像就正常了。
试试45度旋转,修改 angle
的值为45,效果如下,注意图像的尺寸已经发生了改变,重新计算尺寸后不会造成像素丢失的问题
完整代码
C++代码:
void rotate_demo(Mat &image){
int width = image.cols;
int height = image.rows;
//计算旋转中心坐标
Point2f center(width/2.0,height/2.0);
double angle =45;
Mat rotation_matrix = getRotationMatrix2D(center,angle,1.0);
//因为涉及到旋转,图像的高度和宽度其实发生了变化
// 计算旋转后的图像边界尺寸
Rect bbox = RotatedRect(Point2f(), image.size(), angle).boundingRect();
rotation_matrix.at<double>(0, 2) += bbox.width / 2.0 - center.x;
rotation_matrix.at<double>(1, 2) += bbox.height / 2.0 - center.y;
Mat rotate_image ;
warpAffine(image,rotate_image,rotation_matrix,bbox.size());
imshow("Rotate Image",rotate_image);
}
Python代码:
import cv2
import numpy as np
def rotate_demo(image_path, angle=45):
# 读取图像
image = cv2.imread(image_path)
if image is None:
print("Could not read the image.")
return
height, width = image.shape[:2]
# 计算旋转中心坐标
center = (width / 2.0, height / 2.0)
# 计算旋转矩阵
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
# 计算旋转后的图像边界尺寸
corners = np.array([
[0, 0],
[width, 0],
[width, height],
[0, height]
])
corners = np.hstack((corners, np.ones((4, 1))))
rotated_corners = rotation_matrix.dot(corners.T).T
x_coords = rotated_corners[:, 0]
y_coords = rotated_corners[:, 1]
bbox_width = int(np.ceil(x_coords.max() - x_coords.min()))
bbox_height = int(np.ceil(y_coords.max() - y_coords.min()))
# 调整旋转矩阵的平移部分
rotation_matrix[0, 2] += bbox_width / 2.0 - center[0]
rotation_matrix[1, 2] += bbox_height / 2.0 - center[1]
# 执行旋转操作
rotate_image = cv2.warpAffine(image, rotation_matrix, (bbox_width, bbox_height))
# 显示旋转后的图像
cv2.imshow("Rotate Image", rotate_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 调用旋转函数
rotate_demo("path_to_your_image.jpg", 45)