避坑—相机标定

本文采用张氏标定法标定了一个USB相机(网购,一百多块钱),记录了辛酸历程。

废话不多说,直接开始标定!

第一步:拍十几张棋盘格图片

棋盘格是从网上找的图片打印的,整体还是个歪的......

 这一步就有坑,后面解释

 第二步:标定相机内参

直接上代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2
import numpy as np
import glob

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)



# 棋盘格对应的世界坐标下的三维坐标
# xx = [371., 396., 421., 446., 471., 496.]
# yy = [402., 427., 452., 477., 502., 527., 552., 577.]
# 这两种坐标得到的内参一样
xx = [0., 1., 2., 3., 4., 5.]
yy = [0., 1., 2., 3., 4., 5., 6., 7.]

xyz = []
for i in range(len(xx)):
    for j in range(len(yy)):
        xyz.append(np.array([xx[i]*25, yy[j]*25, 0.], dtype=np.float32))
xyz = np.array(xyz, dtype=np.float32)

print(objp)
objppoints = []  # 真实世界下的三维坐标
imgpoints = []
images = glob.glob(r'./chess/*.JPG')


for fname in images:
    img = cv2.imread(fname)
    print(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # print(gray.shape)
    ret, corners = cv2.findChessboardCorners(gray, (8, 6), None)  # 寻找棋盘内角点
    # print('corners', corners)
    if ret == True:
        objppoints.append(objp)
        corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)  # 提取亚像素信息,提高标定精度
        print(corners2)
        imgpoints.append(corners2)

        img = cv2.drawChessboardCorners(img, (8, 6), corners2, ret)
        cv2.imshow('gray', img)
        while cv2.waitKey(100) != 27:  # esc
            if cv2.getWindowProperty('gray', cv2.WND_PROP_AUTOSIZE) < 0:  # 获取窗口状态,如果窗口关闭 执行break
                break
cv2.destroyAllWindows()

# mrx 内参数矩阵,dist 畸变系数,rvecs 旋转向量,tvecs 平移向量
ret, mrx, dist, rvecs, tveces = cv2.calibrateCamera(objppoints, imgpoints, (1920, 1080), None, None)  # 相机标定
print(mrx)
print(dist)
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mrx, dist, (1920, 1080), 1)  # 矫正畸变,优化内参
print(roi)
total_error = 0
for i in range(len(objppoints)):
    imgpoints2, _ = cv2.projectPoints(objppoints[i], rvecs[i], tveces[i], mrx, dist)  # 映射
    error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
    total_error += error
print("total error: ", total_error / len(objppoints))

# 保存内参、畸变参数、新内参
np.savez('c2_parameter_1080', k=mrx, dist=dist, newK=newcameramtx)

 代码是没有问题的,运行的结果如下

贴两张图意思一下,其他图也是这样的。

然后程序会输出内参、畸变参数,我的外参是单独标定的。

第三步:图像去畸变

先上代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-


import cv2
import numpy as np
from tqdm import tqdm
import os

npzfile = np.load('./calibrateCamera/c2_parameter_1080.npz')
K = npzfile['k']
dist = npzfile['dist']
newcameramtx=npzfile['newK']
print(npzfile['k'])
print(npzfile['dist'])


img_root=r'./photos_camera3'
save_root=r'./undistort_photos'
img_list=os.listdir(img_root)
for name in tqdm(img_list):
    img_path=os.path.join(img_root,name)
    img = cv2.imread(img_path)
    dst = cv2.undistort(img, K, dist, None, newcameramtx)
    cv2.imwrite(os.path.join(save_root,name), dst)


填坑来了

第一步、第二步从拍摄到跑代码,看着都很流畅,大家也都是这么做的,本来我也没太注意,直到用标定好的内参给图像去畸变。结果保存出来的是个这鬼样子

 

 

这根本就不能用好吧!然后心里就开始默默的问候代码的祖宗,

开始解决问题

首先我是查看了保存的内参和优化后的内参,发现优化后的内参的cx和cy有些大的离谱。

然后又发现在优化内参时 返回的roi是(0,0,0,0)!!!

代码都知道图像里没一个像素是能用的。

现在就基本锁定了是内参的问题,然后重新拍数据。经过多批数据的拍摄总结出来的教训。

我们都知道相机的照片越靠近边缘,畸变程度越大,所以拍摄的时候要尽量把棋盘格放到边上去。

然后得到的结果就和预期的一样了。

 第四步:定外参

#!/usr/bin/env python
# -*- coding: utf-8 -*-


import cv2
import numpy as np
import math

def rotation_vector_to_euler_angles(rvec):
    # 将旋转向量转换成旋转矩阵
    R, _ = cv2.Rodrigues(rvec)

    # 计算欧拉角(yaw、pitch、roll)
    yaw = np.arctan2(R[1, 0], R[0, 0])
    pitch = np.arctan2(-R[2, 0], np.sqrt(R[2, 1] ** 2 + R[2, 2] ** 2))
    roll = np.arctan2(R[2, 1], R[2, 2])

    # 将弧度转换为角度
    yaw = np.rad2deg(yaw)
    pitch = np.rad2deg(pitch)
    roll = np.rad2deg(roll)

    return yaw, pitch, roll  # z, y, x


