图像几何变换--缩放、平移、镜像、旋转

本文详细介绍了图像几何变换的基础概念,包括平移、缩放、旋转和镜像操作的实现过程,以及如何通过变换矩阵进行复合变换。提供了完整代码示例,涵盖了OpenCV库中常用的方法,如最近邻插值和双线性插值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.图像几何变换基础

1.图像的几何变换是指原始图像按照需要产生大小、形状和位置的变化。
2.不改变图像的像素值,而是改变像素所在的几何位置。
3.对于原图像f(x,y),坐标变换函数
在这里插入图片描述
4.唯一确定了几何变换:
在这里插入图片描述
g(x,y)是目标图像。
5.图像是矩阵,图像的几何变换用矩阵运算来实现。
在这里插入图片描述
6.在数字图像处理中,几何变换由两个基本操作组成:
(1)坐标的空间变换
坐标变换可由下式表示:
在这里插入图片描述
其中,(v,w)是原图像中像素的坐标,(x,y)是变换后图像中像素的坐标。
最常用的空间坐标变换之一是仿射变换,其一般形式如下:
在这里插入图片描述
这个变换可根据矩阵T中元素所选择的值,对一组坐标点做尺度、旋转、平移或偏移。
(2)灰度内插,即对空间变换后的像素赋灰度值。

二.平移

在这里插入图片描述
坐标变换函数:
在这里插入图片描述
用矩阵的形式可表示为:
在这里插入图片描述
(1)齐次坐标
通常将2×3阶矩阵扩充为3×3阶矩阵,以拓宽功能。
点P(x,y)按照3×3的变换矩阵T平移变换的结果。
在这里插入图片描述
可以看出,引入附加坐标后,扩充了矩阵的第3行,并没有使变换结果受到影响。
这种用n+1维向量表示n维向量的方法称为齐次坐标表示法。
(2)平移的变换矩阵
在这里插入图片描述

//平移变换
//tx:水平平移距离,正数向右移动,负数向左移动
//ty:垂直平移距离,正数向下移动,负数向上移动
void trans(Mat& src, Mat& dst, double tx, double ty)
{
	//创建输出图像
	dst.create(src.size(), src.type());
	//平移变换矩阵
	Mat T = (Mat_<double>(3, 3) << 1, 0, 0, 0, 1, 0, tx, ty, 1);
	//求逆矩阵
	Mat T_inv = T.inv();

	for (int i = 0; i < dst.rows; i++)
	{
		for (int j = 0; j < dst.cols; j++)
		{
			Mat dst_coordinate = (Mat_<double>(1, 3) << j, i, 1);
			Mat src_coordinate = dst_coordinate * T_inv;
			double v = src_coordinate.at<double>(0, 0);  //原图像的横坐标,列,宽
			double w = src_coordinate.at<double>(0, 1);  //原图像的纵坐标,行,高

			if (v >= 0 && w >= 0 && v < src.cols - 1 && w <= src.rows - 1)
			{
				int top = floor(w), bottom = ceil(w), left = floor(v), right = ceil(v);
				double pw = w - top;   //pw为坐标 行 的小数部分
				double pv = v - left;  //pv为坐标 列 的小数部分
				if (src.channels() == 1)
				{
					//灰度图像
					dst.at<uchar>(i, j) = (1 - pw) * (1 - pv) * src.at<uchar>(top, left)
						+ (1 - pw) * pv * src.at<uchar>(top, right)
						+ pw * (1 - pv) * src.at<uchar>(bottom, left)
						+ pw * pv * src.at<uchar>(bottom, right);
				}
				else
				{
					//彩色图像
					dst.at<Vec3b>(i, j)[0] = (1 - pw) * (1 - pv) * src.at<Vec3b>(top, left)[0]
						+ (1 - pw) * pv * src.at<Vec3b>(top, right)[0]
						+ pw * (1 - pv) * src.at<Vec3b>(bottom, left)[0]
						+ pw * pv * src.at<Vec3b>(bottom, right)[0];
					dst.at<Vec3b>(i, j)[1] = (1 - pw) * (1 - pv) * src.at<Vec3b>(top, left)[1]
						+ (1 - pw) * pv * src.at<Vec3b>(top, right)[1]
						+ pw * (1 - pv) * src.at<Vec3b>(bottom, left)[1]
						+ pw * pv * src.at<Vec3b>(bottom, right)[1];
					dst.at<Vec3b>(i, j)[2] = (1 - pw) * (1 - pv) * src.at<Vec3b>(top, left)[2]
						+ (1 - pw) * pv * src.at<Vec3b>(top, right)[2]
						+ pw * (1 - pv) * src.at<Vec3b>(bottom, left)[2]
						+ pw * pv * src.at<Vec3b>(bottom, right)[2];
				}
			}

		}
	}

}

