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的函数,

现在我们知道了透视变换最重要是:GetPerspectiveTransformWarpPerspective

公式

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个均分三角形,因此就可以将四边形变换为圆形.
知道了这个东西之后,貌似知道了苹果电脑最小化软件到任务栏时候的动画怎么做了.
妙啊.

(完)

posted @ 2021-08-08 21:04  惊惊  阅读(1022)  评论(1编辑  收藏  举报