cad.net 透視變換
阿新 • • 發佈:2021-08-08
說明
發現網路很多文章的透視變換都是呼叫一下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的函式,
現在我們知道了透視變換最重要是:GetPerspectiveTransform
和WarpPerspective
公式
其中最重要的部分就是這個公式,如果不會建議先看視訊參考.
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邊形再進行每個四邊形變換即可.
(完)