目录
本文主要是基于前面两篇文章来的,建议读者先去看关于QT中OpenCV环境的配置以及基本使用博文,然后来看本篇博文。
cv.warpAffine参考文档 |
QT参考文档 |
1.旋转操作(难点)
第一步:创建旋转矩阵:
cv::Mat RotatedImage = cv::getRotationMatrix2D(Center, Angle, 1.0);
- Center:指定旋转的中心点,即图像围绕这个中心点旋转(注意:在本文中每一次的旋转都是以原图的高宽为中心)。
- Angle:设定旋转的角度,单位是度,正值表示逆时针旋转。其中RotatedImage.shape:[2,3] => [a, b, tx] => [M11,M12,M13] [c, d, ty] [M21,M22,M23] a 和 b :定义了旋转和可能的缩放(如果 scale 不为 1.0)在 x 轴方向上的影响。 c 和 d :定义了旋转和可能的缩放(如果 scale 不为 1.0)在 y 轴方向上的影响。 [tx, ty] :是平移向量,用于将旋转后的图像中心移回原始的中心位置 (或者,如果旋转中心不是图像中心,则将其移至指定的中心位置)。
第二步:计算旋转矩形的最小包围矩形:旋转过程,最小包围矩形在不断的变化。也就是可以通过原图大小以及要旋转的角度得到最小包围矩形,如下图所示:
cv::Rect2f BoundingBox = cv::RotatedRect(cv::Point2f(),
this->Image.size(),
Angle).boundingRect2f();
原点 cv::Point2f()、图像的大小this->Image.size()和旋转角度Angle。
boundingRect2f():此方法返回一个浮点数类型的矩形,作为包含旋转矩形的最小包围矩形,用于确定旋转后图像的新尺寸和位置。
第三步:调整旋转矩阵的平移部分:
RotatedImage.at<double>(0, 2) += BoundingBox.width / 2.0 - this -> Image.cols / 2.0;
RotatedImage.at<double>(1, 2) += BoundingBox.height / 2.0 - this -> Image.rows / 2.0;
如果不进行这个调整也是可以,只不过在旋转过程中看起来会有一点怪怪的。
如果使用了该调整之后,整个图像的旋转看起来就是围绕原图中心在旋转。
RotatedImage.at<double>(0, 2):访问旋转矩阵中平移x轴的值(第三列第一行)
RotatedImage.at<double>(1, 2):访问旋转矩阵中平移y轴的值(第三列第二行)
调整新包围矩形的宽度和高度的一半,减去原图像尺寸的一半,确保旋转后的图像真正居中。也就是在旋转的过程中,最小包围矩形大小发生变化;如下图所示BoundingBox.width / 2.0 - this->Image.cols / 2.0表示最小包围矩形宽度和原图矩形宽度之差的二分之一大小,同理高度也是一样。同时RotatedImage.at<double>(0, 2)和RotatedImage.at<double>(1, 2)表示tx,ty,含义是“用于将旋转后的图像中心移回原始的中心位置”,因此,由于最下包围矩形的不断变换,需要对其tx和ty的值也需要根据最小包围矩形和原矩形之间的差距来重新来计算,让整个图像在旋转过程中看起来是按照指定原始图像中心来旋转的。
dst(x,y)=src(M11x+M12y+M13,M21x+M22y+M23)
= src(ax + cy + tx',bx + dy + ty')
= src(ax + cy + tx + 最小包围矩形.width / 2 - 原图矩形.cols / 2,bx + dy + ty + 最小包围矩形.height / 2 - 原图矩形.rows / 2)
= src(ax + cy + tx + 最小包围矩形和原图矩形宽度之差二分之一,bx + dy + ty + 最小包围矩形和原图矩形高度之差二分之一)
= src(ax + cy + 实际的tx,bx + dy + 实际的ty)
tx' = tx + 最小包围矩形.width / 2 - 原图.cols / 2 = tx + BoundingBox.width / 2.0 - this->Image.cols / 2.0;
ty' = ty + 最小包围矩形.height / 2 - 原图.rows / 2 = ty + BoundingBox.height / 2.0 - this->Image.rows / 2.0;
提示:旋转过程中仔细看以下语句输出的结果,直到旋转到90度,观察输出的数据[a,b,c,d,tx,ty]即可更加的明白【实际图像中心x和实际图像中心y】两个等式的含义。
qDebug()<<"before tx = "<<RotatedImage.at<double>(0,2)<<" ty = "<<RotatedImage.at<double>(1,2);
RotatedImage.at<double>(0,2) += BoundingBox.width / 2.0 - this -> Image.cols / 2.0;
RotatedImage.at<double>(1,2) += BoundingBox.height / 2.0 - this -> Image.rows / 2.0;
qDebug()<<"a = "<<RotatedImage.at<double>(0,0)<<" c = "<<RotatedImage.at<double>(1,0);
qDebug()<<"b = "<<RotatedImage.at<double>(0,1)<<" d = "<<RotatedImage.at<double>(1,1);
qDebug()<<"after tx = "<<RotatedImage.at<double>(0,2)<<" ty = "<<RotatedImage.at<double>(1,2);
qDebug()<<"BoundingBox.width / 2 = "<<BoundingBox.width / 2.0<<" BoundingBox.height / 2.0 = "<<BoundingBox.height / 2.0;
那么关于参数a,b,c,d怎么计算呢,看以下示例图,本文是以5度作为步长进行旋转,也就是0度,5度,10度,……,360度。
void cv::warpAffine(InputArray src,
OutputArray dst,
InputArray M,
Size dsize,
int flags=INTER_LINEAR,
int borderMode=BORDER_CONSTANT,
const Scalar& borderValue=Scalar())
src: [输入图像]
dst: [输出图像,与输入图像具有相同的数据类型,但其大小由 dsize 指定]
M: 2x3 [的变换矩阵]
dsize: [输出图像的大小。当该参数为零时(Size(0,0)),
函数会自动根据变换矩阵 M 和输入图像 src 的大小计算输出图像的大小]
flags: [插值方法的标志。默认值为 INTER_LINEAR(双线性插值)]
borderMode: [边界像素的外推方法。默认值为 BORDER_CONSTANT,
表示使用恒定的颜色值进行填充,该颜色值由 borderValue 参数指定]
borderValue: [当 borderMode 为 BORDER_CONSTANT 时,这个值指定了用于填充边界的常量颜色值。
默认值是 Scalar(),即黑色]
2.缩放和裁剪
Qt 6.6.0中基于OpenCV对图像缩放,裁剪,旋转等