图像的几何变换是在不改变图像内容的前提下对图像像素的进行空间几何变换,主要包括了图像的平移变换、镜像变换、缩放和旋转等。本文首先介绍了图像几何变换的一些基本概念,然后再OpenCV2下实现了图像的平移变换、镜像变换、缩放以及旋转,最后介绍几何的组合变换(平移+缩放+旋转)。

1.几何变换的基本概念

1.1 坐标映射关系

图像的几何变换改变了像素的空间位置,建立一种原图像像素与变换后图像像素之间的映射关系,通过这种映射关系能够实现下面两种计算:

  1. 原图像任意像素计算该像素在变换后图像的坐标位置
  2. 变换后图像的任意像素在原图像的坐标位置

对于第一种计算,只要给出原图像上的任意像素坐标,都能通过对应的映射关系获得到该像素在变换后图像的坐标位置。将这种输入图像坐标映射到输出的过程称为“向前映射”。反过来,知道任意变换后图像上的像素坐标,计算其在原图像的像素坐标,将输出图像映射到输入的过程称为“向后映射”。但是,在使用向前映射处理几何变换时却有一些不足,通常会产生两个问题:映射不完全,映射重叠

  1. 映射不完全
    输入图像的像素总数小于输出图像,这样输出图像中的一些像素找不到在原图像中的映射。

    上图只有(0,0),(0,2),(2,0),(2,2)四个坐标根据映射关系在原图像中找到了相对应的像素,其余的12个坐标没有有效值。
  2. 映射重叠
    根据映射关系,输入图像的多个像素映射到输出图像的同一个像素上。

    上图左上角的四个像素(0,0),(0,1),(1,0),(1,1)都会映射到输出图像的(0,0)上,那么(0,0)究竟取那个像素值呢?

要解决上述两个问题可以使用“向后映射”,使用输出图像的坐标反过来推算改坐标对应于原图像中的坐标位置。这样,输出图像的每个像素都可以通过映射关系在原图像找到唯一对应的像素,而不会出现映射不完全和映射重叠。所以,一般使用向后映射来处理图像的几何变换。从上面也可以看出,向前映射之所以会出现问题,主要是由于图像像素的总数发生了变化,也就是图像的大小改变了。在一些图像大小不会发生变化的变换中,向前映射还是很有效的。

1.2.插值算法

对于数字图像而言,像素的坐标是离散型非负整数,但是在进行变换的过程中有可能产生浮点坐标值。例如,原图像坐标(9,9)在缩小一倍时会变成(4.5,4.5),这显然是一个无效的坐标。插值算法就是用来处理这些浮点坐标的。常见的插值算法有最邻近插值法、双线性插值法,二次立方插值法,三次立方插值法等。本文主要介绍最邻近插值和双线性插值,其他一些高阶的插值算法,以后再做研究。

  1. 最邻近插值
    也被称为零阶插值法,最简单插值算法,当然效果也是最差的。它的思想相当简单,就是四舍五入,浮点坐标的像素值等于距离该点最近的输入图像的像素值。

    上面的代码可以求得(x,y)的最邻近插值坐标(u,v)。
    最邻近插值几乎没有多余的运算,速度相当快。但是这种邻近取值的方法是很粗糙的,会造成图像的马赛克、锯齿等现象。
  2. 双线性插值
    它的插值效果比最邻近插值要好很多,相应的计算速度也要慢上不少。双线性插值的主要思想是计算出浮点坐标像素近似值。那么要如何计算浮点坐标的近似值呢。一个浮点坐标必定会被四个整数坐标所包围,将这个四个整数坐标的像素值按照一定的比例混合就可以求出浮点坐标的像素值。混合比例为距离浮点坐标的距离。
    假设要求坐标为(2.4,3)的像素值P,该点在(2,3)和(3,3)之间,如下图
    u和v分别是距离浮点坐标最近两个整数坐标像素在浮点坐标像素所占的比例
    P(2.4,3) = u * P(2,3) + v * P(3,3),混合的比例是以距离为依据的,那么u = 0.4,v = 0.6。
    上面是只在一条直线的插值,称为线性插值。双线性插值就是分别在X轴和Y轴做线性插值运算。
    下面利用三次的线性插值进行双线性插值运算

    (2.4,3)的像素值 F1 = m * T1 + (1 – m) * T2
    (2.4,4)的像素值 F2 = m * T3 + (1 – m ) * T4
    (2.4,3.5)的像素值 F = n * F1 + (1 – n) * F2
    这样就可以求得浮点坐标(2.4,3.5)的像素值了。
    求浮点坐标像素F,设该浮点坐标周围的4个像素值分别为T1,T2,T3,T4,并且浮点坐标距离其左上角的横坐标的差为m,纵坐标的差为n。
    F1 = m * T1 + (1 – m) * T2
    F2 = m * T3 +  (1 – m) *T4
    F = n * F1 + (1 – n) * F2
    上面就是双线性插值的基本公式,可以看出,计算每个像素像素值需要进行6次浮点运算。而且,由于浮点坐标有4个坐标近似求得,如果这个四个坐标的像素值差别较大,插值后,会使得图像在颜色分界较为明显的地方变得比较模糊。

