将图像 绕任意中心 以任意缩放尺度 旋转 (c++ opencv)

将图像 绕任意中心 以任意缩放尺度 旋转 (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°的结果,看起来,是不是和我展示的第一张图结果有点像?

不是有点像,是完全一样。

绕非中心点旋转,其实就是绕中心点旋转,再加上一个平移分量。

那么我做了什么?
又把这个平移分量补偿了回来……

感觉一下好像明白了一些道理,这回倒是收获不小啊……

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值