三.缩放

(1)变换矩阵
图像的几何变换改变了像素的空间位置,建立一种原图像像素与变换后图像像素之间的映射关系,通过这种映射关系能够实现下面两种计算:
向前映射:原图像任意像素计算该像素在变换后图像的坐标位置。
向后映射:换后图像的任意像素在原图像的坐标位置。
在这里插入图片描述
(2)插值
①最近邻插值
赋予点(x,y)在像素矩阵中离它最近的点的像素值。
最近邻插值的定位误差最大是半个像素。这种误差在物体具有直线边界时就会显现出来,在变换后可能会呈现阶梯状。
在这里插入图片描述
在这里插入图片描述

//最近邻差值缩放
void nearest(Mat& src, Mat& dst, float sx, float sy)  //原图,处理后图,x方向倍数,y方向倍数
{
	//round函数计算之后结果是小数点后全变为0,但其还是float的,需要强制类型转换
	//计算目标图形尺寸
	int dst_cols = (int)round(src.cols * sx);
	int dst_rows = (int)round(src.rows * sy);
	//创建目标图形
	dst.create(dst_rows, dst_cols, src.type());
	//灰度图像处理
	if (src.channels() == 1)
	{
		for (int i = 0; i < dst.rows; i++)
		{
			for (int j = 0; j < dst.cols; j++)
			{
				//插值计算,输出图像的像素点由原图像对应的最近的像素点得到(四舍五入)
				int i_index = (int)round(i / sy);    //y方向缩放,则行数发生改变
				int j_index = (int)round(j / sx);
				//防止越界
				if (i_index > src.rows - 1)
				{
					i_index = src.rows - 1;
				}
				if (j_index > src.cols - 1)
				{
					j_index = src.cols - 1;
				}
				dst.at<uchar>(i, j) = src.at<uchar>(i_index, j_index);
			}
		}
	}
	//彩色图像处理
	else
	{
		for (int i = 0; i < dst.rows; i++)
		{
			for (int j = 0; j < dst.cols; j++)
			{
				//差值计算
				int i_index = (int)round(i / sy);
				int j_index = (int)round(j / sx);
				//防止越界
				if (i_index > src.rows - 1)
				{
					i_index = src.rows - 1;
				}
				if (j_index > src.cols - 1)
				{
					j_index = src.cols - 1;
				}
				//b
				dst.at<Vec3b>(i, j)[0] = src.at<Vec3b>(i_index, j_index)[0];
				//g
				dst.at<Vec3b>(i, j)[1] = src.at<Vec3b>(i_index, j_index)[1];
				//r
				dst.at<Vec3b>(i, j)[2] = src.at<Vec3b>(i_index, j_index)[2];
			}
		}
	}
}

②双线性插值
当求出的分数地址与像素点不一致时,求出周围四个像素点的距离比,根据该比率,由四个邻域的像素灰度值进行线性插值。
在这里插入图片描述
在这里插入图片描述

