摘要

图像几何变换又称为图像空间变换, 它将一幅图像中的坐标位置映射到另一幅图像中的新坐标位置。几何变换不改变图像的像素值, 只是在图像平面上进行像素的重新安排。

几何变换大致分为仿射变换、投影变换、极坐标变换,完成几何变换需要两个独立的算法过程:

1、一个用来实现空间坐标变换的算法,用它描述每个像素如何从初始位置移动到终止位置

2、一个插值算法完成输出图像的每个像素的灰度值


放射变换

首先,先来分析一下放射变换的原理:

  • 什么是放射变换?

仿射变换是从一个二维坐标系变换到另一个二维坐标系,属于线性变换。通过已知3对坐标点可以求得变换矩阵。(不共线的三对对应点,决定了唯一的变换矩阵)

 其中

就是仿射变换矩阵一般形式,根据不同的变换,比如平移、缩放、旋转等等,仿射变换矩阵的值是不一样的。

1、平移

假设空间坐标 (x,y)先沿 x轴平移tx ,在沿 y轴平移 ty,则变换后的坐标为(x+tx,y+ty) ,此时平移变换为:

2、缩放

二维空间坐标 (x,y) 以任意一点 (x0,y0) 为中心在水平方向和垂直方向上分别缩放 sx和 sy倍,缩放后坐标为 (x0+sx (x-x0),y0+sy(y-y0)) ,通俗来讲就是缩放后的坐标离中心点的水平距离变为原坐标离中心点水平距离的 sx 倍。

当 (x0,y0) 为原点 (0,0) 时,缩放变换可以表示为:

当 (x0,y0) 为任意点 时,变换过程理解为,先将中心点平移到原点,再以原点为中心进行缩放,然后移回到原来的中心点。缩放变换表示为:

注意:等式右边的运算应该从右往左看

 3、旋转

顺时针绕原点(0,0)旋转变换的矩阵表示为:

若以任意一点 (x0,y0) 为中心旋转,相当于先将原点移动到旋转中心,然后绕原点旋转,最后移回坐标原点,用矩阵表示为:

注意:上面的运算顺序是从右向左的。

OpenCV提供的旋转函数,实现顺时针90°、180°、270°的旋转

rotate(InputArray src, Output dst, int rotateCode)

rotateCode有以下取值:
ROTATE_90_CLOCKWISE //顺时针旋转90度
ROTATE_180 //顺时针旋转180度
ROTATE_90_COUNTERCLOCKWISE //逆时针旋转90度
需要注意的是OpenCV还有一个函数为flip(src, dst, int flipCode)实现了图像的水平镜像、垂直镜像和逆时针旋转180°,不过并不是通过仿射变换实现的,而是通过行列互换,它与rotate()transpose()函数一样都在core.hpp头文件中。
  • 求解放射变换矩阵

以上都是知道变换前坐标求变换后的坐标,如果我们已经知道了变换前的坐标和变换后的坐标,想求出仿射变换矩阵,可以通过解方程法或矩阵法。

解方程法

由于仿射变换矩阵

有6个未知数,所以我们只需三组坐标列出六个方程即可。OpenCV提供函数getAffineTransform(src, dst)通过方程法求解,其中src和dst分别为前后坐标。

对于C++来说,一种方式是将坐标存在Point2f数组中,另一种方法是保存在Mat中:

// 第一种方法
Point2f src1[] = {Pointy2f(0, 0), Point2f(200, 0), Point2f(0, 200)};
Point2f dst1[] = {Pointy2f(0, 0), Point2f(100, 0), Point2f(0, 100)};
// 第二种方法
Mat src2 = (Mat_<float>(3, 2) << 0, 0, 200, 0, 0, 200);
Mat dst2 = (Mat_<float>(3, 2) << 0, 0, 100, 0, 0, 100); Mat A = getAffineTransform(src1, dst1);

矩阵法

对于等比例缩放的仿射变换,OpenCV提供函数getRotationMatrix2D(center, angle, scale)来计算矩阵,center是变换中心;angle是逆时针旋转的角度,(opencv中正角度代表逆时针旋转);scale是等比例缩放的系数。

我们通过下面的代码来定义这些参数,例如:

Point center = Point( src.cols/2, src.rows/2 ); //中心点
double angle = -50.0;
double scale = 0.6;
  • 插值算法分析

在运算中,我们可能会遇到目标坐标有小数的情况,比如将坐标(3,3)缩放2倍变为了(1.5,1.5),但是对于图像来说并没有这个点,这时候我们就要用周围坐标的值来估算此位置的颜色,也就是插值。

(1)最近邻插值(INTER_NEAREST)

最近邻插值就是从(x,y)的四个相邻坐标中找到最近的那个来当作它的值,如(2.3,4.7),它的相邻坐标分别为(2,4)、(3,4)、(2,5)、(3,5),计算这几个相邻坐标与(2.3,4.7)坐标的距离,若最近的为(2,5),则取(2,5)的颜色值为的(2.3,4.7)值。

此种方法得到的图像会出现锯齿状外观,对于放大图像则更明显。

(2)双线性插值(INTER_LINEAR)(最常用)

要估计输入图像非整数坐标  的值,分为三个步骤:

第一步:先用线性关系估计输入图像中  的值

            

第二步:同理用线性关系估计输入图像中  的值

第三步:根据前两步得到的值,用线性关系估计输入图像中  的值

  • 进行放射变换

将刚刚求得的仿射变换矩阵应用到原图像,OpenCV提供函数warpAffine进行放射变换。

warpAffine(src,dst,M,dsize,flags,bordMode, borderValue)
//src:输入图像
//dst:输出变换后图像,需要初始化一个空矩阵用来保存结果,不用设定矩阵尺寸
//M:2行3列的仿射变换矩阵
//dsize: 二元元组(宽,高),代表输出图像大小
//flags: 插值法 INTER_NEAREST、INTER_LINEAR(默认线性插值)
//bordMode: 边界像素模式,默认值BORDER_CONSTANT
//bordValue: 当填充模式为BORDER_CONSTANT时的填充值(默认为Scalar(),即0)
  • opencv实现放射变换

Mat src, dst_warp1, dst_warp2;
Point2f srcPoints[3];//原图中的三点 (一个包含三维点(x,y)的数组,其中x、y是浮点型数)
Point2f dstPoints[3];//目标图中的三点 int main(int argc, char** argv)
{
src = imread("D:/opencv练习图片/薛之谦.jpg");
namedWindow("SrcImage");
imshow("SrcImage", src);
//第一种仿射变换的调用方式:三点法
//三个点对的值,上面也说了,只要知道你想要变换后图的三个点的坐标,就可以实现仿射变换
srcPoints[0] = Point2f(0, 0);
srcPoints[1] = Point2f(0, src.rows);
srcPoints[2] = Point2f(src.cols, 0);
//映射后的三个坐标值
dstPoints[0] = Point2f(0, src.rows*0.3);
dstPoints[1] = Point2f(src.cols*0.25, src.rows*0.75);
dstPoints[2] = Point2f(src.cols*0.75, src.rows*0.25);
Mat M1 = getAffineTransform(srcPoints, dstPoints);//由三个点对计算变换矩阵
warpAffine(src, dst_warp1, M1, src.size());//仿射变换 //第二种仿射变换的调用方式:直接指定角度和比例
//旋转加缩放
Point2f center(src.cols / 2, src.rows / 2);//旋转中心
double angle = 45;//逆时针旋转45度
double scale = 0.5;//缩放比例
Mat M2 = getRotationMatrix2D(center, angle, scale);//计算旋转加缩放的变换矩阵
warpAffine(src, dst_warp2, M2, Size(src.cols, src.rows), INTER_LINEAR);//仿射变换
imshow("第一种放射变换", dst_warp1);
imshow("第二种放射变换", dst_warp2);
waitKey(0);
return 0;
}


投影变换(透视变换)

如果物体在三维空间中发生旋转,这种变换通常成为投影变换。
投影变换的API和仿射变换的API很像,原理也相同,不同的只是由之前的三点变为四点法求投影变换矩阵,变换矩阵也由2*3变为3*3;可以通过以下公式表述:
                                                                                

OpenCV提供函数getPerspectiveTransform(src, dst)实现求解投影矩阵,需要输入四组对应的坐标。

getPerspectiveTransform(const Point2f* src, const Point2f* dst)
//参数const Point2f* src:原图的四个固定顶点
//参数const Point2f* dst:目标图像的四个固定顶点
//返回值:Mat型变换矩阵,可直接用于warpAffine()函数
//注意,顶点数组长度超4个,则会自动以前4个为变换顶点;数组可用Point2f[]或Point2f*表示

OpenCV提供函数warpPerspective实现投影变换,参数说明和仿射变化类似。

