问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

单目线激光标定一(相机标定)

创作时间:
作者:
@小白创作中心

单目线激光标定一(相机标定)

引用
CSDN
1.
https://blog.csdn.net/2302_80317534/article/details/143478678

单目线激光标定是计算机视觉和机器人领域中的一个重要技术,广泛应用于三维重建、机器人导航等领域。本文将详细介绍单目线激光标定的第一部分——相机标定,包括归一化图像坐标系的定义和转换关系,以及具体的相机标定流程和代码实现。

归一化图像坐标系

像素坐标系

在拍摄图像后,我们得到的是图像的像素坐标系,其原点位于左上角,分辨率代表x和y轴的极限坐标(右下角)。

归一化图像坐标系

为了便于后续计算,引入了一个虚拟的归一化图像坐标系。这个坐标系与像素坐标系平行,且z=f(焦距)。它将像素坐标系的原点从左上角移动到图像中心,并将坐标值转换为实际的毫米值。这种转换的好处是,经过归一化后的每个坐标点实际上代表了一条经过相机光心的射线方向,为后续的线激光平面标定提供了便利。

转化关系

首先将归一化坐标系的中心点定义为光轴(镜头的中心线与相机成像器件的交点)的中心点(u0,v0)。对于一个1280*1080的图像,中心点通常为(640,540),但实际标定后可能会有偏差。然后根据相机像素点之间的距离转换到毫米值,例如,如果相机一个像素点之间的距离为4.3微米。

此时归一化平面与像素坐标系是重叠的,需要将其移至Z=f(相机坐标系)的平面处。从下图可以看出,归一化平面上的一个点坐标可以代表一个经过相机光心的射线方向,射线方向的计算为该坐标系下点坐标(x,y,z)-原点(x0,y0,z0),此地为(x,y,1)-(0,0,0)。

所以最终归一化平面的转化公式为:
(x,y,1)为归一化平面坐标,(u,v,1)为像素坐标。

相机标定流程

1. 打印棋盘格

棋盘格是相机标定的工具,通过检测棋盘格的角点来获取相机的内参(M)和畸变系数(d)。推荐使用calib.io网站生成棋盘格,参数包括行数、列数和每个格子的宽度(mm)。

2. 拍摄棋盘格照片

使用相机在不同位置拍摄棋盘格的照片(至少15张),确保棋盘格在照片内清晰可见,每次拍摄的位置都不相同。

3. 进行相机标定

通过OpenCV库中的函数进行角点检测和相机标定,获取相机的内参矩阵(M)和畸变系数(d)。

import cv2
import numpy as np
import glob
import os

# 棋盘格尺寸(内角点数量:列数-1 × 行数-1)
w, h = 10, 7 # 棋盘格为 11x8,则内角点为 10x7
# 每个单元格的实际物理尺寸(2mm)
cell_size = 2.0 # 单位:毫米
# 创建棋盘格的世界坐标点(考虑 2mm 单元格大小)
objp = np.zeros((w * h, 3), np.float32)
objp[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2) * cell_size
# 存储三维点和二维点
objpoints = [] # 在世界坐标系中的三维点
imgpoints = [] # 在图像平面的二维点
image_files = [
 'D:/code/chessboard/1/1.1.bmp',
 'D:/code/chessboard/1/1.2.bmp',
 'D:/code/chessboard/1/1.3.bmp',
 'D:/code/chessboard/1/1.4.bmp',
 'D:/code/chessboard/1/1.5.bmp',
 'D:/code/chessboard/1/1.6.bmp',
 'D:/code/chessboard/1/1.7.bmp',
 'D:/code/chessboard/1/1.8.bmp',
 'D:/code/chessboard/1/1.9.bmp',
 'D:/code/chessboard/1/1.10.bmp',
 'D:/code/chessboard/1/1.11.bmp',
 'D:/code/chessboard/1/1.12.bmp',
 'D:/code/chessboard/1/1.13.bmp',
 'D:/code/chessboard/1/1.14.bmp',
 'D:/code/chessboard/1/1.15.bmp'
]
# 遍历图片并检测角点
for fname in image_files:
 img = cv2.imread(fname)
 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 # 检测角点,允许部分检测到的情况
 ret, corners = cv2.findChessboardCornersSB(gray, (w, h), None)
 if ret:
 print(f"成功检测到角点: {fname}")
 # 亚像素优化
 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
 corners_refined = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
 # 存储世界坐标系和图像坐标系的数据
 objpoints.append(objp)
 imgpoints.append(corners_refined)
 # 在图像上绘制角点
 cv2.drawChessboardCorners(img, (w, h), corners_refined, ret)
 cv2.imshow("角点检测", img)
 cv2.waitKey(500) # 显示图片 500ms
 else:
 print(f"未能检测到所有角点: {fname}")
