Opencv学习-图像变换

1. 图像连接

        图像连接是指将两个具有相同高度或者宽度的图像连接在一起,图像的下(左)边缘是另一个图像的上(右)边缘。图像连接常在需要对两幅图像内容进行对比或者内容中存在对应信息时显示 对应关系时使用。例如,在使用线段连接两幅图像中相同的像素点时,就需要首先将两幅图像组成 一幅新的图像,再连接相同的区域。
        OpenCV 4 中针对图像左右连接和上下连接两种方式提供了两个不同的函数, vconcat() 函数用于实现图像或者矩阵数据的上下连接,该函数可以连接存放在数组变量中的多个 Mat 类型的数据, 也可以直接连接两个Mat 类型的数据。

1.1 vconcat函数介绍(竖向连接)

void cv::vconcat(const Mat * src, size_t nsrc, OutputArray dst )

src:Mat矩阵类型的数组。
nsrc:数组中 Mat 类型数据的个数。
dst:连接后的 Mat类矩阵。

        该函数对存放在数组矩阵中的Mat 类型数据进行纵向连接。第一个参数是存放多个 Mat 类型数据的数组,要求数组中所有的 Mat 类型具有相同的列数并且具有相同的数据类型和通道数。第二个参数是数组中含有的 Mat 类型数据的个数。最后一个参数是拼接后输出的结果,结果的宽度与第一个 Mat 类型数据相同,高度为数组中所有 Mat 类型数据高度的总和,并且与第一个 Mat 类型数据具有相同的数据类型和通道数

void cv::vconcat(InputArray src1, InputArray src2, OutputArray dst )

src1:第一个需要连接的 Mat 类矩阵。
src2:第二个需要连接的 Mat 类矩阵,与第一个参数具有相同的宽度、数据类型和通道数
dst:连接后的 Mat 类矩阵 

        该函数直接对两个 Mat 类型的数据进行竖向连接。前两个参数分别是需要连接的两个 Mat 类型变量,两者需要具有相同的宽度、数据类型及通道数,第三个参数是连接后的输出结果,在拼接结果中第一个参数在上方,第二个参数在下方。

        hconcat()函数用于实现图像或者矩阵数据的左右连接。与 vconcat() 函数类似,该函数既可以连接存放在数组变量中的多个 Mat 类型的数据,又可以直接连接两个 Mat 类型的数据。

1.2 hconcat函数原型

void cv::hconcat(const Mat * src, size_t nsrc, OutputArray dst )
void cv::hconcat(InputArray src1, InputArray src2, OutputArray dst )

1.3  示例代码

#include <opencv2/opencv.hpp> 
#include <iostream> 
 
using namespace std; 
using namespace cv; 
 
int main() 
{ 
    //矩阵数组的横竖连接
    Mat matArray[] = { Mat(1, 2, CV_32FC1, cv::Scalar(1)), 
    Mat(1, 2, CV_32FC1, cv::Scalar(2)) }; 
    Mat vout, hout; 
    cout << "matArray[0]  ==   " << matArray[0] << "matArray[1]  ==   " << matArray[1] << endl;
    vconcat(matArray, 2, vout); 
    cout << "图像数组竖向连接:" << endl << vout << endl; 
    hconcat(matArray, 2, hout); 
    cout << "图像数组横向连接:" << endl << hout << endl; 
    
    //矩阵的横竖拼接
    Mat A = (cv::Mat_<float>(2, 2) << 1, 7, 2, 8); 
    Mat B = (cv::Mat_<float>(2, 2) << 4, 10, 5, 11); 
 
    cout << "A : \n"<< A << "\nB : \n"<< B << endl;
    Mat vC, hC; 
    vconcat(A, B, vC); 
    cout << "多个图像竖向连接:" << endl << vC << endl; 
    hconcat(A, B, hC); 
    cout << "多个图像横向连接:" << endl << hC << endl; 
    
    //读取 4 个子图像,00 表示左上角、01 表示右上角、10 表示左下角、11 表示右下角
    Mat img00 = imread("lena00.png"); 
    Mat img01 = imread("lena01.png"); 
    Mat img10 = imread("lena10.png"); 
    Mat img11 = imread("lena11.png"); 
    if (img00.empty()||img01.empty()||img10.empty()||img11.empty()) 
    { 
        cout << "请确认图像文件名称是否正确" << endl; 
        return -1; 
    } 
    //显示 4 个子图像
    imshow("img00", img00); 
    imshow("img01", img01); 
    imshow("img10", img10); 
    imshow("img11", img11); 
    
    //图像连接
    Mat img, img0, img1; 
    //图像横向连接
    hconcat(img00, img01, img0); 
    hconcat(img10, img11, img1); 
    //横向连接结果再进行竖向连接
    vconcat(img0, img1, img); 
    
    //显示连接图像的结果
    imshow("img0", img0); 
    imshow("img1", img1); 
    imshow("img", img); 
    waitKey(0); 
    return 0; 
}

