仿射变换,又是一个很复杂的数学概念,具体的描述还是读者们自己取百度一下吧。我找了一段其它网友对于图像仿射变换的描述:仿射变换是指图像可以通过一系列的几何变换来实现平移、旋转等多种操作。该变换能够保持图像的平直性和平行性,平直性是指图像经过仿射变换后,直线仍然是直线;平行性是指图像在完成仿射变换后,平行线仍然是平行线。
作者水平有限,具体的数学原理和实现过程暂时就不解释了。这里只用代码告诉大家它能干什么。先看一下WarpAffine()函数的说明:
public static void WarpAffine(
IInputArray src, // 输入图像
IOutputArray dst, // 输出图像
IInputArray mapMatrix, // 一个2X3的变换矩阵
Size dsize, // 变换后的大小
Inter interMethod = Inter.Linear, // 插值方法标志,不明白
Warp warpMethod = Warp.Default, // 扭曲方法,不明白
BorderType borderMode = BorderType.Constant, // 边类型,不明白
MCvScalar borderValue = default(MCvScalar) // 边界值,默认为 0
)
这个函数比较复杂,好多参数我都不知道什么意思,不过不耽误使用。以一张? 东北虎.jpg(宽度995,高度597)为例,下面逐个功能介绍:
比如想要让图像右移120像素,下移50像素,运行以下代码:
Mat tempMat = srcMat.Clone();
Mat dstMat = new Mat();
Matrix<float> mapMatrix = new Matrix<float>(2, 3, 1);
mapMatrix[0, 0] = 1;
mapMatrix[0, 1] = 0;
mapMatrix[0, 2] = 120;
mapMatrix[1, 0] = 0;
mapMatrix[1, 1] = 1;
mapMatrix[1, 2] = 50;
CvInvoke.WarpAffine(tempMat, dstMat, mapMatrix, new System.Drawing.Size(tempMat.Cols, tempMat.Rows)); // 进行仿射变换
CvInvoke.Imshow("Result Mat, " + dstMat.Size.ToString(), dstMat);
结果是什么样呢,看下图,输出的图像大小没变,但是移动过的部分用黑色填充,原始图像也丢失了一部分细节?
三点法就是仿射变换由平移、缩放、旋转、翻转和错切最终得到结果。具体含义都是线性代数方面的知识,看不懂。读者只需要了解使用方法就行。三个原始点和三个变换后的点对应关系是:
P1=(0,0)? ?-------->? ?P1(Cols * 0.15,Rows * 0.05)
P2=(Cols - 1,0)? -------->????P2(Cols * 0.91,Rows * 0.15)
P3=(0,Rows - 1)? ? ?-------->? ???P3 (Cols * 0.33,Rows 0.72)
实现上面仿射变换的代码如下:
Mat tempMat = srcMat.Clone();
Mat dstMat = new Mat();
PointF[] pointSrc = new PointF[] { new PointF(0, 0), new PointF(tempMat.Cols - 1, 0), new PointF(0, tempMat.Rows - 1) };
PointF[] pointDst = new PointF[] { new PointF((float)(tempMat.Cols * 0.15), (float)(tempMat.Rows * 0.05)), new PointF((float)(tempMat.Cols * 0.91), (float)(tempMat.Rows * 0.15)), new PointF((float)(tempMat.Cols * 0.33), (float)(tempMat.Rows * 0.72)) };
Mat rotationMatrix = CvInvoke.GetAffineTransform(pointSrc, pointDst);
float[,,] fArray = rotationMatrix.ToImage<Gray, float>().Data;
CvInvoke.WarpAffine(tempMat, dstMat, rotationMatrix, srcMat.Size);
CvInvoke.Circle(tempMat, new System.Drawing.Point(0, 0), 10, new MCvScalar(255, 0, 0), -1); // 原始P1,蓝色
CvInvoke.Circle(tempMat, new System.Drawing.Point(tempMat.Cols - 1, 0), 10, new MCvScalar(0, 255, 0), -1); // 原始P2,绿色
CvInvoke.Circle(tempMat, new System.Drawing.Point(0, tempMat.Rows - 1), 10, new MCvScalar(0, 0, 255), -1); // 原始P3,红色
CvInvoke.Circle(dstMat, new System.Drawing.Point((int)(tempMat.Cols * 0.15), (int)(tempMat.Rows * 0.05)), 10, new MCvScalar(255, 0, 0), -1); // 目标P1,蓝色
CvInvoke.Circle(dstMat, new System.Drawing.Point((int)(tempMat.Cols * 0.91), (int)(tempMat.Rows * 0.15)), 10, new MCvScalar(0, 255, 0), -1); // 目标P2,绿色
CvInvoke.Circle(dstMat, new System.Drawing.Point((int)(tempMat.Cols * 0.33), (int)(tempMat.Rows * 0.72)), 10, new MCvScalar(0, 0, 255), -1); // 目标P3,红色
CvInvoke.Imshow("Source Mat, " + tempMat.Size.ToString(), tempMat);
CvInvoke.Imshow("Result Mat, " + dstMat.Size.ToString(), dstMat);
首先要计算出三个原始点和三个目标点,各自存储到PointF[]类型的数组中,再用GetAffineTransform()获取变换矩阵fArray,再次执行WarpAffine()函数就可以,同样,输出的图像大小没变。
还有一种仿射变换的方法,假如我希望原始的图片,以任意一点为中心坐标,旋转一个角度,这种变换方式,就可以使用GetRotationMatrix2D()函数。要先获取旋转矩阵,再执行WarpAffine()函数。
比如让? 东北虎.jpg? 以左上角Point(0,0)为旋转中心,逆时针旋转15度,并且整幅图像缩小为原来的0.65倍,效果是这样的:
代码怎么写呢,看下面:
Mat tempMat = srcMat.Clone();
Mat mapMatrix = new Mat();
PointF p = new PointF(0, 0);
double angle = Double.Parse(-15);
double scale = Double.Parse(0.65);
CvInvoke.GetRotationMatrix2D(p, angle, scale, mapMatrix);
float[,,] fArray = mapMatrix.ToImage<Gray, float>().Data;
Mat dstMat = new Mat();
CvInvoke.WarpAffine(tempMat, dstMat, mapMatrix, srcMat.Size);
CvInvoke.Imshow("Result Mat, " + dstMat.Size.ToString(), dstMat);
GetRotationMatrix2D()就三个参数,旋转中心、旋转角度、缩放比例。旋转中心和缩放比例都好理解,旋转角度要注意,顺时针旋转,角度参数angle是负数,逆时针旋转是正负数。输出的图像大小还是没变。
上面三个例子之所以没有改变输出图像大小,是因为在执行WarpAffine()时,dsize参数就是原始图像的尺寸。仿射变换就是能缩放、平移、旋转、和更复杂的变换。但是记住啊,仿射变换的平直性和平行性,还是以三点法为例,调整的参数不变,代码也不变,换个更明显的例子:
变换后的图像,虽然外形改变了,但直线没有变形,平行线也没变形,明白了吧。
原创不易,请勿抄袭。共同进步,相互学习。??