【转】【opencv】仿射变换
仿射变换
原理
什么是仿射变换?
一个任意的仿射变换都能表示为 乘以一个矩阵 (线性变换) 接着再 加上一个向量 (平移).
综上所述, 我们能够用仿射变换来表示:
- 旋转 (线性变换)
- 平移 (向量加)
- 缩放操作 (线性变换)
你现在可以知道, 事实上, 仿射变换代表的是两幅图之间的 关系 .
我们通常使用
矩阵来表示仿射变换.
考虑到我们要使用矩阵
和
对二维向量
做变换, 所以也能表示为下列形式:
or 

怎样才能求得一个仿射变换?
好问题. 我们在上文有提到过仿射变换基本表示的就是两幅图片之间的 联系 . 关于这种联系的信息大致可从以下两种场景获得:
- 我们已知
和 T 而且我们知道他们是有联系的. 接下来我们的工作就是求出矩阵 
- 我们已知
and
. 要想求得
. 我们只要应用算式
即可. 对于这种联系的信息可以用矩阵
清晰的表达 (即给出明确的2×3矩阵) 或者也可以用两幅图片点之间几何关系来表达.
- 我们已知
让我们形象地说明一下. 因为矩阵
联系着两幅图片, 我们以其表示两图中各三点直接的联系为例. 见下图:
点1, 2 和 3 (在图一中形成一个三角形) 与图二中三个点一一映射, 仍然形成三角形, 但形状已经大大改变. 如果我们能通过这样两组三点求出仿射变换 (你能选择自己喜欢的点), 接下来我们就能把仿射变换应用到图像中所有的点.
例程
- 这个例程是做什么的?
- 加载一幅图片
- 对这幅图片应用仿射变换. 这个变换是从源图像和目标图像的两组三点之间的联系获得的. 这一步我们使用函数 warpAffine 来实现.
- 仿射变换结束后再对图像应用旋转. 这里的旋转绕图像中点
- 等待用户退出程序
- 例程代码在下面给出. 当然你也可以从 这里 下载
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h> using namespace cv;
using namespace std; /// 全局变量
char* source_window = "Source image";
char* warp_window = "Warp";
char* warp_rotate_window = "Warp + Rotate"; /** @function main */
int main( int argc, char** argv )
{
Point2f srcTri[3];
Point2f dstTri[3]; Mat rot_mat( 2, 3, CV_32FC1 );
Mat warp_mat( 2, 3, CV_32FC1 );
Mat src, warp_dst, warp_rotate_dst; /// 加载源图像
src = imread( argv[1], 1 ); /// 设置目标图像的大小和类型与源图像一致
warp_dst = Mat::zeros( src.rows, src.cols, src.type() ); /// 设置源图像和目标图像上的三组点以计算仿射变换
srcTri[0] = Point2f( 0,0 );
srcTri[1] = Point2f( src.cols - 1, 0 );
srcTri[2] = Point2f( 0, src.rows - 1 ); dstTri[0] = Point2f( src.cols*0.0, src.rows*0.33 );
dstTri[1] = Point2f( src.cols*0.85, src.rows*0.25 );
dstTri[2] = Point2f( src.cols*0.15, src.rows*0.7 ); /// 求得仿射变换
warp_mat = getAffineTransform( srcTri, dstTri ); /// 对源图像应用上面求得的仿射变换
warpAffine( src, warp_dst, warp_mat, warp_dst.size() ); /** 对图像扭曲后再旋转 */ /// 计算绕图像中点顺时针旋转50度缩放因子为0.6的旋转矩阵
Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
double angle = -50.0;
double scale = 0.6; /// 通过上面的旋转细节信息求得旋转矩阵
rot_mat = getRotationMatrix2D( center, angle, scale ); /// 旋转已扭曲图像
warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() ); /// 显示结果
namedWindow( source_window, CV_WINDOW_AUTOSIZE );
imshow( source_window, src ); namedWindow( warp_window, CV_WINDOW_AUTOSIZE );
imshow( warp_window, warp_dst ); namedWindow( warp_rotate_window, CV_WINDOW_AUTOSIZE );
imshow( warp_rotate_window, warp_rotate_dst ); /// 等待用户按任意按键退出程序
waitKey(0); return 0;
}
说明
定义一些需要用到的变量, 比如需要用来储存中间和目标图像的Mat和两个需要用来定义仿射变换的二维点数组.
Point2f srcTri[3];
Point2f dstTri[3]; Mat rot_mat( 2, 3, CV_32FC1 );
Mat warp_mat( 2, 3, CV_32FC1 );
Mat src, warp_dst, warp_rotate_dst;加载源图像:
src = imread( argv[1], 1 );
以与源图像同样的类型和大小来对目标图像初始化:
warp_dst = Mat::zeros( src.rows, src.cols, src.type() );
仿射变换: 正如上文所说, 我们需要源图像和目标图像上分别一一映射的三个点来定义仿射变换:
srcTri[0] = Point2f( 0,0 );
srcTri[1] = Point2f( src.cols - 1, 0 );
srcTri[2] = Point2f( 0, src.rows - 1 ); dstTri[0] = Point2f( src.cols*0.0, src.rows*0.33 );
dstTri[1] = Point2f( src.cols*0.85, src.rows*0.25 );
dstTri[2] = Point2f( src.cols*0.15, src.rows*0.7 );你可能想把这些点绘出来以获得对变换的更直观感受. 他们的位置大概就是在上面图例中的点的位置 (原理部分). 你会注意到由三点定义的三角形的大小和方向改变了.
通过这两组点, 我们能够使用OpenCV函数 getAffineTransform 来求出仿射变换:
warp_mat = getAffineTransform( srcTri, dstTri );
我们获得了用以描述仿射变换的
矩阵 (在这里是 warp_mat)将刚刚求得的仿射变换应用到源图像
warpAffine( src, warp_dst, warp_mat, warp_dst.size() );
函数有以下参数:
- src: 输入源图像
- warp_dst: 输出图像
- warp_mat: 仿射变换矩阵
- warp_dst.size(): 输出图像的尺寸
这样我们就获得了变换后的图像! 我们将会把它显示出来. 在此之前, 我们还想要旋转它...
旋转: 想要旋转一幅图像, 你需要两个参数:
- 旋转图像所要围绕的中心
- 旋转的角度. 在OpenCV中正角度是逆时针的
- 可选择: 缩放因子
我们通过下面的代码来定义这些参数:
Point center = Point( warp_dst.cols/2, warp_dst.rows/2 );
double angle = -50.0;
double scale = 0.6;我们利用OpenCV函数 getRotationMatrix2D 来获得旋转矩阵, 这个函数返回一个
矩阵 (这里是 rot_mat)rot_mat = getRotationMatrix2D( center, angle, scale );
现在把旋转应用到仿射变换的输出.
warpAffine( warp_dst, warp_rotate_dst, rot_mat, warp_dst.size() );
最后我们把仿射变换和旋转的结果绘制在窗体中,源图像也绘制出来以作参照:
namedWindow( source_window, CV_WINDOW_AUTOSIZE );
imshow( source_window, src ); namedWindow( warp_window, CV_WINDOW_AUTOSIZE );
imshow( warp_window, warp_dst ); namedWindow( warp_rotate_window, CV_WINDOW_AUTOSIZE );
imshow( warp_rotate_window, warp_rotate_dst );等待用户退出程序
waitKey(0);
结果
编译上述例程之后, 我们给出一个图像的路径作为参数. 比如这样一幅图片:

