OpenCV学习笔记:图像变换

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

本文是我之前在微信公众号上的一篇文章记录。原链接为:# OpenCV学习笔记:图像变换

图像连接

在OpenCV中提供了两个接口用于图形连接,分别是水平连接和垂直连接。连接的两个或者多个图形必须具备相同的高度(水平连接)或者宽度(垂直连接)。函数原型如下:

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

void cv::hconcat(const Mat *src, size_t nsrc, OutputArray dst);
void cv::hconcat(InputArray src1, InputArray src2, OutputArray dst);
复制代码

需要注意的是vconcat函数要求前两个参数具有相同的宽度、数据类型和通道数,不然无法进行横向连接。

hconcat函数要求前两个参数具有相同的高度、数据类型和通道数,不然无法进行横向连接。

示例程序如下:

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

using namespace std;


void testConcat()
{
    std::string img_path = "car.png";

    cv::Mat img1 = cv::imread(img_path, cv::IMREAD_COLOR);
    cv::Mat img2 = cv::imread(img_path, cv::IMREAD_COLOR);

    cv::Mat Array[] = {img1, img2};
    cv::Mat vout;
    cv::vconcat(Array, 2, vout);
    cv::imshow("vout", vout);

    cv::Mat vout2;
    cv::vconcat(img1, img2, vout2);
    cv::imshow("vout2", vout2);


    cv::Mat hout;
    cv::hconcat(Array, 2, hout);
    cv::imshow("hout", hout);

    cv::Mat hout2;
    cv::hconcat(img1, img2, hout2);
    cv::imshow("hout2", hout2);


    cv::waitKey(0); 
}

int main()
{
    testConcat();
    return 0;
}
复制代码

程序输出:

垂直连接

水平连接

图像翻转

opencv flip()函数翻转一个二维的矩阵,包含垂直翻转,水平翻转,以及垂直水平翻转,函数原型如下:

void flip(InputArray src, OutputArray dst, int flipCode);
复制代码
  • flipCode =0,垂直翻转图像,是源图像的top-left(左上)和bottom-left(左下)的交换,也就是说左上变成了左下,左下变成了左上,这是一个典型在的Microsoft Windows操作系统里的视频处理操作。

  • flipCode > 0,图像的水平翻转,随后的水平位移和绝对方差计算,是为了检查图像是否y轴对称。

  • (flipCode < 0),图像同时垂直翻转和水平翻转,是为了以后的位移和绝对方差计算去检查图像是否是中心对称。

示例程序如下:

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

using namespace std;


void testFlip()
{
    std::string img_path = "car.png";
    cv::Mat img = cv::imread(img_path, cv::IMREAD_COLOR);
 
    cv::Mat img_x, img_y, img_xy;

    cv::flip(img, img_x, 0);
    cv::flip(img, img_y, 1);
    cv::flip(img, img_xy, -1);

    cv::imshow("img", img);
    cv::imshow("img_x", img_x);
    cv::imshow("img_y", img_y);
    cv::imshow("img_xy", img_xy);

    cv::waitKey(0); 
}

int main()
{
    testFlip();
    return 0;
}
复制代码

程序输出如下:

image-20210623212845814

图像仿射变换

一般对图像的变化操作有放大、缩小、扭曲、旋转等,统称为几何变换,对一个图像的图像变换主要有两大步骤,一是实现空间坐标的转换,就是使图像从初始位置到终止位置的移动。二是使用一个插值的算法完成输出图像的每个像素的灰度值。其中主要的图像变换有:仿射变换和透视变换。

仿射变换可以将矩形转换成平行四边形,它可以将矩形的边压扁但必须保持边是平行的,也可以将矩形旋转或者按比例变化。透视变换提供了更大的灵活性,一个透视变换可以将矩阵转变成梯形。当然,平行四边形也是梯形,所以仿射变换是透视变换的子集。

OpenCV通过两个函数的组合使用来实现仿射变换,也就是仿射变换的两步:

第一步:获取仿射映射矩阵(两种):

getRotationMatrix2D函数原型

Mat cv::getRotationMatrix2D(Point2f center, double angle, double scale)   
复制代码

. center: Point2f类型,表示原图像的旋转中心 . angle: double类型,表示图像旋转角度,角度为正则表示逆时针旋转,角度为负表示逆时针旋转(坐标原点是图像左上角) . scale: 缩放系数

getAffineTransform函数原型:

//三点法
Mat M1=getAffineTransform(const Point2f* src, const Point2f* dst)
复制代码
  • src:原图的三个固定顶点
  • dst:目标图像的三个固定顶点
  • 返回值:Mat型变换矩阵,可直接用于warpAffine()函数
  • 注意,顶点数组长度超过3个,则会自动以前3个为变换顶点;数组可用Point2f[]或Point2f*表示