1.4 结果 

2. 图像尺寸变换

        图像的尺寸变换实际上就是改变图像的长和宽,实现图像的缩放。在 OpenCV 4 中提供了 resize() 函数用于将图像修改成指定尺寸。

2.1 resize函数介绍

void cv::resize(InputArray src, OutputArray dst, Size dsize, double fx = 0, double fy = 0, int interpolation = INTER_LINEAR )

src:输入图像。
dst:输出图像,图像的数据类型与 src 相同。
dsize:输出图像的尺寸。
fx:水平轴的比例因子,如果将水平轴变为原来的两倍,则赋值为 2。
fy:垂直轴的比例因子,如果将垂直轴变为原来的两倍,则赋值为 2。
interpolation:插值方法的标志

        该函数主要用来对图像尺寸进行缩放,前两个参数分别是输入图像和尺寸缩放之后的输出图像。该函数的 dsize 和 fx ( fy )同时可以调整输出图像的参数,因此两类参数在实际使用时只需要使用一类,当根据两个参数计算出来的输出图像尺寸不一致时,以 dsize 设置的图像尺寸为准。这两类调整图像尺寸的参数的关系如下:

        最后一个参数是选择图像插值方法的标志。在图像缩放相同的尺寸时,选择不同的插值方法会具有不同效果 。一般来讲,如果要缩小图像,那么通常使用 INTER_AREA 标志会有较好的效果。而在放大图像时,采用 INTER_CUBIC 和 INTER_LINEAR 标志通常会有比较好的效果,这两个标志,前者计算速度较慢,后者速度较快,虽然前者效果较好,但是后者效果也相对比较理想。

        示例程序中首先以灰度图像的形式读入一幅图像,之后利用 INTER_AREA 将图像缩小,并分别利用INTER_CUBIC、INTER_NEAREST 和 INTER_LINEAR 这 3 种方法将图像放大到相同的尺寸,根据结果比较两种插值方法效果的差异。 

2.2 示例代码

#include <opencv2/opencv.hpp> 
#include <iostream> 
 
using namespace std; 
using namespace cv; 
 
int main() 
{ 
    Mat gray = imread("../pic/gril.jpg", IMREAD_GRAYSCALE); 
    if (gray.empty()) 
    { 
        cout << "请确认图像文件名称是否正确" << endl; 
        return -1; 
    } 
    cout << " width == " << gray.cols << "   height == " << gray.rows << endl;
    Mat smallImg, bigImg0, bigImg1, bigImg2; 
    resize(gray, smallImg, Size(300, 300), 0, 0, INTER_AREA); //先将图像缩小
    resize(smallImg, bigImg0, Size(300, 300), 0, 0, INTER_NEAREST); //最近邻插值
    resize(smallImg, bigImg1, Size(300, 300), 0, 0, INTER_LINEAR); //双线性插值
    resize(smallImg, bigImg2, Size(300, 300), 0, 0, INTER_CUBIC); //双三次插值
    //namedWindow("smallImg", WINDOW_NORMAL); //图像尺寸太小,一定要设置可以调节窗口大小标志
    imshow("gray", gray); 
    imshow("smallImg", smallImg); 
    //namedWindow("bigImg0", WINDOW_NORMAL); 
    imshow("bigImg0", bigImg0);
    //namedWindow("bigImg1", WINDOW_NORMAL); 
    imshow("bigImg1", bigImg1); 
    //namedWindow("bigImg2", WINDOW_NORMAL); 
    imshow("bigImg2", bigImg2); 
    waitKey(0); 
    return 0; 
}

2.3 结果:

只需要弄前3个参数就好,一般只需要做裁剪就好了 

3. 图像翻转

3.1 flip函数介绍(翻转)

void cv::flip(InputArray src, OutputArray dst, int flipCode )

src:输入图像。
dst:输出图像,与 src 具有相同的大小、数据类型及通道数。
flipCode:翻转方式标志。数值大于 0 表示绕 y 轴进行翻转;数值等于 0,表示绕 x 轴进行翻转;数值小于 0,表示绕两个轴翻转。

3.2 代码示例:

#include <opencv2/opencv.hpp> 
#include <iostream> 

using namespace std; 
using namespace cv; 