仿射变换后我们获得:

最后, 应用了一个负角度旋转 (记住负角度指的是顺时针) 和基于一个缩放因子的缩放之后, 我们得到:

翻译者
zhlifly@ OpenCV中文网站 <zhlifly@gmail.com>
【转】【opencv】仿射变换的更多相关文章
- 【OpenCV新手教程之十八】OpenCV仿射变换 & SURF特征点描写叙述合辑
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/33320997 作者:毛星云(浅墨) ...
- OpenCV——仿射变换
什么是仿射变换? 一个任意的仿射变换都能表示为 乘以一个矩阵 (线性变换) 接着再 加上一个向量 (平移). 综上所述, 我们能够用仿射变换来表示: 旋转 (线性变换) 平移 (向量加) 缩放操作 ( ...
- OpenCV仿射变换+投射变换+单应性矩阵
本来想用单应性求解小规模运动的物体的位移,但是后来发现即使是很微小的位移也会带来超级大的误差甚至错误求解,看起来这个方法各种行不通,还是要匹配知道深度了以后才能从三维仿射变换来入手了,纠结~ esti ...
- opencv 仿射变换 投射变换, 单应性矩阵
仿射 estimateRigidTransform():计算多个二维点对或者图像之间的最优仿射变换矩阵 (2行x3列),H可以是部分自由度,比如各向一致的切变. getAffineTransform( ...
- opencv 仿射变换
import cv2 as cv import numpy as np img = cv.imread('../images/face.jpg') h, w = img.shape[:2] mat_s ...
- 系列文章 -- OpenCV入门教程
<OpenCV3编程入门>内容简介&勘误&配套源代码下载 [OpenCV入门教程之十八]OpenCV仿射变换 & SURF特征点描述合辑 [OpenCV入门教程之 ...
- 目标检测之harr---角点检测harr 的opencv实现
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/29356187 作者:毛星云(浅墨) ...
- opencv 用户文档 错误更正 仿射变换
今天在看opencv官方给出的仿射变换计算仿射变换矩阵的文档的时候,发现官方文档中有个很明显的错误,再次给大家提个醒. 官方文档连接: http://opencv.willowgarage.com/d ...
- opencv 图像仿射变换 计算仿射变换后对应特征点的新坐标 图像旋转、缩放、平移
常常需要最图像进行仿射变换,仿射变换后,我们可能需要将原来图像中的特征点坐标进行重新计算,获得原来图像中例如眼睛瞳孔坐标的新的位置,用于在新得到图像中继续利用瞳孔位置坐标. 仿射变换在:http:// ...
随机推荐
- 简单的doc命令
cd 切换目录 dir 显示目录列表 mkdir 创建目录(mkdir) rmdir 删除空目录(rmdir test) rmdir /s 删除非空目录(rmdir test /s) echo 创建 ...
- php随机生成福彩双色球号码
发布:thebaby 来源:net [大 中 小] 不瞒您说,俺也是个双色球爱好者,经常买,但迟迟没有中过一等奖,哈哈.这里为大家介绍用php随机生成福彩双色球号码的二种方法,供朋友们学习 ...
- 打开网页自动弹出qq客户端
新建js后调用即可,打开网站自动弹出qq对话框,若qq为关闭状态则启动qq,之后弹出对话框. document.write("<iframe src='tencent://messag ...
- int 占一个机器字长
int与short int是不一样的. C++标准规定,int占一个机器字长.在32位系统中int占32位,也就是4个字节, 而在老式的16位系统中,int占16位,即2个字节. 而C++标准中只限制 ...
- 用Thread类创建线程-2
支持原创,本系列文章均转自:http://www.blogjava.net/nokiaguy/category/38172.html 在Java中创建线程有两种方法:使用Thread类和使用Runna ...
- log.isDebugEnabled()的使用
转自: http://huangxx.iteye.com/blog/190693 在使用log4j,common-log这样的log框架时,发现很多代码中这样写 if (log.isDebugEn ...
- 【HDOJ】2546 饭卡
01背包,需要先对数据升序排序.这样保证优先购买最贵的东西,才满足背包条件. #include <stdio.h> #include <string.h> #include & ...
- EditPlus+MinGW搭建简易的C/C++开发环境
EditPlus+MinGW搭建简易的C/C++开发环境 有时候想用C编点小程序,但是每次都要启动那难用又难看的VC实在是不情愿,而且老是会生成很多没用的中间文件,很讨厌,后来看到网上有很多人用Edi ...
- bzoj3238
都LCP了很显然是要用到后缀数组的 显然前面的那个东西是可以直接算出来的 关键在于LCP的和怎么快速的计算 不难想到穷举height[i],然后判断这个height[i]可能成为多少对后缀的LCP 考 ...
- bzoj2705
一个常用的结论(方法) 只要知道gcd(i,n)=L 的i的个数s,我们就能很轻易得出答案 gcd(i,n)=L gcd(i/L,n/L)=1 不难得到这样的s=与n/L互质的个数=phi(n/L) ...