2.图像平移

图像的平移变换就是将图像所有的像素坐标分别加上指定的水平偏移量和垂直偏移量。平移变换根据是否改变图像大小分为两种

左边平移图像的大小发生了,在保证图像平移的同时,也保存了完整的图像信息。右边的平移图像大小没有变化,故图像右下角的部分被截除了。

2.1平移变换原理

设dx为水平偏移量,dy为垂直偏移量,(x0,y0)为原图像坐标,(x,y)为变换后图像坐标,则平移变换的坐标映射为

这是向前映射,即将原图像的坐标映射到变换后的图像上。
其逆变换为
,向后映射,即将变换后的图像坐标映射到原图像上。在图像的几何变换中,一般使用向后映射。

2.2 基于OpenCV的实现

图像的平移变换实现还是很简单的,这里不再赘述.

平移后图像的大小不变

void GeometricTrans::translateTransform(cv::Mat const& src, cv::Mat& dst, int dx, int dy)
{
CV_Assert(src.depth() == CV_8U); const int rows = src.rows;
const int cols = src.cols; dst.create(rows, cols, src.type()); Vec3b *p;
for (int i = ; i < rows; i++)
{
p = dst.ptr<Vec3b>(i);
for (int j = ; j < cols; j++)
{
//平移后坐标映射到原图像
int x = j - dx;
int y = i - dy; //保证映射后的坐标在原图像范围内
if (x >= && y >= && x < cols && y < rows)
p[j] = src.ptr<Vec3b>(y)[x];
}
}
}

平移后图像的大小变化

void GeometricTrans::translateTransformSize(cv::Mat const& src, cv::Mat& dst, int dx, int dy)
{
CV_Assert(src.depth() == CV_8U); const int rows = src.rows + abs(dy); //输出图像的大小
const int cols = src.cols + abs(dx); dst.create(rows, cols, src.type());
Vec3b *p;
for (int i = ; i < rows; i++)
{
p = dst.ptr<Vec3b>(i);
for (int j = ; j < cols; j++)
{
int x = j - dx;
int y = i - dy; if (x >= && y >= && x < src.cols && y < src.rows)
p[j] = src.ptr<Vec3b>(y)[x];
}
}
}

ps:这里图像变换的代码以三通道图像为例,单通道的于此类似,代码中没有做处理。

3.图像的镜像变换

图像的镜像变换分为两种:水平镜像和垂直镜像。水平镜像以图像垂直中线为轴,将图像的像素进行对换,也就是将图像的左半部和右半部对调。垂直镜像则是以图像的水平中线为轴,将图像的上半部分和下班部分对调。效果如下:

