Python编写获取标定板坐标系与相机坐标系坐标变化之debug记录-solvePnP()

作者在学习eyetohand手眼标定时,使用opencv的findChessboardCorners检测棋盘角点并进行标定。遇到的第一个问题是输入参数错误,解决方法是确保object_points和image_points为二维数组。第二个问题是solvePnP返回值过多,通过弃用多余值解决。最终成功获取旋转和平移向量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在学习eyetohand手眼标定,一开始迟迟不肯自己写程序,总想着找网上现成的,但是没找到,最后还是咬咬牙学习矩阵变换原理巴拉巴拉的,尝试用python程序写。

在这里插入图片描述
思路是:在拍摄了一组标定板的照片后,如上图1所示。
利用opencv的findChessboardCorners()函数检测棋盘角点,如图2所示在这里插入图片描述
从而获取目标真实坐标(3D)和图片角点坐标(2D),接着利用opencv的solvePnP()函数获取标定板到相机的旋转矩阵和平移向量。
实际编程中遇到了一系列困扰很久的问题:
第一个问题:

rvec, tvec = cv2.solvePnP(np.array(object_points), np.array(image_points), cv2.UMat(camera_matrix), cv2.UMat(dist_coeffs))
报了以下错误:
发生异常: error
OpenCV(4.8.0) D:\a\opencv-python\opencv-python\opencv\modules\calib3d\src\solvepnp.cpp:825: error: (-215:Assertion failed) ( (npoints >= 4) || (npoints == 3 && flags == SOLVEPNP_ITERATIVE && useExtrinsicGuess) || (npoints >= 3 && flags == SOLVEPNP_SQPNP) ) && npoints == std::max(ipoints.checkVector(2, CV_32F), ipoints.checkVector(2, CV_64F)) in function 'cv::solvePnPGeneric'
  File "C:\Users\z\Desktop\py\eye2hand\board2camera.py", line 42, in get_calibration_parameters
    rvec, tvec = cv2.solvePnP(np.array(object_points), np.array(image_points), cv2.UMat(camera_matrix), cv2.UMat(dist_coeffs))
  File "C:\Users\z\Desktop\py\eye2hand\board2camera.py", line 62, in <module>
    rvec, pnp_t = get_calibration_parameters(object_points, image_points, camera_matrix, dist_coeffs)
cv2.error: OpenCV(4.8.0) D:\a\opencv-python\opencv-python\opencv\modules\calib3d\src\solvepnp.cpp:825: error: (-215:Assertion failed) ( (npoints >= 4) || (npoints == 3 && flags == SOLVEPNP_ITERATIVE && useExtrinsicGuess) || (npoints >= 3 && flags == SOLVEPNP_SQPNP) ) && npoints == std::max(ipoints.checkVector(2, CV_32F), ipoints.checkVector(2, CV_64F)) in function 'cv::solvePnPGeneric'

问题排查:
排除了照片路径问题,最后发现是输入参数中存在数据不匹配问题。
object_points数组的每个元素都应该是一个二维数组,其中每一行表示一个物体点的坐标。例如,我使用的棋盘格是 9x6 的,那么 object_points 数组的每个元素应该是一个 Nx3 的二维数组,其中 N 表示检测到的角点数量54*14。
image_points 数组的每个元素也应该是一个二维数组,其中每一行表示一个图像点的坐标。例如,我这里测试14张图像,那么 image_points 数组的每个元素应该是一个 N x 2 的二维数组,其中 N 表示检测到的角点数量54乘以14。
如图3所示,两个数组都不是二维的,不满足输入要求。
在这里插入图片描述

解决办法:
在这里插入图片描述
设定shape为二维。

接着运行程序,爆出第二个错误:

发生异常: ValueError
too many values to unpack (expected 2)
  File "C:\Users\z\Desktop\py\eye2hand\board2camera.py", line 42, in get_calibration_parameters
    rvec, tvec = cv2.solvePnP(np.array(object_points), np.array(image_points), cv2.UMat(camera_matrix), cv2.UMat(dist_coeffs))
  File "C:\Users\z\Desktop\py\eye2hand\board2camera.py", line 62, in <module>
    rvec, pnp_t = get_calibration_parameters(object_points, image_points, camera_matrix, dist_coeffs)
