開局一張圖
透視變換可以形成一種近大遠小的效果.
可以把左邊變成右邊,也可以把右邊變成左邊.
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個均分三角形,因此就可以將四邊形變換為圓形.
知道了這個東西之后,貌似知道了蘋果電腦最小化軟件到任務欄時候的動畫怎么做了.
妙啊.
(完)