int main() 
{ 
    Mat img = imread("../pic/gril_1.jpg"); 
    if (img.empty()) 
    { 
        cout << "请确认图像文件名称是否正确" << endl; 
        return -1; 
    } 
    
    Mat img_x, img_y, img_xy; 
    flip(img, img_x, 0); //以 x 轴对称
    flip(img, img_y, 1); //以 y 轴对称
    flip(img, img_xy, -1); //先以 x 轴对称,再以 y 轴对称
    imshow("img", img); 
    imshow("img_x", img_x); 
    imshow("img_y", img_y); 
    imshow("img_xy", img_xy); 
    waitKey(0); 
    return 0; 
}

3.3 结果:

4. 图像仿射变换

        在 OpenCV 4 中并没有专门用于图像旋转的函数,而是通过图像的仿射变换实现图像的旋转。实现图像的旋转,首先需要确定旋转角度和旋转中心,之后确定旋转矩阵,最终通过仿射变换实现图像旋转。针对这个流程,OpenCV 4 提供了 getRotationMatrix2D() 函数用于计算旋转矩阵,提供了 warpAffine() 函数用于实现图像的仿射变换。下面首先介绍用于计算旋转矩阵的 getRotationMatrix2D() 函数,

4.1 getRotationMatrix2D()函数介绍

Mat cv::getRotationMatrix2D (Point2f center, double angle, double scale )

center:图像旋转的中心位置。
angle:图像旋转的角度,单位为度,正值为逆时针旋转。
scale:两个轴的比例因子,可以实现旋转过程中的图像缩放,不缩放则输入1。

        该函数输入旋转角度和旋转中心,返回图像旋转矩阵,返回值的数据类型为 Mat 类,是一个2× 3 的矩阵。如果我们已知图像旋转矩阵,那么可以自己生成旋转矩阵而不调用该函数。该函数生成的旋转矩阵与旋转角度和旋转中心的关系如下:

4.2  warpAffine()函数介绍

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:仿射变换后输出图像,与src数据类型相同,尺寸与dsize相同。
M:  2×3的变换矩阵。
dsize:输出图像的尺寸。
flags:插值方法标志,。
borderMode:像素边界外推方法标志。
borderValue:填充边界使用的数值,默认情况下为0。

                                                                 插值方法标志

                                                            边界外推的方法标志

        这个函数使用仿射变换来将输入图像映射到输出图像。仿射变换包括旋转、缩放、平移等操作,但不包括扭曲和剪切。这个函数非常有用,特别是在需要将图像映射到另一个大小或以特定方式旋转或倾斜图像时。 

        仿射变换又称为三点变换。如果知道变换前后两幅图像中 3 个像素点坐标的对应关系,就可以 求得仿射变换中的变换矩阵 MOpenCV 4 提供了利用 3 个对应像素点来确定变换矩阵 M 的函数 getAffineTransform(),

warpAffine函数可以参考:

【C++】【Opencv】cv::warpAffine()仿射变换函数详解,实现平移、缩放和旋转等功能-CSDN博客

4.3 getAffineTransform函数介绍

Mat cv::getAffineTransform(const Point2f src[], const Point2f dst[] )

src[]:源图像中的 3 个像素坐标。

dst[] :目标图像中的 3 个像素坐标
该函数两个输入量都是存放浮点坐标的数组,在生成数组的时候,与像素点的输入顺序无关, 但是需要保证像素点的对应关系,函数的返回值是一个 2 × 3 的变换矩阵。有了前面变换矩阵的取,就可以利用 warpAffine() 函数实现矩阵的仿射变换。

4.4 代码示例:

#include <opencv2/opencv.hpp> 
#include <iostream> 
#include <vector> 

using namespace std; 
using namespace cv; 

