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

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

1.图像缩放

图像的缩放主要用于改变图像的大小,缩放后图像的图像的宽度和高度会发生变化。水平缩放系数,控制图像宽度的缩放,其值为1,则图像的宽度不变;垂直缩放系数控制图像高度的缩放,其值为1,则图像的高度不变。如果水平缩放系数和垂直缩放系数不相等,那么缩放后图像的宽度和高度的比例会发生变化,会使图像变形。要保持图像宽度和高度的比例不发生变化,就需要水平缩放系数和垂直缩放系数相等。

左边的图像水平缩放系数和垂直缩放系数都是0.5;右边的图像的水平缩放系数为1,垂直缩放系数为0.5,缩放后图像宽度和高度比例发生变化,图像变形。

1.1 缩放原理

设水平缩放系数为sx,垂直缩放系数为sy,(x0,y0)为缩放前坐标,(x,y)为缩放后坐标,其缩放的坐标映射关系:

矩阵表示的形式为:

这是向前映射,在缩放的过程改变了图像的大小,使用向前映射会出现映射重叠和映射不完全的问题,所以这里更关心的是向后映射,也就是输出图像通过向后映射关系找到其在原图像中对应的像素。

向后映射关系:

1.2基于OpenCV的缩放实现

在图像缩放的时首先需要计算缩放后图像的大小,设newWidth,newHeight为缩放后的图像的宽和高,width,height为原图像的宽度和高度,那么有:

然后遍历缩放后的图像,根据向后映射关系计算出缩放的像素在原图像中像素的位置,如果得到的浮点坐标,就需要使用插值算法取得近似的像素值。

根据上面公式可知,缩放后图像的宽和高用原图像宽和高和缩放因子相乘即可。

int rows = static_cast<int>(src.rows * xRatio + 0.5);
int cols = static_cast<int>(src.cols * yRatio + 0.5);

在向后映射时有可能得到浮点坐标,这里使用最邻近插值和双线性插值来处理。

最邻近插值

for (int i = 0; i < rows; i++){
            int row = static_cast<int>(i / xRatio + 0.5);
            if (row >= src.rows)
                row--;
            origin = src.ptr<uchar>(row);
            p = dst.ptr<uchar>(i);

            for (int j = 0; j < cols; j++){
                int col = static_cast<int>(j / yRatio + 0.5);
                if (col >= src.cols)
                    col--;
                p[j] = origin[col];
            }
        }

最邻近插值只需要对浮点坐标“四舍五入”运算。但是在四舍五入的时候有可能使得到的结果超过原图像的边界(只会比边界大1),所以要进行下修正。

双线性插值

双线性插值的精度要比最邻近插值好很多,相对的其计算量也要大的多。双线性插值使用浮点坐标周围四个像素的值按照一定的比例混合近似得到浮点坐标的像素值。

设浮点坐标F,其周围的四个整数坐标别为T1,T2,T3,T4,并且F和其左上角的整数坐标的纵轴差的绝对值为n,横轴差的绝对值为n。据上一篇文章分析可得浮点坐标F的像素值T,有下面公式计算得到:

F1为([F.y],F.x),F2为([F.y]+1,F.x)。具体的参见OpenCV2:图像的几何变换,平移、镜像、缩放、旋转(1).

在实现的时候首先要根据浮点坐标计算出其周围的四个整数坐标

double row = i / xRatio;
            double col = j / yRatio;

            int lRow = static_cast<int>(row);
            int nRow = lRow + 1;
            int lCol = static_cast<int>(col);
            int rCol = lCol + 1;

            double u = row - lRow;
            double v = col - lCol;

缩放放后图像的坐标(i,j),根绝向后映射关系找到其在原图像中对应的坐标(i / xRatio,j / yRatio),接着找到改坐标周围的四个整数坐标(lcol,lRow),(lCol,nrow),

(rCol,lRow),(rCo1,nRow)。下面根据双线性插值公式得到浮点坐标的像素值

//坐标在图像的右下角
            if ((row >= src.rows - 1) && (col >= src.cols - 1)) {
                lastRow = src.ptr<Vec3b>(lRow);
                p[j] = lastRow[lCol];
            }
            //最后一行
            else if (row >= src.rows - 1) {
                lastRow = src.ptr<Vec3b>(lRow);
                p[j] = v * lastRow[lCol] + (1 - v) * lastRow[rCol];
            }
            //最后一列
            else if (col >= src.cols - 1){
                lastRow = src.ptr<Vec3b>(lRow);
                nextRow = src.ptr<Vec3b>(nRow);
                p[j] = u * lastRow[lCol] + (1 - u) * nextRow[lCol];
            }
            else {
                lastRow = src.ptr<Vec3b>(lRow);
                nextRow = src.ptr<Vec3b>(nRow);
                Vec3b f1 = v * lastRow[lCol] + (1 - v) * lastRow[rCol];
                Vec3b f2 = v * nextRow[lCol] + (1 - v) * lastRow[rCol];
                p[j] = u * f1 + (1 - u) * f2;
            }

由于使用四个像素进行计算,在边界的时候,会有不存在的像素,这里把在图像的右下角、最后一行、最后一列三种特殊情形分别处理。

2.图像旋转

2.1旋转原理

图像的旋转就是让图像按照某一点旋转指定的角度。图像旋转后不会变形,但是其垂直对称抽和水平对称轴都会发生改变,旋转后图像的坐标和原图像坐标之间的关系已不能通过简单的加减乘法得到,而需要通过一系列的复杂运算。而且图像在旋转后其宽度和高度都会发生变化,其坐标原点会发生变化。

图像所用的坐标系不是常用的笛卡尔,其左上角是其坐标原点,X轴沿着水平方向向右,Y轴沿着竖直方向向下。而在旋转的过程一般使用旋转中心为坐标原点的笛卡尔坐标系,所以图像旋转的第一步就是坐标系的变换。设旋转中心为(x0,y0),(x’,y’)是旋转后的坐标,(x,y)是旋转后的坐标,则坐标变换如下:

矩阵表示为:

在最终的实现中,常用到的是有缩放后的图像通过映射关系找到其坐标在原图像中的相应位置,这就需要上述映射的逆变换

坐标系变换到以旋转中心为原点后,接下来就要对图像的坐标进行变换。

上图所示,将坐标(x0,y0)顺时针方向旋转a,得到(x1,y1)。

旋转前有:

旋转a后有:

矩阵的表示形式:

其逆变换:

由于在旋转的时候是以旋转中心为坐标原点的,旋转结束后还需要将坐标原点移到图像左上角,也就是还要进行一次变换。这里需要注意的是,旋转中心的坐标(x0,y0)实在以原图像的左上角为坐标原点的坐标系中得到,而在旋转后由于图像的宽和高发生了变化,也就导致了旋转后图像的坐标原点和旋转前的发生了变换。

上边两图,可以清晰的看到,旋转前后图像的左上角,也就是坐标原点发生了变换。

在求图像旋转后左上角的坐标前,先来看看旋转后图像的宽和高。从上图可以看出,旋转后图像的宽和高与原图像的四个角旋转后的位置有关。

设top为旋转后最高点的纵坐标,down为旋转后最低点的纵坐标,left为旋转后最左边点的横坐标,right为旋转后最右边点的横坐标。

旋转后的宽和高为newWidth,newHeight,则可得到下面的关系:

也就很容易的得出旋转后图像左上角坐标(left,top)(以旋转中心为原点的坐标系)

故在旋转完成后要将坐标系转换为以图像的左上角为坐标原点,可由下面变换关系得到:

矩阵表示:

其逆变换:

综合以上,也就是说原图像的像素坐标要经过三次的坐标变换:

  1. 将坐标原点由图像的左上角变换到旋转中心
  2. 以旋转中心为原点,图像旋转角度a
  3. 旋转结束后,将坐标原点变换到旋转后图像的左上角

可以得到下面的旋转公式:(x’,y’)旋转后的坐标,(x,y)原坐标,(x0,y0)旋转中心,a旋转的角度(顺时针)

这种由输入图像通过映射得到输出图像的坐标,是向前映射。常用的向后映射是其逆运算

2.2基于OpenCV的实现

得到了上述的旋转公式,实现起来就不是很困难了.

首先计算四个角的旋转后坐标(以旋转中心为坐标原点)

const double cosAngle = cos(angle);
    const double sinAngle = sin(angle);

    //原图像四个角的坐标变为以旋转中心的坐标系
    Point2d leftTop(-center.x, center.y); //(0,0)
    Point2d rightTop(src.cols - center.x,center.y); // (width,0)
    Point2d leftBottom(-center.x, -src.rows + center.y); //(0,height)
    Point2d rightBottom(src.cols - center.x, -src.rows + center.y); // (width,height)

    //以center为中心旋转后四个角的坐标
    Point2d transLeftTop, transRightTop, transLeftBottom, transRightBottom;
    transLeftTop = coordinates(leftTop, angle);
    transRightTop = coordinates(rightTop, angle);
    transLeftBottom = coordinates(leftBottom, angle);
    transRightBottom = coordinates(rightBottom, angle);