warpPerspective(src,dst,M,dsize,flags,bordMode, borderValue)
//src:输入图像
//dst:输出变换后图像,需要初始化一个空矩阵用来保存结果,不用设定矩阵尺寸
//M:2行3列的仿射变换矩阵
//dsize: 二元元组(宽,高),代表输出图像大小
//flags: 插值法 INTER_NEAREST、INTER_LINEAR(默认线性插值)
//bordMode: 边界像素模式,默认值BORDER_CONSTANT
//bordValue: 当填充模式为BORDER_CONSTANT时的填充值(默认为Scalar(),即0)

 opencv实现投影变换:

Mat src, dst_warp1;
Point2f srcPoints[4];//原图中的三点 (一个包含三维点(x,y)的数组,其中x、y是浮点型数)
Point2f dstPoints[4];//目标图中的三点 int main(int argc, char** argv)
{
src = imread("D:/opencv练习图片/薛之谦.jpg");
namedWindow("SrcImage");
imshow("SrcImage", src);
//变换前四点坐标
srcPoints[0] = Point2f(0, 0);
srcPoints[1] = Point2f(0, src.rows);
srcPoints[2] = Point2f(src.cols, 0);
srcPoints[3] = Point2f(src.cols, src.rows);
//变换后的四点坐标
dstPoints[0] = Point2f(src.cols*0.1, src.rows*0.1);
dstPoints[1] = Point2f(0, src.rows);
dstPoints[2] = Point2f(src.cols, 0);
dstPoints[3] = Point2f(src.cols*0.7, src.rows*0.8);
Mat M1 = getPerspectiveTransform(srcPoints, dstPoints);//由四个点对计算透视变换矩阵
warpPerspective(src, dst_warp1, M1, src.size());//投影变换
imshow("投影变换(四点法)", dst_warp1);
waitKey(0);
return 0;
}


极坐标变换

通常通过极坐标变化校正图像中的圆形物体或包含在圆环中的物体。

OpenCV 中提供了warpPolar()函数用于实现图像的极坐标变换,该函数的函数原型如下:

warpPolar( src, dst, Size dsize, Point2f center, double maxRadius, int flags )
  • src:原图像,可以是灰度图像或者彩色图像
  • dst:极坐标变换后输出图像(与原图像具有相同的数据类型和通道数)。

  • dsize:输出图像大小。(自行决定)

  • center:极坐标变换时极坐标原点在原图像中的位置。

  • maxRadius:变换时边界圆的半径,它也决定了逆变换时的比例参数。

  • flags: 插值方法与极坐标映射方法标志,两个方法之间通过“+”或者“|”号进行连接。

warpPolar()函数极坐标映射方法标志:

WARP_POLAR_LINEAR     //极坐标正变换(直角坐标变换到极坐标)
WARP_POLAR_LOG //半对数极坐标变换
WARP_INVERSE_MAP //逆变换(极坐标变换到直角坐标)

opencv实现极坐标变换:

Mat src, dst;
int main(int argc, char** argv)
{
src = imread("D:/opencv练习图片/环形字符.jpg");
namedWindow("SrcImage");
imshow("SrcImage", src);
Point2f center = Point2f(src.cols / 2, src.rows / 2); //极坐标在图像中的原点
// 圆的半径
double maxRadius = min(center.y, center.x);
//正极坐标变换
warpPolar(src, dst, Size(200, 500), center, maxRadius, INTER_LINEAR | WARP_POLAR_LINEAR);
// 改变结果方向
rotate(dst, dst, ROTATE_90_COUNTERCLOCKWISE);
imshow("极坐标变换", dst);
waitKey(0);
return 0;
}

参考链接:(8条消息) 【OpenCV学习笔记】之仿射变换(Affine Transformation)_zhu_hongji的博客-CSDN博客_仿射变换

【从零学习OpenCV 4】图像极坐标变换 - 知乎 (zhihu.com)

OpenCV算法学习笔记之几何变换 - 简书 (jianshu.com)

