将图像 绕任意中心 以任意缩放尺度 旋转 (c++ opencv)
首先我们来看一段将图像绕其中心点进行简单旋转的代码。
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
Mat RotateImg(Mat &src, float angle) //angel 弧度制
{
Mat dst;
//填充图像
int maxBorder = (int)(max(src.cols, src.rows)* 1.414); //即为sqrt(2)*max
int dx = (maxBorder - src.cols) / 2;
int dy = (maxBorder - src.rows) / 2;
copyMakeBorder(src, dst, dy, dy, dx, dx, BORDER_CONSTANT);
//旋转
Point2f center((float)(dst.cols / 2), (float)(dst.rows / 2));
Mat affine_matrix = getRotationMatrix2D(center, angle / CV_PI * 180.0, 1.0);//求得旋转矩阵
warpAffine(dst, dst, affine_matrix, dst.size());
//计算图像旋转之后包含图像的最大的矩形
float sinVal = abs(sin(angle));
float cosVal = abs(cos(angle));
Size targetSize((int)(src.cols * cosVal + src.rows * sinVal),
(int)(src.cols * sinVal + src.rows * cosVal));
//剪掉多余边框
int x = (dst.cols - targetSize.width) / 2;
int y = (dst.rows - targetSize.height) / 2;
Rect rect(x, y, targetSize.width, targetSize.height);
dst = Mat(dst, rect);
return dst;
}
代码非常简单,逻辑也很好理解。
对其进行测试:
Mat src = imread("test.jpg", CV_LOAD_IMAGE_UNCHANGED);
double angle = CV_PI / 4;
Mat dst1 = RotateImg(src, angle);
imwrite("dst1.jpg", dst1);
输入测试图像:
得到结果:
可以看到图像的信息都被完整地保存了下来没有被裁剪掉,并且四周也没有多余地黑边。
但是,如果我想要将图像 绕任意中心 以任意缩放尺度 旋转,那该怎么办呢?
缩放尺度的问题,在计算变换矩阵的时候,将
Mat affine_matrix = getRotationMatrix2D(center, angle / CV_PI * 180.0, 1.0);//求得旋转矩阵
改为
Mat affine_matrix = getRotationMatrix2D(center, angle / CV_PI * 180.0, scale);
scale为缩放尺度。当然这里不能单纯只改scale,填充图像的部分以及裁剪多余的部分也需要做相应的修改。
Mat RotateImg(Mat &src, float angle, double scale)//重载1
{
Mat dst;
int maxBorder;
//填充图像
if(scale>=1)
maxBorder = (int)(max(src.cols, src.rows)* 1.414*scale); //即为sqrt(2)*max
else
maxBorder = (int)(max(src.cols, src.rows)* 1.414); //即为sqrt(2)*max
int dx = (maxBorder - src.cols) / 2;
int dy = (maxBorder - src.rows) / 2;
copyMakeBorder(src, dst, dy, dy, dx, dx, BORDER_CONSTANT);
//旋转
Point2f center((float)(dst.cols / 2), (float)(dst.rows / 2));
Mat affine_matrix = getRotationMatrix2D(center, angle / CV_PI * 180.0, scale);//求得旋转矩阵
warpAffine(dst, dst, affine_matrix, dst.size());
//计算图像旋转之后包含图像的最大的矩形
float sinVal = abs(sin(angle));
float cosVal = abs(cos(angle));
Size targetSize((int)(src.cols * cosVal + src.rows * sinVal)*scale,
(int)(src.cols * sinVal + src.rows * cosVal)*scale);
//剪掉多余边框
int x = (dst.cols - targetSize.width) / 2;
int y = (dst.rows - targetSize.height) / 2;
Rect rect(x, y, targetSize.width, targetSize.height);
dst = Mat(dst, rect);
return dst;
}
但是在改变旋转中心的时候遇到了麻烦。
也许有人会想,要改旋转中心,直接将getRotationMatrix2D()函数中的center改变不就好了吗?
让我们来看看这样做的效果。
这是绕中心点center(100,200)的旋转结果。
可以观察到,图像有很大一部分飞出了画面之外,这是我们不想看到的。
下面我提供一种解决思路:
通过变换矩阵,计算图像进行仿射变换后四个角的坐标,根据四个坐标横纵坐标的最大、最小值,确定变换后图像的边界。
话不多说来看代码:
Mat RotateImg(Mat &src, Point center, float angle, double scale) // 重载2
{
Mat dst;
int height = src.rows;
int width = src.cols;
//求得旋转矩阵
Mat affine_matrix = getRotationMatrix2D(center, angle / CV_PI * 180.0, scale);
//计算图像旋转之后包含图像的最大的矩形
float a[12] = { 0,width,0,width,0,0,height,height,1,1,1,1 };
Mat A(3, 4, CV_32FC1, a);//图像的四个角
float b[9] = { affine_matrix.at<double>(0,0),affine_matrix.at<double>(0,1),affine_matrix.at<double>(0,2),
affine_matrix.at<double>(1,0),affine_matrix.at<double>(1,1),affine_matrix.at<double>(1,2),
0,0,1 };
Mat B(3, 3, CV_32FC1, b);//变换矩阵
Mat C = B * A;//算出变换后的矩阵
int MinWidth = min(min(C.at<float>(0, 0), C.at<float>(0, 1)), min(C.at<float>(0, 2), C.at<float>(0, 3)));
int MaxWidth = max(max(C.at<float>(0, 0), C.at<float>(0, 1)), max(C.at<float>(0, 2), C.at<float>(0, 3)));
int MinHeight = min(min(C.at<float>(1, 0), C.at<float>(1, 1)), min(C.at<float>(1, 2), C.at<float>(1, 3)));
int MaxHeight = max(max(C.at<float>(1, 0), C.at<float>(1, 1)), max(C.at<float>(1, 2), C.at<float>(1, 3)));
int dxLeft, dxRight, dyTop, dyBottom; //填充变量
int Cutx, Cuty; //裁剪变量
if (MinWidth <= 0)
{
dxLeft = -MinWidth;
affine_matrix.at<double>(0, 2) += dxLeft;//左边转到了图像之外,为了对齐原点,需要在变换矩阵中加入平移分量
Cutx = 0;
}
else
{
dxLeft = 0;
Cutx = MinWidth;
}
if (MinHeight <= 0)
{
dyTop = -MinHeight;
affine_matrix.at<double>(1, 2) += dyTop;//上面转到了图像之外,为了对齐原点,需要在变换矩阵中加入平移分量
Cuty = 0;
}
else
{
dyTop = 0;
Cuty = MinHeight;
}
if (MaxWidth - width >= 0)
{
dxRight = MaxWidth - width;
}
else
{
dxRight = 0;
}
if (MaxHeight - height >= 0)
{
dyBottom = MaxHeight - height;
}
else
{
dyBottom = 0;
}
//填充图像
copyMakeBorder(src, dst, dyTop, dyBottom, dxLeft, dxRight, BORDER_CONSTANT);
//旋转
warpAffine(src, dst, affine_matrix, dst.size());
//剪掉多余边框
Rect rect(Cutx, Cuty, MaxWidth - MinWidth, MaxHeight - MinHeight);
dst = Mat(dst, rect);
return dst;
}
这段代码中有两个比较关键的部分
if (MinWidth <= 0)
{
dxLeft = -MinWidth;
affine_matrix.at<double>(0, 2) += dxLeft;//左边转到了图像之外,为了对齐原点,需要在变换矩阵中加入平移分量
Cutx = 0;
}
else
{
dxLeft = 0;
Cutx = MinWidth;
}
if (MinHeight <= 0)
{
dyTop = -MinHeight;
affine_matrix.at<double>(1, 2) += dyTop;//上面转到了图像之外,为了对齐原点,需要在变换矩阵中加入平移分量
Cuty = 0;
}
else
{
dyTop = 0;
Cuty = MinHeight;
}
我在注释中已经说明,为了防止有人看不明白,我可以再解释一下。
在copyMakeBorder()进行图像填充过后,如果对图像 左边 以及图像 上面 进行过填充,图像的原点就会改变。
如果忽视这改变直接进行图像旋转,再裁剪的时候你会发现,由于原点的移动,导致了图像映射时的有效部分也发生了移动,裁剪的时候会把有效的信息也裁剪掉。
所以我在这里的处理方法是直接对变换矩阵加入相应的平移分量,关于仿射变换矩阵的内容不清楚的同学,可以百度一下。
最后得到结果:
做到这里任务已经基本完成了,让我们回过头来看看自己做过的事情。
上面这张图是原图绕center(100,500)旋转45°的结果,看起来,是不是和我展示的第一张图结果有点像?
不是有点像,是完全一样。
绕非中心点旋转,其实就是绕中心点旋转,再加上一个平移分量。
那么我做了什么?
又把这个平移分量补偿了回来……
感觉一下好像明白了一些道理,这回倒是收获不小啊……