void linear(Mat& src, Mat& dst, float sx, float sy)
{
	//计算目标图形尺寸
	int dst_cols = (int)round(src.cols * sy);
	int dst_rows = (int)round(src.rows * sx);
	//创建目标图形
	dst.create(dst_rows, dst_cols, src.type());
	for (int i = 0; i < dst.rows; i++)
	{
		//几何中心对齐
		//中心对齐(OpenCV也是如此):
			//SrcX = (dstX + 0.5) * (srcWidth / dstWidth) - 0.5
			//SrcY = (dstY + 0.5) * (srcHeight / dstHeight) - 0.5
		double index_i = (i + 0.5) / sx - 0.5;
		//防止越界
		if (index_i < 0)
		{
			index_i = 0;
		}
		if (index_i >= (double)src.rows - 1)
		{
			index_i = (double)src.rows - 2;
		}
		//相邻4×4像素的行(坐标)
		int i1 = floor(index_i);
		int i2 = ceil(index_i);
		//u为得到浮点型坐标的小数部分
		double u = index_i - i1;
		for (int j = 0; j < dst.cols; j++)
		{
			//几何中心对齐
			double index_j = (j + 0.5) / sy - 0.5;
			//防止越界
			if (index_j < 0)
			{
				index_j = 0;
			}
			if (index_j >= (double)src.cols - 1)
			{
				index_j = (double)src.cols - 2;
			}
			//相邻4×4像素的列(坐标)
			int j1 = floor(index_j);
			int j2 = ceil(index_j);
			//u为得到浮点型坐标的小数部分
			double v = index_j - j1;
			//处理灰度图像
			if (src.channels() == 1)
			{
				dst.at<uchar>(i, j) = (1 - u) * (1 - v) * src.at<uchar>(i1, j1)
					+ (1 - u) * v * src.at<uchar>(i1, j2)
					+ u * (1 - v) * src.at<uchar>(i2, j1)
					+ u * v * src.at<uchar>(i2, j2);
			}
			//处理彩色图像
			else
			{
				dst.at<Vec3b>(i, j)[0] = (1 - u) * (1 - v) * src.at<Vec3b>(i1, j1)[0] 
					+ (1 - u) * v * src.at<Vec3b>(i1, j2)[0]
					+ u * (1 - v) * src.at<Vec3b>(i2, j1)[0] 
					+ u * v * src.at<Vec3b>(i2, j2)[0];
				dst.at<Vec3b>(i, j)[1] = (1 - u) * (1 - v) * src.at<Vec3b>(i1, j1)[1] 
					+ (1 - u) * v * src.at<Vec3b>(i1, j2)[1] 
					+ u * (1 - v) * src.at<Vec3b>(i2, j1)[1] 
					+ u * v * src.at<Vec3b>(i2, j2)[1];
				dst.at<Vec3b>(i, j)[2] = (1 - u) * (1 - v) * src.at<Vec3b>(i1, j1)[2]
					+ (1 - u) * v * src.at<Vec3b>(i1, j2)[2]
					+ u * (1 - v) * src.at<Vec3b>(i2, j1)[2]
					+ u * v * src.at<Vec3b>(i2, j2)[2];
			}
		}
	}
}

四.旋转

1.一般图像的旋转是以图像的中心为原点,将图像上的所有像素都旋转一个相同的角度。
2.图像的旋转变换是图像的位置变换,但旋转后,图像的大小一般会改变。
3.图像旋转变换后,既可以把转出显示区域的图像截去,也可以扩大图像范围以显示所有的图像。
4.变换矩阵
在这里插入图片描述
在这里插入图片描述

