python+opencv+棋盘格实现相机标定及相对位姿估计

python+opencv+棋盘格实现相机标定及相对位姿估计

引言

相机标定的主要目的是为了求相机的内参及畸变系数等参数,通过相机的内参等可以将像素坐标转换为相机坐标,相机坐标又可通过变换得到世界坐标。

下面为了验证标定算法的准确性,采用了同一组棋盘格数据,通过与另外两个测试软件标定结果进行对比,其标定结果基本一致。

1,使用相机采集含棋盘格图像14张

2,进行相机标定

(1)测试软件1标定结果(内参及畸变系数)

https://blog.csdn.net/qq_42951560/article/details/126248810

【原创工具 | OpenCV-CamCalib】一个基于 OpenCV 的自动化相机数据采集和标定程序

R: 
[0.0013, -0.0684, 1.5544]
[0.0313, -0.0572, 0.6501]
[0.0561, -0.0275, -0.6579]
[-0.0813, -0.1536, -1.4020]
[0.2893, -0.1042, -2.0699]
[-0.1846, -0.0407, -2.1670]
[0.2581, -0.0658, 3.0851]
[0.4249, 0.0644, -3.0719]
[-0.3689, 0.0267, 2.5249]
[0.1895, 0.0745, 1.8568]
[0.1489, -0.2197, 1.6593]
[0.1603, -0.1441, 1.0397]
[-0.2111, -0.0610, -0.0107]
[0.2734, -0.0441, 0.0333]
T: 
[57.2825, -164.7185, 657.7062]
[-39.4535, -154.2506, 653.6776]
[-125.4226, -36.9211, 655.3406]
[-114.6057, 114.1176, 616.0881]
[-71.4379, 43.0685, 624.4835]
[56.3861, 127.9344, 618.4703]
[162.7121, 26.3284, 619.2786]
[25.0943, 24.0527, 654.0335]
[61.2546, 46.1862, 655.6716]
[165.0184, -153.1011, 611.0288]
[64.0926, -122.1281, 613.9142]
[7.0449, -136.1629, 608.7691]
[-115.8937, 13.3168, 642.5956]
[-97.2399, -115.5981, 610.6633]

(2)测试软件2标定结果(内参及畸变系数)

https://blog.csdn.net/Big_Huang/article/details/106166254

基于opencv 和 pyqt5 的相机标定助手的设计

相机内部矩阵:
[[1.09803736e+03 0.00000000e+00 6.63737765e+02]
[0.00000000e+00 1.09791044e+03 4.38657425e+02]
[0.00000000e+00 0.00000000e+00 1.00000000e+00]]
畸变参数:
[[ 0.00218144 -0.00925042 0.00024789 -0.0002825 0.20841267]]
旋转矩阵:
[array([[ 1.33903055e-03],
[-6.83598890e-02],
[ 1.55438056e+00]]), array([[ 0.03125883],
[-0.05715461],
[ 0.65011621]]), array([[ 0.05605446],
[-0.02753977],
[-0.65790633]]), array([[-0.08126108],
[-0.15363931],
[-1.40200001]]), array([[ 0.28931372],
[-0.10420944],
[-2.06993946]]), array([[-0.18457149],
[-0.0407243 ],
[-2.16699213]]), array([[ 0.25805958],
[-0.06580844],
[ 3.08507844]]), array([[ 0.42490026],
[ 0.06435579],
[-3.07186989]]), array([[-0.36890203],
[ 0.02674808],
[ 2.52486102]]), array([[0.18946187],
[0.07454542],
[1.85677179]]), array([[ 0.14889827],
[-0.21970662],
[ 1.65930809]]), array([[ 0.16029379],
[-0.14407197],
[ 1.03966592]]), array([[-0.21111973],
[-0.06096786],
[-0.01069444]]), array([[ 0.27336398],
[-0.04413062],
[ 0.03325271]])]
平移矩阵:
[array([[ 57.28312236],
[-164.71792893],
[ 657.70732026]]), array([[ -39.45288044],
[-154.25005049],
[ 653.67874071]]), array([[-125.42200989],
[ -36.92050016],
[ 655.34164677]]), array([[-114.60511114],
[ 114.11816637],
[ 616.08898813]]), array([[-71.43727126],
[ 43.06902124],
[624.48437836]]), array([[ 56.38668594],
[127.93492448],
[618.4712321 ]]), array([[162.7126308 ],
[ 26.32896621],
[619.27933957]]), array([[ 25.09489496],
[ 24.05330674],
[654.03439771]]), array([[ 61.25519273],
[ 46.18683531],
[655.67243968]]), array([[ 165.01894101],
[-153.10050991],
[ 611.02969995]]), array([[ 64.09318336],
[-122.12753319],
[ 613.9153159 ]]), array([[ 7.04548205],
[-136.16238216],
[ 608.7700887 ]]), array([[-115.89311233],
[ 13.31740073],
[ 642.59649204]]), array([[ -97.23935687],
[-115.59753028],
[ 610.66435603]])]

