计算机视觉(三)图像的几何变换

嵌牛导读:本文通过矩阵的变化用c++算法实现了对图像的处理。

嵌牛鼻子:图像的几何变换

嵌牛问题:如何保证变化过程中数据与数据类型不发生变化。

转自: https://blog.csdn.net/qq_34784753/article/details/56004248?utm_source=app&app_version=4.8.0&code=app_1562916241&uLinkId=usr1mkqgl919blen

嵌牛正文

图像的几何变换是指在不改变图像像素的前提下对图像像素进行空间几何变换。常见的变换有距离变换,坐标映射,平移,镜像,旋转,缩放和仿射变换等等。

也就是说,图像的几何变换就是建立一种源图像像素与变换后的图像像素之间的映射关系。也正是通过这种映射关系可以知道原图像任意像素点变换后的坐标,或者是变换后的图像在原图像的坐标位置等。用简单的数学公式可以表示为

其中,x,y代表输出图像像素的坐标,x0,y0表示输入图像的像素坐标,而U,V表示的是两种映射关系,需要说明的是,映射关系可以是线性关系,也可以是多项式关系

从上面的映射关系可以看到,只要给出了图像上任意的像素坐标,都能够通过对应的映射关系获得几何变换后的像素坐标。这种将输入映射到输出的过程我们称之为“向前映射”。但是在实际应用中,向前映射会出现如下几个问题:

a.浮点数坐标,如(1,1)映射为(0.5,0.5),显然这是一个无效的坐标,这时我们可以使用插值算法进行进一步处理。

b.映射不完全和映射重叠。

映射不完全是说输入图像的像素总数小于输出的像素总数,这会使得输出图像的部分像素与原始图像并没有映射关系,如进行放大操作。映射重叠是与映射不完全正好相反,输出图像会存在映射后的像素重叠。

为了克服前向映射的这些不足,因此引进了“后向映射”,它的数学表达式为:

同样的,x,y表示输出图像像素的坐标,x0,y0表示输入图像像素的坐标,U'和V'表示两种映射方式。

可以看出,后向映射与前向映射刚好相反,它是由输出图像的像素坐标反过来推算该像素为在源图像中的坐标位置。这样,输出图像的每个像素值都能够通过这个映射关系找到对应的为止。而不会造成上面所提到的映射不完全和映射重叠的现象。

在实际处理中基本上都运用向后映射来进行图像的几何变换。

在使用过程中,如果在一些不改变图像大小的几何变换中,向前映射还是十分有效的,向后映射主要运用在图像的旋转的缩放中,因为这些几何变换都会改变图像的大小。

在本篇文章里图像的几何变换全部都采用统一的矩阵表示法,形式如下:

这就是向前映射的矩阵表示法,其中x,y表示输出图像像素的坐标,x0,y0表示输入图像像素的坐标

同理,向后映射的矩阵表示为:

可以证明,向后映射的矩阵的表示正好是向前映射的逆变换。

4.1坐标映射。

4.1这部分文字内容一部分参考了朱伟等主编的《OpenCV图像处理编程实例》

图像的坐标映射是通过原图像与目标图像与目标图像之间建立一种映射关系,这种映射关系有两种,也就是上面所提到的向前映射和向后映射。

在OpenCV中提供了重映射相关的操作,而对于映射后出现了目标图像像素是非整数的情况,一般可以考虑插值或是向上取整。

void remap( InputArray src, OutputArray dst, InputArray map1, InputArray map2,int interpolation, borderMode=BORDER_CONSTANT,const Scalar& borderValue=Scalar());

这个函数的主要作用是进行图像的重映射操作,参数 src 和 dst 分别表示输入原图像和映射后的图像,参数 map1 表示(x,y)点的坐标或x坐标,可以是CV_16SC2,CV_32FC1或者CV_32FC2类型, map2 表示y坐标,可以使CV_16UC1,CV_32FC1类型,如果 map1 为(x,y),则 map2 可以不使用, interpolation 表示使用的插值方法,有四种可以选择,

·    INTER_NEAREST -最近邻插值

·    INTER_LINEAR –双线性插值(默认值)

·    INTER_CUBIC –双三次样条插值(逾4×4像素邻域内的双三次插值)

·    INTER_LANCZOS4 -Lanczos插值(逾8×8像素邻域的Lanczos插值)

boderMode表示边界插值的类型,有默认值BORDER_CONSTANT,表示目标图像中“离群点(outliers)”的像素值不会被此函数修改。

boderValue表示插值数值,其有默认值Scalar( ),即默认值为0。

使用OpenCV实现图像的坐标映射相关代码如下:

#include <iostream>

#include <opencv2\core\core.hpp>

#include <opencv2\highgui\highgui.hpp>

#include <opencv2\imgproc\imgproc.hpp>

using namespace std;

using namespace cv;

int main()

{

Mat srcImage = imread("2345.jpg");

if (!srcImage.data)

{

cout << "读入图片错误!" << endl;

return -1;

}

imshow("原始图", srcImage);

//创建输出矩阵

Mat dstImage(srcImage.size(), srcImage.type());

//定义x和y方向的矩阵

Mat xMap(srcImage.size(), CV_32FC1);

Mat yMap(srcImage.size(), CV_32FC1);

//获取图像的宽和高

int rowNumber = srcImage.rows;

int colNumber = srcImage.cols;

//对图像进行遍历操作

for (int i = 0; i < rowNumber; i++)

{

for (int j = 0; j < colNumber; j++)

{

//x和y都进行翻转操作

xMap.at<float>(i, j) = static_cast<float>(srcImage.cols - j);

yMap.at<float>(i, j) = static_cast<float>(srcImage.rows - i);

}

}

//进行重映射操作

remap(srcImage, dstImage, xMap, yMap,

CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));