第二步:进行仿射变换

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: 输出图像,尺寸由dsize指定,图像类型与原图像一致 . M: 2X3的变换矩阵 . dsize: 指定图像输出尺寸 . flags: 插值算法标识符,有默认值INTER_LINEAR,如果插值算法为WARP_INVERSE_MAP, warpAffine函数使用如下矩阵进行图像转换

废话不多说,直接看下面程序,主要实现4个功能:

  • 以某个中心点进行旋转和缩放
  • 以三个点进行仿射变换(因为要求保持平行,三个点够了)
  • 变换平移
  • 仿射变换—翻转、镜像

后面这三个都是使用三点法进行操作,只是修改了变换后图的三个点的坐标即可实现不一样的变换。

#include <iostream>

#include "opencv2/opencv.hpp"

using namespace std;

void testAffineTransform() {
    std::string img_path = "car.png";
    cv::Mat src = cv::imread(img_path, cv::IMREAD_COLOR);

    // 1.中心点进行旋转和缩放
    double angle = 30;   // 逆时针旋转xx度
    double scale = 0.8;  // 缩放比例
    cv::Point2f centerPoint(src.cols / 2, src.rows / 2);  // 旋转中心
    cv::Mat out_img = cv::getRotationMatrix2D(centerPoint, angle, scale);

    cv::Mat imgWarp;
    cv::Size dstSize(src.cols, src.rows);
    cv::warpAffine(src, imgWarp, out_img, dstSize);

    cv::imshow("src", src);
    cv::imshow("旋转加缩放", imgWarp);

    // 2.仿射变换的调用方式:三点法
    cv::Point2f srcPoints[3];  // 原图中的三点
    // ,一个包含三维点(x,y)的数组,其中x、y是浮点型数
    cv::Point2f dstPoints[3];  // 目标图中的三点

    // 三个点对的值,只要知道你想要变换后图的三个点的坐标,就可以实现仿射变换
    srcPoints[0] = cv::Point2f(0, 0);         // left top
    srcPoints[1] = cv::Point2f(0, src.rows);  // left bottom
    srcPoints[2] = cv::Point2f(src.cols, 0);  // right top

    // 映射后的三个坐标值
    dstPoints[0] = cv::Point2f(0, src.rows * 0.3);
    dstPoints[1] = cv::Point2f(src.cols * 0.25, src.rows * 0.75);
    dstPoints[2] = cv::Point2f(src.cols * 0.75, src.rows * 0.25);

    cv::Mat M1 =
        cv::getAffineTransform(srcPoints, dstPoints);  // 由三个点对计算变换矩阵
    cv::Mat dst_warp;
    warpAffine(src, dst_warp, M1, src.size());  // 仿射变换
    cv::imshow("三点法变换", dst_warp);

    // 3.变换平移
    cv::Point2f dstPoints1[3];  // 目标图中的三点
    dstPoints1[0] = cv::Point2i(src.cols / 4, 0);
    dstPoints1[1] = cv::Point2i(src.cols / 4, src.rows);
    dstPoints1[2] = cv::Point2i(src.cols + src.cols / 4, 0);

    cv::Mat M3 = cv::getAffineTransform(srcPoints, dstPoints1);
    cv::Mat dst_warpTransformation;
    cv::warpAffine(src, dst_warpTransformation, M3,
                   cv::Size(src.cols + src.cols / 4, src.rows));
    cv::imshow("变换平移", dst_warpTransformation);

    // 4.仿射变换—翻转、镜像
    cv::Point2f dstPoints2[3];  // 目标图中的三点
    dstPoints2[0] = cv::Point2i(src.cols, 0);
    dstPoints2[1] = cv::Point2i(src.cols, src.rows);
    dstPoints2[2] = cv::Point2i(0, 0);

    cv::Mat M4 = cv::getAffineTransform(srcPoints, dstPoints2);
    cv::Mat dst_warpFlip;
    cv::warpAffine(src, dst_warpFlip, M4, cv::Size(src.cols, src.rows));
    cv::imshow("翻转镜像", dst_warpFlip);

    cv::waitKey(0);
}

int main() {
    testAffineTransform();
    return 0;
}
复制代码

程序输出:

image-20210626074400721

image-20210626074458180

图像透视变换

图像透视变换(Perspective Transformation)的本质是将图像从一个几何平面投影到另一个几何平面,透视变换保证同一条直线的点还是在同一条直线上,但不再保证平行了。因为这是一个二维图像经过一个三维变换,然后映射到另外一个二维空间,二维图像的二维空间与映射后的二维空间不一样,如果一样就是仿射变换。