ValueError: too many values to unpack (expected 2)

原来是solvePnP返回的值数量超过了两个,但是我只有两个,于是其余值用‘_’代替。

_, rvec, tvec= cv2.solvePnP(np.array(object_points), np.array(image_points), cv2.UMat(camera_matrix), cv2.UMat(dist_coeffs))

至此问题解决。

Rotation Vector:
[[ 0.99426456 -0.00119183 -0.10694183]
 [-0.00344484  0.99906216 -0.04316168]
 [ 0.10689298  0.04328253  0.993328  ]]
Translation Vector:
[[ -9.73908016]
 [-11.06567888]
 [ 59.21845568]]

最后贴上这段程序:

import cv2
import numpy as np
import os
# 提取角点
def extract_chessboard_corners(folder_path):
    # 获取文件夹中的标定图像
    calibration_images = [file for file in os.listdir(folder_path) if file.startswith('ColorMap')]
    #棋盘格尺寸,隔间25mm
    board_size = (9, 6)
    # 创建用于存储目标点和图像点的列表
    object_points = []
    image_points = []

    # 读取标定图像,并提取角点
    for i,image_file in enumerate(calibration_images):

        # 读取彩色图像
        image_file = 'ColorMap' + str(i) + '.png'        
        color_image_path = os.path.join(folder_path, image_file)    
        color_image_path = os.path.normpath(color_image_path)  # 将路径规范化    
        color_image = cv2.imread(color_image_path)

        # 转换为灰度图像
        gray_image = cv2.cvtColor(color_image, cv2.COLOR_BGR2GRAY)

        # 检测棋盘格角点
        ret, corners = cv2.findChessboardCorners(gray_image, board_size, None)

        # 如果成功找到角点,则添加到列表中
        if ret:
            object_points.append(np.zeros((54, 3), np.float32))
            object_points[-1][:, :2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2)
            image_points.append(corners.reshape(-1, 2))
            _, rvec, tvec = cv2.solvePnP(object_points[-1], image_points[-1], camera_matrix, dist_coeffs)
        else:
            print("未找到角点")
    return object_points, image_points

# 进行相机标定
def get_calibration_parameters(object_points, image_points, camera_matrix, dist_coeffs):   
    _, rvec, pnp_t= cv2.solvePnP(np.array(object_points), np.array(image_points), cv2.UMat(camera_matrix), cv2.UMat(dist_coeffs))

    return rvec, pnp_t

# 文件夹路径
folder_path = './data'

# 相机内参矩阵
#camera_matrix = [[2426.171811863809, 0, 977.4637153834994], [0, 2426.671349554621, 613.0850541358279], [0, 0, 1]]
camera_matrix = np.array([[2426.171811863809, 0, 977.4637153834994], [0, 2426.671349554621, 613.0850541358279], [0, 0, 1]])

# 相机畸变系数
#dist_coeffs = [-0.02665168168678513,0.08966277495184476,-0.0003435021135962532,-0.0003253252064104588,0]
dist_coeffs = np.array([-0.02665168168678513, 0.08966277495184476, -0.0003435021135962532, -0.0003253252064104588, 0])

# 提取棋盘格角点
temp_object, temp_image = extract_chessboard_corners(folder_path)

# 将三维数组拼接成二维数组
object_points = np.concatenate(temp_object)

# 先去除冗余维度 1,再将三维数组拼接成二维数组
ttemp_image = np.squeeze(temp_image)
image_points = np.concatenate(ttemp_image)

# 获取标定参数
rvec, pnp_t = get_calibration_parameters(object_points, image_points, camera_matrix, dist_coeffs)

# 将 rvec 和 tvec 转换为 NumPy 数组
rvec = rvec.get()
pnp_R, _= cv2.Rodrigues(rvec)
pnp_t = pnp_t.get()

