OpenCV计算机视觉开发入门与实践<二十四>:几何变换之平移、缩放、旋转

摘要:本文系统讲解图像几何变换的核心理论与 OpenCV 实现。首先介绍几何变换的基础------齐次坐标与 3×3 变换矩阵,随后依次深入图像平移、旋转、仿射变换与缩放,涵盖原理推导、数学公式与完整 C++ 代码示例,帮助读者掌握从手动实现到调用 OpenCV 库函数的完整路径。

前言

  在处理图像时,我们往往会遇到需要对图像进行几何变换的情况。图像的几何变换是图像处理和图像分析的基础内容之一,它不仅提供了产生某些图像的可能,还可以简化图像处理和分析的程序,特别是当图像具有一定的规律性时,一幅图像可以由其他图像通过

几何变换来实现。因此,为了提高图像处理和分析程序设计的速度和质量,开拓图像程序应用范围的新领域,对图像进行几何变换是十分必要的。

  中值滤波用像素图像的几何变换不改变图像的像素值,而是改变像素所在的几何位置。从变换的性质来分,图像的几何变换有图像的位置变换(平移、镜像、旋转)、图像的形状变换(放大、缩小、错切)等基本变换,以及图像的复合变换等。其中使用最频繁的是图像的缩放和旋转,不论是照片、图画、书报,还是医学X光影像和卫星遥感图像,都会用到这两项技术。

  中值滤波用像素本章主要深讨图像的几何变换(主要包括图像的平移、缩放、旋转、镜像等)理论,并在此基础上介绍使用OpenCV实现的过程。

1.几何变换基础

  图像的几何变换是指改变图像的位置、形状、尺寸等几何特征。从图像类型来分,图像的几何变换有二维平面图像的几何变换、三维图像的几何变换以及由三维向二维平面投影变换等。从变换的性质来分,图像的几何变换有平移、缩放、旋转、反射和错切等基本变换,透视变换等复合变换,以及插值运算等。在本章中,我们只讨论二维图像的几何变换。

  对于二维图像几何变换及变换中心在坐标原点的缩放、反射、错切和旋转等各种变换,都可以用2×2的矩阵来表示和实现,但是一个2×2的变换矩阵并不能实现图像的平移以及绕任意点的缩放、反射、错切和旋转等各种变换。因此,为了能够用统一的矩阵线性变换形式表示和实现这些常见的图像几何变换,就需要引入一种新的坐标,即齐次坐标。利用齐次坐标来变换处理才能实现上述各种二维图像的几何变换。下面以图像的平移为例,说明用齐次坐标表示的二维图像几何变换的矩阵,并在此基础上推广至其他情况。

  假设点P(x,y)进行平移后,到了P(x,y)处,其中x方向的平移量为Δx,y方向的平移量为Δy。那么点P(x,y)的坐标为:,这个变换用矩阵的形式可以表示为:

  而平面上点的变换矩阵中没有引入平移常量,无论a、b、c、d取什么值,都不能实现上述的平移变换。因此,需要使用2×3阶变换矩阵,其形式为:

  此矩阵的第1、2列构成单位矩阵,第3列元素为平移常量。由上述内容可知,对二维图像进行变换,只需要将图像的点集矩阵乘以变换矩阵即可,二维图像对应的点集矩阵是2×n阶的,而上式扩展后的变换矩阵是2×3阶的,这不符合矩阵相乘时要求前者的列数与后者的行数相等的规则。因此,需要在点的坐标列矩阵中引入第3个元素,增加一个附加坐标,扩展为3×1的列矩阵,这样用三维空间点(x,y,1)表示了二维空间点(x,y),即采用一种特殊的坐标实现了平移变换,变换结果为:

  符合上述平移后的坐标位置。通常将2×3阶矩阵扩充为3×3阶矩阵,以拓宽功能。由此可得平移变换矩阵为:

  下面再验证一下点P(x,y)按照3×3的变换矩阵T平移变换的结果:

  从上式可以看出,引入附加坐标后,扩充了矩阵的第3行,并没有使变换结果受到影响。这种用n+1维向量表示n维向量的方法被称为齐次坐标表示法。

  因此,二维图像中的点坐标(x,y)通常表示成齐次坐标(Hx,Hy,H),其中H表示非零的任意实数。当H=1时,(x,y,1)就称为点(x,y)的规范化齐次坐标。显然规范化齐次坐标的前两个数是相应二维点的坐标,没有变化,仅在原坐标中增加了H=1的附加坐标。由点的齐次坐标(Hx,Hy,H)求点的规范化齐次坐标(x,y,1),可按这个公式进行:

  齐次坐标在二维图像几何变换中的另一个应用:某点S(60000,40000)在16位计算机上表示大于32767的最大坐标值,需要进行复杂的操作,但是如果把S的坐标形式变成(Hx,Hy,H)形式的齐次坐标,情况就不同了。在齐次坐标系中,设,则(60000,40000)的齐次坐标为所要表示的点变为,此点显然在16位计算机的二进制数所能表示的范围之内。因此,采用齐次坐标,并将变换矩阵改成3×3阶的形式后,便可实现所有二维图像几何变换的基本变换。

  在上述基础上,将二维图像几何变换的矩阵推广至其他情况。利用齐次坐标以及3×3阶形式的变换矩阵,实现二维图像几何变换的一般过程是:将2×n阶的二维点集矩阵表示成齐次坐标的形式,然后乘以相应的变换矩阵,即变换后的点集矩阵=变换矩阵T×变换前