int main() 
{ 
    Mat img = imread("../pic/gril_1.jpg"); 
    if (img.empty()) 
    { 
        cout << "请确认图像文件名称是否正确" << endl; 
        return -1; 
    } 
    
    Mat rotation0, rotation1, img_warp0, img_warp1,img_warp2; 
    double angle = -90; //设置图像旋转的角度
    Size dst_size(img.rows, img.cols); //设置输出图像的尺寸
    Point2f center(img.rows / 2.0, img.cols / 2.0); //设置图像的旋转中心
    rotation0 = getRotationMatrix2D(center, angle, 1); //计算仿射变换矩阵

    //cout << "rotation0 : \n" <<  rotation0 << endl;
    warpAffine(img, img_warp0, rotation0, dst_size); //进行仿射变换
    imshow("img_warp0", img_warp0); 
    //根据定义的 3 个点进行仿射变换
    Point2f src_points[3]; 
    Point2f dst_points[3]; 
    src_points[0] = Point2f(0, 0); //原始图像中的 3 个点
    src_points[1] = Point2f(0, (float)(img.cols - 1)); 
    src_points[2] = Point2f((float)(img.rows - 1), (float)(img.cols - 1)); 
    //仿射变换后图像中的 3 个点
    dst_points[0] = Point2f((float)(img.rows)*0.1, (float)(img.cols)*0.10); 
    dst_points[1] = Point2f((float)(img.rows)*0.1, (float)(img.cols)*0.80); 
    dst_points[2] = Point2f((float)(img.rows)*0.81, (float)(img.cols)*0.8); 
    rotation1 = getAffineTransform(src_points, dst_points); //根据对应点求取仿射变换矩阵
    //cout << "rotation1 : \n" <<  rotation1 << endl;
    warpAffine(img, img_warp1, rotation1, dst_size); //进行仿射变换
    warpAffine(img, img_warp2, rotation1, dst_size,WARP_FILL_OUTLIERS,BORDER_REPLICATE,10); //进行仿射变换
    imshow("img_warp1", img_warp1); 
    imshow("img_warp2", img_warp2); 
    waitKey(0); 
    return 0; 
}

 4.5 结果:

5. 图像透视变换

        透视变换是按照物体成像投影规律进行变换,即将物体重新投影到新的成像平面,示意图如下图所示。透视变换常用于机器人视觉导航研究中,由于相机视场与地面存在倾斜角使得物体成像产生畸变,通常通过透视变换实现对物体图像的校正。在透视变换中,透视前的图像和透视后的图像之间的变换关系可以用一个 3×3 的变换矩阵表示,该矩阵可以通过两幅图像中 4 个对应点的坐标求取,因此透视变换又称作“四点变换”。与仿射变换一样,OpenCV 4 中提供了根据 4 个对应点求取变换矩阵的 getPerspectiveTransform()函数和进行透视变换的 warpPerspective()函数。

透视变换原理示意图 

5.1 getPerspectiveTransform()函数原型

Mat cv::getPerspectiveTransform (const Point2f src[], const Point2f dst[], int solveMethod = DECOMP_LU )

src[]:原图像中的 4 个像素坐标。
dst[]:目标图像中的 4 个像素坐标。
solveMethod:选择计算透视变换矩阵方法的标志

        该函数两个输入量都是存放浮点坐标的数组,在生成数组的时候,与像素点的输入顺序无关, 但是需要注意像素点的对应关系。该函数的返回值是一个 3 × 3 的变换矩阵。该函数中最后一个参 数是根据 4 个对应点坐标计算透视变换矩阵方法的选择标志。
getPerspectiveTransform() 函数计算方法标志

5.2 warpPerspective()函数原型

void cv::warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT,const Scalar & borderValue = Scalar() )

src:输入图像。
dst:透视变换后输出图像,与 src 数据类型相同,但是尺寸与 dsize 相同。
M:3×3 的变换矩阵。
dsize:输出图像的尺寸。
flags:插值方法标志。
borderMode:像素边界外推方法的标志。
borderValue:填充边界使用的数值,默认情况下为 0。

        该函数所有参数的含义与 warpAffine()函数的参数含义相同,这里不再赘述 。示例代码中平面图片经过透视变换有立体感觉。

5.3 示例代码

#include <opencv2/opencv.hpp> 
#include <iostream> 

using namespace cv; 
using namespace std; 

int main() 
{ 
    Mat img = imread("../pic/gril.jpg"); 
    if (img.empty()) 
    { 
        cout << "请确认图像文件名称是否正确" << endl; 
        return -1; 
    } 
    
    Point2f src_points[4]; 
    Point2f dst_points[4]; 
    //通过 Image Watch 查看的二维码 4 个角点坐标
    src_points[0] = Point2f(0, 0); 
    src_points[1] = Point2f(647, 0); 
    src_points[2] = Point2f(0, 324); 
    src_points[3] = Point2f(647, 324); 
    //期望透视变换后二维码 4 个角点的坐标
    dst_points[0] = Point2f(100.0, 150.0); 
    dst_points[1] = Point2f(527.0, 150.0); 
    dst_points[2] = Point2f(0.0, 300); 
    dst_points[3] = Point2f(627.0, 300); 
    Mat rotation, img_warp; 
    rotation = getPerspectiveTransform(src_points, dst_points); //计算透视变换矩阵
    warpPerspective(img, img_warp, rotation, img.size()); //透视变换投影
    imshow("img", img); 
    imshow("img_warp", img_warp); 
    waitKey(0);
    return 0;
}

5.4 结果

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值