//图像旋转
void img_rotate(Mat& src, Mat& dst, double Angle)
{
	double angle = Angle * CV_PI / 180.0;
	//构造输出图形
	int dst_rows = (int)round(fabs(src.rows * cos(angle)) + fabs(src.cols * sin(angle)));//图像高度
	int dst_cols = (int)round(fabs(src.cols * cos(angle)) + fabs(src.rows * sin(angle)));//图像宽度
	dst.create(dst_rows, dst_cols, src.type());

	//将原图像坐标映射到数学笛卡尔坐标
	Mat T1 = (Mat_<double>(3, 3) << 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, -0.5 * src.cols, 0.5 * src.rows, 1.0);
	//数学笛卡尔坐标下顺时针旋转的变换矩阵
	Mat T2 = (Mat_<double>(3, 3) << cos(angle), -sin(angle), 0.0, sin(angle), cos(angle), 0.0, 0.0, 0.0, 1.0);
	//将数学笛卡尔坐标映射到旋转后的图像坐标
	double t3[3][3] = { {1.0,0.0,0.0},{0.0,-1.0,0.0},{0.5 * dst.cols,0.5 * dst.rows,1.0} };
	Mat T3 = Mat(3.0, 3.0, CV_64FC1, t3);
	Mat T = T1 * T2 * T3;
	Mat T_inv = T.inv();    //求逆矩阵

	for (double i = 0.0; i < dst.rows; i++)
	{
		for (double j = 0.0; j < dst.cols; j++)
		{
			Mat dst_coordinate = (Mat_<double>(1, 3) << j, i, 1.0);
			Mat src_coordinate = dst_coordinate * T_inv;
			double v = src_coordinate.at<double>(0, 0);   //原图像的横坐标,列,宽
			double w = src_coordinate.at<double>(0, 1);  //原图像的纵坐标,行,高

			//判断是否越界
			if (int(Angle) % 90 == 0)
			{
				if (v < 0)
				{
					v = 0;
				}
				if (v > src.cols - 1)
				{
					v = src.cols - 1;
				}
				if (w < 0)
				{
					w = 0;
				}
				if (w > src.rows - 1)
				{
					w= src.rows - 1;
				}
			}

			if (v >= 0 && w >= 0 && v < src.cols - 1 && w <= src.rows - 1)
			{
				int top = floor(w), bottom = ceil(w), left = floor(v), right = ceil(v);
				double pw = w - top;   //pw为坐标 行 的小数部分
				double pv = v - left;  //pv为坐标 列 的小数部分
				if (src.channels() == 1)
				{
					//灰度图像
					dst.at<uchar>(i, j) = (1 - pw) * (1 - pv) * src.at<uchar>(top, left)
						+ (1 - pw) * pv * src.at<uchar>(top, right)
						+ pw * (1 - pv) * src.at<uchar>(bottom, left)
						+ pw * pv * src.at<uchar>(bottom, right);
				}
				else
				{
					//彩色图像
					dst.at<Vec3b>(i, j)[0] = (1 - pw) * (1 - pv) * src.at<Vec3b>(top, left)[0]
						+ (1 - pw) * pv * src.at<Vec3b>(top, right)[0]
						+ pw * (1 - pv) * src.at<Vec3b>(bottom, left)[0]
						+ pw * pv * src.at<Vec3b>(bottom, right)[0];
					dst.at<Vec3b>(i, j)[1] = (1 - pw) * (1 - pv) * src.at<Vec3b>(top, left)[1]
						+ (1 - pw) * pv * src.at<Vec3b>(top, right)[1]
						+ pw * (1 - pv) * src.at<Vec3b>(bottom, left)[1]
						+ pw * pv * src.at<Vec3b>(bottom, right)[1];
					dst.at<Vec3b>(i, j)[2] = (1 - pw) * (1 - pv) * src.at<Vec3b>(top, left)[2]
						+ (1 - pw) * pv * src.at<Vec3b>(top, right)[2]
						+ pw * (1 - pv) * src.at<Vec3b>(bottom, left)[2]
						+ pw * pv * src.at<Vec3b>(bottom, right)[2];
				}
			}
		}
	}
}

五.镜像

1.水平镜像操作是将图像左半部分和右半部分以图像垂直中轴线为中心进行镜像对换。
2.垂直镜像操作是将图片上半部分和下半部分以图像水平中轴线为中心进行镜像对换。
在这里插入图片描述
3.变换矩阵
在这里插入图片描述

六.复合变换

