【3D目标检测】激光雷达和相机联合标定(二)——MATLAB联合标定工具使用

MATLAB联合标定工具使用

  • 引言
  • [1 MATLAB 样例标定(lidar+camera)](#1 MATLAB 样例标定(lidar+camera))
    • [1.1 官网数据集准备](#1.1 官网数据集准备)
    • [1.2 激光雷达相机联合校准](#1.2 激光雷达相机联合校准)
        • [(1)打开 Lidar Camera Calibrator App](#(1)打开 Lidar Camera Calibrator App)
        • (2)导入数据
        • [(3)编辑感兴趣区域 Edit ROI](#(3)编辑感兴趣区域 Edit ROI)
        • [(4)选择棋盘格位置 Select Checkerboard](#(4)选择棋盘格位置 Select Checkerboard)
        • [(5)Detect 和Calibrate](#(5)Detect 和Calibrate)
    • [1.3 导出标定结果](#1.3 导出标定结果)
      • [1.3.1 Export Parameters to Workspace(导出参数到工作区)如图所示](#1.3.1 Export Parameters to Workspace(导出参数到工作区)如图所示)
      • [1.3.2 Export Parameters to File (导出参数到文件)`推荐`](#1.3.2 Export Parameters to File (导出参数到文件)推荐)
      • [1.3.3 Generate MATLAB Script**(生成 MATLAB 脚本)](#1.3.3 Generate MATLAB Script**(生成 MATLAB 脚本))
  • [2 标定信息标准化文件生成](#2 标定信息标准化文件生成)
  • [3 点云与图像配准映射](#3 点云与图像配准映射)

引言

激光雷达-摄像机校准包括将激光雷达传感器和摄像机的数据转换到同一坐标系。这样就能融合两个传感器的数据,准确识别场景中的物体。

总结步骤如下:

  1. 采集同步数据:ROS录制(推荐),或者代码同步触发采集
  2. ROS同步解包:一一对应的图片和点云数据
  3. 相机内参标定
  4. 激光雷达与相机外参标定
  5. 标定信息标准化文件生成
  6. 点云与图像配准映射

参考链接🔗:
1. 激光雷达和相机联合标定保姆级教程
2. 激光雷达和相机联合标定(一)------ROS同步解包
3. MATLAB安装参考链接🔗:https://mp.weixin.qq.com/s/y1VMqjHStq_e1UL0TbAD9w

参考文档下载:单相机校准程序中文.pdf

链接: https://pan.baidu.com/s/1bV1iRvYVxMdkwL30pUXlHg?pwd=2410

MATLAB标定工具包括如下:

  1. Camera Calibrator 单目相机标定
  2. Lidar Camera Calibrator 激光雷达-相机联合标定
  3. Stereo Camera Calibrator 双目相机联合标定

1 MATLAB 样例标定(lidar+camera)

1.1 官网数据集准备

打开MATLAB R2024a安装路径,找到~\MATLAB\R2024a\toolbox\lidar\lidardata\lcc文件夹,复制到自己的文件夹下面

1.2 激光雷达相机联合校准

MATLAB官方链接:

  1. 激光雷达相机校准的概念
  2. 激光雷达相机校准指南
  3. 激光雷达相机校准操作视频
(1)打开 Lidar Camera Calibrator App

激光雷达相机校准器(Lidar Camera Calibrator)应用程序可使用相机和激光雷达传感器分别捕获的图像和点云进行交互式校准,以估算它们之间的刚性变换。便于使用变换矩阵融合激光雷达和相机数据,或将激光雷达点投射到图像上。

该应用程序功能如下:

  1. 从图像和点云数据中检测、提取和可视化棋盘式特征。
  2. 利用特征检测结果估算相机和激光雷达之间的刚性变换。
  3. 使用校准结果融合两个传感器的数据,可以直观地看到投射到图像上的点云数据,以及图像与点云数据融合后的彩色或灰度信息。
  4. 查看绘制的校准误差指标,可以使用阈值线移除异常值,并重新校准剩余数据。
  5. 在棋盘格周围定义感兴趣区域 (ROI),以减少变换估算过程所需的计算资源。
  6. 将变换和误差度量数据导出为工作区变量或 MAT 文件,还可以为整个工作流程创建 MATLAB脚本。
(2)导入数据

使用vip16文件夹下的数据,根据官方提供的测试数据,棋盘格单元尺寸为81mm




(3)编辑感兴趣区域 Edit ROI

通过调整感兴趣区域,缩小范围,减少计算量

(4)选择棋盘格位置 Select Checkerboard

在点云数据中框选棋盘格位置

(5)Detect 和Calibrate




1.3 导出标定结果

导出结果Export

三种选择

  1. Export Parameters to Workspace
  2. Export Parameters to File
  3. Generate MATLAB Script

1.3.1 Export Parameters to Workspace(导出参数到工作区)如图所示

导出方式:将标定得到的参数作为变量直接导出到 MATLAB 的当前工作区(Workspace)。

导出内容:通常包括一个或多个 cameraParameters 对象,这些对象包含相机的内参(Intrinsic Parameters)和畸变系数(Distortion Coefficients)等信息,临时保存。

相机内参信息

外参信息

在命令行窗口输入参数变量即可显示具体的值

1.3.2 Export Parameters to File (导出参数到文件)推荐

导出方式:将标定参数保存到外部文件中,通常为 .mat 格式,也可能支持其他格式(如 .json、.xml 等)。

导出内容:包含相机的内参、畸变系数、旋转向量、平移向量等完整的标定信息,长久保存。

1.3.3 Generate MATLAB Script**(生成 MATLAB 脚本)

自动化标定流程,生成可复用的标定代码,使于在不同数据集或项目中使用

python 复制代码
% Auto-generated by LidarCameraCalibrator app on 2024-10-05
%-------------------------------------------------------
%

%% Image files
imageFilePaths = { 'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\images\0001.png'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\images\0002.png'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\images\0003.png'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\images\0004.png'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\images\0005.png'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\images\0006.png'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\images\0007.png'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\images\0008.png'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\images\0009.png'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\images\0010.png'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\images\0011.png'  };

%% Point cloud files
ptcFilePaths = { 'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\pointCloud\0001.pcd'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\pointCloud\0002.pcd'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\pointCloud\0003.pcd'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\pointCloud\0004.pcd'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\pointCloud\0005.pcd'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\pointCloud\0006.pcd'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\pointCloud\0007.pcd'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\pointCloud\0008.pcd'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\pointCloud\0009.pcd'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\pointCloud\0010.pcd'; ...
    'C:\Users\Alian\Documents\MATLAB\lcc\vlp16\pointCloud\0011.pcd'  };

%% Load initial parameters
squareSize = 81;
padding = [0 0 0 0];

%% Compute camera intrinsics
% Detect calibration pattern
[imagePoints, boardSize] = detectCheckerboardPoints(imageFilePaths);

% Generate world coordinates of the corners of the squares
worldPoints = generateCheckerboardPoints(boardSize, squareSize);

% Calibrate the camera
I = imread(imageFilePaths{1});
imageSize = [size(I, 1), size(I, 2)];
params = estimateCameraParameters(imagePoints, worldPoints, 'ImageSize', imageSize);
intrinsics = params.Intrinsics;

%% Estimate 3D checkerboard points from images
minCornerMetric = 0.150000;
imageCorners3d = nan(4,3,numel(imageFilePaths));
[validImageCorners3d, planeDimension, imgDataUsed] = estimateCheckerboardCorners3d(imageFilePaths, intrinsics, squareSize, 'Padding', padding, 'MinCornerMetric', minCornerMetric);
imageCorners3d(:,:,imgDataUsed) = validImageCorners3d;

% Get the data pairs rejected by the user
isRejectedByUser = [false false false false false false false false false false false]';

%% Detect plane segment from point clouds
minDistance = 0.500000;
roi = [[0 10 -5 5 -2 2]; [0 10 -5 5 -2 2]; [0 10 -5 5 -2 2]; [0 10 -5 5 -2 2]; [0 10 -5 5 -2 2]; [0 10 -5 5 -2 2]; [0 10 -5 5 -2 2]; [0 10 -5 5 -2 2]; [0 10 -5 5 -2 2]; [0 10 -5 5 -2 2]; [0 10 -5 5 -2 2]];
dimTol = 0.050000;
removeGround = true;
rng('default');
detectionResults = struct();

for i = 1:numel(ptcFilePaths)
    [detectionResults(i).lidarCheckerboardPlane, detectionResults(i).ptCloudUsed] = detectRectangularPlanePoints(ptcFilePaths{i}, planeDimension, 'RemoveGround', removeGround, 'ROI', roi(i,:), 'DimensionTolerance', dimTol, 'MinDistance', minDistance);
    if detectionResults(i).ptCloudUsed
        lidarCheckerboardPlanes(i) = detectionResults(i).lidarCheckerboardPlane;
    else
        lidarCheckerboardPlanes(i) = pointCloud(nan(1,3));
    end
end

%% Filter the data
validIdx = transpose([detectionResults.ptCloudUsed]) & imgDataUsed & ~isRejectedByUser;

% Filter images and point clouds
imageFilePaths = imageFilePaths(validIdx);
ptcFilePaths = ptcFilePaths(validIdx);

% Filter 3-D image corners
imageCorners3d = imageCorners3d(:,:,validIdx);

% Filter lidar checkerboard planes
lidarCheckerboardPlanes = lidarCheckerboardPlanes(validIdx);

%% Estimate transformation between lidar point cloud and image 3-D corners
initialTransform = rigidtform3d( ...
    [1 0 0;0 1 0;0 0 1], ...
    [0 0 0]);
[tform, errors] = estimateLidarCameraTransform(lidarCheckerboardPlanes, imageCorners3d, intrinsics,'InitialTransform', initialTransform, 'verbose', true);

%% Project lidar points to an image
figure
im = imread(imageFilePaths{1});
im = undistortImage(im, intrinsics);
imPts = projectLidarPointsOnImage(lidarCheckerboardPlanes(1),intrinsics, tform);
im = insertMarker(im ,imPts,'*','color','blue','size', 3);
imshow(im);

%% Plot the errors
figure
subplot(1,3,1);
h1 = bar(errors.TranslationError, 0.4);
subplot(1,3,2);
h2 = bar(errors.RotationError, 0.4);
subplot(1,3,3);
h3 = bar(errors.ReprojectionError, 0.4);
t1 = title(h1.Parent, 'Translation Errors', 'Units', 'normalized');
t2 = title(h2.Parent, 'Rotation Errors', 'Units', 'normalized');
t3 = title(h3.Parent, 'Reprojection Errors', 'Units', 'normalized');
set(t1, 'Position', get(t1, 'Position')+[0 0.04 0]);
set(t2, 'Position', get(t2, 'Position')+[0 0.04 0]);
set(t3, 'Position', get(t3, 'Position')+[0 0.04 0]);
xlabel(h1.Parent, 'Image - Point Cloud Pairs');
xlabel(h2.Parent, 'Image - Point Cloud Pairs');
xlabel(h3.Parent, 'Image - Point Cloud Pairs');
ylabel(h1.Parent, 'Error (meters)');
ylabel(h2.Parent, 'Error (degrees)');
ylabel(h3.Parent, 'Error (pixels)');

2 标定信息标准化文件生成

标定信息最终是为了能够在自定义3D目标检测数据集上使用的,参考KITTI标定文件的格式(如下图),将标定信息导出为txt文件

创建matlab脚本mat2txt.m,代码如下:

python 复制代码
% 加载 results.mat 文件
data = load('results.mat');

% 创建 results.txt 文件并写入参数
fid = fopen('0001.txt', 'w');

% 获取相机内参
intrinsics = data.intrinsics;
focalLength = intrinsics.FocalLength;        % [fx, fy]
principalPoint = intrinsics.PrincipalPoint;  % [cx, cy]

% 获取变换矩阵
tform = data.tform;
R = tform.R;                                  % 3x3 旋转矩阵
t = tform.Translation;                        % [tx, ty, tz]

% 将旋转矩阵和位移向量转换为齐次坐标形式
% Tr_velo_to_cam = [R, t';0, 0, 0, 1];
Tr_velo_to_cam = tform.A;
% 计算相机投影矩阵 P0, P1, P2, P3
P = [focalLength(1), 0, principalPoint(1), 0;
     0, focalLength(2), principalPoint(2), 0;
     0, 0, 1, 0];

% 定义写入矩阵的函数
function writeMatrix(fid, label, matrix)
    fprintf(fid, '%s: %e %e %e %e %e %e %e %e %e %e %e %e\n', label, ...
        matrix(1,1), matrix(1,2), matrix(1,3), matrix(1,4), ...
        matrix(2,1), matrix(2,2), matrix(2,3), matrix(2,4), ...
        matrix(3,1), matrix(3,2), matrix(3,3), matrix(3,4));
end

% 写入 P0, P1, P2, P3
for i = 0:3
    writeMatrix(fid, ['P' num2str(i)], P);
end

% 写入 R0_rect(假设为单位矩阵)
R0_rect = eye(3);
fprintf(fid, 'R0_rect: %e %e %e %e %e %e %e %e %e\n', R0_rect');

% 写入 Tr_velo_to_cam
writeMatrix(fid, 'Tr_velo_to_cam', Tr_velo_to_cam);

% 写入 Tr_imu_to_velo(需要您根据实际情况进行计算和定义)
fprintf(fid, 'Tr_imu_to_velo: %e %e %e %e %e %e %e %e %e %e %e %e\n', ...
    zeros(1, 12)); % 示例

fclose(fid);

生成的txt标定文件如下:

3 点云与图像配准映射

创建python脚本lidar2img.py,代码如下:

python 复制代码
import numpy as np
import cv2
import open3d as o3d
import argparse

# 读取标定文件
def read_calibration_file(filename):
    parameters = {}
    with open(filename, 'r') as f:
        for line in f:
            parts = line.strip().split(':')
            if len(parts) == 2:
                key = parts[0].strip()
                values = np.fromstring(parts[1].strip(), sep=' ')
                parameters[key] = values
    return parameters

# 读取点云文件(支持 .pcd 和 .bin)
def read_point_cloud(filename):
    if filename.endswith('.pcd'):
        pcd = o3d.io.read_point_cloud(filename)
        return np.asarray(pcd.points)
    elif filename.endswith('.bin'):
        points = np.fromfile(filename, dtype=np.float32).reshape(-1, 4)[:, :3]
        return points
    else:
        raise ValueError("Unsupported point cloud file format: {}".format(filename))

# 主函数
def main():
    # 设置命令行参数解析
    parser = argparse.ArgumentParser(description='将 KITTI 点云投影到图像上并可视化。')
    parser.add_argument('--calib', type=str, default='0001.txt', help='校正文件路径')
    parser.add_argument('--image', type=str, default='0001.png', help='图像文件路径')
    parser.add_argument('--point_cloud', type=str, default='0001.pcd', help='点云文件路径')
    parser.add_argument('--output', type=str, default='0001.jpg', help='输出图像文件路径')
    parser.add_argument('--color', type=str, default='0,255,0', help='投影点颜色,格式为 R,G,B')
    parser.add_argument('--size', type=int, default=2, help='投影点大小')

    args = parser.parse_args()

    # 从标定文件中提取参数
    calib_params = read_calibration_file(args.calib)
    P0 = calib_params['P0'].reshape(3, 4)
    Tr_velo_to_cam = calib_params['Tr_velo_to_cam'].reshape(3, 4)

    # 读取点云文件
    points = read_point_cloud(args.point_cloud)

    # 读取图像
    image = cv2.imread(args.image)
    image_height, image_width = image.shape[:2]

    # 将点云坐标从点云坐标系转换到相机坐标系
    points_homogeneous = np.hstack((points, np.ones((points.shape[0], 1))))  # 变为齐次坐标
    cam_points = Tr_velo_to_cam @ points_homogeneous.T
    cam_points = cam_points.T[:, :3]  # 只取前3列

    # 投影点云到图像平面
    # 使用相机内参 P0 进行投影
    u = cam_points[:, 0] / cam_points[:, 2] * P0[0, 0] + P0[0, 2]  # x 坐标
    v = cam_points[:, 1] / cam_points[:, 2] * P0[1, 1] + P0[1, 2]  # y 坐标

    # 过滤在图像外的点
    valid_mask = (u >= 0) & (u < image_width) & (v >= 0) & (v < image_height)
    u = u[valid_mask].astype(int)
    v = v[valid_mask].astype(int)

    # 解析颜色
    color = [int(c) for c in args.color.split(',')]
    
    # 绘制标记点
    for i in range(len(u)):
        cv2.drawMarker(image, (u[i], v[i]), color=color, markerType=cv2.MARKER_CROSS, markerSize=args.size)

    # 保存投影结果
    cv2.imwrite(args.output, image)

    # 展示结果
    cv2.imshow('Projected Lidar Points', image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    main()
相关推荐
机器懒得学习2 小时前
基于YOLOv5的智能水域监测系统:从目标检测到自动报告生成
人工智能·yolo·目标检测
mirrornan16 小时前
3D和AR技术在电商行业的应用有哪些?
3d·ar·3d建模·3d模型·三维建模
KeepThinking!16 小时前
YOLO-World:Real-Time Open-Vocabulary Object Detection
人工智能·yolo·目标检测·多模态
工业3D_大熊17 小时前
3D开发工具HOOPS助力造船业加速设计与数字化转型
3d
zaf赵17 小时前
3D 高斯溅射 (Gaussian Splatting)技术,一种实现超写实、高效渲染的突破性技术
3d
发呆小天才O.oᯅ20 小时前
YOLOv8目标检测——详细记录使用OpenCV的DNN模块进行推理部署C++实现
c++·图像处理·人工智能·opencv·yolo·目标检测·dnn
只待花开21 小时前
ROS2 python编写 intel realsense D405相机节点通过launch.py启动多个相机并发送图像话题,基于pyrealsense2库
数码相机
前端Hardy21 小时前
HTML&CSS:酷炫的3D开关控件
前端·javascript·css·3d·html
深度学习lover21 小时前
<项目代码>YOLO Visdrone航拍目标识别<目标检测>
python·yolo·目标检测·计算机视觉·visdrone航拍目标识别
KeyPan1 天前
【ORB-SLAM3:相机针孔模型和相机K8模型】
数码相机