尽管在上篇文章中有了原文的链接,但是吸取以往的经验教训,还是全部拿来主义比较好,担心哪天原文链接就不能查看了,那岂不是让人心痛的碎了一地梨花
一、仿射变换
仿射变换的性质:平面上任意两条直线,经仿射变换后,仍然保持平行。
仿射变换的功能,是一种二维坐标到二维坐标之间的线性变换,保持二维图形的“平直性”(straightness,即变换后直线还是直线不会打弯,圆弧还是圆弧)和“平行性”(parallelness,其实是指保二维图形间的相对位置关系不变,平行线还是平行线,相交直线的交角不变)。仿射变换可以通过一系列的原子变换的复合来实现,包括:平移(Translation)、缩放(Scale)、翻转(Flip)、旋转(Rotation)和剪切(Shear)。
此类变换可以用一个3×3的矩阵来表示,其最后一行为(0, 0, 1)。该变换矩阵将原坐标(x, y)变换为新坐标(x', y'),这里原坐标和新坐标皆视为最末一行为(1)的三维列向量,原列向量左乘变换矩阵得到新的列向量:
[x'] [a00 a01 a02] [x] [a00*x+a01*y+a02]
[y'] = [a10 a11 a12] [y] = [a10*x+a11*y+a12]
[1 ] [0 0 1] [1] [ 1 ]
仿射变换的矩阵由图像上不共线的三个点的坐标确定,看做2*3的矩阵。
几种典型的仿射变换:
平移变换,将每一点移动到(x+tx, y+ty),变换矩阵为:
[ 1 0 tx ]
[ 0 1 ty ]
[ 0 0 1 ]
平移变换是一种“刚体变换”,就是不会产生形变的理想物体,平移当然不会改变二维图形的形状。同理,下面的“旋转变换”也是刚体变换,而“缩放”、“错切”都是会改变图形形状的。
缩放变换,将每一点的横坐标放大(缩小)至sx倍,纵坐标放大(缩小)至sy倍,变换矩阵为:
[ sx 0 0 ]
[ 0 sy 0 ]
[ 0 0 1 ]
剪切变换,变换矩阵为:
[ 1 shx 0 ]
[ shy 1 0 ]
[ 0 0 1 ]
相当于一个横向剪切与一个纵向剪切的复合,即
[ 1 0 0 ][ 1 shx 0 ]
[ shy 1 0 ][ 0 1 0 ]
[ 0 0 1 ][ 0 0 1 ]
“剪切变换”又称“错切变换”,指的是类似于四边形不稳定性那种性质,街边小商店那种铁拉门都见过吧?想象一下上面铁条构成的菱形拉动的过程,那就是“错切”的过程。
旋转变换原点,目标图形围绕原点顺时针旋转theta弧度,变换矩阵为:
[ cos(theta) -sin(theta) 0 ]
[ sin(theta) cos(theta) 0 ]
[ 0 0 1 ]
旋转变换,目标图形以(x, y)为轴心顺时针旋转theta弧度,变换矩阵为:
[ cos(theta) -sin(theta) x-x*cos+y*sin]
[ sin(theta) cos(theta) y-x*sin-y*cos ]
[ 0 0 1 ]
相当于两次平移变换与一次原点旋转变换的复合:
[1 0 -x] [cos(theta) -sin(theta) 0] [1 0 x]
[0 1 -y] [sin(theta) cos(theta) 0] [0 1 y]
[0 0 1 ] [ 0 0 1 ] [0 0 1]
opencv中的实现函数:
void cvWarpAffine( const CvArr* src, CvArr* dst, const CvMat* map_matrix,
int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,
CvScalar fillval=cvScalarAll(0) );
实现图像的仿射变换,map_matrix是2*3的仿射矩阵,要求输入和输出图像有相同的数据类型。
通过下面两个函数得到仿射矩阵:
CvMat* cvGetAffineTransform( const CvPoint2D32f* src, const CvPoint2D32f* dst,
CvMat* map_matrix ); //由三个不共线的点得到,这样才具有唯一性
CvMat* cv2DRotationMatrix( CvPoint2D32f center, double angle, double scale,
CvMat* map_matrix ); //由旋转中心,旋转角度和各向通行因子,矩阵如下:
[ a b (1-a)*center.x-b*center.y ]
[-b a b*center.x-(1-a)*center.y ] 其中:a=scale*cos(angle), b=scale*sin(angle)
void cvGetQuadrangleSubPix( const CvArr* src, CvArr* dst, const CvMat* map_matrix );
实现图像的仿射变换,使用于亚像素。
实现图像旋转的例子:
#include "stdafx.h"
#include "cv.h"
#include "highgui.h"
#include "math.h"
int main(int argc, char* argv[])
{
IplImage *src = 0;
IplImage *dst = 0;
IplImage *dst1 = 0;
/* the first command line parameter must be image file name */
if ((argc == 2) && (src = cvLoadImage (argv[1], -1)) != 0)
{
int delta = 1;
int angle = 0;
int opt = 0; // 1: 旋转加缩放
// 0: 仅仅旋转
double factor;
dst = cvCloneImage (src);
dst1 = cvCloneImage (src);
cvNamedWindow ("src", 1);
cvShowImage ("src", src);
for (;;)
{
float m[6];
// Matrix m looks like:
//
// [ m0 m1 m2 ] ===> [ A11 A12 b1 ]
// [ m3 m4 m5 ] [ A21 A22 b2 ]
//
CvMat M = cvMat (2, 3, CV_32F, m);
int w = src->width;
int h = src->height;
if (opt) // 旋转加缩放
factor = (cos (angle * CV_PI / 180.) + 1.0) * 2;
else // 仅仅旋转
factor = 1;
m[0] = (float) (factor * cos (-angle * 2 * CV_PI / 180.));
m[1] = (float) (factor * sin (-angle * 2 * CV_PI / 180.));
m[3] = -m[1];
m[4] = m[0];
// 将旋转中心移至图像中间
m[2] = w * 0.5f;
m[5] = h * 0.5f;
// dst(x,y) = A * src(x,y) + b
cvZero (dst);
cvGetQuadrangleSubPix (src, dst, &M);
cvNamedWindow ("dst", 1);
cvShowImage ("dst", dst);
if (cvWaitKey (1) == 27) //ESC
break;
angle = (int) (angle + delta) % 360;
} // for-loop
}
return 0;
}
两个函数的区别:
前者要求输入和输出图像具有相同的数据类型,有更大的资源开销(因此对小图像不太合适)且部分输出图像可以保持不变。
后者可以精确的从8位图像中提取四边形到浮点数缓存区中,具有比较小的系统开销,且总是全部改变输出图像的内容。
实验:使用例子中的仿射矩阵使用前者进行仿射变换,效果是不一样的。
对于前者:
[ m0 m1 m2 ]
[ m3 m4 m5 ]
m0 = factor * cos (angle);
m1 = factor * sin (angle);
m3 = - factor * sin (angle);
m4 = factor * cos (angle);
m2 = x * (1- factor * cos (angle)) – y * factor * sin (angle); //x * (1-m0) – y * m1
m5 = y * (1- factor * cos (angle)) + x * factor * sin (angle); //y * (1-m0) + x* m1
图像逆时针旋转Angle弧度。如果factor=1,图像不进行缩放,factor>1,图像会放大显示,factor<1,图像缩小显示。(m2,m5)由(x,y)确定,表示源图像以(x,y)位置为中心,对图像进行缩放和旋转。部分没有灰度值的区域的元素灰度值置0。以上矩阵可以通过函数cv2DRotationMatrix( cvPoint2D32f(x, y), angle, factor, map_matrix ); 其中,CvMat* map_matrix。直观上,先将变换后的图像保存为与源图像一样大小的,然后从左上角开始取出与目的图像一样大小的图像,作为输出结果,没有灰度值的置为0。
对于后者:
[ m0 m1 m2 ]
[ m3 m4 m5 ]
m0 = factor * cos (angle); //同前
m1 = factor * sin (angle); //同前
m3 = - factor * sin (angle); //同前
m4 = factor * cos (angle); //同前
m2 = x;
m5 = y;
以上表示:图像顺时针旋转Angle弧度。如果factor=1,图像不进行缩放,factor>1,图像会缩小显示,factor<1,图像放大显示。(m2,m5):表示将源图像中的(m2,m5)位置移动到新图像的中心位置,然后在新图像的中心位置处对图像进行缩放和旋转。部分没有灰度值的区域会使用附近的边界灰度值进行插值。根据输出图像的大小,直观上,输出前面旋转好后图像中心位置向外扩展的输出图像大小的图像部分,没有灰度值的部分进行插值。
二、透视变换
opencv中的实现函数:
void cvWarpPerspective( const CvArr* src, CvArr* dst, const CvMat* map_matrix, int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS, CvScalar fillval=cvScalarAll(0));
实现透视变换,map_matrix是3*3的变换矩阵,有下面函数可得到:
CvMat* cvGetPerspectiveTransform( const CvPoint2D32f* src, const CvPoint2D32f* dst, CvMat* map_matrix ); //用四边形的四个点得到
三、二次线性变换
目前为止还没使用过。