2017-05-07 167 views
7

這是我在Stack上的第一篇文章,所以我很抱歉我的笨拙。無論如何,請讓我知道我是否可以改進我的問題。OpenCV for Unity:4點校準/再投影

►我想實現(在長期)什麼:

我試圖操縱我的Unity3d演示使用OpenCV的FO團結激光指示器。

我相信一張圖片勝過千言萬語,所以這應該說是最:

enter image description here

►什麼問題:

我試圖做一個簡單的從相機視圖(某種梯形)到投影平面的4點校準(投影)。

我認爲這將是非常基本和簡單的事情,但我沒有OpenCV的經驗,我無法使它工作。

►樣品:

我做了一個更復雜的例子,沒有任何激光探測和所有其他的東西。我試圖重新投射到飛機空間中的只有4點梯形。

鏈接到整個樣本項目:https://1drv.ms/u/s!AiDsGecSyzmuujXGQUapcYrIvP7b

從我的例子中的核心腳本:

using OpenCVForUnity; 
using System.Collections; 
using System.Collections.Generic; 
using UnityEngine; 
using UnityEngine.UI; 
using System; 

public class TestCalib : MonoBehaviour 
{ 
    public RawImage displayDummy; 
    public RectTransform[] handlers; 
    public RectTransform dummyCross; 
    public RectTransform dummyResult; 

    public Vector2 webcamSize = new Vector2(640, 480); 
    public Vector2 objectSize = new Vector2(1024, 768); 

    private Texture2D texture; 

    Mat cameraMatrix; 
    MatOfDouble distCoeffs; 

    MatOfPoint3f objectPoints; 
    MatOfPoint2f imagePoints; 

    Mat rvec; 
    Mat tvec; 
    Mat rotationMatrix; 
    Mat imgMat; 


    void Start() 
    { 
     texture = new Texture2D((int)webcamSize.x, (int)webcamSize.y, TextureFormat.RGB24, false); 
     if (displayDummy) displayDummy.texture = texture; 
     imgMat = new Mat(texture.height, texture.width, CvType.CV_8UC3); 
    } 


    void Update() 
    { 
     imgMat = new Mat(texture.height, texture.width, CvType.CV_8UC3); 
     Test(); 
     DrawImagePoints(); 
     Utils.matToTexture2D(imgMat, texture); 
    } 

    void DrawImagePoints() 
    { 
     Point[] pointsArray = imagePoints.toArray(); 
     for (int i = 0; i < pointsArray.Length; i++) 
     { 
      Point p0 = pointsArray[i]; 
      int j = (i < pointsArray.Length - 1) ? i + 1 : 0; 
      Point p1 = pointsArray[j]; 

      Imgproc.circle(imgMat, p0, 5, new Scalar(0, 255, 0, 150), 1); 
      Imgproc.line(imgMat, p0, p1, new Scalar(255, 255, 0, 150), 1); 
     } 
    } 


    private void DrawResults(MatOfPoint2f resultPoints) 
    { 
     Point[] pointsArray = resultPoints.toArray(); 
     for (int i = 0; i < pointsArray.Length; i++) 
     { 
      Point p = pointsArray[i]; 
      Imgproc.circle(imgMat, p, 5, new Scalar(255, 155, 0, 150), 1); 
     } 
    } 

    public void Test() 
    { 
     float w2 = objectSize.x/2F; 
     float h2 = objectSize.y/2F; 

     /* 
     objectPoints = new MatOfPoint3f(
      new Point3(-w2, -h2, 0), 
      new Point3(w2, -h2, 0), 
      new Point3(-w2, h2, 0), 
      new Point3(w2, h2, 0) 
     ); 
     */ 

     objectPoints = new MatOfPoint3f(
      new Point3(0, 0, 0), 
      new Point3(objectSize.x, 0, 0), 
      new Point3(objectSize.x, objectSize.y, 0), 
      new Point3(0, objectSize.y, 0) 
     ); 

     imagePoints = GetImagePointsFromHandlers(); 

     rvec = new Mat(1, 3, CvType.CV_64FC1); 
     tvec = new Mat(1, 3, CvType.CV_64FC1); 
     rotationMatrix = new Mat(3, 3, CvType.CV_64FC1); 


     double fx = webcamSize.x/objectSize.x; 
     double fy = webcamSize.y/objectSize.y; 
     double cx = 0; // webcamSize.x/2.0f; 
     double cy = 0; // webcamSize.y/2.0f; 
     cameraMatrix = new Mat(3, 3, CvType.CV_64FC1); 
     cameraMatrix.put(0, 0, fx); 
     cameraMatrix.put(0, 1, 0); 
     cameraMatrix.put(0, 2, cx); 
     cameraMatrix.put(1, 0, 0); 
     cameraMatrix.put(1, 1, fy); 
     cameraMatrix.put(1, 2, cy); 
     cameraMatrix.put(2, 0, 0); 
     cameraMatrix.put(2, 1, 0); 
     cameraMatrix.put(2, 2, 1.0f); 

     distCoeffs = new MatOfDouble(0, 0, 0, 0); 
     Calib3d.solvePnP(objectPoints, imagePoints, cameraMatrix, distCoeffs, rvec, tvec); 

     Mat uv = new Mat(3, 1, CvType.CV_64FC1); 
     uv.put(0, 0, dummyCross.anchoredPosition.x); 
     uv.put(1, 0, dummyCross.anchoredPosition.y); 
     uv.put(2, 0, 0); 

     Calib3d.Rodrigues(rvec, rotationMatrix); 
     Mat P = rotationMatrix.inv() * (cameraMatrix.inv() * uv - tvec); 

     Vector2 v = new Vector2((float)P.get(0, 0)[0], (float)P.get(1, 0)[0]); 
     dummyResult.anchoredPosition = v; 
    } 

