带有openCv的Aruco标记,获取3d角坐标?

Posted

技术标签:

【中文标题】带有openCv的Aruco标记,获取3d角坐标?【英文标题】:Aruco markers with openCv, get the 3d corner coordinates? 【发布时间】:2017-09-22 11:24:11 【问题描述】:

我正在使用 opencv 3.2 检测打印的 Aruco 标记:

aruco::estimatePoseSingleMarkers(corners, markerLength, camMatrix, distCoeffs, rvecs,tvecs);

这会返回标记的平移和旋转向量。我需要的是标记每个角的 3d 坐标。

我知道标记长度,我可以做类似的事情

corner1 = tvecs[0] - markerlength /2;
corner2 = tvecs[0] + markerlength /2;

....

但是有更好的方法吗?还是现有的功能? 总结一下,我有:

2d 正方形中心的 3d 点。

那个正方形的边长。

正方形的旋转值。

如何找到角的 3d 坐标?

【问题讨论】:

我认为没有嵌入式 OpenCV 函数可以做到这一点。当然可以实现这样的功能。 感谢您的回复。你能指出我正确的方向吗?如上所述,我是否会找到相对于中心点的角位置,然后旋转整个地块以匹配旋转矢量? 【参考方案1】:

首先,假设我们只有一个标记为side = 2 * half_side

其次,aruco::detectMarker 返回相机在标记世界中的相对位置。因此,我假设您正在寻找相机世界中的角点坐标

然后,在标记的空间:

     [ half_side ]      [     0     ]
E  = [     0     ], F = [ half_side ]
     [     0     ]      [     0     ]

正方形的中心O 的坐标为tvec(在相机世界中),标记rot_mat 的旋转垫由cv::Rodrigues(rvec,rot_mat) 计算得出。

现在,使用针孔camera model,凸轮世界中的点P与标记世界的坐标之间的关系为:

[P_x_cam]             [P_x_marker]
[P_y_cam] = rot_mat * [P_y_marker] + tvec
[P_z_cam]             [P_z_marker]    

例如,中心O,在标记世界中是[0,0,0],在凸轮世界中是tvec

所以,E 在 cam 的世界中的坐标是:

[E_x_cam]             [half_side]
|E_y_cam| = rot_mat * |    0    | + tvec
[E_z_cam]             [    0    ] 

神奇的是,它是rot_mat 的第一列乘以half_sizetvec 的总和。相似地, F 的坐标是 rot_mat 的第二列乘以 half_sizetvec

现在,可以计算角点,例如

C - O = (E - O) + (F - O), B - O = (E - O) - (F - O)

其中E-O 正好是rot_mat 的第一列乘以half_size

考虑到所有这些,我们可以编写函数:

vector<Point3f> getCornersInCameraWorld(double side, Vec3d rvec, Vec3d tvec)

     double half_side = side/2;


     // compute rot_mat
     Mat rot_mat;
     Rodrigues(rvec, rot_mat);

     // transpose of rot_mat for easy columns extraction
     Mat rot_mat_t = rot_mat.t();

     // the two E-O and F-O vectors
     double * tmp = rot_mat_t.ptr<double>(0);
     Point3f camWorldE(tmp[0]*half_side,
                       tmp[1]*half_side,
                       tmp[2]*half_side);

     tmp = rot_mat_t.ptr<double>(1);
     Point3f camWorldF(tmp[0]*half_side,
                       tmp[1]*half_side,
                       tmp[2]*half_side);

     // convert tvec to point
     Point3f tvec_3f(tvec[0], tvec[1], tvec[2]);

     // return vector:
     vector<Point3f> ret(4,tvec_3f);

     ret[0] +=  camWorldE + camWorldF;
     ret[1] += -camWorldE + camWorldF;
     ret[2] += -camWorldE - camWorldF;
     ret[3] +=  camWorldE - camWorldF;

     return ret;