3.1变换原理

设图像的宽度为width,长度为height。(x,y)为变换后的坐标,(x0,y0)为原图像的坐标

  1. 水平镜像变换

    向前映射

    其逆变换为

    向后映射

  2. 垂直镜像变换

    其逆变换为

3.2基于OpenCV的实现

水平镜像的实现

void GeometricTrans::hMirrorTrans(const Mat &src, Mat &dst)
{
CV_Assert(src.depth() == CV_8U);
dst.create(src.rows, src.cols, src.type()); int rows = src.rows;
int cols = src.cols; switch (src.channels())
{
case :
const uchar *origal;
uchar *p;
for (int i = ; i < rows; i++){
origal = src.ptr<uchar>(i);
p = dst.ptr<uchar>(i);
for (int j = ; j < cols; j++){
p[j] = origal[cols - - j];
}
}
break;
case :
const Vec3b *origal3;
Vec3b *p3;
for (int i = ; i < rows; i++) {
origal3 = src.ptr<Vec3b>(i);
p3 = dst.ptr<Vec3b>(i);
for(int j = ; j < cols; j++){
p3[j] = origal3[cols - - j];
}
}
break;
default:
break;
} }

分别对三通道图像和单通道图像做了处理,由于比较类似以后的代码只处理三通道图像,不再做特别说明。

在水平镜像变换时,遍历了整个图像,然后根据映射关系对每个像素都做了处理。实际上,水平镜像变换就是将图像坐标的列换到右边,右边的列换到左边,是可以以列为单位做变换的。同样垂直镜像变换也如此,可以以行为单位进行变换。

垂直镜像变换

void GeometricTrans::vMirrorTrans(const Mat &src, Mat &dst)
{
CV_Assert(src.depth() == CV_8U);
dst.create(src.rows, src.cols, src.type()); int rows = src.rows; for (int i = ; i < rows; i++)
src.row(rows - i - ).copyTo(dst.row(i));
}
src.row(rows - i - ).copyTo(dst.row(i));

上面一行代码是变换的核心代码,从原图像中取出第i行,并将其复制到目标图像。

顶不住了啊,写理论部分太痛苦了啊,明天继续几何变换的后续几种:转置、缩放、旋转以及组合变换。

OpenCV2:图像的几何变换,平移、镜像、缩放、旋转(1)的更多相关文章

  1. OpenCV2:图像的几何变换,平移、镜像、缩放、旋转(2)

    在OpenCV2:图像的几何变换,平移.镜像.缩放.旋转(1)主要介绍了图像变换中的向前映射.向后映射.处理变换过程中浮点坐标像素值的插值算法,并且基于OpenCV2实现了两个简单的几何变换:平移和镜 ...

  2. 【数字图像处理】六.MFC空间几何变换之图像平移、镜像、旋转、缩放具体解释

    本文主要讲述基于VC++6.0 MFC图像处理的应用知识,主要结合自己大三所学课程<数字图像处理>及课件进行解说,主要通过MFC单文档视图实现显示BMP图片空间几何变换.包含图像平移.图形 ...

  3. 【C#/WPF】Image图片的Transform变换:平移、缩放、旋转

    WPF中图像控件Image的变换属性Transform: 平移 缩放 旋转 即要想实现图片的平移.缩放.旋转,是修改它所在的Image控件的Transform变换属性. 下面在XAML中定义了Imag ...

  4. Canvas绘图之平移translate、旋转rotate、缩放scale

    画布操作介绍 画布绘图的环境通过translate(),scale(),rotate(), setTransform()和transform()来改变,它们会对画布的变换矩阵产生影响. 函数 方法 描 ...

  5. WPF/Silverlight中图形的平移,缩放,旋转,倾斜变换演示

    原文:WPF/Silverlight中图形的平移,缩放,旋转,倾斜变换演示 为方便描述, 这里仅以正方形来做演示, 其他图形从略. 运行时效果图:XAML代码:// Transform.XAML< ...

  6. View的平移、缩放、旋转以及位置、坐标系

    原创 2015年05月12日 13:15:29 标签: Android / Scroll / Scale / Translation / Rotation 24733 Android开发中,经常会接触 ...

  7. 软件项目技术点(2)——Canvas之平移translate、旋转rotate、缩放scale

    AxeSlide软件项目梳理   canvas绘图系列知识点整理 画布操作介绍 画布绘图的环境通过translate(),scale(),rotate(), setTransform()和transf ...

  8. ARFoundation - 实现物体旋转, 平移,缩放

    ARFoundation - 实现物体旋转, 平移,缩放 本文目的是为了确定在移动端怎样通过单指滑动实现物体的旋转,双指实现平移和缩放. 前提知识: ARFoundation - touch poin ...

  9. 【opencv基础】图像的几何变换

    参考 1. 图像的几何变换-平移和镜像: 2.图像的几何变换-缩放和旋转: 3. opencv图像旋转实现: 完