需要注意的是要将原图像四个角的坐标变为以旋转中心为坐标原点的坐标系坐标。然后通过旋转变换公式

得到旋转后四个角的坐标。

由于旋转角度的不同旋转后四个角的位置和其在原图像的位置是不相同的,也就是说原图像的左上角在旋转后不一定是旋转后图像的左上角,有可能是右下角。所以在计算旋转后图像的宽度就不能使用原图右上角旋转后的横坐标减去原图像左下角旋转后的横坐标,高度也是如此。(在查找资料时发现,大部分都是使用这种方式计算的图像的宽度和高度)。

//计算旋转后图像的width,height
    double left = min({ transLeftTop.x, transRightTop.x, transLeftBottom.x, transRightBottom.x });
    double right = max({ transLeftTop.x, transRightTop.x, transLeftBottom.x, transRightBottom.x });
    double top = max({ transLeftTop.y, transRightTop.y, transLeftBottom.y, transRightBottom.y });
    double down = min({ transLeftTop.y, transRightTop.y, transLeftBottom.y, transRightBottom.y });

    int width = static_cast<int>(abs(left - right) + 0.5);
    int height = static_cast<int>(abs(top - down) + 0.5);

计算旋转图像的宽度,可以使用四个角旋转后最右边点的横坐标减去最左边点的横坐标;高度时最上边点的纵坐标减去最下边点的纵坐标。

然后,就可以使用最终的那个旋转公式,处理图像的每一个像素了。

const double num1 = -abs(left) * cosAngle - abs(top) * sinAngle + center.x;
    const double num2 = abs(left) * sinAngle - abs(top) * cosAngle + center.y;

    Vec3b *p;
    for (int i = 0; i < height; i++)
    {
        p = dst.ptr<Vec3b>(i);
        for (int j = 0; j < width; j++)
        {
            //坐标变换
            int x = static_cast<int>(j  * cosAngle + i * sinAngle + num1 + 0.5 );
            int y = static_cast<int>(-j * sinAngle + i * cosAngle + num2 + 0.5 );

            if (x >= 0 && y >= 0 && x < src.cols && y < src.rows)
                p[j] = src.ptr<Vec3b>(y)[x];
        }
    }

这使用的插值方法是最邻近插值,双线性插值的实现方法和图像缩放类似,不再赘述。

使用上述算法进行图像旋转,会发现不论使用图像内的那个位置作为旋转的中心,最后得到的结果都是一样的。这是因为,不同位置作为旋转中心,旋转后图像的大小都是一样,所不同的只是其位置。而在最后的一次变换中(图像旋转用了三次坐标变换),统一的把坐标原点移到了旋转后图像的左上角。这就相当于对图像做了一次平移,把其位置也挪到了一起,最后旋转得到的图像也就一样了。

如果,在旋转结束后把坐标原点不是移到旋转后图像的左上角,而是原图像的左上角,会是怎么一个情形呢?

就像上图,图像的部分区域会被截掉。当然,这时旋转中心不同的话最终得到的图像也就不同了,被截掉的部分不相同。

3.组合变换

组合变换就是把多种几何放在一起进行。上面推导了旋转的变换公式,那么组合变换也就不是很困难了,无非是多了几个矩阵相乘。比较常见的组合变换:缩放+旋转+平移,下面以此为例推导下组合变换的公式。

  1. 缩放
    设(x0,y0)是缩放后的坐标,(x,y)是缩放前的坐标,sx,sy为缩放因子

  2. 平移

    设(x0,y0)是平移后的坐标,(x,y)是平移前的坐标,dx,dy为偏移量

  3. 旋转

    设(x0,y0)是旋转后坐标,(x,y)是旋转前坐标,(m,n)是旋转中心,a是旋转的角度,(left,top)是旋转后图像的左上角坐标

分别得到了三个变换矩阵,按照缩放、平移、旋转的顺序组合起来

组合变换的时候要注意顺序,毕竟矩阵的左乘和右乘是不一样的。

4.最后

到现在,写的最长的一篇文章。和上一篇放到一起弄了差不多快一个周了,算法的实现除了旋转有个几次挫折外,其余的几种变换都很顺利。耗时间的主要是画图和数学公式,画图一直没有找到合适的工具,不了了之;数学公式特意花了一天的时间学了Latex,并且找了些把Tex转换为HTML的工具,但是效果都不是很好,还是粘贴图片。没有图,有些东西只靠文字确实很难说的清楚,抽空得学习学习MATLAB画图了。

时间: 2024-10-14 14:48:37

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

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

