【ROS2相机标定】基于棋盘格的单目标定法

前言


1 棋盘格生成

1-1 思路
  • 一般来说,相机的标定需要借助标准的标定板。标定板越大,标定相对来说越准确。

  • 但是标定板越大,价格就越贵,之前用的这块30mm的标定板价格大致在¥400左右(虽然被打碎了)

  • 那么考虑到价格和性价比以及学习时候的临时使用,我们可以自己打印标定板。

1-2 自制打印棋盘格
  • 借助ReportLab(一个 Python 生成 PDF 的库 ),我们可以直接生成一个用于标定的20mm的棋盘格pdf文件
python 复制代码
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Spacer, Paragraph
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import mm

# Fix orientation per user: swapped width/height.
# Generate 9 columns x 12 rows, each 20 mm.
cols = 9
rows = 12
square = 20 * mm

filename = "chessboard_12rows_9cols_20mm.pdf"
styles = getSampleStyleSheet()

data = [["" for _ in range(cols)] for _ in range(rows)]
style_cmds = [("GRID", (0, 0), (-1, -1), 0.5, colors.black)]

for r in range(rows):
    for c in range(cols):
        fill = colors.black if (r + c) % 2 == 0 else colors.white
        style_cmds.append(("BACKGROUND", (c, r), (c, r), fill))

table = Table(
    data,
    colWidths=[square] * cols,
    rowHeights=[square] * rows,
)
table.setStyle(TableStyle(style_cmds))

doc = SimpleDocTemplate(
    filename,
    pagesize=A4,
    rightMargin=10 * mm,
    leftMargin=10 * mm,
    topMargin=10 * mm,
    bottomMargin=10 * mm,
)

story = [
    Paragraph("Camera Calibration Checkerboard --- 9×12 squares, 20 mm each", styles["Title"]),
    Spacer(1, 5 * mm),
    table,
]

doc.build(story)

print(f"Created {filename}")
  • 生成的效果大致如下:

  • 经过测试,实际去打印店打出来的误差差不多在1mm左右,可以接受。


2 ROS2 相机标定

2-1 安装
  • ros1的安装一样,我们直接通过apt安装
bash 复制代码
sudo apt install ros-$ROS_DISTRO-camera-calibration
2-2 前置准备
  • 注意:cameracalibrator节点需要接受sensor_msgs/Image格式的图像文件
  • 如果有些相机驱动发布的是sensor_msgs/CompressedImage,你需要进行一次转发,这里可以使用ros2自带的工具进行转发:
bash 复制代码
ros2 run image_transport republish compressed raw \
  --ros-args \
  -r in/compressed:=/camera/image_raw/compressed \
  -r out:=/camera/image_raw
  • in/compressed:=/camera/image_raw/compressed:原始压缩输入
  • out:=/camera/image_raw:转换后话题输出

  • 这里也提供一个cpp用 OpenCV 读取摄像头,并发布 sensor_msgs/msg/Image 话题(通过 cv_bridge 转换)。
  • 确保你安装了一下依赖
bash 复制代码
sudo apt install ros-${ROS_DISTRO}-cv-bridge ros-${ROS_DISTRO}-image-transport
sudo apt install libopencv-dev
  • 新建cpp
cpp 复制代码
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/image.hpp>

#include <cv_bridge/cv_bridge.h>
#include <opencv2/opencv.hpp>

class CameraPublisher : public rclcpp::Node
{
public:
    CameraPublisher() : Node("camera_publisher")
    {
        pub_ = this->create_publisher<sensor_msgs::msg::Image>(
            "/camera/image_raw", 10);

        cap_.open(0,cv::CAP_V4L2);  // 默认摄像头 /dev/video0

        if (!cap_.isOpened()) {
            RCLCPP_ERROR(this->get_logger(), "Failed to open camera!");
            rclcpp::shutdown();
            return;
        }

        timer_ = this->create_wall_timer(
            std::chrono::milliseconds(33),  // ~30 FPS
            std::bind(&CameraPublisher::timerCallback, this));
        std::cout<<"CameraPublisher Started!"<<std::endl;
    }

private:
    void timerCallback()
    {
        cv::Mat frame;
        cap_ >> frame;

        if (frame.empty()) {
            RCLCPP_WARN(this->get_logger(), "Empty frame!");
            return;
        }

        auto msg = cv_bridge::CvImage(
            std_msgs::msg::Header(),
            "bgr8",
            frame
        ).toImageMsg();

        msg->header.stamp = this->now();

        pub_->publish(*msg);
    }

    rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr pub_;
    rclcpp::TimerBase::SharedPtr timer_;
    cv::VideoCapture cap_;
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<CameraPublisher>());
    rclcpp::shutdown();
    return 0;
}
  • 运行后我们可以借助rqt进行可视化
bash 复制代码
ros2 run rqt_image_view rqt_image_view
  • 效果如下
2-3 使用
bash 复制代码
ros2 run camera_calibration cameracalibrator \
 --size 8x11 \
 --square 0.02 \
 --no-service-check \
 --ros-args --remap image:=/camera/image_raw
  • 其中的参数:

    • size 8x11为内部的角点数量

    • square 0.02为指定正方体的长度,单位是m

    • no-service-check:禁用 service 可用性检查

    • --ros-args --remap image:=/camera/image_raw设置图像输入,格式一定要是sensor_msgs::msg::Image

  • 运行有:

  • 基础参数:

    • Camera type摄像头类型:,这里我们选择0
      • 0 pinhole针孔
      • 1 fisheye鱼眼
    • scale:比例,设置为1即可
  • 根据右侧进度条提示进行移动棋盘格:

    • X: 棋盘格在摄像头视野中的左右移动
    • Y:棋盘格在摄像头视野中的上下移动
    • Size:棋盘格在摄像头视野中的前后移动
    • Skew:棋盘格在摄像头视野中的倾斜转动
  • 我们需要不断移动棋盘格,不断采集四个进度条的数据,尽量让四个条都变成绿色

  • 晃的差不多以后,CALIBRATE按钮会变色

  • 点击SAVE,可以看到命令行输出:

  • 解压后就有标定文件了

yaml 复制代码
image_width: 640
image_height: 480
camera_name: narrow_stereo

camera_matrix:
  rows: 3
  cols: 3
  data: [589.30405, 0.0, 329.54919,
         0.0, 586.3807, 249.68556,
         0.0, 0.0, 1.0]

distortion_model: plumb_bob
distortion_coefficients:
  rows: 1
  cols: 5
  data: [-0.037621, -0.167530, -0.007268, 0.000397, 0.0]

rectification_matrix:
  rows: 3
  cols: 3
  data: [1.0, 0.0, 0.0,
         0.0, 1.0, 0.0,
         0.0, 0.0, 1.0]

projection_matrix:
  rows: 3
  cols: 4
  data: [589.30405, 0.0, 329.54919, 0.0,
         0.0, 586.3807, 249.68556, 0.0,
         0.0, 0.0, 1.0, 0.0]

2-4 标定参数
  • 注意正常情况下单目相机标定的结果会是:
yaml 复制代码
image_width: 640
image_height: 480

camera_name: camera

camera_matrix:
  rows: 3
  cols: 3
  data: [fx,  0.0, cx,
         0.0, fy,  cy,
         0.0, 0.0, 1.0]

distortion_model: plumb_bob

distortion_coefficients:
  rows: 1
  cols: 5
  data: [k1, k2, p1, p2, k3]

rectification_matrix:
  rows: 3
  cols: 3
  data: [1.0, 0.0, 0.0,
         0.0, 1.0, 0.0,
         0.0, 0.0, 1.0]

projection_matrix:
  rows: 3
  cols: 4
  data: [fx,  0.0, cx,  0.0,
         0.0, fy,  cy,  0.0,
         0.0, 0.0, 1.0,  0.0]