1.图像的复合变换是指对给定的图像连续施行若干次如前所述的平移、镜像、比例缩放、旋转等基本操作后所完成的变换,图像的复合变换又叫级联变换。
2.利用齐次坐标,对给定的图像依次按一定顺序连续施行若干次基本变换,其变换的矩阵仍可以用3×3阶的矩阵表示。复合变换的矩阵等于基本变换的矩阵按顺序依次相乘得到的组合矩阵。
3.设对给定的图像依次进行了基本变换F1,F2,…,FN,他们的变换矩阵分别为T1,T2,…TN,按照几何变换公式的表示形式,图像复合变换的矩阵T可以表示为:T=TNTN-1···T1

七.变换矩阵总结

在这里插入图片描述

八.完整代码

#include<opencv2/core.hpp>
#include<opencv2/highgui.hpp>
#include<iostream>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/imgproc/types_c.h>

using namespace cv;
using namespace std;

//最近邻差值缩放
void nearest(Mat& src, Mat& dst, float sx, float sy)  //原图,处理后图,x方向倍数,y方向倍数
{
	//round函数计算之后结果是小数点后全变为0,但其还是float的,需要强制类型转换
	//计算目标图形尺寸
	int dst_cols = (int)round(src.cols * sx);
	int dst_rows = (int)round(src.rows * sy);
	//创建目标图形
	dst.create(dst_rows, dst_cols, src.type());
	//灰度图像处理
	if (src.channels() == 1)
	{
		for (int i = 0; i < dst.rows; i++)
		{
			for (int j = 0; j < dst.cols; j++)
			{
				//插值计算,输出图像的像素点由原图像对应的最近的像素点得到(四舍五入)
				int i_index = (int)round(i / sy);    //y方向缩放,则行数发生改变
				int j_index = (int)round(j / sx);
				//防止越界
				if (i_index > src.rows - 1)
				{
					i_index = src.rows - 1;
				}
				if (j_index > src.cols - 1)
				{
					j_index = src.cols - 1;
				}
				dst.at<uchar>(i, j) = src.at<uchar>(i_index, j_index);
			}
		}
	}
	//彩色图像处理
	else
	{
		for (int i = 0; i < dst.rows; i++)
		{
			for (int j = 0; j < dst.cols; j++)
			{
				//差值计算
				int i_index = (int)round(i / sy);
				int j_index = (int)round(j / sx);
				//防止越界
				if (i_index > src.rows - 1)
				{
					i_index = src.rows - 1;
				}
				if (j_index > src.cols - 1)
				{
					j_index = src.cols - 1;
				}
				//b
				dst.at<Vec3b>(i, j)[0] = src.at<Vec3b>(i_index, j_index)[0];
				//g
				dst.at<Vec3b>(i, j)[1] = src.at<Vec3b>(i_index, j_index)[1];
				//r
				dst.at<Vec3b>(i, j)[2] = src.at<Vec3b>(i_index, j_index)[2];
			}
		}
	}
}