图像的几何变换是在不改变图像内容的前提下对图像像素的进行空间几何变换,主要包括了图像的平移变换.镜像变换.缩放和旋转等.本文首先介绍了图像几何变换的一些基本概念,然后再OpenCV2下实现了图像的平移变换.镜像变换.缩放以及旋转,最后介绍几何的组合变换(平移+缩放+旋转). 1.几何变换的基本概念 1.1 坐标映射关系 图像的几何变换改变了像素的空间位置,建立一种原图像像素与变换后图像像素之间的映射关系,通过这种映射关系能够实现下面两种计算: 原图像任意像素计算该像素在变换后图像的坐标位置 变换

Android单点触控技术,对图片进行平移,缩放,旋转操作

相信大家使用多点对图片进行缩放,平移的操作很熟悉了,大部分大图的浏览都具有此功能,有些app还可以对图片进行旋转操作,QQ的大图浏览就可以对图片进行旋转操作,大家都知道对图片进行缩放,平移,旋转等操作可以使用Matrix来实现,Matrix就是一个3X3的矩阵,对图片的处理可分为四个基础变换操作,Translate(平移变换).Rotate(旋转变换.Scale (缩放变换).Skew(错切变换),如果大家对Matrix不太了解的话可以看看这篇文章(点击查看),作者对每一种Matrix的变换写的

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

本文主要讲述基于VC++6.0 MFC图像处理的应用知识,主要结合自己大三所学课程<数字图像处理>及课件进行讲解,主要通过MFC单文档视图实现显示BMP图片空间几何变换,包括图像平移.图形旋转.图像反转倒置镜像和图像缩放的知识.同时文章比较详细基础,没有采用GDI+获取矩阵,而是通过读取BMP图片信息头和矩阵像素实现变换,希望该篇文章对你有所帮助,尤其是初学者和学习图像处理的学生. [数字图像处理]一.MFC详解显示BMP格式图片 [数字图像处理]二.MFC单文档分割窗口显示图片 [数字图像处

opencv 图像平移、缩放、旋转、翻转 图像仿射变换

图像几何变换 图像几何变换从原理上看主要包括两种:基于2x3矩阵的仿射变换(平移.缩放.旋转.翻转).基于3x3矩阵的透视变换. 图像平移 opencv实现图像平移 实现图像平移,我们需要定义下面这样一个矩阵,tx和ty分别是x和y方向上平移的距离: 图像平移利用仿射变换函数 cv.warpAffine() 实现 实验 # 图像平移 import numpy as np import cv2 as cv img = cv.imread('paojie.jpg') rows, cols = img

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

原文:WPF/Silverlight中图形的平移,缩放,旋转,倾斜变换演示 为方便描述, 这里仅以正方形来做演示, 其他图形从略. 运行时效果图: XAML代码:// Transform.XAML <Canvas Width="700" Height="700" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  xmlns:x="http://sc

【Android】自定义ImageView实现图片的平移、缩放、旋转(手势操作)

http://blog.csdn.net/happy_bug/article/details/7895244 在网上搜到很多都是一样的版本,只有平移和缩放的功能.我在搜到的源代码基础上添加了旋转和边界检查的功能.

【WebGL初学系列之五】旋转,平移,缩放

nbcoder.com地址:http://nbcoders.com/webgl-chu-xue-xi-lie-zhi-wu-ai.html 最近把WebGL做的相关Demo已经放在 http://www.nbcoders.com 上了,这样就可以直观的进行看效果. 地址:    http://lab.nbcoders.com/ixshells/ 本文旋转平移缩放Demo的地址: http://lab.nbcoders.com/ixshells/Html/ScaleRotateTranslate.

Android 中实现图片平移、缩放、旋转同步进行

前言 之前因为项目需求,其中使用到了图片的单击显示取消,图片平移缩放功能,昨天突然想再加上图片的旋转功能,在网上看了很多相关的例子,可是没看到能同时实现我想要的功能的. 需求:(1)图片平移.缩放.旋转等一系列操作后,图片需要自动居中显示.(2)图片旋转后选自动水平显示或者垂直显示(3)图片在放大缩小的同时都能旋转 Demo实现部分效果截图 Demo主要代码 Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 2

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

画布操作介绍 画布绘图的环境通过translate(),scale(),rotate(), setTransform()和transform()来改变,它们会对画布的变换矩阵产生影响. 函数 方法 描述 translate dx,dx 转换的量的 X 和 Y 大小 scale sx,sy 水平和垂直的缩放因子 rotate angle 旋转的量,用弧度表示.正值表示顺时针方向旋转,负值表示逆时针方向旋转. setTransform a,b,c,d,e,f 水平缩放,水平倾斜(与旋转有关),垂直倾