的点集矩阵。设变换矩阵T为:

则上述变化可以用公式表示为:

图像上各点的新齐次坐标规范化后的点集矩阵为:

  引入齐次坐标后,表示二维图像几何变换的3×3矩阵的功能就完善了,可以用它完成二维图像的各种几何变换。

  下面讨论3×3阶变换矩阵中各元素在变换中的功能。几何变换的3×3矩阵的一般形式为:

  3×3阶矩阵T可以分成4个子矩阵。其中这一子矩阵可使图像实现恒等、缩放、反射(或镜像)、错切和旋转变换。l m这一行矩阵可以使图像实现平移变换。这一列矩阵可以使图像实现透视变换,但当p=0、q=0时,它没有透视作用。s这一元素可以使图像实

现全比例变换。例如,对图像进行全比例变换,即

  由此可见,当s>1时,图像按比例缩小;当0<s<1时,图像按比例放大;当s=1时,图像大小不变。

2. 图像平移

  图像平移是将一幅图像中所有的点都按照指定的平移量在水平、垂直方向移动,平移后的图像上的每一个点都可以在原图像中找到对应的点。我们知道,图像是由像素组成的,而像素的集合就相当于一个二维矩阵,每一个像素都有一个"位置",也就是每一个像素都有一个坐标。假设原来的像素的坐标为(x,y),经过平移量(Δx, Δy)后,坐标变为(x,y),

用数学公式可以表示为:

x = x +Δx

y = y +Δy

  平移变换分为两种:一种是图像大小不改变,这样原图像中会有一部分不在平移后的图像中;另一种是图像大小改变,这样可以保全原图像的内容。

【例7.1】实现图像平移

cpp 复制代码
#include <iostream>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <iostream>

//平移操作,图像大小不变
cv::Mat imageTranslation1(cv::Mat& srcImage, int x0ffset, int y0ffset)
{
    int nRows = srcImage.rows;
    int nCols = srcImage.cols;
    cv::Mat resultImage(srcImage.size(), srcImage.type());
    //遍历图像
    for (int i = 0; i < nRows; i++)
    {
        for (int j = 0; j < nCols; j++)
        {
            //映射变换
            int x = j - x0ffset;
            int y = i - y0ffset;
            //边界判断
            if (x >= 0 && y >= 0 && x < nCols && y < nRows)
            {
                //把行y列x的srcImage上的图像元素值复制到目标图像(i, j)位置上
                resultImage.at<cv::Vec3b>(i, j) = srcImage.ptr<cv::Vec3b>(y)[x];
            }
        }
    }
    return resultImage;
}
//平移操作,图像大小改变
cv::Mat imageTranslation2(cv::Mat& srcImage, int x0ffset, int y0ffset)
{
    //设置平移尺寸
    //这里是区别,先对目标图像的行进行扩展,扩展到原来图像的行列范围,再加上偏移量绝对值
    int nRows = srcImage.rows + abs(y0ffset);
    int nCols = srcImage.cols + abs(x0ffset);
    cv::Mat resultImage(nRows, nCols, srcImage.type());
    //图像遍历
    for (int i = 0; i < nRows; i++)
    {
        for (int j = 0; j < nCols; j++)
        {
            int x = j - x0ffset;
            int y = i - y0ffset;
            //边界判断
            if (x >= 0 && y >= 0 && x < nCols && y < nRows)
            {
                resultImage.at<cv::Vec3b>(i, j) = srcImage.ptr<cv::Vec3b>(y)[x];
            }
        }
    }
    return resultImage;
}