(3)Python算法标定及原点位姿获取

参考1:
https://blog.csdn.net/qq_29931565/article/details/119395353

【OpenCV】OpenCV-Python实现相机标定+利用棋盘格相对位姿估计

参考2:

(3-1)python代码如下(此代码经修改验证,以下方为准)

import numpy as np
import glob
import cv2
import math

#当前验证此算法的标定结果与其他标定基本一致

#1,相机标定获取内参及畸变系数
#角点个数
w = 11
h = 8
b_w = 20  #棋盘格边长20mm
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)

objp = np.zeros((w * h, 3), np.float32)
objp[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2)  # 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
objp = b_w * objp  # 打印棋盘格一格的边长为2.6cm
#print(objp)
obj_points = []  # 存储3D点
img_points = []  # 存储2D点

#images = glob.glob("E:/image/*.png")  # 黑白棋盘的图片路径

def get_image_paths(folder_path):
    # 使用通配符筛选出所有jpg/png图片
    return glob.glob(f"{folder_path}/**/*.jpg", recursive=True)
    # 如果需要包括其他格式的图片,可以在这里添加,例如:png
    # return glob.glob(f"{folder_path}/**/*.jpg", recursive=True) + \
    #        glob.glob(f"{folder_path}/**/*.png", recursive=True)


# 使用示例
#folder_path = "G:/3dversion/weiziguji/8mm/"  # 替换为你的文件夹路径
folder_path = "C:\\Users\\zhaocai\\Pictures\\test"
images = get_image_paths(folder_path)

size = None
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    size = gray.shape[::-1]
    ret, corners = cv2.findChessboardCorners(gray, (w, h), None)
    if ret:
        obj_points.append(objp)     #世界坐标系中的三维点始终不变
        #此处的winsize(会影响到畸变系数)
        corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1),
                                    (cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS, 30, 0.001))
        #寻找棋盘格角点,若是有则进行保存
        if [corners2]:
            img_points.append(corners2)
        else:
            img_points.append(corners)
        cv2.drawChessboardCorners(img, (w, h), corners, ret)  # 记住,OpenCV的绘制函数一般无返回值
        #cv2.imshow("demo",img)
        #cv2.waitKey(0)

# print(obj_points)
# print(img_points)
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, size, None, None)
result = "摄像机矩阵:\n {}\n 畸变参数:\n {}\n 旋转矩阵:\n {}\n 平移矩阵:\n {}".format(mtx, dist, rvecs, tvecs)
print(result)

# 内参数矩阵、畸变系数
Camera_intrinsic = {"mtx": mtx, "dist": dist, }

#2,获取当前位姿(原点位姿)
obj_points = objp  # 存储3D点
img_points = []  # 存储2D点