opencv——几何变换原理与实现的更多相关文章

  1. OpenCV SIFT原理与源码分析

    http://blog.csdn.net/xiaowei_cqu/article/details/8069548 SIFT简介 Scale Invariant Feature Transform,尺度 ...

  2. opencv 边缘检测原理

    只是实现一下,暂不考虑效率 import cv2 as cv import numpy as np import math # 从源码层面实现边缘检测 img = cv.imread('../imag ...

  3. OpenCV图像金字塔

    图像金字塔 目标 本文档尝试解答如下问题: 如何使用OpenCV函数 pyrUp 和 pyrDown 对图像进行向上和向下采样. 原理 Note 以下内容来自于Bradski和Kaehler的大作:  ...

  4. Opencv笔记(十五)——图像金字塔

    参考文献 目标 学习图像金字塔 学习函数cv2.pyrUp()和cv2.pyrDown() 原理 当我们需要将图像转换到另一个尺寸的时候, 有两种可能,一种是放大图像,另一种是缩小图像.尽管在Open ...

  5. 学习OpenCV第0天

    自2011年接触OpenCV已经有几年了,一直停留在写一些小程序,利用手冊完毕一些任务,一直没有深入研究当中代码,现在毕业,但各种原因未能进入图像处理行业,故现重学OpenCV,包含分析代码,学习算法 ...

  6. Python使用opencv

    Python配置opencv 原理 Python调用opencv的原理是:opencv编译出共享库文件,python把这个共享库文件作为一个模块加载并使用. 通俗点就是,编译opencv的时候开启py ...

  7. OpenCV实现人脸检测

    OpenCV实现人脸检测(转载)  原文链接:https://www.cnblogs.com/mengdd/archive/2012/08/01/2619043.html 本文介绍最基本的用OpenC ...

  8. opencv-图像金字塔

    图像金字塔 目标 原理摘自:http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/pyramids/pyramids. ...

  9. 基于OpenCV进行图像拼接原理解析和编码实现(提纲 代码和具体内容在课件中)

    一.背景 1.1概念定义 我们这里想要实现的图像拼接,既不是如题图1和2这样的"图片艺术拼接",也不是如图3这样的"显示拼接",而是实现类似"BaiD ...

随机推荐

  1. 对象存储服务-Minio

    Mino 目录 Mino 对象存储服务 Minio 参考 Minio 架构 为什么要用 Minio 存储机制 纠删码 MinIO概念 部署 单机部署: Docker 部署Minio 分布式Minio ...

  2. 优化 ASP.NET Core Docker 镜像的大小

    在这容器化的世界里,我们已经很少直接通过文件发布来运行asp.net core程序了.现在大多数情况下,我们都会使用docker来运行程序.在使用docker之前,我们往往需要打包我们的应用程序.as ...

  3. Java学习之路 -- Java怎么学?

    @ 目录 java基础怎么学? 学完基础学什么? 几个常用框架学完学什么? MQ JVM的知识跑不掉 微服务等等 其他 数据结构和算法 java基础怎么学? 当时,作为懵懂的小白,大一学习了c和c++ ...

  4. 干货!Apache Hudi如何智能处理小文件问题

    1. 引入 Apache Hudi是一个流行的开源的数据湖框架,Hudi提供的一个非常重要的特性是自动管理文件大小,而不用用户干预.大量的小文件将会导致很差的查询分析性能,因为查询引擎执行查询时需要进 ...

  5. mvn 报错 - The POM for <name> is invalid, transitive dependencies (if any) will not be available

    核心:  通过 mvn dependency:tree -X 分析依赖解决方案:  解决依赖冲突版本 1. MILGpController 编译突然报错 14:10:28 [ERROR] Failed ...

  6. 【转载】C# get 与set的一些说明

    转载 在面向对象编程(OOP)中,是不允许外界直接对类的成员变量直接访问的,既然不能访问,那定义这些成员变量还有什么意义呢?所以C#中就要用set和get方法来访问私有成员变量,它们相当于外界访问对象 ...

  7. spring-cloud-consul 服务注册发现与配置

    下面是 Spring Cloud 支持的服务发现软件以及特性对比(Eureka 已停止更新,取而代之的是 Consul): Feature euerka Consul zookeeper etcd 服 ...

  8. Java实现十个经典排序算法(带动态效果图)

    前言 排序算法是老生常谈的了,但是在面试中也有会被问到,例如有时候,在考察算法能力的时候,不让你写算法,就让你描述一下,某个排序算法的思想以及时间复杂度或空间复杂度.我就遇到过,直接问快排的,所以这次 ...

  9. day11.迭代器与生成器

    一.迭代器 1.迭代器即用来迭代取值的工具,而迭代是重复反馈过程的活动,其目的通常是为了逼近所需的目标或结果,每一次对过程的重复称为一次"迭代",而每一次迭代得到的结果会作为下一次 ...

  10. Mybatis3源码笔记(三)Configuration

    1. XMLConfigBuilder 上一篇大致介绍了SqlSession的生成.在DefaultSqlSessionFactory的构造函数中就提到了Configuration这个对象.现在我们来 ...