int main()
{
    //读取图像
    cv::Mat srcImage = cv::imread("1.jpg");
    if (srcImage.empty())
    {
        return -1;
    }

    //显示原图像
    cv::imshow("src", srcImage);
    int x0ffset = 50;
    int y0ffset = 80;
    //图像左平移不改变大小(相对于原图像,目标图像左移了)
    cv::Mat resultImage1 = imageTranslation1(srcImage, x0ffset, y0ffset);
    cv::imshow("resultImage1", resultImage1);
    //图像左平移改变大小
    cv::Mat resultImage2 = imageTranslation2(srcImage, x0ffset, y0ffset);
    cv::imshow("resultImage2", resultImage2);
    //图像右平移不改变大小
    x0ffset = -50;
    y0ffset = -80;
    cv::Mat resultImage3 = imageTranslation1(srcImage, x0ffset, y0ffset);
    cv::imshow("resultImage3", resultImage3);
    cv::waitKey(0);
    return 0;
}

  注意映射变换,比如加入offset=2后,目标图像的j=10位置的元素值对应原图像的j=8位置的元素值。原图像(0,0)放到目标图像(xOffset,yOffset)。另外,Vec3b是一种图像像素值的类型。

3.图像旋转

  图像旋转是数字图像处理的一个非常重要的环节,是图像的几何变换手法之一。图像旋转算法是图像处理的基础算法。在数字图像处理过程中,经常要用到旋转,例如在进行图像扫描时,需要运用旋转实现图像的倾斜校正;在进行多幅图像的比较、模式识别及对图像进行剪裁和拼接前,都需要进行图像的旋转处理。

  图像旋转是指图像以某一点为中心旋转一定的角度,形成一幅新的图像的程。图像旋转通常可以分为两种情况,一种是以坐标原点为中心进行旋转;另外一种是以任意图形中的某点为坐标原点进行旋转。图像的旋转变换是图像的位置变换,旋转后图像的大小一般会改变。在图像旋转变换中,既可以把转出显示区域的图像截去,也可以扩大图像范围以显示所有的图像。

1)以坐标原点为中心进行旋转的原理

  点P绕坐标原点逆时针旋转θ角度得到点P,如图所示。

从图中的①②公式可以得到旋转变换的公式:

还可以得到逆运算矩阵公式:

2)以任意图形中的某点为坐标原点进行旋转的原理

  以任意图形中的某点为坐标原点进行旋转的原理如图所示。

  由图可以看出,以任意图形中心点为坐标原点进行旋转需要如下3步:

步骤01 :将坐标系Ⅰ变成坐标系Ⅱ。

由Figure1得到Figure2可知,变换矩阵为:

步骤02 :在坐标系Ⅱ中旋转θ角度。参考以坐标原点为中心进行旋转的原理。

步骤03 :将坐标系Ⅱ变成坐标系Ⅰ。

  由图中的Figure3得到Figure4可知,变换矩阵为(其实就是步骤01中变换矩阵的逆变换):

  在OpenCV中,图像旋转首先根据旋转角度和旋转中心获取旋转矩阵,然后根据旋转矩阵进行变换,即可实现任意角度和任意中心的旋转效果。

  下面我们实现图像旋转算法,有两种方式:一种是根据算法手动实现,另一种是根据OpenCV提供的现成函数自动实现。现在先来介绍根据算法手动实现的方式。

【例7.2】手动实现图像旋转

cpp 复制代码
#include <iostream>
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <string>
#include <cmath>

