1. 为什么相机内参这件事,绕不过去也急不得
做视觉SLAM、机械臂抓取、三维重建或者简单的图像去畸变,绕来绕去都会撞上同一个东西——相机内参。你可以把它理解成相机的"身份证":焦距、主点、畸变系数这几个数字一旦不准,后面所有基于像素坐标的计算都会跟着歪。我见过太多人在这上面栽跟头:标定出来的参数看着像模像样,跑起来点云却是弯的,抓取位置总是差那么几厘米,最后排查半天发现是内参里某个畸变系数填错了。
这篇内容就是把我自己反复折腾Realsense Viewer和ROS获取内参的完整流程梳理一遍,包括两种主流路径的取舍、每一步背后的逻辑,以及那些官方文档不会写、但实际一操作就会撞上的报错。适合刚上手Realsense、正在搭视觉pipeline、或者被内参问题卡住的朋友。不管你是用Ubuntu 20.04配Noetic,还是Ubuntu 22.04配Humble,核心思路是通的,差异我会在具体环节点出来。
先说一个反直觉的结论:获取内参这件事,最省事的路径往往不是最可靠的路径。很多人图快,直接rostopic echo一下相机信息就抄下来用了,结果发现和实际对不上。原因后面会展开。真正稳的做法,是把"读取出厂标定"和"自己动手标定"这两件事分清楚,知道什么时候该用哪个。
2. 两条获取路径的本质区别:出厂标定 vs 现场标定
在动手之前,得先搞清楚你要的内参到底从哪来。Realsense这类相机出厂时,厂商已经用专业设备标定过一组参数,烧在设备固件里;而ROS生态里又提供了自己标定的工具链。这两者不是替代关系,而是适用场景不同。
2.1 出厂内参:快,但有前提
Realsense Viewer和ROS的camera_info话题读到的,本质上都是出厂标定值。它的优点是即取即用,不需要你摆标定板、跑采集流程。对于D435、D455这类结构光/双目相机,出厂标定在常规室内环境下精度是够用的,尤其是深度和RGB对齐之后做点云,基本能直接跑。
但出厂标定有几个隐含前提你得知道。第一,它是在特定温度下标定的,相机长时间工作发热后,内参会轻微漂移,高精度场景下这个漂移不能忽略。第二,如果你动过相机的物理结构,比如拆过镜头、换过外壳,出厂值就失效了。第三,不同固件版本读出来的参数可能有细微差异,升级固件后建议重新确认一遍。
2.2 现场标定:慢,但可控
当你需要更高精度,或者相机被改动过,就得自己标。ROS里常用的是camera_calibration包,配合棋盘格或AprilTag。这条路的好处是你能控制标定条件——光照、距离、标定板姿态,标出来的结果更贴合你的实际使用场景。
代价是流程长、对环境要求高。标定板要平整,采集时要覆盖图像各个区域,姿态要多样。我自己的经验是,一次认真的现场标定,从准备到出结果,半小时到一小时是常态,而且经常要重来。
2.3 怎么选:一张表说清楚
| 场景 | 推荐路径 | 理由 |
|---|---|---|
| 快速验证、demo搭建 | 出厂内参 | 省时,精度够用 |
| 高精度抓取、测量 | 现场标定 | 可控,贴合实际工况 |
| 相机被拆改过 | 现场标定 | 出厂值已失效 |
| 长时间连续运行 | 现场标定+温度补偿 | 抵消热漂移 |
| 多相机系统 | 各自现场标定 | 保证一致性 |
提示:不要盲目迷信出厂值,也不要一上来就自己标。先用出厂值跑通流程,发现精度不够再上现场标定,这个顺序最省时间。
3. 用Realsense Viewer读取内参:图形化路径的细节
Realsense Viewer是Intel官方提供的图形化工具,最直观。但很多人只会在界面上点来点去,不知道背后发生了什么,遇到读不出参数的情况就懵了。
3.1 安装与启动的坑
在Ubuntu上,Realsense Viewer通常通过librealsense的apt源安装。这里第一个坑就来了:不同Ubuntu版本对应的源不一样。20.04和22.04的仓库地址有差异,装错了要么装不上,要么装上了但识别不到设备。
启动命令很简单:
realsense-viewer但如果你是在虚拟机里跑,或者通过USB Hub连接,很可能出现"设备识别到了但打不开流"的情况。这通常是USB带宽或权限问题。权限问题可以用udev规则解决,官方脚本install_udev_rules.sh跑一遍基本能搞定。带宽问题则要确保相机直连主板USB口,别经过劣质Hub。
3.2 在界面里找到内参
启动后,左侧选择相机,打开RGB或深度流。然后在右侧面板里找到"Info"或者通过菜单进入设备信息页。这里能看到Intrinsics相关的字段:fx、fy、ppx、ppy,以及畸变模型model和系数coeffs。
这里有个容易忽略的点:RGB和深度是两套独立的内参。很多人只抄了深度那套,结果做RGB-D对齐时发现对不上。正确的做法是两套都记下来,并且注意它们各自对应的分辨率——内参是和分辨率绑定的,改了分辨率,内参要按比例缩放。
3.3 分辨率变化对内参的影响
假设你在640x480下读到的fx是615,现在想用1280x960,那fx大致要变成1230,ppx、ppy同理按比例放大。这个换算在只做缩放、不做裁剪时成立。如果涉及裁剪(比如ROI),主点要减去裁剪偏移量。这个细节在官方文档里藏得很深,但实际项目中经常用到。
注意:Realsense Viewer里显示的内参,单位是像素。畸变系数是无量纲的。抄的时候别把单位搞混。
4. 用ROS话题拿内参:程序化路径与常见报错
图形化工具适合手动查看,但真正写代码时,你需要从ROS话题里拿camera_info。这条路看似简单,坑却最多。
4.1 启动realsense2_camera节点
标准启动方式:
roslaunch realsense2_camera rs_camera.launchHumble下则是:
ros2 launch realsense2_camera rs_launch.py启动后,/camera/color/camera_info和/camera/depth/camera_info这两个话题就会发布内参。用rostopic echo或ros2 topic echo就能看到。
4.2 为什么echo出来的值和Viewer对不上
这是最经典的困惑。原因通常有三个:
第一,分辨率设置不同。launch文件里默认的分辨率可能和你在Viewer里选的不一样,内参自然不同。检查launch参数里的color_width、color_height。
第二,对齐模式影响。如果你开了align_depth,深度图会被对齐到彩色坐标系,这时候深度内参的含义就变了。要对齐后的内参,得看对齐节点输出的话题。
第三,固件与驱动版本差异。老版本驱动读出的参数可能和Viewer有细微出入,升级到匹配的版本能消除。
4.3 常见报错逐条拆解
报错一:No device connected
相机没被识别。先lsusb看有没有Intel设备,没有就是物理连接或供电问题;有但ROS读不到,多半是udev规则没装或权限不够。
报错二:Failed to open stream
流打不开。常见于USB2.0接口跑高分辨率高帧率,带宽不够。降分辨率或换USB3.0口。
报错三:camera_info话题存在但数据全零
这种情况通常是相机还没完成初始化,或者固件里的标定数据读取失败。等几秒再echo,或者重启节点。如果一直全零,考虑重新烧录固件。
报错四:Humble下launch文件找不到
ROS2的launch文件名和ROS1不同,rs_camera.launch在Humble里是rs_launch.py。用错名字会直接报找不到文件。
报错五:arm64平台下Viewer装不上
在ARM平台(比如某些开发板)上,官方apt源可能没有对应的Viewer包。这时候可以只装librealsense和ROS驱动,用ROS话题拿内参,绕开Viewer。这也是热词里"realsense viewer arm64"被频繁搜索的原因。
4.4 用Python脚本自动提取内参
手动echo效率低,写个小脚本更实用:
import rospy from sensor_msgs.msg import CameraInfo def callback(msg): print("fx:", msg.K[0], "fy:", msg.K[4]) print("ppx:", msg.K[2], "ppy:", msg.K[5]) print("distortion:", msg.D) rospy.signal_shutdown("done") rospy.init_node('get_intrinsics') rospy.Subscriber('/camera/color/camera_info', CameraInfo, callback) rospy.spin()这个脚本订阅一次就退出,把K矩阵和畸变系数D打印出来。K是3x3内参矩阵,按行优先展开成9个元素,K[0]是fx,K[4]是fy,K[2]是ppx,K[5]是ppy。这个对应关系记牢,写代码时不会错位。
5. 自己动手标定:camera_calibration实操链路
当出厂值不够用,就得自己标。ROS的camera_calibration是标准工具,但流程里的细节决定成败。
5.1 标定板的选择与制作
常用棋盘格,比如8x6的内角点,方格边长已知(比如25mm)。打印时一定要保证实际尺寸准确,打印缩放会让标定结果系统性偏大或偏小。打印后贴到硬质平板上,翘边会导致角点检测误差。
AprilTag是另一种选择,抗遮挡和抗模糊更好,但配置稍复杂。新手建议从棋盘格入手。
5.2 采集时的姿态覆盖
标定质量高低,八成取决于采集。要点:
- 标定板要出现在图像的四个角和中心区域
- 要有不同距离的样本,近中远都覆盖
- 要有不同倾斜角度,绕X、Y、Z轴都要转
- 每个姿态保持稳定再采集,运动模糊是角点检测的大敌
我自己的习惯是采集30到50组有效样本,太少拟合不稳,太多边际收益低。
5.3 运行标定与结果解读
rosrun camera_calibration cameracalibrator.py \ --size 8x6 --square 0.025 \ image:=/camera/color/image_raw \ camera:=/camera/color界面里X、Y、Size、Skew四个进度条都变绿,CALIBRATE按钮才会亮。点完之后等计算,然后SAVE会把结果写到/tmp/calibrationdata.tar.gz,COMMIT会直接更新相机配置。
结果里的camera matrix就是内参,distortion是畸变系数。重点看重投影误差,一般要小于0.5像素,超过1像素说明采集质量有问题,得重来。
5.4 标定结果的验证方法
标定完别急着用,先验证。最简单的方法是拿标定后的参数做去畸变,看直线是否变直。或者用image_proc节点加载新参数,观察边缘区域的畸变是否被正确校正。如果边缘还有明显弯曲,说明畸变系数没标好。
6. 内参用起来:从参数到实际计算的衔接
拿到内参只是第一步,怎么用对才是关键。这里说几个实际项目里高频出问题的衔接点。
6.1 像素坐标到相机坐标的转换
给定像素点(u, v)和深度Z,相机坐标计算:
X = (u - ppx) * Z / fx Y = (v - ppy) * Z / fy Z = Z这个公式看着简单,但fx、fy、ppx、ppy用错一套(比如用了RGB的却配深度图),结果就全错。建议在代码里把两套内参分别命名,比如color_intr和depth_intr,避免混用。
6.2 畸变校正的顺序
如果图像有畸变,要先校正再算坐标,或者把畸变模型代入计算。顺序反了,边缘区域的误差会很大。OpenCV的undistort和initUndistortRectifyMap是常用工具,传入的就是标定得到的camera matrix和distortion coeffs。
6.3 多分辨率下的参数管理
项目里经常要在不同分辨率间切换。建议把内参按分辨率存成配置,用的时候按当前分辨率取。缩放公式前面提过,裁剪的话记得调整主点。这个管理做得好,能省掉大量调试时间。
7. 那些让我重来过好几次的经验
最后分享几个踩坑换来的教训,都是文档里不会写的。
第一,固件版本要统一。团队里多台相机,固件版本不一致会导致内参读取行为有差异。统一升级到同一版本,能避免很多"为什么这台行那台不行"的问题。
第二,标定环境的光照要稳定。我在窗边标过一次,下午阳光变化导致部分样本过曝,角点检测飘了,标出来的参数偏差明显。后来改到稳定光源下,一次就过。
第三,别忽略温度。相机连续跑一小时后内参会有漂移,高精度场景下要么预热后再标定,要么做温度补偿。这个在长时间运行的产线设备上尤其重要。
第四,保存好标定文件。标定结果建议连同当时的固件版本、分辨率、环境温度一起记录。下次出问题,能快速定位是不是环境变了。
第五,ARM平台优先走ROS路径。如果Viewer装不上,别死磕,直接用ROS话题拿内参,功能上完全够用。这也是很多开发板用户的现实选择。
相机内参这东西,说难不难,说简单也容易翻车。核心就一句话:搞清楚参数从哪来、对应哪个分辨率、用在哪套坐标系。把这三件事理顺,后面的事情就顺了。