随机推荐

  1. C语言内存分配

      (1)代码区(text segment).存放CPU执行的机器指令(machine instructions).通常,代码区是可共享的 (即另外的执行程序可以调用它),因为对于频繁被执行的程序,只 ...

  2. 利用js来实现文字的滚动(也就是我们常常见到的新闻版块中的公示公告)

    首先先看一下大致效果图(因为是动态的,在页面无法显示出来) 具体的实现代码如下: 1.首先是css代码: <style type="text/css"> body,ul ...

  3. int (*p)[10] 与*p[10]的区别

    定义指向具有10个整型元素的一维数组的指针格式为:int (*p)[10] ,而起初我一直以为int (*p)[10] 是定义二维数组的方法 ][],(*p)[]; p=a; /*有了这个定义后,指针 ...

  4. JSP里面ajax不能返回后台传出的值得问题。。。。

    问题代码: <%@ page contentType="text/html;charset=gb2312"%><html> <head> < ...

  5. 关于Node.js的总结

    Node是个啥? 1.Node 是一个服务器端 JavaScript 解释器,可是真的以为JavaScript不错的同学学习Node就能轻松拿下,那么你就错了,总结:水深不深我还不知道,不过确实不浅. ...

  6. 为首次部署MongoDB做好准备:容量计划和监控

    如果你已经完成了自己新的MongoDB应用程序的开发,并且现在正准备将它部署进产品中,那么你和你的运营团队需要讨论一些关键的问题: 最佳部署实践是什么? 为了确保应用程序满足它所必须的服务层次我们需要 ...

  7. HTML5- Canvas入门(二)

    上篇文章我们了解了canvas的定义.获取和基础的绘图操作,其中的绘图功能我们讲解了线段绘制.上色.描边等方面知识点. 今天我们来讲讲矩形(Rectangle)和多边形的绘制. 矩形的绘制一共有两个口 ...

  8. 剑指Offer面试题:12.在O(1)时间删除链表结点

    一.题目:在O(1)时间删除链表结点 题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点. 原文采用的是C/C++,这里采用C#,节点定义如下: public class ...

  9. 操作系统核心原理-7.设备管理:I/O原理

    一.I/O的基本知识 1.1 为何要有I/O 前面阐述了操作系统具有进程管理.内存管理.外存管理三大核心功能,但是计算机归根是为人类服务的,这就要求计算机必须提供某种机制使得人们可以向计算机发出命令或 ...

  10. ASP.NET MVC 过滤器(一)

    ASP.NET MVC 过滤器(一) 前言 前面的篇幅中,了解到了控制器的生成的过程以及在生成的过程中的各种注入点,按照常理来说篇幅应该到了讲解控制器内部的执行过程以及模型绑定.验证这些知识了.但是呢 ...