# 输出结果
print("Object Points:")
print(np.array(object_points))
print("Image Points:")
print(np.array(image_points))
print("Rotation Vector:")
print(pnp_R)
print("Translation Vector:")
print(pnp_t)
三角测距原理是通过测量物体在不同位置或角度下的视角来计算物体距离的方法。在内参标定中,我们需要使用相机拍摄不同位置或角度下的标定图像,并根据相机内参计算出相机在不同位置或角度下的旋转矩阵R和平移向量T,从而得到相机的外参。接下来,我们可以使用三角测距原理计算标定上某个点的三维坐标。 下面是一个基于OpenCV库实现的三角测距示例代码: ```c++ #include <opencv2/opencv.hpp> #include <iostream> using namespace std; using namespace cv; // 根据相机内参和相机在世界坐标系中的位置计算相机的外参 void calcExtrinsic(Mat K, Mat distCoeffs, vector<Point2f> imagePoints, vector<Point3f> objectPoints, Mat& R, Mat& T) { Mat rvec; solvePnP(objectPoints, imagePoints, K, distCoeffs, rvec, T); Rodrigues(rvec, R); } // 根据相机内参和相机的外参计算某个点在世界坐标系中的坐标 Point3f triangulatePoint(Mat K, Mat distCoeffs, Mat R, Mat T, Point2f p2d, Point2f p3d) { Mat P(3, 4, CV_32FC1); Mat K_R(3, 3, CV_32FC1), K_T(3, 1, CV_32FC1); K.copyTo(K_R); T.copyTo(K_T); R.col(0).copyTo(P.col(0)); R.col(1).copyTo(P.col(1)); R.col(2).copyTo(P.col(2)); T.copyTo(P.col(3)); Mat p1(2, 1, CV_32FC1), p2(2, 1, CV_32FC1); p1.at<float>(0, 0) = p2d.x; p1.at<float>(1, 0) = p2d.y; p2.at<float>(0, 0) = p3d.x; p2.at<float>(1, 0) = p3d.y; Mat X(4, 1, CV_32FC1); triangulatePoints(K_P, P, p1, p2, X); Point3f p3d_; p3d_.x = X.at<float>(0, 0) / X.at<float>(3, 0); p3d_.y = X.at<float>(1, 0) / X.at<float>(3, 0); p3d_.z = X.at<float>(2, 0) / X.at<float>(3, 0); return p3d_; } int main() { Mat image = imread("calibration.jpg"); Mat K = Mat::eye(3, 3, CV_32FC1); // 相机内参 K.at<float>(0, 0) = 1000.0; // fx K.at<float>(1, 1) = 1000.0; // fy K.at<float>(0, 2) = image.cols / 2.0; // cx K.at<float>(1, 2) = image.rows / 2.0; // cy Mat distCoeffs = Mat::zeros(5, 1, CV_32FC1); // 畸变参数 // 标定上的3D点坐标 vector<Point3f> objectPoints; for (int i = 0; i < 6; i++) { for (int j = 0; j < 7; j++) { objectPoints.push_back(Point3f(i * 0.04, j * 0.04, 0)); } } // 检测标定角点 vector<Point2f> imagePoints; bool found = findChessboardCorners(image, Size(6, 7), imagePoints); if (found) { // 计算相机外参 Mat R, T; calcExtrinsic(K, distCoeffs, imagePoints, objectPoints, R, T); // 计算标定某个点的3D坐标 Point2f p2d = imagePoints[0]; Point3f p3d = objectPoints[0]; Point3f p3d_ = triangulatePoint(K, distCoeffs, R, T, p2d, p3d); cout << "3D point: (" << p3d_.x << ", " << p3d_.y << ", " << p3d_.z << ")" << endl; } return 0; } ``` 在上面的代码中,我们首先定义了相机的内参K和畸变参数distCoeffs,然后定义了标定上的3D点坐标objectPoints。接着,我们使用OpenCV内置的函数findChessboardCorners检测标定角点,并通过calcExtrinsic函数计算相机的外参R和T。最后,我们使用triangulatePoint函数计算标定上某个点的3D坐标
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值