//双线性插值缩放
//对于一个目的像素,设置坐标通过反向变换得到的浮点坐标为(i+u,j+v) (其中i、j均为浮点坐标的整数部分,
//u、v为浮点坐标的小数部分,是取值[0,1)区间的浮点数),则这个像素得值 f(i+u,j+v) 可由原图像中坐标为
//(i,j)、(i+1,j)、(i,j+1)、(i+1,j+1)所对应的周围四个像素的值决定,即:
//f(i+u,j+v) = (1-u)(1-v)f(i,j) + (1-u)vf(i,j+1) + u(1-v)f(i+1,j) + uvf(i+1,j+1) 
//其中f(i, j)表示源图像(i, j)处的的像素值,以此类推。
void linear(Mat& src, Mat& dst, float sx, float sy)
{
	//计算目标图形尺寸
	int dst_cols = (int)round(src.cols * sy);
	int dst_rows = (int)round(src.rows * sx);
	//创建目标图形
	dst.create(dst_rows, dst_cols, src.type());
	for (int i = 0; i < dst.rows; i++)
	{
		//几何中心对齐
		//中心对齐(OpenCV也是如此):
			//SrcX = (dstX + 0.5) * (srcWidth / dstWidth) - 0.5
			//SrcY = (dstY + 0.5) * (srcHeight / dstHeight) - 0.5
		double index_i = (i + 0.5) / sx - 0.5;
		//防止越界
		if (index_i < 0)
		{
			index_i = 0;
		}
		if (index_i >= (double)src.rows - 1)
		{
			index_i = (double)src.rows - 2;
		}
		//相邻4×4像素的行(坐标)
		int i1 = floor(index_i);
		int i2 = ceil(index_i);
		//u为得到浮点型坐标的小数部分
		double u = index_i - i1;
		for (int j = 0; j < dst.cols; j++)
		{
			//几何中心对齐
			double index_j = (j + 0.5) / sy - 0.5;
			//防止越界
			if (index_j < 0)
			{
				index_j = 0;
			}
			if (index_j >= (double)src.cols - 1)
			{
				index_j = (double)src.cols - 2;
			}
			//相邻4×4像素的列(坐标)
			int j1 = floor(index_j);
			int j2 = ceil(index_j);
			//u为得到浮点型坐标的小数部分
			double v = index_j - j1;
			//处理灰度图像
			if (src.channels() == 1)
			{
				dst.at<uchar>(i, j) = (1 - u) * (1 - v) * src.at<uchar>(i1, j1)
					+ (1 - u) * v * src.at<uchar>(i1, j2)
					+ u * (1 - v) * src.at<uchar>(i2, j1)
					+ u * v * src.at<uchar>(i2, j2);
			}
			//处理彩色图像
			else
			{
				dst.at<Vec3b>(i, j)[0] = (1 - u) * (1 - v) * src.at<Vec3b>(i1, j1)[0] 
					+ (1 - u) * v * src.at<Vec3b>(i1, j2)[0]
					+ u * (1 - v) * src.at<Vec3b>(i2, j1)[0] 
					+ u * v * src.at<Vec3b>(i2, j2)[0];
				dst.at<Vec3b>(i, j)[1] = (1 - u) * (1 - v) * src.at<Vec3b>(i1, j1)[1] 
					+ (1 - u) * v * src.at<Vec3b>(i1, j2)[1] 
					+ u * (1 - v) * src.at<Vec3b>(i2, j1)[1] 
					+ u * v * src.at<Vec3b>(i2, j2)[1];
				dst.at<Vec3b>(i, j)[2] = (1 - u) * (1 - v) * src.at<Vec3b>(i1, j1)[2]
					+ (1 - u) * v * src.at<Vec3b>(i1, j2)[2]
					+ u * (1 - v) * src.at<Vec3b>(i2, j1)[2]
					+ u * v * src.at<Vec3b>(i2, j2)[2];
			}
		}
	}
}

