带有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_size
和tvec
的总和。相似地,
F
的坐标是 rot_mat
的第二列乘以 half_size
和 tvec
。
现在,可以计算角点,例如
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# 代码。当然它需要R
和t
向量,所以你需要一个标记来获取它们。
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角坐标?的主要内容,如果未能解决你的问题,请参考以下文章