using namespace cv;
Mat imgRotate(Mat matSrc, float angle, bool direction)
{
	float theta = angle * CV_PI / 180.0;
	const int nRowsSrc = matSrc.rows;
	const int nColsSrc = matSrc.cols;
	// 如果是顺时针旋转
	if (!direction)
		theta = 2 * CV_PI - theta;
	// 全部以逆时针旋转来计算
	// 逆时针旋转矩阵
	float matRotate[3][3]{
		{std::cos(theta), -std::sin(theta), 0},
		{std::sin(theta), std::cos(theta), 0 },
		{0, 0, 1}
	};
	float pt[3][2]{ { 0, (float)nRowsSrc},		{(float)nColsSrc, (float)nRowsSrc},		{(float)nColsSrc, 0}
	};
	for (int i = 0; i < 3; i++)
	{
		float x = pt[i][0] * matRotate[0][0] + pt[i][1] * matRotate[1][0];
		float y = pt[i][0] * matRotate[0][1] + pt[i][1] * matRotate[1][1];
		pt[i][0] = x;
		pt[i][1] = y;
	}
	// 计算出旋转后图像的极值点和尺寸
	float fMin_x = min(min(min(pt[0][0], pt[1][0]), pt[2][0]), (float)0.0);
	float fMin_y = min(min(min(pt[0][1], pt[1][1]), pt[2][1]), (float)0.0);
	float fMax_x = max(max(max(pt[0][0], pt[1][0]), pt[2][0]), (float)0.0);
	float fMax_y = max(max(max(pt[0][1], pt[1][1]), pt[2][1]), (float)0.0);
	int nRows = cvRound(fMax_y - fMin_y + 0.5) + 1;
	int nCols = cvRound(fMax_x - fMin_x + 0.5) + 1;
	int nMin_x = cvRound(fMin_x + 0.5);
	int nMin_y = cvRound(fMin_y + 0.5);
	// 拷贝输出图像
	Mat matRet(nRows, nCols, matSrc.type(), Scalar(0));
	for (int j = 0; j < nRows; j++)
	{
		for (int i = 0; i < nCols; i++)
		{
			// 计算出输出图像在原图像中对应点的坐标,然后复制该坐标的灰度值
			// 因为是逆时针转换,所以这里映射到原图像的时候可以看成是输出图像
			// 到顺时针旋转到原图像的,而顺时针旋转矩阵刚好是逆时针旋转矩阵的转置
			// 同时还要考虑把旋转后的图像的左上角移动到坐标原点
			int x = (i + nMin_x) * matRotate[0][0] + (j + nMin_y) * matRotate[0][1];
			int y = (i + nMin_x) * matRotate[1][0] + (j + nMin_y) * matRotate[1][1];
			if (x >= 0 && x < nColsSrc && y >= 0 && y < nRowsSrc)
			{
				matRet.at<Vec3b>(j, i) = matSrc.at<Vec3b>(y, x);
			}
		}
	}
	return matRet;
}

int main()
{
	Mat matSrc = imread("cat.png");
	if (matSrc.empty())
		return 1;
	float angle = 30;
	Mat matRet = imgRotate(matSrc, angle, true);
	imshow("src", matSrc);
	imshow("rotate", matRet);
	// 保存图像
	imwrite("rotate_panda.jpg", matRet);

	waitKey();
	return 0;
}

  以上代码完全是根据前面的算法原理公式来实现的。图像旋转是指图像按照某个位置转动一定角度的过程,旋转中图像仍保持原始尺寸。图像旋转后,图像的水平对称轴、垂直对称轴及中心坐标原点都可能发生变换,因此需要对图像旋转中的坐标进行相应转换。

  如果不想通过算法公式手动实现图像旋转,也可以利用OpenCV提供的库函数

getRotationMatrix2D来实现图像旋转。该函数用来计算旋转矩阵,其声明如下:

Mat getRotationMatrix2D(Point2f center, double angle, double scale);

  其中参数center表示旋转的中心点;angle表示旋转的角度;scale表示图像缩放因子。该函数的返回值为一个2×3的矩阵,其中矩阵前两列代表旋转,最后一列代表平移。计算出旋转矩阵后,还需要把旋转应用到仿射变换的输出,仿射变换函数是warpAffine,该函数在下一节介绍。

【例7.3】使用函数实现图像的旋转

cpp 复制代码
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;