cv2.destroyAllWindows()
# 检查是否有足够的数据进行标定
if len(objpoints) >= 3: # 至少 3 张有效图片
 print("正在进行标定...")
 ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
 objpoints, imgpoints, gray.shape[::-1], None, None
 )
 print(f"标定结果: {ret}")
 print(f"内参矩阵:\n{mtx}")
 print(f"畸变系数:\n{dist}")
else:
 print("有效图片数量不足,无法进行标定")

4. 根据相机内参矩阵和畸变系数进行归一化坐标转化

根据标定结果,转化公式为:(x,y,z)——归一化图像坐标系,(u,v)为像素图像坐标,(cx,cy)为主点坐标。

完整代码如下,包括相机标定及归一化坐标转化函数:

import cv2
import numpy as np
import glob
import os

# 棋盘格尺寸(内角点数量:列数-1 × 行数-1)
w, h = 10, 7 # 棋盘格为 11x8,则内角点为 10x7
# 每个单元格的实际物理尺寸(2mm)
cell_size = 2.0 # 单位:毫米
# 创建棋盘格的世界坐标点(考虑 2mm 单元格大小)
objp = np.zeros((w * h, 3), np.float32)
objp[:, :2] = np.mgrid[0:w, 0:h].T.reshape(-1, 2) * cell_size
# 存储三维点和二维点
objpoints = [] # 在世界坐标系中的三维点
imgpoints = [] # 在图像平面的二维点
image_files = [
 'D:/code/chessboard/1/1.1.bmp',
 'D:/code/chessboard/1/1.2.bmp',
 'D:/code/chessboard/1/1.3.bmp',
 'D:/code/chessboard/1/1.4.bmp',
 'D:/code/chessboard/1/1.5.bmp',
 'D:/code/chessboard/1/1.6.bmp',
 'D:/code/chessboard/1/1.7.bmp',
 'D:/code/chessboard/1/1.8.bmp',
 'D:/code/chessboard/1/1.9.bmp',
 'D:/code/chessboard/1/1.10.bmp',
 'D:/code/chessboard/1/1.11.bmp',
 'D:/code/chessboard/1/1.12.bmp',
 'D:/code/chessboard/1/1.13.bmp',
 'D:/code/chessboard/1/1.14.bmp',
 'D:/code/chessboard/1/1.15.bmp'
]
# 遍历图片并检测角点
for fname in image_files:
 img = cv2.imread(fname)
 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 # 检测角点,允许部分检测到的情况
 ret, corners = cv2.findChessboardCornersSB(gray, (w, h), None)
 if ret:
 print(f"成功检测到角点: {fname}")
 # 亚像素优化
 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
 corners_refined = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
 # 存储世界坐标系和图像坐标系的数据
 objpoints.append(objp)
 imgpoints.append(corners_refined)
 # 在图像上绘制角点
 cv2.drawChessboardCorners(img, (w, h), corners_refined, ret)
 cv2.imshow("角点检测", img)
 cv2.waitKey(500) # 显示图片 500ms
 else:
 print(f"未能检测到所有角点: {fname}")
cv2.destroyAllWindows()
# 检查是否有足够的数据进行标定
if len(objpoints) >= 3: # 至少 3 张有效图片
 print("正在进行标定...")
 ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
 objpoints, imgpoints, gray.shape[::-1], None, None
 )
 print(f"标定结果: {ret}")
 print(f"内参矩阵:\n{mtx}")
 print(f"畸变系数:\n{dist}")
else:
 print("有效图片数量不足,无法进行标定")

# 归一化平面转化函数
def pixel_to_ray_with_physical_units(centers, K, pixel_size=0.0043):
 rays = []
 fx, fy = K[0, 0], K[1, 1]
 cx, cy = K[0, 2], K[1, 2]
 for (c, r) in centers:
 x = (c - cx) /fx # 转换为物理单位
 y = (r - cy) / fy # 转换为物理单位
 z = 1 # 焦距转换为物理单位
 rays.append(np.array([x, y, z]))
 return np.array(rays)
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号