摘要

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

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

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. P1048_采药(JAVA语言)

    思路:动态规划的背包问题.把时间看作重量,转换为01背包问题求解. 题目描述 辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师.为此,他想拜附近最有威望的医师为师.医师为了判断他的资质,给他出 ...

  2. 剪切DOM节点中断transition执行【问题】

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  3. 攻防世界 reverse 进阶 8-The_Maya_Society Hack.lu-2017

    8.The_Maya_Society Hack.lu-2017 在linux下将时间调整为2012-12-21,运行即可得到flag. 下面进行分析 1 signed __int64 __fastca ...

  4. Python3实现短信轰炸机

    短信轰炸机的基本原理:利用某些限制不严格的网站短信注册接口,用Python模拟请求,传入被炸人手机号码,实现轰炸 实现方式:利用requests模块.time模块.完成请求模拟 模块安装: 在终端窗口 ...

  5. irace package -- 参数调优神器

    目录 1. irace 是什么 2. 安装 irace 3. irace 的运行机制 4. irace 的配置环境 4.1. parameters 4.2. target algorithm runn ...

  6. jasypt-spring-boot提示Failed to bind properties

    1 问题描述 在Spring Boot中使用jasypt-spring-boot进行加密,但是提示: Description: Failed to bind properties under 'spr ...

  7. Molar mass UVA - 1586

    ​ An organic compound is any member of a large class of chemical compounds whose molecules contain c ...

  8. Python Basics with numpy (optional)

    Python Basics with Numpy (optional assignment) Welcome to your first assignment. This exercise gives ...

  9. IDEA中集成Git

    一.新建项目,绑定GIT 1.新建spring boot项目 2.路径选择git本地文件地址 3.新的项目文件绑定git,将远程的git文件拷贝至项目中  二.修改文件,使用IDEA操作GIT 1.提 ...

  10. 【Java基础】ConcurrentHashMap为什么不能存null键和null值

    代码如下 /** * 测试ConcurrentHashMap null键和null值的问题 * @return */ @RequestMapping(value = "/get_nacos& ...