imshow("映射效果图", dstImage);

waitKey();

return 0;

}

运行后效果图为:

可以看出,图像上下和左右都进行了翻转操作。

4.2 平移变换

图像的平移变换是最简单的几何变换,就是将图像中所有像素的坐标分别加上或减去指定的水平和垂直偏移量,从而使整张图片出现移位的效果。

对于原始图像而言,它的正变换矩阵为:

而对于目标图像而言,其逆变换矩阵为:

平移变换用程序实现过程如下:

//实现图像的平移变换

#include <iostream>

#include <opencv2\core\core.hpp>

#include <opencv2\highgui\highgui.hpp>

#include <opencv2\imgproc\imgproc.hpp>

using namespace std;

using namespace cv;

int main()

{

Mat srcImage, dstImage;

int xOffset, yOffset; //x和y方向的平移量

srcImage = imread("2345.jpg");

if (!srcImage.data)

{

cout << "读入图片错误!" << endl;

return -1;

}

dstImage.create(srcImage.size(), srcImage.type());

cout<< "请输入x方向和y方向的平移量:";

cin >> xOffset >> yOffset;

int rowNumber = srcImage.rows;

int colNumber = srcImage.cols;

//进行遍历图像

for (int i = 0; i < rowNumber; i++)

{

for (int j = 0; j < colNumber; j++)

{

//平移变换

int x = j - xOffset;

int y = i - yOffset;

//判断边界情况

if (x >= 0 && y >= 0 && x < colNumber && y < rowNumber)

dstImage.at<Vec3b>(i, j) = srcImage.at<Vec3b>(y,x);

}

}

imshow("原图像", srcImage);

imshow("平移后的图像", dstImage);

waitKey();

return 0;

}

程序效果如下图所示:

这里x和y方向的平移量都取+50个像素

4.3 镜像变换

图像的镜像变换和数学上的轴对称非常类似,水平镜像和垂直镜像分别对应着以图像的水平中轴线和垂直中轴线为对称轴做对称变换操作。

总之,水平镜像变换产生的是原始图像的水平投影,类似于在镜子中显示的物体,而垂直镜像变换是原始图在垂直方向上进行投影,效果类似于水中的倒影。

基本原理:

a.水平镜像变换

设图像的宽度是width,则水平镜像变换的映射关系如下:

用矩阵可以表示为:

相应的逆运算矩阵如下:

可以发现,水平镜像变换的向前映射和向后映射的两个关系式相同,也就是说,将水平镜像变换得到的结果再做水平变化会得到原来的图像,从数学的角度上思考,这是显然的。同理,在垂直镜像变换中也有这样的结论。

b.垂直镜像变换

设变换的图像的高度为height,垂直镜像变换的映射关系如下:

使用矩阵可以表示为:

相应的逆运算为:

下面使用OpenCV和C++语言编程实现水平镜像变换和垂直镜像变换过程。

需要说明的是,在OpenCV中有flip函数可以实现镜像变换功能,函数说明如下:

void flip(InputArray src, OutputArray dst, int flipCode)

其中, src表示输入图像, dst表示输出图像, flipCode表示翻转模式, flipCode==0垂直翻转(沿X轴翻转),flipCode>0水平翻转(沿Y轴翻转),flipCode<0水平垂直翻转(先沿X轴翻转,再沿Y轴翻转,等价于旋转180°)

//实现图像的镜像变换

//包括水平镜像和竖直镜像

#include <iostream>

#include <opencv2\core\core.hpp>

#include <opencv2\highgui\highgui.hpp>

#include <opencv2\imgproc\imgproc.hpp>

using namespace std;

using namespace cv;

int main()

{

Mat srcImage, dstImage1,dstImage2;

srcImage = imread("2345.jpg");

dstImage1.create(srcImage.size(), srcImage.type());

dstImage2.create(srcImage.size(), srcImage.type());

//方法1,使用flip函数对图像进行水平镜像变换操作

flip(srcImage, dstImage1, 1); //第三个参数flipCode>0表示沿y轴做镜像

//方法2,遍历图像像素

int rowNumber = srcImage.rows;

int colNumber = srcImage.cols;

for (int i = 0; i < rowNumber; i++)

{

for (int j = 0; j < colNumber; j++)

{

dstImage2.at<Vec3b>(i, j)[0] = srcImage.at<Vec3b>(i, colNumber - j - 1)[0];

dstImage2.at<Vec3b>(i, j)[1] = srcImage.at<Vec3b>(i, colNumber - j - 1)[1];

dstImage2.at<Vec3b>(i, j)[2] = srcImage.at<Vec3b>(i, colNumber - j - 1)[2];

}

}

imshow("原图像", srcImage);

imshow("flip方法水平镜像", dstImage1);

imshow("遍历像素水平镜像", dstImage2);

//方法1,使用flip函数对元对象进行垂直镜像变换操作

flip(srcImage, dstImage1, 0);

//方法2,遍历图像像素

for (int i = 0; i < rowNumber; i++)

{

for (int j = 0; j < colNumber; j++)

{

dstImage2.at<Vec3b>(i, j)[0] = srcImage.at<Vec3b>(rowNumber - i - 1, j)[0];

dstImage2.at<Vec3b>(i, j)[1] = srcImage.at<Vec3b>(rowNumber - i - 1, j)[1];

dstImage2.at<Vec3b>(i, j)[2] = srcImage.at<Vec3b>(rowNumber - i - 1, j)[2];

}

}

imshow("flip方法垂直镜像", dstImage1);

imshow("遍历像素垂直镜像", dstImage2);

waitKey();

return 0;

}

程序效果如下:

图像几何变换的上半部分就

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容