// 图像旋转
void Rotate(const Mat& srcImage, Mat& destImage, double angle)//angle表示要旋转的角度
{
	Point2f center(srcImage.cols / 2, srcImage.rows / 2);//中心
	Mat M = getRotationMatrix2D(center, angle, 1);//计算旋转的仿射变换矩阵
	//现在把旋转应用到仿射变换的输出
	warpAffine(srcImage, destImage, M, Size(srcImage.cols, srcImage.rows));
	//仿射变换
	circle(destImage, center, 2, Scalar(255, 0, 0));
}

int main()
{
	//读入图像,并判断图像是否读入正确
	cv::Mat srcImage = imread("lena.png");
	if (!srcImage.data)
	{
		puts("Fail to open file.");
		return -1;
	}
	imshow("srcImage", srcImage); //源图像也绘制出来以作参照

	Mat destImage;
	double angle = 9.9;//角度
	Rotate(srcImage, destImage, angle);
	//最后把仿射变换和旋转的结果绘制在窗体中
	imshow("dst", destImage);
	waitKey(0);
	return 0;
}

4.仿射变换

  平移、旋转、缩放、翻转、剪切等变换都属于仿射变换,图7-6显示了各种仿射变换。

  仿射变换是一种常用的图像几何变换。使用仿射变换矩阵能够方便地描述图像的线性变换以及平移等非线性变换。仿射变换可以通过一系列原子变换的复合来实现。这类变换可以用一个3×3的矩阵M来表示,其最后一行为(0,0,1)。该变换矩阵将原坐标(x,y)变换为新坐标(x',y'),如图所示。

  图中所示的仿射变换描述了一种二维仿射变换的功能,它是一种二维坐标之间的线性变换,保持二维图形的"平直性"(变换后直线还是直线,圆弧还是圆弧)和"平行性"(其实是保持二维图形间的相对位置关系不变,平行线还是平行线,而直线上的点位置顺序不变,注意向量间的夹角可能会发生变化)。

  在OpenCV中进行仿射变换的函数是warpAffine,该函数声明如下:

cpp 复制代码
void 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表示2×3的变换矩阵;dsize指定图像输出尺寸;flags表示插值算法标识符,默认值为INTER_LINEAR;borderMode表示边界像素模式,默认值为BORDER_CONSTANT;borderValue表示边界取值,默认值为Scalar(),即0。

【例7.4】实现图像的仿射变换

  在上述代码中,getRotationMatrix2D先计算出旋转矩阵,再利用函数warpAffine实现旋转。除了图像旋转外,我们还进行了仿射变换和旋转仿射变换。仿射变换的5种变换方式是平移、旋转、缩放、翻转(翻转镜像除了用三点法求仿射变换矩阵外,还可以用flip函数实现)和剪切,主要是仿射映射矩阵的设置,灵活地设置不同的变换矩阵可以得到不同的变换效果。

  这里的仿射变换,如果想得到很好的效果,如旋转以后图像的大小不变,而且图像还是位于窗口中心,就需要进行窗口大小的调整,以及对旋转后的图像进行平移。

cpp 复制代码
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;

//全局变量
String src_windowName = "原图像";
String warp_windowName = "仿射变换";
String warp_rotate_windowName = "仿射旋转变换";
String rotate_windowName = "图像旋转";

int main()
{
	Point2f srcTri[3];
	Point2f dstTri[3];

	Mat rot_mat(2, 3, CV_32FC1);
	Mat warp_mat(2, 3, CV_32FC1);
	Mat srcImage, warp_dstImage, warp_rotate_dstImage, rotate_dstImage;

	//加载图像
	srcImage = imread("cat.png");

	//判断文件是否加载成功
	if (srcImage.empty())
	{
		cout << "Fail to open file" << endl;
		return -1;
	}

	//创建的仿射变换目标图像与原图像尺寸类型相同
	warp_dstImage = Mat::zeros(srcImage.rows, srcImage.cols, srcImage.type());

	//设置三个点来计算仿射变换
	srcTri[0] = Point2f(0, 0);
	srcTri[1] = Point2f(srcImage.cols - 1, 0);
	srcTri[2] = Point2f(0, srcImage.rows - 1);
	dstTri[0] = Point2f(srcImage.cols * 0.0, srcImage.rows * 0.33);
	dstTri[1] = Point2f(srcImage.cols * 0.85, srcImage.rows * 0.25);
	dstTri[2] = Point2f(srcImage.cols * 0.15, srcImage.rows * 0.7);

	//计算仿射变换矩阵
	warp_mat = getAffineTransform(srcTri, dstTri);
	//对加载图形进行仿射变换操作
	warpAffine(srcImage, warp_dstImage, warp_mat, warp_dstImage.size());
	//计算图像中点顺时针旋转50度、缩放因子为0.6的旋转矩阵
	Point center = Point(warp_dstImage.cols / 2, warp_dstImage.rows / 2);
	double angle = -50.0;
	double scale = 0.6;

	//计算旋转矩阵
	rot_mat = getRotationMatrix2D(center, angle, scale);
	//旋转已扭曲图像
	warpAffine(warp_dstImage, warp_rotate_dstImage, rot_mat, warp_dstImage.size());
	//将原图像旋转
	warpAffine(srcImage, rotate_dstImage, rot_mat, srcImage.size());

	//显示变换结果
	namedWindow(src_windowName, WINDOW_AUTOSIZE);
	imshow(src_windowName, srcImage);
	namedWindow(warp_windowName, WINDOW_AUTOSIZE);
	imshow(warp_windowName, warp_dstImage);
	namedWindow(warp_rotate_windowName, WINDOW_AUTOSIZE);
	imshow(warp_rotate_windowName, warp_rotate_dstImage);
	namedWindow(rotate_windowName, WINDOW_AUTOSIZE);
	imshow(rotate_windowName, rotate_dstImage);
	waitKey(0);
	return 0;
}

5.图像缩放

5.1 缩放原理

  图像按比例缩放是指将给定的图像在x轴方向按比例缩放f倍,在y轴方向按比例缩放f倍,从而获得一幅新的图像。如果f=f,即在x轴和y轴方向缩放的比例相同,则称这样的比例缩放为图像的全比例缩放;如果f≠f,图像的比例缩放则会改变原始图像的像素间的

相对位置,产生几何畸变。

  设原图像中的点P(x,y)按比例缩放后,在新图像中的对应点为P(x,y),则按比例缩放前后两点P(x,y)、P(x,y)之间的关系用矩阵形式可以表示为:

其逆运算为:

  按比例缩放所产生的图像中的像素可能在原图像中找不到相应的像素点,这样就必须进行插值处理。有关插值的内容在后面介绍。下面首先介绍图像的比例缩小。最简单

的比例缩小是当时,图像被缩到一半大小,此时缩小后图像中的(0,0)像素对应原图像中的

(0,0)像素,(0,1)像素对应原图像中的(0,2)像素,(1,0)像素对应原图像中的(2,0)像素,以此

类推。

  图像缩小之后,因为承载的数据量小了,所以画布可相应缩小。此时,只需在原图像的基础上,每行隔一个像素取一点,每隔一行进行操作,即取原图的偶(奇)数行和偶(奇)数列构成新的图像。如果图像按任意比例缩小,就需要计算选择的行和列。

  当f≠f(f,f>0)时,图像不按比例缩小,这种操作因为在x轴方向和y轴方向的缩小比例不同,一定会带来图像的几何畸变。图像不按比例缩小的方法是:若M×N大小的旧图像F(x,y)缩小为kM×kN(k<1, k<1)大小的新图像I(x,y),则I(x,y)=F(int(c×x),int(c×y)),其中,

由此公式可以构造新图像。

  图像的缩小操作是在现有的信息中挑选出所需的有用信息;而图像的放大操作则需要对放大后多出来的空格填入适当的像素值,这是信息的估计问题,所以较图像的缩小要难一些。

  当f=f=2时,图像按全比例放大两倍,放大后图像中的(0,0)像素对应原图中的(0,0)像素;(0,1)像素对应原图中的(0,0.5)像素,该像素不存在,可以近似为(0,0),也可以近似为(0,1);(0,2)像素对应原图像中的(0,1)像素;(1,0)像素对应原图中的(0.5,0)像素,该像素近似于(0,0)或(1,0)像素;(2,0)像素对应原图中的(1,0)像素,以此类推。其实这是将原图像每行中的像素重复取值一遍,然后每行重复一次。

  按比例将原图像放大k倍时,如果按照最近邻域法,就需要将一个像素值添在新图像的k×k子块中。显然,如果放大倍数太大,按照这种方法处理会出现马赛克效应。

  当f≠f (f,f>0)时,图像在x轴方向和y轴方向不按比例放大,此时这种操作由于x轴方向和y轴方向的放大倍数不同,一定会带来图像的几何畸变。放大的方法是将原图像的一个像素添加到新图像的一个k×k子块中。为了提高几何变换后的图像质量,常采用线性插值法。该方法的原理是,当求出的分数地址与像素点不一致时,求出周围4个像素点的距离比,根据该比率,由4个邻域的像素灰度值进行线性插值。

