OpenCV for Unity:4 点 calibration/reprojection
OpenCV for Unity : 4-point calibration/reprojection
这是我在 Stack 上的第一个 post,因此对于我的笨拙,我深表歉意。请让我知道我是否可以改进我的问题。
► 我想达到的目标(长期而言):
我尝试使用 OpenCV fo Unity 使用激光指示器来操纵我的 Unity3d 演示文稿。
我相信一张图片胜过千言万语,所以这应该是最能说明问题的:
► 问题是什么:
我尝试从相机视图(某种梯形)到平面 space 进行简单的 4 点校准(投影)。
我认为这将是非常基础和简单的事情,但我没有使用 OpenCV 的经验,所以我无法让它工作。
► 示例:
我做了一个简单得多的例子,没有任何激光检测和所有其他东西。我尝试重新投影到平面中的只有 4 点梯形 space。
Link 到整个示例项目: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;
}
}
在此先感谢您的帮助。
这个问题不是特定于 opencv 的,而是基于数学的,在计算机图形学领域更常见。你要找的是 Projective Transformation.
投影变换采用一组坐标并将它们投影到某物上。在您的情况下,您希望将相机视图中的 2D 点投影到平面上的 2D 点。
所以我们想要一个 2D-Space 的投影变换。要执行投影变换,我们需要找到我们想要应用的变换的投影矩阵。在这种情况下,我们需要一个矩阵来表示相机相对于平面的投影变形。
要使用投影,我们首先需要将我们的点转换为 homogeneous coordinates。为此,我们只需向向量中添加一个值为 1 的新分量。因此 (x,y)
变为 (x,y,1)
。我们将用我们所有的五个可用点来做到这一点。
现在我们从实际的数学开始。先定义一下:摄像机的视角和各自的坐标为camera space
,相对于平面的坐标在flat space
。设 c₁
到 c₄
是平面相对于相机 space 作为齐次向量的角点。设 p
是我们在相机 space 中找到的点,p'
是我们想要在平面 space 中找到的点,它们都是齐次向量。
从数学上讲,我们正在寻找一个矩阵 C
,它可以让我们通过给定 p
.
来计算 p'
p' = C * p
现在我们显然需要找到 C
。要找到二维 space 的投影矩阵,我们需要四个点(多么方便..)我假设 c₁
会去 (0,0)
,c₂
会去(0,1)
、c₃
到 (1,0)
和 c₄
到 (1,1)
。您需要使用例如求解两个矩阵方程高斯行消除或 LR 分解算法。 OpenCV 应该包含为您完成这些任务的函数,但要注意矩阵调节及其对可用解决方案的影响。
现在回到矩阵。您需要计算两个基本变化矩阵,因为它们被调用。它们用于更改坐标系的参考系(这正是我们想要做的)。第一个矩阵会将我们的坐标转换为三维基向量,第二个矩阵会将我们的 2D 平面转换为三维基向量。
对于坐标一,您需要使用以下等式计算 λ
、μ
和 r
:
⌈ c₁.x c₂.x c₃.x ⌉ ⌈ λ ⌉ ⌈ c₄.x ⌉
c₁.y c₂.y c₃.y * μ = c₄.y
⌊ 1 1 1 ⌋ ⌊ r ⌋ ⌊ 1 ⌋
这将带您进入您的第一个 Matrix,A
⌈ λ*c₁.x μ*c₂.x r*c₃.x ⌉
A = λ*c₁.y μ*c₂.y r*c₃.y
⌊ λ μ r ⌋
A 现在会将点 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 ⌋
A
和 B
现在将从这些三维基础向量映射到您各自的 space。但这并不是我们想要的。我们想要 camera space -> basis -> flat space
,所以只有矩阵 B
在正确的方向上操作。但这很容易通过反转 A
来解决。这将为我们提供矩阵 C = B * A⁻¹
(注意 B
和 A⁻¹
的顺序不可互换)。这给我们留下了一个公式来计算 p'
out of p
.
p' = C * p
p' = B * A⁻¹ * p
从左到右阅读它,例如:取 p,将 p 从相机 space 转换为基向量并将其转换为平面 space。
如果没记错的话,p'
还是三个分量,所以我们需要先去均质化p'
才能使用。这将产生
x' = p'.x / p'.z
y' = p'.y / p'.z
和中提琴,我们已经成功地将激光点从相机视图转换到一张平面纸上。完全没有太复杂......
我开发代码。 MouseUp 调用此函数。和分辨率编辑;
void Cal()
{
// Webcam Resolution 1280*720
MatOfPoint2f pts_src = new MatOfPoint2f(
new Point(Double.Parse(imagePoints.get(0,0).GetValue(0).ToString()), Double.Parse(imagePoints.get(0, 0).GetValue(1).ToString())),
new Point(Double.Parse(imagePoints.get(1,0).GetValue(0).ToString()), Double.Parse(imagePoints.get(1, 0).GetValue(1).ToString())),
new Point(Double.Parse(imagePoints.get(2,0).GetValue(0).ToString()), Double.Parse(imagePoints.get(2, 0).GetValue(1).ToString())),
new Point(Double.Parse(imagePoints.get(3,0).GetValue(0).ToString()), Double.Parse(imagePoints.get(3, 0).GetValue(1).ToString()))
);
//Resolution 1920*1080
MatOfPoint2f pts_dst = new MatOfPoint2f(
new Point(0, 0),
new Point(1920, 0),
new Point(1920, 1080),
new Point(0, 1080)
);
// 1. Calculate Homography
Mat h = Calib3d.findHomography((pts_src), (pts_dst));
// Pick Point (WebcamDummy Cavas : 1280*0.5f / 720*0.5f)
MatOfPoint2f srcPointMat = new MatOfPoint2f(
new Point(dummyCross.anchoredPosition.x*2.0f, dummyCross.anchoredPosition.y*2.0f)
);
MatOfPoint2f dstPointMat = new MatOfPoint2f();
{
//2. h Mat Mul srcPoint to dstPoint
Core.perspectiveTransform(srcPointMat, dstPointMat, h);
Vector2 v = new Vector2((float)dstPointMat.get(0, 0)[0], (float)dstPointMat.get(0, 0)[1]);
//(ResultDummy Cavas: 1920 * 0.5f / 1080 * 0.5f)
dummyResult.anchoredPosition = v*0.5f;
Debug.Log(dummyCross.anchoredPosition.ToString() + "\n" + dummyResult.anchoredPosition.ToString());
}
}
这是我在 Stack 上的第一个 post,因此对于我的笨拙,我深表歉意。请让我知道我是否可以改进我的问题。
► 我想达到的目标(长期而言):
我尝试使用 OpenCV fo Unity 使用激光指示器来操纵我的 Unity3d 演示文稿。
我相信一张图片胜过千言万语,所以这应该是最能说明问题的:
► 问题是什么:
我尝试从相机视图(某种梯形)到平面 space 进行简单的 4 点校准(投影)。
我认为这将是非常基础和简单的事情,但我没有使用 OpenCV 的经验,所以我无法让它工作。
► 示例:
我做了一个简单得多的例子,没有任何激光检测和所有其他东西。我尝试重新投影到平面中的只有 4 点梯形 space。
Link 到整个示例项目: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;
}
}
在此先感谢您的帮助。
这个问题不是特定于 opencv 的,而是基于数学的,在计算机图形学领域更常见。你要找的是 Projective Transformation.
投影变换采用一组坐标并将它们投影到某物上。在您的情况下,您希望将相机视图中的 2D 点投影到平面上的 2D 点。
所以我们想要一个 2D-Space 的投影变换。要执行投影变换,我们需要找到我们想要应用的变换的投影矩阵。在这种情况下,我们需要一个矩阵来表示相机相对于平面的投影变形。
要使用投影,我们首先需要将我们的点转换为 homogeneous coordinates。为此,我们只需向向量中添加一个值为 1 的新分量。因此 (x,y)
变为 (x,y,1)
。我们将用我们所有的五个可用点来做到这一点。
现在我们从实际的数学开始。先定义一下:摄像机的视角和各自的坐标为camera space
,相对于平面的坐标在flat space
。设 c₁
到 c₄
是平面相对于相机 space 作为齐次向量的角点。设 p
是我们在相机 space 中找到的点,p'
是我们想要在平面 space 中找到的点,它们都是齐次向量。
从数学上讲,我们正在寻找一个矩阵 C
,它可以让我们通过给定 p
.
p'
p' = C * p
现在我们显然需要找到 C
。要找到二维 space 的投影矩阵,我们需要四个点(多么方便..)我假设 c₁
会去 (0,0)
,c₂
会去(0,1)
、c₃
到 (1,0)
和 c₄
到 (1,1)
。您需要使用例如求解两个矩阵方程高斯行消除或 LR 分解算法。 OpenCV 应该包含为您完成这些任务的函数,但要注意矩阵调节及其对可用解决方案的影响。
现在回到矩阵。您需要计算两个基本变化矩阵,因为它们被调用。它们用于更改坐标系的参考系(这正是我们想要做的)。第一个矩阵会将我们的坐标转换为三维基向量,第二个矩阵会将我们的 2D 平面转换为三维基向量。
对于坐标一,您需要使用以下等式计算 λ
、μ
和 r
:
⌈ c₁.x c₂.x c₃.x ⌉ ⌈ λ ⌉ ⌈ c₄.x ⌉
c₁.y c₂.y c₃.y * μ = c₄.y
⌊ 1 1 1 ⌋ ⌊ r ⌋ ⌊ 1 ⌋
这将带您进入您的第一个 Matrix,A
⌈ λ*c₁.x μ*c₂.x r*c₃.x ⌉
A = λ*c₁.y μ*c₂.y r*c₃.y
⌊ λ μ r ⌋
A 现在会将点 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 ⌋
A
和 B
现在将从这些三维基础向量映射到您各自的 space。但这并不是我们想要的。我们想要 camera space -> basis -> flat space
,所以只有矩阵 B
在正确的方向上操作。但这很容易通过反转 A
来解决。这将为我们提供矩阵 C = B * A⁻¹
(注意 B
和 A⁻¹
的顺序不可互换)。这给我们留下了一个公式来计算 p'
out of p
.
p' = C * p
p' = B * A⁻¹ * p
从左到右阅读它,例如:取 p,将 p 从相机 space 转换为基向量并将其转换为平面 space。
如果没记错的话,p'
还是三个分量,所以我们需要先去均质化p'
才能使用。这将产生
x' = p'.x / p'.z
y' = p'.y / p'.z
和中提琴,我们已经成功地将激光点从相机视图转换到一张平面纸上。完全没有太复杂......
我开发代码。 MouseUp 调用此函数。和分辨率编辑;
void Cal()
{
// Webcam Resolution 1280*720
MatOfPoint2f pts_src = new MatOfPoint2f(
new Point(Double.Parse(imagePoints.get(0,0).GetValue(0).ToString()), Double.Parse(imagePoints.get(0, 0).GetValue(1).ToString())),
new Point(Double.Parse(imagePoints.get(1,0).GetValue(0).ToString()), Double.Parse(imagePoints.get(1, 0).GetValue(1).ToString())),
new Point(Double.Parse(imagePoints.get(2,0).GetValue(0).ToString()), Double.Parse(imagePoints.get(2, 0).GetValue(1).ToString())),
new Point(Double.Parse(imagePoints.get(3,0).GetValue(0).ToString()), Double.Parse(imagePoints.get(3, 0).GetValue(1).ToString()))
);
//Resolution 1920*1080
MatOfPoint2f pts_dst = new MatOfPoint2f(
new Point(0, 0),
new Point(1920, 0),
new Point(1920, 1080),
new Point(0, 1080)
);
// 1. Calculate Homography
Mat h = Calib3d.findHomography((pts_src), (pts_dst));
// Pick Point (WebcamDummy Cavas : 1280*0.5f / 720*0.5f)
MatOfPoint2f srcPointMat = new MatOfPoint2f(
new Point(dummyCross.anchoredPosition.x*2.0f, dummyCross.anchoredPosition.y*2.0f)
);
MatOfPoint2f dstPointMat = new MatOfPoint2f();
{
//2. h Mat Mul srcPoint to dstPoint
Core.perspectiveTransform(srcPointMat, dstPointMat, h);
Vector2 v = new Vector2((float)dstPointMat.get(0, 0)[0], (float)dstPointMat.get(0, 0)[1]);
//(ResultDummy Cavas: 1920 * 0.5f / 1080 * 0.5f)
dummyResult.anchoredPosition = v*0.5f;
Debug.Log(dummyCross.anchoredPosition.ToString() + "\n" + dummyResult.anchoredPosition.ToString());
}
}