【问题标题】:Interpreting the rotation matrix with respect to a defined World coordinates解释相对于定义的世界坐标的旋转矩阵
【发布时间】:2020-09-01 14:13:59
【问题描述】:

在下图中,我们看到了一个定义的世界平面坐标 (X,Y,0),其中 Z=0。我们可以看到,相机正朝着定义的世界平面前进。

世界参考点位于网格的左上角 (0,0,0)。每两个黄点之间的距离为40厘米

我已经使用棋盘校准了我的相机,然后使用内置函数 cv2.solvePnP 来估计相机相对于我定义的世界坐标的旋转和平移矢量。结果如下:

   tvec_cam= [[-5.47884374]
               [-3.08581371]
               [24.15112048]]
   rvec_cam= [[-0.02823308]
              [ 0.08623225]
              [ 0.01563199]]

根据结果,(tx,ty,tz) 似乎是正确的,因为相机位于 X,Y 世界坐标的负四分之一

但是,我对旋转向量的解释感到困惑。!

得到的旋转向量是否表明相机坐标几乎与世界坐标轴对齐(意味着几乎没有旋转!)?,

如果是,这怎么可能是真的?,由于根据 OPENCV 的相机坐标,相机的 Z 轴指向场景(即指向世界平面),X-轴指向图像写入(这意味着与 X 世界轴相反),相机的 Y 轴指向图像底部(也意味着与 Y 世界轴相反)

另外,tvec的单位是什么?

注意:我已经根据平移向量的结果说明了定义的世界坐标轴的方向(tx 和 ty 都是负数)

我用于计算旋转和平移向量的代码如下所示:

import cv2 as cv 
import numpy as np


WPoints = np.zeros((9*3,3), np.float64)
WPoints[:,:2] = np.mgrid[0:9,0:3].T.reshape(-1,2)*0.4




imPoints=np.array([[20,143],[90,143],[161,143],[231,144],[303,144],
              [374,144],[446,145],[516,146],[587,147],[18,214],[88,214]
               ,[159,215],[230,215],[302,216],[374,216],[446,216]
               ,[517,217],[588,217],[16,285],[87,285],[158,286],[229,287], 
                [301,288]
               ,[374,289],[446,289],[518,289],[589,289]],dtype=np.float64)
      
    
#load the rotation matrix [[4.38073915e+03 0.00000000e+00 1.00593352e+03]
                       #  [0.00000000e+00 4.37829226e+03 6.97020491e+02]
                     #    [0.00000000e+00 0.00000000e+00 1.00000000e+00]]
with np.load('parameters_cam1.npz') as X:
mtx, dist, _, _ = [X[i] for i in ('mtx','dist','rvecs','tvecs')]


ret,rvecs, tvecs = cv.solvePnP(WPoints, imPoints, mtx, dist)


np.savez("extrincic_camera1.npz",rvecs=rvecs,tvecs=tvecs)

print(tvecs)
print(rvecs)
cv.destroyAllWindows()

估计内在函数的代码如下所示

import numpy as np
import cv2
import glob
import argparse
import pathlib
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--path", required=True, help="path to images folder")
ap.add_argument("-e", "--file_extension", required=False, default=".jpg", 
help="extension of images")
args = vars(ap.parse_args())
path = args["path"] + "*" + args["file_extension"]
# termination criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

# prepare object points, like (0,0,0), (0.03,0,0), (0.06,0,0) ...., 
#(0.18,0.12,0)
objp = np.zeros((5*7,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:5].T.reshape(-1,2)*0.03
#print(objp)


# Arrays to store object points and image points from all the images.
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.

#images = glob.glob('left/*.jpg') #read a series of images

images = glob.glob(path)

path = 'foundContours'
#pathlib.Path(path).mkdir(parents=True, exist_ok=True) 

found = 0
for fname in images: 
    
  img = cv2.imread(fname) # Capture frame-by-frame
  #print(images[im_i])
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  # Find the chess board corners
  ret, corners = cv2.findChessboardCorners(gray, (7,5), None)
  #  print(corners)
  # If found, add object points, image points (after refining them)
  if ret == True:
    print('true')
    objpoints.append(objp)   # Certainly, every loop objp is the same, in 3D.
    #print('obj_point',objpoints)
    corners2 = cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
    # print(corners2)
    imgpoints.append(corners2)
    print(imgpoints)
    print('first_point',imgpoints[0])
    #print(imgpoints.shape())
    # Draw and display the corners
    img = cv2.drawChessboardCorners(img, (7,5), corners2, ret)
    found += 1
    cv2.imshow('img', img)
    cv2.waitKey(1000)
    # if you want to save images with detected corners 
    # uncomment following 2 lines and lines 5, 18 and 19
    image_name = path + '/calibresult' + str(found) + '.jpg'
    cv2.imwrite(image_name, img)

print("Number of images used for calibration: ", found)

# When everything done, release the capture
# cap.release()
cv2.destroyAllWindows()
#calibration
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, 
gray.shape[::-1],None,None)