# 旋转矩阵到欧拉角(角度制)
def rotateMatrixToEulerAngles2(rvec):
    RM, _ = cv2.Rodrigues(rvec)
    theta_z = np.arctan2(RM[1, 0], RM[0, 0]) / np.pi * 180
    theta_y = np.arctan2(-1 * RM[2, 0], np.sqrt(RM[2, 1] * RM[2, 1] + RM[2, 2] * RM[2, 2])) / np.pi * 180
    theta_x = np.arctan2(RM[2, 1], RM[2, 2]) / np.pi * 180
    print(f"Euler angles:\ntheta_x: {theta_x}\ntheta_y: {theta_y}\ntheta_z: {theta_z}")
    return theta_x, theta_y, theta_z


npzfile = np.load('c3_parameter_1080.npz')
cameraMatrix = npzfile['k']
distCoeffs = npzfile['dist']
print(npzfile['k'])
print(npzfile['dist'])

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
objppoints = []  # 真实世界下的三维坐标
imgpoints = []


# 拿尺子量的真实坐标
xx = [406., 431., 456., 481., 506., 531.]
yy = [305., 330., 355., 380., 405., 430., 455., 480.]
xyz = []
for i in range(len(xx)):
    for j in range(len(yy)):
        xyz.append(np.array([xx[i], yy[j], 0.], dtype=np.float32))

xyz = np.array(xyz, dtype=np.float32)
objp = xyz
objp = xyz[::-1]


print(objp)

img = cv2.imread(r'D:\gzz\data\monkey_hand\20230523three\camera3\Rt/1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (8, 6), None)  # 寻找棋盘内角点
print(ret)
if ret:
    objppoints.append(objp)
    corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)  # 提取亚像素信息,提高标定精度
    print(corners2)
    # print(len(corners2))
    # corners2 = mypoints
    imgpoints.append(corners2)

    img = cv2.drawChessboardCorners(img, (8, 6), corners2, ret)
    cv2.imshow('gray', img)
    while cv2.waitKey(100) != 27:  # esc
        if cv2.getWindowProperty('gray', cv2.WND_PROP_AUTOSIZE) < 0:  # 获取窗口状态,如果窗口关闭 执行break
            break
    cv2.destroyAllWindows()

    retval, rvec, tvec = cv2.solvePnP(objp, corners2, cameraMatrix, distCoeffs)
    print(rvec)
    print(tvec)

    # 测试原点
    imgp, _ = cv2.projectPoints(np.array([[0, 0, 0], [100, 0, 0], [0, 100, 0], [0, 0, 100]], dtype=np.float64), rvec,
                                tvec, cameraMatrix, distCoeffs)
    print('imgp:', imgp)

    total_error = 0
    for i in range(len(objppoints)):
        imgpoints2, _ = cv2.projectPoints(objp, rvec, tvec, cameraMatrix, distCoeffs)  # 映射
        error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
        total_error += error
    print("total error: ", total_error / len(objppoints))

    rvec_matrix = cv2.Rodrigues(rvec)[0]
    proj_matrix = np.hstack((rvec_matrix, tvec))
    eulerAngles = cv2.decomposeProjectionMatrix(proj_matrix)[6]  # 欧拉角
    pitch, yaw, roll = [math.radians(_) for _ in eulerAngles]
    pitch = math.degrees(math.asin(math.sin(pitch)))
    roll = -math.degrees(math.asin(math.sin(roll)))
    yaw = math.degrees(math.asin(math.sin(yaw)))
    print(pitch, yaw, roll)

    roll, yaw, pitch = rotation_vector_to_euler_angles(rvec)
    print(pitch, yaw, roll)
    a = rotateMatrixToEulerAngles2(rvec)

    # 保存内参、畸变参数、新内参
    np.savez('c3_parameter_1080', k=cameraMatrix, newK= npzfile['newK'], dist=distCoeffs, rvec=rvec, tvec=tvec)

又来坑了,总会有这样那样的原因出错,继续问候代码的祖宗

 

这能咋办,我手动调的角点的坐标

 

避坑1,这里要说一下,检测的棋盘格角点是有顺序的,所以给定的真实坐标也要一一对应;

避坑2,注意你定义的是左手坐标系还是右手坐标系,会影响结果。

完结,撒花❀

如果你不知道棋盘格的大小,可以尝试使用cv2.findChessboardCorners函数的自适应模式。这个模式会在不知道棋盘格大小的情况下尝试找到所有可能的棋盘格,并返回一个矩阵,其中每一行包含棋盘格的所有角点的坐标。你可以使用这些坐标来计算棋盘格的大小,然后再使用cv2.findChessboardCorners函数的固定模式来获取更精确的结果。 以下是一个使用自适应模式的示例代码: ``` import cv2 # 读取图片 img = cv2.imread('chessboard.jpg') # 转换为灰度图 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 设置自适应模式的参数 flags = cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE # 查找棋盘格的角点 corners = cv2.findChessboardCorners(gray, (10, 7), flags=flags) # 如果找到了棋盘格 if corners is not None: # 绘制角点 cv2.drawChessboardCorners(img, (10, 7), corners, True) # 显示图片 cv2.imshow('Chessboard', img) cv2.waitKey(0) else: print('Unable to find chessboard corners.') ``` 在这个例子中,我们使用了一个 10x7 的棋盘格,但是我们并不知道棋盘格的大小。我们将flags参数设置为cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE,这会告诉cv2.findChessboardCorners函数自适应地查找棋盘格的角点。如果找到了角点,我们会使用cv2.drawChessboardCorners函数将其绘制到图片上。如果未能找到角点,我们会输出一条错误信息。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值