2-4-1 camera_matrix(内参 K)
  • 含义:把"相机坐标系里的3D点"投影成"像素坐标"
    K = f x 0 c x 0 f y c y 0 0 1 K = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} K= fx000fy0cxcy1

  • 其中:

    • f x fx fx / f y fy fy:焦距(像素), 越大 → 视角越窄(像望远镜)
    • c x cx cx / c y cy cy:主点(图像中心偏移),感光元件的偏移
  • 本质:

    • 把"归一化相机坐标"变成"像素坐标"

2-4-2 distortion_coefficients(畸变)
  • 含义:修正"镜头不是完美针孔"的问题
  • 总共有两种畸变:
    • 径向畸变(k1, k2, k3):镜头边缘光学弯曲,有桶形,枕形
    • 切向畸变(p1, p2):镜头和传感器没有完全平行,左右不对称
  • k 1 , k 2 , p 1 , p 2 , k 3 \] \[k_1, k_2, p_1, p_2, k_3\] \[k1,k2,p1,p2,k3


2-4-3 rectification_matrix(R)
  • 含义:把相机坐标"旋正"

    P = f x 0 c x 0 0 f y c y 0 0 0 1 0 P = \begin{bmatrix} f_x & 0 & c_x & 0 \\ 0 & f_y & c_y & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} P= fx000fy0cxcy1000

  • 对于单目永远是单位矩阵

    • 单目没有 stereo(没有左右相机对齐)
    • 不需要旋转到统一视角

2-4-4 projection_matrix(P)
  • 含义: "用于最终投影到图像的矩阵(给 SLAM / stereo 用)"
    P = f x 0 c x 0 0 f y c y 0 0 0 1 0 P = \begin{bmatrix} f_x & 0 & c_x & 0 \\ 0 & f_y & c_y & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix} P= fx000fy0cxcy1000

  • 和 K 的区别:

    • K 用于原始相机模型
    • P 用于校正后投影模型
  • 最后一列表示单目没有 baseline(没有左右偏移)

    [0, 0, 0]


2-5 全流程
bash 复制代码
3D点 (X, Y, Z)
      ↓
相机坐标系
      ↓
归一化投影
      ↓
K(焦距+主点)
      ↓
畸变修正
      ↓
rectification(旋正)
      ↓
P(最终输出像素)
      ↓
2D图像点 (u, v)

总结

  • 本文提到了相机标定的棋盘格生成、以及ROS2中如何对单目相机进行标定。
  • 如有错误,欢迎指出!
  • 感谢观看!
相关推荐
爱网络爱Linux19 小时前
【系列】每天一个 Linux 命令(53)startx 命令
linux·rhce·rhca·linux运维·linux命令大全·红帽认证
讳疾忌医丶19 小时前
深度拆解 RocksDB 内核:基于 C++17 的 FIFO 调度状态机与温度阶梯自愈设计
java·c++·算法·架构
ZhengEnCi19 小时前
L2A-一个域名如何访问多台云服务器-子域名、路径前缀与Nginx反向代理选型指南
linux·人工智能
M78佐菲20 小时前
51单片机学习笔记:ds18b20要点整理
linux·笔记·嵌入式硬件·学习·51单片机
2401_8685347820 小时前
OpenStack 架构全景:八个核心组件深度拆解
c++·需求分析
高亦真20 小时前
今天是学习嵌入式的第37天
linux·学习·算法
软件课代表CiCi20 小时前
运行库是什么?电脑缺运行库怎么办?c++运行库缺失修复全攻略
开发语言·c++·windows·电脑·dll修复·dll丢失·运行库
一个有温度的技术博主20 小时前
Linux 核心命令速查指南:从文件操作到系统运维
linux·服务器
小小的木头人21 小时前
Docker 环境下迁移 containerd 数据目录:从 `/var/lib/containerd` 到 `/data/containerd`
linux·docker
蒸蒸yyyyzwd21 小时前
从面经中学到的三件事
c++·笔记