透视变换的API和仿射变换的API很像,原理也相同,不同的只是由之前的三点变为四点法求透视变换矩阵,变换矩阵也由23变为33;OpenCV 4中提供了根据四个对应点求取变换矩阵的getPerspectiveTransform()函数和进行透视变换的warpPerspective()函数,函数原型如下:

Mat cv::getPerspectiveTransform(const Point2f src[], const Point2f dst[],
                                int solveMethod = DECOMP_LU)
复制代码
  • src[]:原图像中的四个像素坐标。
  • dst[]:目标图像中的四个像素坐标。
  • solveMethod:选择计算透视变换矩阵方法的标志,可以选择参数及含义在如下表:
标志参数 简记 作用
DECOMP_LU 0 最佳主轴元素的高斯消元法
DECOMP_SVD 1 奇异值分解(SVD)方法
DECOMP_EIG 2 特征值分解法
DECOMP_CHOLESKY 3 Cholesky分解法
DECOMP_QR 4 QR分解法
DECOMP_NORMAL 16 使用正规方程公式,可以去前面的标志一起使用

该函数两个输入量都是存放浮点坐标的数组,在生成数组的时候像素点的输入顺序无关,但是需要注意像素点的对应关系,函数的返回值是一个3×3的变换矩阵。函数中最后一个参数是根据四个对应点坐标计算透视变换矩阵方法的选择标志,其可以选择的参数标志在上面表格中给出,默认情况下选择的是最佳主轴元素的高斯消元法DECOMP_LU。

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

直接看程序了:

void testPerspectiveTransform() {
    std::string img_path = "car.png";
    cv::Mat src = cv::imread(img_path, cv::IMREAD_COLOR);
    cv::Point2f srcPoints[4];                        // 原图中的四点
    cv::Point2f dstPoints[4];                        // 目标图中的四点
    srcPoints[0] = cv::Point2f(0, 0);                // left top
    srcPoints[1] = cv::Point2f(0, src.rows);         // left bottom
    srcPoints[2] = cv::Point2f(src.cols, 0);         // right top
    srcPoints[3] = cv::Point2f(src.cols, src.rows);  // right bottom
    // 映射后的四个坐标值
    dstPoints[0] = cv::Point2f(src.cols * 0.1, src.rows * 0.1);
    dstPoints[1] = cv::Point2f(0, src.rows);
    dstPoints[2] = cv::Point2f(src.cols, 0);
    dstPoints[3] = cv::Point2f(src.cols * 0.7, src.rows * 0.8);
    // 由四个点对计算透视变换矩阵
    cv::Mat M1 = cv::getPerspectiveTransform(srcPoints, dstPoints);
    cv::Mat dst_warp;
    cv::warpPerspective(src, dst_warp, M1, src.size());
    cv::imshow("src", src);
    cv::imshow("透视变换", dst_warp);
    cv::waitKey(0);
}
复制代码

输出:

image-20210626083406349

程序2:

void testPerspectiveTransform2() {
    std::string img_path = "ma.jpeg";
    cv::Mat src = cv::imread(img_path, cv::IMREAD_COLOR);
    cv::Point2f srcPoints[4];  // 原图中的四点
    cv::Point2f dstPoints[4];  // 目标图中的四点
    srcPoints[0] = cv::Point2f(src.cols / 8.0, src.rows / 4.0);  // left top
    srcPoints[1] = cv::Point2f(0, src.rows * 3.0 / 4.0);         // left bottom
    srcPoints[2] =
        cv::Point2f(src.cols * 7.0 / 8.0, src.rows / 4.0);       // right top
    srcPoints[3] = cv::Point2f(src.cols, src.rows * 3.0 / 4.0);  // right bottom
    // 映射后的四个坐标值
    dstPoints[0] = cv::Point2f(0, src.rows / 4.0);
    dstPoints[1] = cv::Point2f(0, src.rows * 3.0 / 4.0);
    dstPoints[2] = cv::Point2f(src.cols, src.rows / 4.0);
    dstPoints[3] = cv::Point2f(src.cols, src.rows * 3.0 / 4.0);
    // 由四个点对计算透视变换矩阵
    cv::Mat M1 = cv::getPerspectiveTransform(srcPoints, dstPoints);
    cv::Mat dst_warp;
    cv::warpPerspective(src, dst_warp, M1, src.size());
    cv::imshow("src", src);
    cv::imshow("透视变换", dst_warp);
    cv::waitKey(0);
}
复制代码

输出:

image-20210626083257996

猜你喜欢

转载自juejin.im/post/7105225040391569421