cad.net 透视变换
开局一张图
透视变换可以形成一种近大远小的效果.
可以把左边变成右边,也可以把右边变成左边.
cad在实现的时候需要把圆,弧,曲线等等采样成点集...逆变的时候可以依照一定规则将他们改回圆,弧,曲线之类的...这一步有一些cv经常用到的概念...真就cv,gl不分家呗🤣
说明
发现网络很多文章的透视变换都是调用一下openCV的函数就算了,没有讲到核心,尤其是没有展示里面两个核心的代码:
生成透视变换矩阵 GetPerspectiveTransform
应用透视变换矩阵 WarpPerspective
直到利用 matlab透视变换
作为关键字才搜到带完整内容的,但是没有Warp..这个函数.害我不能白嫖
怎么他们都喜欢凡事做一半...
阅读资料的时候发现普通变换是仿射变换的子集,仿射变换是透视变换的子集,至于怎么推理出左下角两个变量就是透视变换的,我就没看懂了(套用就行了...
相关阅读
OpenCV的调用
既然有那么多CV代码,那么按照这个切入点来尝试一下先:
弄个控制台,然后工程.csproj上面引用一下:
<ItemGroup>
<PackageReference Include="OpenCvSharp4" Version="4.5.1.20210210" />
<PackageReference Include="OpenCvSharp4.runtime.win" Version="4.5.1.20210210" />
<PackageReference Include="OpenCvSharp4.Windows" Version="4.5.1.20210210" />
</ItemGroup>
c# 代码
static void Main(string[] args)
{
//一个小例子,打开路径下所有图片
var path = "../../../../OpenCVForm/Resource";//图片资源路径
var pathinfo = new DirectoryInfo(path);
if (!pathinfo.Exists)
{
System.Windows.MessageBox.Show("路径不存在" + path);
return;
}
Test2(pathinfo);
Cv2.WaitKey();
}
private static void Test2(DirectoryInfo picture)
{
var imageIn = Cv2.ImRead(picture.FullName + "\\lena.jpg"); //随便找个图片
/*以下代码只展示变换部分,其中ImageIn为输入图像,ImageOut为输出图像*/
//变换前的四点
var srcPoints = new Point2f[] {
new Point2f(0, 0),
new Point2f(imageIn.Size().Width,0),
new Point2f(imageIn.Size().Width,imageIn.Size().Height),
new Point2f(0,imageIn.Size().Height),
};
//变换后的四点
var dstPoints = srcPoints;//不变
#if true
//透视变换
dstPoints = new Point2f[] {
new Point2f(0, 0),
new Point2f(imageIn.Size().Width,imageIn.Size().Height/2),
new Point2f(imageIn.Size().Width,imageIn.Size().Height),
new Point2f(0,imageIn.Size().Height/2),
};
#endif
//根据变换前后四个点坐标,获取变换矩阵
Mat warpPerspective_mat = Cv2.GetPerspectiveTransform(srcPoints, dstPoints);
var print = Cv2.Format(warpPerspective_mat);
Debug.WriteLine(print);
//进行透视变换
Mat ImageOut = Mat.Zeros(imageIn.Rows, imageIn.Cols, imageIn.Type());
Cv2.WarpPerspective(imageIn, ImageOut, warpPerspective_mat, imageIn.Size());
//展示图片
Cv2.ImShow(picture.Name, ImageOut);
}
这段代码主要告诉我们怎么去调用CV的函数,
现在我们知道了透视变换最重要是:GetPerspectiveTransform
和WarpPerspective
公式
GetPerspectiveTransform 的返回值是一个3*3矩阵
,
制作这个3*3矩阵
需要一个8*8矩阵
和一个8*1矩阵
,再把他们结果填充进去.
如下图所示:
X矩阵的内容是用来填充进去3*3矩阵
的,3*3矩阵
的末尾是1.
A矩阵和B矩阵都是已知的,求X矩阵,所以代码是: xMatrix = aMatrix.Inverse() * bMatrix;
ps:(A^-1)*B
这样的求逆再乘的操作相当于矩阵除法
WarpPerspective 的内容比较简单,就如下计算即可
OpenCV的源码
生成AB两个矩阵,我首先去找到openCV的imgwarp.cpp源码来看,发现很秀...(怎么搜的?bing: GetPerspectiveTransform code 就出来了)
在浏览器Ctrl+F GetPerspectiveTransform
这个函数,能够看到如下内容,虽然很秀,但是没有参考文章的写法和上面图片的8*8矩阵
直观...
//两两一组是规律,循环四次就八行
//每次加4个表示数学矩阵图的第2行放在第4行,也就是数组一半的位置.很聪明的做法
for (int i = 0; i < 4; ++i)
{
var ii = i + 4;
a[i, 0] = a[ii, 3] = src[i].X;
a[i, 1] = a[ii, 4] = src[i].Y;
a[i, 2] = a[ii, 5] = 1;
a[i, 3] = a[i, 4] = a[i, 5] = a[ii, 0] = a[ii, 1] = a[ii, 2] = 0;
a[i, 6] = -src[i].X * dst[i].X;
a[i, 7] = -src[i].Y * dst[i].X;
a[ii, 6] = -src[i].X * dst[i].Y;
a[ii, 7] = -src[i].Y * dst[i].Y;
b[i, 0] = dst[i].X;
b[ii, 0] = dst[i].Y;
}
所以首先需要处理一下这个二分的矩阵,把它变成图片那样,记得A和B都要.
此处函数我写在Matrix类里面了
/// <summary>
/// 隔行赋值
/// </summary>
public void DetachmentEvenLineOddLine()
{
//拆离奇数行和偶数行
var thisClone = this.Clone();
//将数组一分为二,然后上半部分隔行分配
for (int i = 0; i < thisClone.Rows / 2; i++)//0~3
{
this.SetRow(i * 2, thisClone.GetRows(i));//覆盖此行
}
//下半部分插入上半部分的奇数位置
int v = 0;
for (int i = thisClone.Rows / 2; i < thisClone.Rows; i++)//4~7
{
this.SetRow(v * 2 + 1, thisClone.GetRows(i));//覆盖此行 将4写入到1
++v;
}
}
给cad用核心代码
这里Matrix类随便去找一个c#矩阵类...或者调用Math.net库
using System.Collections.Generic;
namespace JoinBoxCurrency
{
public partial class MathHelper
{
/// <summary>
/// 透视矩阵
/// </summary>
/// <param name="src">来源边界(通常是四个点)</param>
/// <param name="dst">目标边界(通常是四个点)</param>
/// <returns>3*3矩阵</returns>
public static Matrix GetPerspectiveTransform(Pt2[] src, Pt2[] dst)
{
Matrix xMatrix;
{
var a = new double[8, 8];
var b = new double[8, 1];
//每次加4个表示数学矩阵图的第2行放在第4行
for (int i = 0; i < 4; ++i)
{
var ii = i + 4;
a[i, 0] = a[ii, 3] = src[i].X;
a[i, 1] = a[ii, 4] = src[i].Y;
a[i, 2] = a[ii, 5] = 1;
a[i, 3] = a[i, 4] = a[i, 5] = a[ii, 0] = a[ii, 1] = a[ii, 2] = 0;
a[i, 6] = -src[i].X * dst[i].X;
a[i, 7] = -src[i].Y * dst[i].X;
a[ii, 6] = -src[i].X * dst[i].Y;
a[ii, 7] = -src[i].Y * dst[i].Y;
b[i, 0] = dst[i].X;
b[ii, 0] = dst[i].Y;
}
var aMatrix = new Matrix(a);
var bMatrix = new Matrix(b);
//隔行赋值
aMatrix.DetachmentEvenLineOddLine();
bMatrix.DetachmentEvenLineOddLine();
xMatrix = aMatrix.Inverse() * bMatrix;
}
//填充矩阵:将8*1转为3*3矩阵
double[,] mat;
{
var a = xMatrix[0, 0]; //m0
var b = xMatrix[1, 0]; //m1
var c = xMatrix[2, 0]; //m2
var d = xMatrix[3, 0]; //m3
var e = xMatrix[4, 0]; //m4
var f = xMatrix[5, 0]; //m5
var g = xMatrix[6, 0]; //m6
var h = xMatrix[7, 0]; //m7
mat = new double[3, 3]
{
{ a,b,c },
{ d,e,f },
{ g,h,1 }
};
}
return new Matrix(mat);
}
/// <summary>
/// 应用透视变换矩阵
/// </summary>
/// <param name="pts">来源边界内的图形点集</param>
/// <param name="matrix">3*3矩阵</param>
/// <returns>变换后的点集</returns>
public static Pt2[] WarpPerspective(Pt2[] pts, Matrix matrix)
{
var a = matrix[0, 0]; //m0
var b = matrix[0, 1]; //m1
var c = matrix[0, 2]; //m2
var d = matrix[1, 0]; //m3
var e = matrix[1, 1]; //m4
var f = matrix[1, 2]; //m5
var g = matrix[2, 0]; //m6
var h = matrix[2, 1]; //m7
var j = matrix[2, 2]; //m8
var outPts = new List<Pt2>();
foreach (var ptItem in pts)
{
var x = ptItem.X;
var y = ptItem.Y;
var www = g * x + h * y + j;
var u = (a * x + b * y + c) / www;
var v = (d * x + e * y + f) / www;
outPts.Add(new Pt2(u, v));
}
return outPts.ToArray();
}
}
}
这里的计算仅适用cad,在图片上面用可能导致像素点变形裂开,裂开就变成"黑像素点"了,需要邻值补偿(最小二乘法),那图片就openCV好啦.
非四边变换
拆离成多个四边形,再进行每个四边形变换.
其中两个点离得很近的话,就是三角形,所以奇数点集可以通过补点变成四个点.
由于圆形可以切为n个均分三角形,因此就可以将四边形变换为圆形.
知道了这个东西之后,貌似知道了苹果电脑最小化软件到任务栏时候的动画怎么做了.
妙啊.
(完)