1. 程式人生 > 其它 >cad.net 透視變換

cad.net 透視變換

說明

發現網路很多文章的透視變換都是呼叫一下openCV的函式就算了,沒有講到核心,尤其是沒有展示裡面兩個核心的程式碼:
生成透視變換矩陣 GetPerspectiveTransform
應用透視變換矩陣 WarpPerspective

相關閱讀

首先看 參考視訊
再來看 參考文章

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

公式

其中最重要的部分就是這個公式,如果不會建議先看視訊參考.

X矩陣8*1矩陣的內容是用來填充進去3*3矩陣的,3*3矩陣的末尾是1.

A矩陣B矩陣都是已知的,所以程式碼要求A逆向*B: xMatrix = aMatrix.Inverse() * bMatrix;

OpenCV的原始碼

不過在此之前,我首先去找到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都要.

/// <summary>
/// 隔行賦值
/// </summary>
public void DetachmentEvenLineOddLine()
{
    //拆離奇數行和偶數行
    Matrix ac = this.Clone();//this是Matrix
    //將陣列一分為二,然後上半部分隔行分配
    for (int i = 0; i < ac.Rows / 2; i++)//0~3
    {
        this.Insert(i * 2, ac.GetRows(i));//覆蓋此行
    }
    //下半部分插入上半部分的奇數位置
    int v = 0;
    for (int i = ac.Rows / 2; i < ac.Rows; i++)//4~7
    {
        this.Insert(v * 2 + 1, ac.GetRows(i));//覆蓋此行 將4插入到1
        ++v;
    }
}

給cad用核心程式碼

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];

                //這裡摘錄自openCV的程式碼段 https://github.com/opencv/opencv/blob/master/modules/imgproc/src/imgwarp.cpp
                //兩兩一組是規律,迴圈四次就八行
                //每次加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;
                }

                //那現在錯開了矩陣行數,要把它復原
                //結果矩陣:8*8矩陣 乘 8*1矩陣 == 8*1矩陣
                var aMatrix = new Matrix(a);
                var bMatrix = new Matrix(b);
                aMatrix.DetachmentEvenLineOddLine();
                bMatrix.DetachmentEvenLineOddLine();
                xMatrix = aMatrix.Inverse() * bMatrix; //求逆,也是全域性變換系數 8*1
            }

            //將8*1轉為3*3矩陣,每三個為一組
            Matrix matrix;
            {
                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

                var mat = new double[3, 3]
                {
                    { a,b,c },
                    { d,e,f },
                    { g,h,1 }
                };
                matrix = new Matrix(mat);
            }
            return matrix;
        }

        /// <summary>
        /// 應用透視變換矩陣
        /// </summary>
        /// <param name="pts">來源邊界內的圖形點集</param>
        /// <param name="matrix">矩陣</param>
        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 outPts = new List<Pt2>();
            foreach (var ptItem in pts)
            {
                var x = ptItem.X;
                var y = ptItem.Y;
                var ggg = g * x + h * y + 1;
                var u = (a * x + b * y + c) / ggg;
                var v = (d * x + e * y + f) / ggg;

                outPts.Add(new Pt2(u, v));
            }
            return outPts.ToArray();
        }
    }
}

超過4邊變換

拆離成多個4邊形再進行每個四邊形變換即可.

(完)