//图像旋转
void img_rotate(Mat& src, Mat& dst, double Angle)
{
	double angle = Angle * CV_PI / 180.0;
	//构造输出图形
	int dst_rows = (int)round(fabs(src.rows * cos(angle)) + fabs(src.cols * sin(angle)));//图像高度
	int dst_cols = (int)round(fabs(src.cols * cos(angle)) + fabs(src.rows * sin(angle)));//图像宽度
	dst.create(dst_rows, dst_cols, src.type());

	//将原图像坐标映射到数学笛卡尔坐标
	Mat T1 = (Mat_<double>(3, 3) << 1.0, 0.0, 0.0, 0.0, -1.0, 0.0, -0.5 * src.cols, 0.5 * src.rows, 1.0);
	//数学笛卡尔坐标下顺时针旋转的变换矩阵
	Mat T2 = (Mat_<double>(3, 3) << cos(angle), -sin(angle), 0.0, sin(angle), cos(angle), 0.0, 0.0, 0.0, 1.0);
	//将数学笛卡尔坐标映射到旋转后的图像坐标
	double t3[3][3] = { {1.0,0.0,0.0},{0.0,-1.0,0.0},{0.5 * dst.cols,0.5 * dst.rows,1.0} };
	Mat T3 = Mat(3.0, 3.0, CV_64FC1, t3);
	Mat T = T1 * T2 * T3;
	Mat T_inv = T.inv();    //求逆矩阵

	for (double i = 0.0; i < dst.rows; i++)
	{
		for (double j = 0.0; j < dst.cols; j++)
		{
			Mat dst_coordinate = (Mat_<double>(1, 3) << j, i, 1.0);
			Mat src_coordinate = dst_coordinate * T_inv;
			double v = src_coordinate.at<double>(0, 0);   //原图像的横坐标,列,宽
			double w = src_coordinate.at<double>(0, 1);  //原图像的纵坐标,行,高

			//判断是否越界
			if (int(Angle) % 90 == 0)
			{
				if (v < 0)
				{
					v = 0;
				}
				if (v > src.cols - 1)
				{
					v = src.cols - 1;
				}
				if (w < 0)
				{
					w = 0;
				}
				if (w > src.rows - 1)
				{
					w= src.rows - 1;
				}
			}

			if (v >= 0 && w >= 0 && v < src.cols - 1 && w <= src.rows - 1)
			{
				int top = floor(w), bottom = ceil(w), left = floor(v), right = ceil(v);
				double pw = w - top;   //pw为坐标 行 的小数部分
				double pv = v - left;  //pv为坐标 列 的小数部分
				if (src.channels() == 1)
				{
					//灰度图像
					dst.at<uchar>(i, j) = (1 - pw) * (1 - pv) * src.at<uchar>(top, left)
						+ (1 - pw) * pv * src.at<uchar>(top, right)
						+ pw * (1 - pv) * src.at<uchar>(bottom, left)
						+ pw * pv * src.at<uchar>(bottom, right);
				}
				else
				{
					//彩色图像
					dst.at<Vec3b>(i, j)[0] = (1 - pw) * (1 - pv) * src.at<Vec3b>(top, left)[0]
						+ (1 - pw) * pv * src.at<Vec3b>(top, right)[0]
						+ pw * (1 - pv) * src.at<Vec3b>(bottom, left)[0]
						+ pw * pv * src.at<Vec3b>(bottom, right)[0];
					dst.at<Vec3b>(i, j)[1] = (1 - pw) * (1 - pv) * src.at<Vec3b>(top, left)[1]
						+ (1 - pw) * pv * src.at<Vec3b>(top, right)[1]
						+ pw * (1 - pv) * src.at<Vec3b>(bottom, left)[1]
						+ pw * pv * src.at<Vec3b>(bottom, right)[1];
					dst.at<Vec3b>(i, j)[2] = (1 - pw) * (1 - pv) * src.at<Vec3b>(top, left)[2]
						+ (1 - pw) * pv * src.at<Vec3b>(top, right)[2]
						+ pw * (1 - pv) * src.at<Vec3b>(bottom, left)[2]
						+ pw * pv * src.at<Vec3b>(bottom, right)[2];
				}
			}
		}
	}
}