#save parameters needed in undistortion
np.savez("parameters_cam1.npz",mtx=mtx,dist=dist,rvecs=rvecs,tvecs=tvecs)
np.savez("points_cam1.npz",objpoints=objpoints,imgpoints=imgpoints)

print ("Camera Matrix = |fx  0 cx|")
print ("                | 0 fy cy|")
print ("                | 0  0  1|")
print (mtx)
print('distortion coefficients=\n', dist)
print('rotation vector for each image=', *rvecs, sep = "\n")
print('translation vector for each image=', *tvecs, sep= "\n")

希望你能帮助我理解这一点

提前致谢

【问题讨论】:

  • @morynicz 非常感谢您的帮助

标签: python opencv camera-calibration rotational-matrices photogrammetry


【解决方案1】:

首先,tvec 是轴角表示 (https://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation)。

您可以使用 cv2.Rodrigues() 获得旋转矩阵。对于你的数据,我几乎得到了身份:

[[ 0.99616253 -0.01682635  0.08588995]
 [ 0.01439347  0.99947963  0.02886672]
 [-0.08633098 -0.02751969  0.99588635]]

现在,根据你图片中xy的方向,z轴指向向下(小心应用右边-手规则)。这就解释了为什么相机的 z 轴几乎与世界参考系的 z 轴对齐。


编辑:从您发布的代码中进一步挖掘:

WPoints = np.zeros((9*3,3), np.float64)
WPoints[:,:2] = np.mgrid[0:9,0:3].T.reshape(-1,2)*0.4

X 和 Y 的值都是正数,并且分别向右和向下递增,因此您确实使用了通常的约定。 您实际上是在使用 X 和 Y 分别向右和向下递增,而问题只是您在图片中绘制的箭头


编辑关于平移向量的解释:在OpenCV约定中,本地相机参考系中的点是从世界参考系中的点获得的,如下所示:

|x_cam|          |x_world|
|y_cam| = Rmat * |y_world| + tvec
|z_cam|          |z_world|

根据这个约定,tvec 是世界原点在相机参考系中的位置。更容易解释的是相机原点在世界参考系中的位置,可以得到:

cam_center = -(tvec * R_inv)

其中 R_inv 是旋转矩阵的逆矩阵。这里的旋转矩阵几乎是恒等式,因此快速近似为 -tvec,即 (5.4, 3.1, -24.1)。

【讨论】:

  • @Milo。非常感谢您的回答,我已经上传了计算 tvec 和 rvec 的代码。是的,我将网格上所有世界点的 Z 值设置为 z=0,因为所有点都位于一个平面上。对于您对轴对齐的疑惑,我想知道两个,因为 SolvePnP 认为相机轴方向与图像坐标相同。 (x ax 指向相机的右侧,y ax 指向下方,z 指向相机面对的方向)。因此,我定义的世界轴方向相应地相反
  • 我用于图像坐标的约定是你说的左上角,向右增加,y向下增加
  • 更新了答案(不要忘记将其标记为已接受 :-))相机的计算位置值似乎太大。这可能是由于内在校准期间对焦距的错误估计造成的;您对校准网格的 3D 坐标使用什么值? 0, 1, 2, 3... ?
  • 1. z 轴仍然对齐,但当 Z 指向下方穿过地板时,相机位于 z 轴的负侧。 2. 在固有校准期间的某个时刻,您通过了网格点的 3D 坐标。在互联网上的大多数教程中,您使用整数坐标,例如 (0, 0, 0), (0, 1, 0), (1, 0, 0),其中 x 和 y 等于 0, 1, 2... z = 0。你使用了什么价值观?
  • 我不这么认为。我认为您没有正确缩放网格坐标以获得正确的焦距。你能发布你用于内在校准的代码吗?
猜你喜欢
  • 2022-01-01
  • 2020-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-18
相关资源
最近更新 更多