5.2 OpenCV中的缩放

  在OpenCV中,实现图像缩放的函数是resize,该函数声明如下:

cpp 复制代码
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double
fy=0, int interpolation=INTER_LINEAR );

  其中参数src表示原图像;dst表示输出图像;dsize表示目标图像的大小;fx表示在x轴上的缩放比例;fy表示在y轴上的缩放比例;interpolation表示插值方式,有4种方式:INTER_NN(最近邻插值)、INTER_LINEAR(默认值,双线性插值)、INTER_AREA(使用像素关系重采样,当图像缩小时,该方法可以避免波纹出现;当图像放大时,类似于INTER_NN方法)、INTER_CUBIC(立方插值)。注意:dsize与f和f必须不能同时为零。

【例7.5】实现图像的缩放

cpp 复制代码
#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;

string src_windowName = "原图像";
string small_windowName = "缩小图";
string big_windowName = "放大图";
int main()
{
	// 读取图片
	Mat srcImage = imread("cat.png");
	if (!srcImage.data)   //Check for invalid input
	{
		cout << "Could not open or find the image" << endl;
		return -1;
	}
	Mat dstImage1, dstImage2;
	Mat tempImage = srcImage;
	// 显示图片
	namedWindow(src_windowName, WINDOW_AUTOSIZE);
	imshow(src_windowName, srcImage);

	// 图片的缩小
	resize(tempImage, dstImage1, Size(tempImage.cols / 2, tempImage.rows / 2), 0, 0, INTER_NEAREST);
	// 图片的放大
	resize(tempImage, dstImage2, Size(tempImage.cols * 2, tempImage.rows * 2), 0, 0, INTER_NEAREST);
	namedWindow(small_windowName, WINDOW_AUTOSIZE);
	imshow(small_windowName, dstImage1);
	namedWindow(big_windowName, WINDOW_AUTOSIZE);
	imshow(big_windowName, dstImage2);

	waitKey(0);
	return 0;
}
相关推荐
AI服务老曹16 分钟前
水位识别算法接入AI视频分析平台全流程与误报优化指南(附接口字段与排查表)
人工智能·算法·音视频
FII工业富联科技服务17 分钟前
工业AI Agent从单点应用到规模化落地:多智能体、Agentic Layer与制造运营协同架构解析
大数据·人工智能
IPdodo_17 分钟前
2026年AI 数据采集代理 IP 选型:成功率、并发、轮换与成本评估
前端·网络·人工智能·chrome·python·http·网络调试
时代分流20 分钟前
供应商管理系统:SRM数字化采购协同方案
大数据·人工智能
qq_252941316820 分钟前
建筑物缺陷目标检测数据集 | 建筑裂缝检测 房屋缺陷 结构损伤 目标检测9022期
人工智能·yolo·目标检测·计算机视觉·视觉检测·建筑裂缝·建筑外立面
云和数据.ChenGuang21 分钟前
git revert回退问题
java·服务器·人工智能·git·fastapi·强化学习
啥都想学点的研究生23 分钟前
一篇文章讲清楚:超参数的选择方法——交叉验证和网格搜索
人工智能·深度学习·机器学习
鱼很腾apoc24 分钟前
【Linux】第13期 详解应用层协议HTTP+Cookie/Session+HTTPS
linux·服务器·c++·学习·http·https
DS随心转小程序28 分钟前
Gemini只能导出了一部分Word文档,很少的一部分,不是我这个窗口所有的对话内容?
开发语言·人工智能·c#·word·豆包·deepseek·ai导出鸭