//平移变换
//tx:水平平移距离,正数向右移动,负数向左移动
//ty:垂直平移距离,正数向下移动,负数向上移动
void trans(Mat& src, Mat& dst, double tx, double ty)
{
	//创建输出图像
	dst.create(src.size(), src.type());
	//平移变换矩阵
	Mat T = (Mat_<double>(3, 3) << 1, 0, 0, 0, 1, 0, tx, ty, 1);
	//求逆矩阵
	Mat T_inv = T.inv();

	for (int i = 0; i < dst.rows; i++)
	{
		for (int j = 0; j < dst.cols; j++)
		{
			Mat dst_coordinate = (Mat_<double>(1, 3) << j, i, 1);
			Mat src_coordinate = dst_coordinate * T_inv;
			double v = src_coordinate.at<double>(0, 0);  //原图像的横坐标,列,宽
			double w = src_coordinate.at<double>(0, 1);  //原图像的纵坐标,行,高

			if (v >= 0 && w >= 0 && v < src.cols - 1 && w <= src.rows - 1)
			{
				int top = floor(w), bottom = ceil(w), left = floor(v), right = ceil(v);
				double pw = w - top;   //pw为坐标 行 的小数部分
				double pv = v - left;  //pv为坐标 列 的小数部分
				if (src.channels() == 1)
				{
					//灰度图像
					dst.at<uchar>(i, j) = (1 - pw) * (1 - pv) * src.at<uchar>(top, left)
						+ (1 - pw) * pv * src.at<uchar>(top, right)
						+ pw * (1 - pv) * src.at<uchar>(bottom, left)
						+ pw * pv * src.at<uchar>(bottom, right);
				}
				else
				{
					//彩色图像
					dst.at<Vec3b>(i, j)[0] = (1 - pw) * (1 - pv) * src.at<Vec3b>(top, left)[0]
						+ (1 - pw) * pv * src.at<Vec3b>(top, right)[0]
						+ pw * (1 - pv) * src.at<Vec3b>(bottom, left)[0]
						+ pw * pv * src.at<Vec3b>(bottom, right)[0];
					dst.at<Vec3b>(i, j)[1] = (1 - pw) * (1 - pv) * src.at<Vec3b>(top, left)[1]
						+ (1 - pw) * pv * src.at<Vec3b>(top, right)[1]
						+ pw * (1 - pv) * src.at<Vec3b>(bottom, left)[1]
						+ pw * pv * src.at<Vec3b>(bottom, right)[1];
					dst.at<Vec3b>(i, j)[2] = (1 - pw) * (1 - pv) * src.at<Vec3b>(top, left)[2]
						+ (1 - pw) * pv * src.at<Vec3b>(top, right)[2]
						+ pw * (1 - pv) * src.at<Vec3b>(bottom, left)[2]
						+ pw * pv * src.at<Vec3b>(bottom, right)[2];
				}
			}

		}
	}

}
int main()
{
	Mat src = imread("cat.webp");
	
	//图像的缩放
	Mat dst1,dst2;
	//使用函数resize
	dst1 = Mat::zeros(812, 612, CV_8UC3);    //放大
	dst2 = Mat::zeros(200, 300, CV_8UC3);    //缩小
	resize(src, dst1, dst1.size());
	resize(src, dst2, dst2.size());
	//使用像素矩阵
	float sx0 = 3.0f, sy0 = 2.0f;    //放大的倍数
	float sx1 = 0.4f, sy1 = 0.6f;     //缩小的倍数
	//在 C 语言中,如果不指定数据类型,那么小数常量会被认为是 double 类型的
	//由于它是常量,所以编译器称为 const double。
	//double 是不能隐式转换为 float 的。
	//在后面加一个f或F,且需写成小数形式
	//最近邻插值放大缩小
	Mat dst3, dst4;
	nearest(src, dst3, sx0, sy0);
	nearest(src, dst4, sx1, sy1);
	//双线性插值
	Mat dst5, dst6;
	linear(src, dst5, sx0, sy0);
	linear(src, dst6, sx1, sy1);

	//旋转
	Mat dst7;
	img_rotate(src, dst7, 250);

	//平移
	Mat dst8;
	trans(src, dst8, 80.11, 100.33);

	imshow("原图", src);
	imshow("调用函数-放大", dst1);
	imshow("调用函数-缩小", dst2);
	imshow("最近邻插值-放大", dst3);
	imshow("最近邻插值-缩小", dst4);
	imshow("双线性插值-放大", dst5);
	imshow("双线性插值-缩小", dst6);
	imshow("旋转250", dst7);
	imshow("平移", dst8);
	cout << "size:" << src.size() << endl;
	cout << "row:" << src.rows << endl;
	cout << "col:" << src.cols << endl;
	waitKey(0);
	return 0;
}

参考文章:https://blog.csdn.net/weixin_40647819/article/details/87912122

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

llurran

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值