    private MatOfPoint2f GetImagePointsFromHandlers() 
    { 
     MatOfPoint2f m = new MatOfPoint2f(); 
     List<Point> points = new List<Point>(); 
     foreach (RectTransform handler in handlers) 
     { 
      Point p = new Point(handler.anchoredPosition.x, handler.anchoredPosition.y); 
      points.Add(p); 
     } 

     m.fromList(points); 
     return m; 
    } 
} 

預先感謝任何幫助。

回答

1

這個問題不是opencv特有的,但是基於數學和計算機圖形領域。你在找什麼叫做Projective Transformation

投影變換需要一組座標並將它們投影到某物上。在你的情況下,你想在相機視圖中投影2D點到平面上的2D點。

所以我們需要2D空間的投影變換。爲了執行投影變換,我們需要找到我們想要應用的變換的投影矩陣。在這種情況下,我們需要一個矩陣來表達攝像機相對於平面的投影變形。

要使用預測,我們首先需要將點轉換爲homogeneous coordinates。要做到這一點,我們只需將一個新組件添加到我們的向量中,值爲1.因此(x,y)變爲(x,y,1)。我們將用我們所有五個可用的點來做到這一點。

現在我們從實際的數學開始。首先是一些定義:相機的視角和相應的座標應爲camera space,相對於平面的座標爲flat space。令c₁c₄爲與相機空間相關的平面的角點爲均勻向量。讓p成爲我們在相機空間找到的一點,並且我們希望在平坦空間中找到這一點,這兩點同樣是同質矢量。

在數學上,我們正在尋找一個矩陣C,這將允許我們通過給p計算p'

p' = C * p 

現在我們顯然需要找到C。爲了找到二維空間中的投影矩陣,我們需要四點(如何方便..)我會假設c₁會去(0,0)c₂會去(0,1)c₃(1,0)c₄(1,1)。您需要使用例如解決兩個矩陣方程。高斯行消除或LR分解算法。 OpenCV應包含爲您完成這些任務的功能,但請注意矩陣調節及其對可用解決方案的影響。

現在回到矩陣。當他們被調用時,你需要計算兩個基礎變化矩陣。它們用於改變座標的參照系(正是​​我們想要做的)。第一個矩陣將把我們的座標轉換成三維基向量,第二個矩陣將把我們的二維平面轉換成三維基向量。

爲配合一個你需要計算λμr在下面的公式:

⌈ c₁.x c₂.x c₃.x ⌉  ⌈ λ ⌉ ⌈ c₄.x ⌉ 
    c₁.y c₂.y c₃.y * μ = c₄.y 
⌊ 1  1  1 ⌋  ⌊ r ⌋ ⌊ 1 ⌋ 

這將導致你的第一矩陣,A

⌈ λ*c₁.x μ*c₂.x r*c₃.x ⌉ 
A = λ*c₁.y μ*c₂.y r*c₃.y 
    ⌊ λ   μ  r ⌋ 

遺囑現在將點c₁c₄映射到基礎座標(1,0,0)(0,1,0)(0,0,1)(1,1,1)。我們現在爲我們的飛機做同樣的事情。首先解決

⌈ 0 0 1 ⌉  ⌈ λ ⌉ ⌈ 1 ⌉ 
    0 1 0 * μ = 1 
⌊ 1 1 1 ⌋  ⌊ r ⌋ ⌊ 1 ⌋ 

,並得到B

⌈ 0 0 r ⌉ 
B = 0 μ 0 
    ⌊ λ μ r ⌋ 

AB現在將從這三個維的基向量映射到你的各個空間。但那不是我們想要的。我們想要camera space -> basis -> flat space,所以只有矩陣B在正確的方向操縱。但這很容易通過反轉A來解決。這將給我們矩陣C = B * A⁻¹(觀看的訂單BA⁻¹它是不可互換的)。這給我們留下了一個公式來計算p中的p'

p' = C * p 
p' = B * A⁻¹ * p 

從左到右讀取:p,將相機空間中的p變換爲基本向量,並將它們轉換爲平面空間。

如果您沒有記錯,p'仍然有三個組件,所以我們需要首先將p'去混勻,然後才能使用它。這將產生

x' = p'.x/p'.z 
y' = p'.y/p'.z 

和中提琴我們已經成功地將相機視圖中的激光點轉換到平坦的紙上。完全不會過於複雜或如此...

+0

非常感謝 - 這不完全是答案,但它幫助我瞭解要尋找的東西:) – IronWolf