注 1:我讨厌 SO 没有 MathJax

注意 2:一定有一些我不知道的更快的实现。

【讨论】:

这是一个了不起的答案。完美运行的代码和易于理解的解释。 +100。 :) 非常感谢。 我知道这是一个非常古老的问题,但我只是想了解这一点。当您说“相机世界”时,您的意思是像素域?所以基本上我们试图从像素的 UV 中获取角的 XYZ ? @MohamedMostafa no, camera world 是指相机的坐标系,我认为x是向右,y是向下,z是向外。更多详情请看图here。【参考方案2】:

我使用 rvec 和 tvec 为上述标记角的旋转编写了一个 python 实现,它是从 cv2.aruco.estimatePoseSingleMarkers() 返回的。感谢@Quang Hoang 的详细解释。

import numpy as np

# rotate a markers corners by rvec and translate by tvec if given
# input is the size of a marker.
# In the markerworld the 4 markercorners are at (x,y) = (+- markersize/2, +- markersize/2)
# returns the rotated and translated corners and the rotation matrix
def rotate_marker_corners(rvec, markersize, tvec = None):

    mhalf = markersize / 2.0
    # convert rot vector to rot matrix both do: markerworld -> cam-world
    mrv, jacobian = cv2.Rodrigues(rvec)

    #in markerworld the corners are all in the xy-plane so z is zero at first
    X = mhalf * mrv[:,0] #rotate the x = mhalf
    Y = mhalf * mrv[:,1] #rotate the y = mhalf
    minusX = X * (-1)
    minusY = Y * (-1)

    # calculate 4 corners of the marker in camworld. corners are enumerated clockwise
    markercorners = []
    markercorners.append(np.add(minusX, Y)) #was upper left in markerworld
    markercorners.append(np.add(X, Y)) #was upper right in markerworld
    markercorners.append(np.add( X, minusY)) #was lower right in markerworld
    markercorners.append(np.add(minusX, minusY)) #was lower left in markerworld
    # if tvec given, move all by tvec
    if tvec is not None:
        C = tvec #center of marker in camworld
        for i, mc in enumerate(markercorners):
            makercorners[i] = np.add(C,mc) #add tvec to each corner
    #print('Vec X, Y, C, dot(X,Y)', X,Y,C, np.dot(X,Y)) # just for debug
    markercorners = np.array(markercorners,dtype=np.float32) # type needed when used as input to cv2
    return markercorners, mrv

'''
Copyright 2019 Marco Noll, Garmin International Inc. Licensed under the Apache 
License, Version 2.0 (the "License"); you may not use this file except in compliance 
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed 
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific 
language governing permissions and limitations under the License.
'''

【讨论】:

【参考方案3】:

基于@Quang 的回答,用于将任意点转换为相机坐标的 C# 代码。当然它需要Rt 向量,所以你需要一个标记来获取它们。

private Point3d GetWorldPoint(Point3d input, Vec3d rvec, Vec3d tvec)

    var rot_mat = new Mat();

    Cv2.Rodrigues(MatOfDouble.FromArray(rvec.Item0, rvec.Item1, rvec.Item2), rot_mat);

    var pointProject = (rot_mat * MatOfDouble.FromArray(input.X, input.Y, input.Z)).ToMat();
    return tvec + new Point3d(pointProject.Get<double>(0, 0), pointProject.Get<double>(0, 1), pointProject.Get<double>(0, 2));

【讨论】:

以上是关于带有openCv的Aruco标记,获取3d角坐标?的主要内容,如果未能解决你的问题,请参考以下文章

带有特定标记的 OpenCV 自定义 Aruco 字典?

Aruco Marker World Coordinates

使用Python,OpenCV生成Aruco标记

使用opencv将旋转矩阵旋转到欧拉角

使用Python,Opencv检测AprilTag

如何使用 OpenCV 绘制 3D 坐标轴以进行人脸姿态估计?