for fname in images:
    #_, frame = camera.read()
    frame = cv2.imread(fname)
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    size = gray.shape[::-1]
    ret, corners = cv2.findChessboardCorners(gray, (w, h), None)
    if ret:  # 画面中有棋盘格
        img_points = np.array(corners)
        cv2.drawChessboardCorners(frame, (w, h), corners, ret)
        # rvec: 旋转向量 tvec: 平移向量
        _, rvec, tvec = cv2.solvePnP(obj_points, img_points, Camera_intrinsic["mtx"], Camera_intrinsic["dist"])  # 解算位姿
        # print(rvec)
        #print(tvec)
        distance = math.sqrt(tvec[0][0] ** 2 + tvec[1][0] ** 2 + tvec[2][0] ** 2)  # 计算距离,距离相机的距离
        #将旋转向量转换成欧拉角(绕x轴转动pitch,绕y轴转动yaw,绕z轴转动roll)
        rvec_matrix = cv2.Rodrigues(rvec)[0]  # 旋转向量->旋转矩阵
        proj_matrix = np.hstack((rvec_matrix, tvec))  # hstack: 水平合并
        eulerAngles = cv2.decomposeProjectionMatrix(proj_matrix)[6]  # 欧拉角
        #print(eulerAngles)
        pitch, yaw, roll = eulerAngles[0][0], eulerAngles[1][0], eulerAngles[2][0]
        cv2.putText(frame, "dist: %.2fmm, yaw: %.2f, pitch: %.2f, roll: %.2f" % (distance, yaw, pitch, roll), (10, frame.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
        cv2.imshow('frame', frame)
        if cv2.waitKey(1) & 0xFF == 27:  # 按ESC键退出
            break
    else:  # 画面中没有棋盘格
        cv2.putText(frame, "Unable to Detect Chessboard", (20, frame.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX, 1.3,
                    (0, 0, 255), 3)
        cv2.imshow('frame', frame)
        if cv2.waitKey(1) & 0xFF == 27:  # 按ESC键退出
            break


#3,画坐标轴和立方体
len = b_w
def draw(img, corners, imgpts, imgpts2):
    #line必须转为int型才能绘制
    corners = np.int32(corners)
    imgpts2 = np.int32(imgpts2)
    corner = tuple(corners[0].ravel())  # ravel()方法将数组维度拉成一维数组
    # img要画的图像,corner起点,tuple终点,颜色,粗细
    img = cv2.line(img, corner, tuple(imgpts2[0].ravel()), (255, 0, 0), 8)
    img = cv2.line(img, corner, tuple(imgpts2[1].ravel()), (0, 255, 0), 8)
    img = cv2.line(img, corner, tuple(imgpts2[2].ravel()), (0, 0, 255), 8)
    font = cv2.FONT_HERSHEY_SIMPLEX
    cv2.putText(img, 'X', tuple(imgpts2[0].ravel() + 2), font, 1, (255, 0, 0), 2, cv2.LINE_AA)
    cv2.putText(img, 'Y', tuple(imgpts2[1].ravel() + 2), font, 1, (0, 255, 0), 2, cv2.LINE_AA)
    cv2.putText(img, 'Z', tuple(imgpts2[2].ravel() + 2), font, 1, (0, 0, 255), 2, cv2.LINE_AA)

    imgpts = np.int32(imgpts).reshape(-1, 2)  # draw ground floor in green
    for i, j in zip(range(4), range(4, 8)):  # 正方体顶点逐个连接

        img = cv2.line(img, tuple(imgpts[i]), tuple(imgpts[j]), (255, 215, 0), 3)  # draw top layer in red color
    # imgpts[4:]是八个顶点中上面四个顶点
    # imgpts[:4]是八个顶点中下面四个顶点
    # 用函数drawContours画出上下两个盖子,它的第一个参数是原始图像,第二个参数是轮廓,一个python列表,第三个参数是轮廓的索引(当设置为-1时绘制所有轮廓)
    img = cv2.drawContours(img, [imgpts[4:]], -1, (255, 215, 0), 3)
    img = cv2.drawContours(img, [imgpts[:4]], -1, (255, 215, 0), 3)

    return img


objp = np.zeros((w * h, 3), np.float32)
objp[:, :2] = np.mgrid[0:w * len:len, 0:h * len:len].T.reshape(-1, 2)
axis = np.float32([[0, 0, 0], [0, 2 * len, 0], [2 * len, 2 * len, 0], [2 * len, 0, 0],
                   [0, 0, -2 * len], [0, 2 * len, -2 * len], [2 * len, 2 * len, -2 * len], [2 * len, 0, -2 * len]])
axis2 = np.float32([[3 * len, 0, 0], [0, 3 * len, 0], [0, 0, -3 * len]]).reshape(-1, 3)
# images = glob.glob('*.jpg')
i = 1
for fname in images:
    img = cv2.imread(fname)
    # cv2.imshow('世界坐标系与小盒子', img)
    # cv2.waitKey(0)
    #img = cv2.resize(img, None, fx=0.4, fy=0.4, interpolation=cv2.INTER_CUBIC)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 找到棋盘格角点
    # 寻找角点,存入corners,ret是找到角点的flag
    ret, corners = cv2.findChessboardCorners(gray, (w, h), None)
    if ret is True:
        corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        #print(corners2)
        # 求解物体位姿的需要
        _, rvecs, tvecs, inliers = cv2.solvePnPRansac(objp, corners2, mtx, dist)
        # projectPoints()根据所给的3D坐标和已知的几何变换来求解投影后的2D坐标。
        # imgpts是整体的8个顶点
        imgpts, _ = cv2.projectPoints(axis, rvecs, tvecs, mtx, dist)
        # imgpts2是三个坐标轴的x,y,z划线终点
        imgpts2, _ = cv2.projectPoints(axis2, rvecs, tvecs, mtx, dist)
        #绘制方格
        img = draw(img, corners2, imgpts, imgpts2)

        #绘制x、y、z
        distance = math.sqrt(tvec[0][0] ** 2 + tvec[1][0] ** 2 + tvec[2][0] ** 2)  # 计算距离
        # print(distance)
        rvec_matrix = cv2.Rodrigues(rvec)[0]  # 旋转向量->旋转矩阵
        proj_matrix = np.hstack((rvec_matrix, tvec))  # hstack: 水平合并
        eulerAngles = cv2.decomposeProjectionMatrix(proj_matrix)[6]  # 欧拉角
        # print(eulerAngles)
        pitch, yaw, roll = eulerAngles[0][0], eulerAngles[1][0], eulerAngles[2][0]
        p0 = tuple(corners[0].ravel())
        cv2.putText(img, "x: %.2f, y: %.2f, dist: %.2fmm, yaw: %.2f, pitch: %.2f, roll: %.2f" % (p0[0],p0[1],distance, yaw, pitch, roll),
                    (10, frame.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)

        cv2.imshow('世界坐标系与小盒子', img)
        #cv2.imwrite(str(i) + '.png', img)
        cv2.waitKey(0)
        i += 1

cv2.destroyAllWindows()
print("完毕")

(3-2)标定及位姿结果如下

12920

输入时w=11,h=8,b-w=20

G:\3dversion\weiziguji\.venv\Scripts\python.exe G:\3dversion\weiziguji\XiangJiBiaoDing.py 
摄像机矩阵:
 [[1.09803736e+03 0.00000000e+00 6.63737765e+02]
 [0.00000000e+00 1.09791044e+03 4.38657425e+02]
 [0.00000000e+00 0.00000000e+00 1.00000000e+00]]
 畸变参数:
 [[ 0.00218144 -0.00925043  0.00024789 -0.0002825   0.20841267]]
 旋转矩阵:
 (array([[ 1.33903054e-03],
       [-6.83598890e-02],
       [ 1.55438056e+00]]), array([[ 0.03125883],
       [-0.05715461],
       [ 0.65011621]]), array([[ 0.05605446],
       [-0.02753977],
       [-0.65790633]]), array([[-0.08126108],
       [-0.15363931],
       [-1.40200001]]), array([[ 0.28931372],
       [-0.10420944],
       [-2.06993946]]), array([[-0.18457149],
       [-0.0407243 ],
       [-2.16699213]]), array([[ 0.25805958],
       [-0.06580844],
       [ 3.08507844]]), array([[ 0.42490026],
       [ 0.06435579],
       [-3.07186989]]), array([[-0.36890203],
       [ 0.02674808],
       [ 2.52486102]]), array([[0.18946187],
       [0.07454542],
       [1.85677179]]), array([[ 0.14889827],
       [-0.21970662],
       [ 1.65930809]]), array([[ 0.16029379],
       [-0.14407197],
       [ 1.03966592]]), array([[-0.21111973],
       [-0.06096786],
       [-0.01069444]]), array([[ 0.27336398],
       [-0.04413062],
       [ 0.03325271]]))
 平移矩阵:
 (array([[  57.28312237],
       [-164.71792893],
       [ 657.70732026]]), array([[ -39.45288044],
       [-154.25005049],
       [ 653.67874071]]), array([[-125.42200989],
       [ -36.92050016],
       [ 655.34164677]]), array([[-114.60511114],
       [ 114.11816638],
       [ 616.08898813]]), array([[-71.43727126],
       [ 43.06902124],
       [624.48437836]]), array([[ 56.38668594],
       [127.93492448],
       [618.47123209]]), array([[162.7126308 ],
       [ 26.32896621],
       [619.27933956]]), array([[ 25.09489497],
       [ 24.05330674],
       [654.03439771]]), array([[ 61.25519273],
       [ 46.18683531],
       [655.67243967]]), array([[ 165.01894101],
       [-153.1005099 ],
       [ 611.02969995]]), array([[  64.09318337],
       [-122.12753319],
       [ 613.9153159 ]]), array([[   7.04548206],
       [-136.16238216],
       [ 608.7700887 ]]), array([[-115.89311233],
       [  13.31740073],
       [ 642.59649204]]), array([[ -97.23935687],
       [-115.59753028],
       [ 610.66435603]]))

此时可以求得x、y、z,rx(pitch)、ry(yaw)、rz(roll)的位姿

有了此位姿即可进行下一步

相关推荐
一道微光9 分钟前
Mac的M2芯片运行lightgbm报错,其他python包可用,x86_x64架构运行
开发语言·python·macos
四口鲸鱼爱吃盐37 分钟前
Pytorch | 利用AI-FGTM针对CIFAR10上的ResNet分类器进行对抗攻击
人工智能·pytorch·python
是娜个二叉树!44 分钟前
图像处理基础 | 格式转换.rgb转.jpg 灰度图 python
开发语言·python
互联网杂货铺1 小时前
Postman接口测试:全局变量/接口关联/加密/解密
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·postman
南七澄江2 小时前
各种网站(学习资源及其他)
开发语言·网络·python·深度学习·学习·机器学习·ai
无泡汽水3 小时前
漏洞检测工具:Swagger UI敏感信息泄露
python·web安全
暮暮七3 小时前
理想很丰满的Ollama-OCR
linux·python·大模型·ocr·markdown·ollama
ai_lian_shuo4 小时前
四、使用langchain搭建RAG:金融问答机器人--构建web应用,问答链,带记忆功能
python·ai·金融·langchain·机器人
cwj&xyp7 小时前
Python(二)str、list、tuple、dict、set
前端·python·算法