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個均分三角形,因此就可以將四邊形變換為圓形.
知道了這個東西之后,貌似知道了蘋果電腦最小化軟件到任務欄時候的動畫怎么做了.
妙啊.

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM