Ubuntu下Matlab双目视觉标定:从原理到实践的完整指南
1. 项目缘起为什么在Ubuntu上做双目标定是个好主意如果你正在折腾机器人、自动驾驶或者三维重建那“双目视觉”这个词肯定不陌生。简单说就是模仿人眼用两个摄像头从不同角度拍同一个东西然后通过计算视差来获取深度信息。而这一切的基础就是“标定”——你得先知道两个摄像头各自长啥样内参以及它俩之间的相对位置关系外参后续的立体匹配、三维重建才能玩得转。很多人一提到标定第一反应是OpenCV。确实OpenCV的stereoCalibrate函数很强大社区资源也多。但说实话对于刚上手或者对C/Python环境配置头大的朋友来说OpenCV那一套配置、编译、找bug的流程足够劝退一大半人。特别是当你的主要工作流在Ubuntu这类Linux系统上时环境依赖问题就像地雷不知道哪一步就会炸。这时候Matlab的优势就凸显出来了。它提供了一个叫Camera Calibrator和Stereo Camera Calibrator的App完全是图形化界面操作。你不需要写一行代码去计算重投影误差不需要手动解析复杂的旋转平移矩阵甚至不需要理解张正友标定法背后那套复杂的数学。你只需要拍好照片点几下鼠标Matlab就能把内参、外参、畸变系数、标定误差全都算得明明白白并且用非常直观的图表展示给你看。这对于验证标定结果、快速迭代实验方案来说效率提升不是一点半点。所以这篇内容就是基于我在Ubuntu 20.04/22.04环境下多次使用Matlab R2022b进行双目摄像头标定的实战经验。我会带你走通从环境准备、拍摄标定板图像、使用App标定、到最终参数验证和导出的完整闭环。你会发现在Linux上用Matlab做视觉标定可以如此省心。2. 环境准备在Ubuntu上安装和配置Matlab万事开头难在Ubuntu上装Matlab算是第一道小坎。和Windows下一路“下一步”不同在Linux下需要一些终端操作。别怕跟着步骤来很稳。2.1 获取Matlab安装包首先你需要有Matlab的安装文件。通常是一个ISO镜像或者一个包含安装文件的文件夹。假设你下载后得到一个名为matlab_R2022b_glnxa64.zip的压缩包我们把它解压到某个目录比如~/Downloads/matlab_installer。cd ~/Downloads unzip matlab_R2022b_glnxa64.zip -d matlab_installer cd matlab_installer2.2 以非root用户身份运行安装一个重要的经验尽量不要用root权限安装Matlab。用你自己的普通用户安装可以避免很多后续的文件权限问题。直接运行安装脚本./install这时会启动图形化安装界面。如果报错说找不到图形界面可能是因为你通过SSH远程连接或者使用的是纯命令行环境。你需要确保X11转发已启用对于SSH加-X或-Y参数或者直接使用带桌面的Ubuntu环境。在安装过程中有几个关键点需要注意选择安装类型建议选择“自定义”安装。在“选择产品”页面确保勾选MATLAB核心组件以及Computer Vision Toolbox。双目标定App就在这个工具箱里。Image Processing Toolbox通常也会被依赖安装一并勾选上更省事。选择安装路径默认是/usr/local/MATLAB/R2022b。如果你没有root权限或者想安装在自己家目录下可以修改为~/MATLAB/R2022b。我个人的习惯是安装在/opt目录下方便多用户使用但这需要sudo权限。这里我们以家目录为例。创建符号链接在安装过程的最后会问你是否创建符号链接。建议勾选“是”这样你可以在终端任何地方直接输入matlab来启动程序而不需要输入完整的安装路径。2.3 处理常见的安装后问题安装完成后第一次启动可能会遇到两个经典问题问题一libfreetype.so.6未找到启动Matlab时终端可能报错error while loading shared libraries: libfreetype.so.6: cannot open shared object file。 这是因为Matlab自带的库版本与系统库不兼容。解决方法是指定Matlab使用系统自带的库cd ~/MATLAB/R2022b/sys/os/glnxa64 sudo mv libfreetype.so.6 libfreetype.so.6.bak # 备份Matlab自带的旧库 sudo ln -s /usr/lib/x86_64-linux-gnu/libfreetype.so.6 # 链接到系统的新库这个操作的本质是让Matlab放弃它自己那个可能过时的库转而使用Ubuntu系统里更新的版本。问题二中文界面乱码如果你安装时选择了中文可能会发现菜单和提示框里的中文全是方块。这是因为Matlab使用的Java环境缺少中文字体。解决方法是指定Java使用系统的字体配置在Matlab的启动脚本中通常是~/MATLAB/R2022b/bin/matlab或者在你家目录下的~/.matlab/R2022b/matlab.prj相关文件但更简单的方法是在启动命令前设置环境变量export MATLAB_JAVA/usr/lib/jvm/java-11-openjdk-amd64/jre # 路径可能因Java版本而异 export MATLAB_FONTS_CONFIG/etc/fonts/fonts.conf更一劳永逸的做法是创建一个启动脚本start_matlab.sh#!/bin/bash export MATLAB_JAVA/usr/lib/jvm/java-11-openjdk-amd64/jre export MATLAB_FONTS_CONFIG/etc/fonts/fonts.conf ~/MATLAB/R2022b/bin/matlab -desktop然后给脚本执行权限chmod x start_matlab.sh以后都通过这个脚本启动。2.4 连接你的双目摄像头在Ubuntu下摄像头通常被识别为/dev/video0/dev/video1等设备。插入你的双目摄像头通常是一个USB接口包含两个摄像头模块使用ls /dev/video*命令查看。 如果你的摄像头是常见的UVC兼容设备Linux内核会自动识别无需额外驱动。你可以用cheese或guvcview这类工具先测试一下每个摄像头是否能单独正常工作。注意有些双目摄像头在系统里会显示为两个独立的video设备例如video0和video1这正好方便我们分别采集左右眼的图像。但也有些摄像头会通过一个设备号输出拼接好的图像这就需要你查阅摄像头说明书了解如何在Matlab中对其进行分割。环境准备好后输入matlab命令看到熟悉的Matlab桌面界面在Ubuntu上弹出来第一步就成功了。3. 标定前期拍摄高质量的标定板图像标定的精度八成取决于你拍摄的标定板图像质量。这一步做不好后面App算得再花哨也是白搭。3.1 制作或选择标定板Matlab的标定工具支持多种标定板最常用的是“棋盘格”和“圆形网格”。我强烈推荐使用棋盘格因为它检测起来更鲁棒对光照和模糊的容忍度更高。你可以在网上搜索“checkerboard pattern generator”并打印出来或者直接使用Matlab自带的生成功能 在Matlab命令行输入% 生成一个边长为30毫米8行11列内角点的棋盘格图像 I checkerboard(30, 8, 11); imshow(I); imwrite(I, checkerboard.png);打印时务必确保“缩放”选项设置为“无”或“实际大小”否则每个方格的实际物理尺寸就变了标定出来的焦距等参数会完全错误。用尺子量一下打印后每个黑白方格的实际边长例如30mm这个值后面要精确输入到标定App中。3.2 设计拍摄方案拍摄时你需要用双目摄像头同时拍摄这个标定板。注意是“同时”拍摄左右摄像头的图像而不是拿着标定板拍完左边再拍右边。这样才能保证每一组图像对左图右图中标定板都处于完全相同的空间位置和姿态。这是双目标定能解算出正确外参两个摄像头的相对位置的前提。你需要设计一个拍摄脚本或者手动控制让摄像头和标定板满足以下条件覆盖整个视野将标定板放在摄像头视野的不同位置——左上、右上、左下、右下、中心。覆盖不同距离将标定板分别靠近、远离摄像头。覆盖不同角度将标定板绕X、Y、Z轴旋转倾斜、俯仰、偏转制造不同的“姿态”。均匀光照避免标定板上有强烈反光或阴影。阴影会导致棋盘格角点检测失败。清晰对焦图像不能模糊。模糊的图像会让角点定位不准极大增加重投影误差。建议拍摄至少15-20组图像对。太少的话标定算法容易过拟合泛化能力差太多的话处理时间变长收益边际递减。20组左右是一个比较好的平衡点。3.3 使用Matlab采集图像替代方案如果你没有现成的图像采集程序可以用Matlab的imaqtoolImage Acquisition Toolbox来快速抓取。首先在命令行输入imaqtool打开工具它会自动检测到你的摄像头设备。你可以预览并设置分辨率、帧率。更直接的方法是用几行脚本同步采集左右摄像头% 假设左摄像头是video0右摄像头是video1 left_cam videoinput(linuxvideo, 1, RGB24_640x480); % 设备索引可能不同 right_cam videoinput(linuxvideo, 2, RGB24_640x480); % 配置为手动触发一次拍一张 triggerconfig([left_cam, right_cam], manual); start([left_cam, right_cam]); for i 1:20 fprintf(准备拍摄第%d组按任意键继续...\n, i); pause; % 此时你调整标定板姿态 % 同步触发 trigger(left_cam); trigger(right_cam); % 获取图像 left_img getdata(left_cam); right_img getdata(right_cam); % 保存图像建议用有意义的文件名如 left_01.jpg, right_01.jpg imwrite(left_img, sprintf(left_%02d.jpg, i)); imwrite(right_img, sprintf(right_%02d.jpg, i)); end stop([left_cam, right_cam]); delete([left_cam, right_cam]);这个脚本能保证左右图像是几乎同时捕获的。把拍好的图像对left_01.jpgright_01.jpg, ...放在一个单独的文件夹里比如~/stereo_calib_images。4. 核心实操使用Stereo Camera Calibrator App图像准备好后重头戏来了。打开Matlab在“APP”标签页里找到“Stereo Camera Calibrator”或者直接在命令行输入stereoCameraCalibrator。4.1 导入图像并设置参数点击“Add Images”按钮分别导入左摄像头和右摄像头的图像。Matlab会自动根据文件名排序配对前提是你的文件名规律一致如left_01.jpg,right_01.jpg。导入后最关键的一步来了设置棋盘格的实际物理尺寸。Square Size: 输入你之前用尺子量出来的每个黑色或白色方格的实际边长单位是毫米mm。比如你打印的是30mm每格这里就填30。这个值直接影响标定出的焦距fx, fy等内参的物理意义单位是像素/毫米。Radial Distortion: 畸变模型。对于普通镜头建议选择“2 Coefficients”就足够了它对应k1和k2可以校正桶形和枕形畸变。对于鱼眼等畸变严重的镜头才需要考虑“3 Coefficients”增加k3。Skew: 对于现代的数字传感器像元像素基本都是严格正交排列的所以“Skew”通常可以忽略保持为0。除非你用的是非常老式的或特殊构造的相机。4.2 运行标定与解读结果点击“Calibrate”按钮Matlab就开始自动检测每张图像中的棋盘格角点并进行优化计算。这个过程可能需要几分钟取决于图像数量和分辨率。标定完成后你会看到一个非常丰富的图形化报告界面这里的信息量很大重投影误差图Reprojection Errors: 这是衡量标定精度的核心指标。它显示了每个角点在每张图像上其检测到的位置与根据标定参数反投影回去的理论位置之间的像素距离。这个误差值最好能控制在0.5个像素以内。如果某几张图的误差明显偏大比如标红你可以考虑在图像列表中将其勾选掉然后重新标定。通常模糊的、角度极端的、标定板部分在视野外的图像误差会比较大。相机参数Camera Parameters:内参Intrinsics: 包括焦距Focal Length、主点Principal Point、畸变系数Radial Distortion, Tangential Distortion。焦距fx和fy应该接近如果相差很大说明图像可能存在各向异性缩放但现代相机很少见或者标定板拍摄姿态分布不够好。主点(cx, cy)理论上应该在图像中心附近如果偏离太远也需要警惕。外参Extrinsics: 这里显示的是右摄像头相对于左摄像头的旋转矩阵Rotation Matrix和平移向量Translation Vector。平移向量的第一个分量通常是Tx应该显著大于其他两个分量因为双目摄像头一般是水平摆放的两个光心在X方向上有基线距离Baseline而Y和Z方向的偏移应该很小。如果Tx是负值这很正常只是坐标系定义的方向问题。关键是它的绝对值这就是你后续做立体视觉时最重要的参数——基线长度单位与你输入的方格尺寸一致例如毫米。三维可视化Show Extrinsics: 点击这个按钮会弹出一个3D图显示每一张标定板姿态相对于左摄像头坐标系的位置。这是一个非常好的定性检查工具。你应该看到这些姿态点均匀地分布在摄像头前方的一个扇形空间里而不是挤在一坨或排成一条线。均匀的分布意味着你的数据质量高标定结果更可靠。4.3 常见问题与处理技巧角点检测失败如果某些图像的角点检测不出来图像上没画出彩色的检测框首先检查标定板是否完整出现在视野内光照是否均匀。可以尝试手动调整“棋盘格检测灵敏度”滑块或者使用“手动添加/删除角点”功能进行微调。但手动调整工作量巨大不如重新拍一张。误差过大如果整体重投影误差超过1个像素首先检查物理尺寸是否输错这是新手最容易犯的错误。其次检查是否有明显模糊或畸变极大的图像将其剔除。最后考虑是否拍摄的姿态不够丰富补充一些不同角度和距离的图像重新标定。外参不合理如果平移向量T的Y或Z分量很大甚至和X分量差不多那很可能是因为你的图像对不是“同时”拍摄的。标定板在左右图像中的位置发生了相对移动导致算法解算出了一个奇怪的外参。务必确保左右图像是同步采集的。5. 参数导出与应用验证标定结果满意后点击工具栏的“Export Camera Parameters”按钮。这会将标定结果保存到Matlab的工作区Workspace通常是一个名为stereoParams的结构体变量。5.1 理解stereoParams结构体在命令行输入stereoParams查看其内容你会看到它包含左右摄像头的内参、它们之间的旋转和平移矩阵、以及畸变系数等所有信息。其中几个关键字段stereoParams.CameraParameters1: 左摄像头参数内参畸变。stereoParams.CameraParameters2: 右摄像头参数。stereoParams.RotationOfCamera2: 右相机相对于左相机的旋转矩阵3x3。stereoParams.TranslationOfCamera2: 右相机相对于左相机的平移向量1x3单位是毫米如果你输入的方格尺寸单位是毫米。stereoParams.FundamentalMatrix/EssentialMatrix: 基础矩阵和本质矩阵用于极线几何计算。你可以将这些参数保存到文件供后续的C/Python程序调用save(stereo_calib_params.mat, stereoParams);对于想在OpenCV中使用的朋友需要将参数格式进行转换。Matlab和OpenCV的相机模型特别是畸变系数的顺序略有不同需要小心处理。一个简单的做法是在Matlab中按照OpenCV的格式[k1, k2, p1, p2, k3]重新排列畸变向量并将矩阵转置因为Matlab和OpenCV的矩阵存储顺序不同。5.2 进行立体校正与极线验证标定的最终目的是为了后续的立体匹配。在匹配之前必须进行“立体校正”将两个摄像头的图像平面重投影到共面且行对齐的平面上这样匹配点只需要在同一水平线上搜索极大简化算法。Matlab提供了rectifyStereoImages函数来完成这件事% 读取一对新的、未参与标定的图像用于验证 I_left imread(test_left.jpg); I_right imread(test_right.jpg); % 进行立体校正 [J1, J2] rectifyStereoImages(I_left, I_right, stereoParams); % 并排显示校正前后的图像 figure; subplot(2,1,1); imshowpair(I_left, I_right, montage); title(原始图像); subplot(2,1,2); imshowpair(J1, J2, montage); title(校正后图像已行对齐);验证校正效果的关键在校正后的图像对(J1, J2)上任意选择一个特征明显的点比如标定板的某个角点观察它在左右图中的纵坐标Y坐标是否相同或极其接近。如果行对齐成功它们的纵坐标差应该在1-2个像素以内。你也可以用imshow(stereoAnaglyph(J1, J2))生成红蓝图戴上红蓝眼镜看是否有明显的3D效果这也是一个快速的定性检查。5.3 生成深度图简易示例作为标定成果的最终验证我们可以用最简单的块匹配Block Matching算法生成一张深度图。Matlab的Computer Vision Toolbox里有现成的函数% 计算视差图 disparityMap disparitySGM(rgb2gray(J1), rgb2gray(J2), DisparityRange, [0, 128]); % 将视差转换为深度Z f * B / d focal_length mean(stereoParams.CameraParameters1.FocalLength); % 平均焦距单位像素 baseline norm(stereoParams.TranslationOfCamera2); % 基线长度单位毫米 depthMap focal_length * baseline ./ (double(disparityMap) 1e-5); % 避免除零 % 显示结果 figure; subplot(1,2,1); imshow(disparityMap, []); colormap jet; colorbar; title(视差图); subplot(1,2,2); imshow(depthMap, []); colormap jet; colorbar; title(深度图毫米);注意disparitySGM是半全局匹配算法比简单的块匹配效果好但计算稍慢。DisparityRange是视差搜索范围需要根据你的摄像头基线和距离目标的远近大致估计。如果生成的深度图在标定板区域比较平滑且深度值符合你测量的实际距离例如标定板距离摄像头大约500mm深度图显示该区域值在500左右那就说明你的标定参数是相当准确的。6. 避坑指南与进阶思考走完整个流程你可能觉得还挺顺利。但实际项目中总会遇到一些坑。这里分享几个我踩过的以及对应的解决方案。坑一标定结果每次都不一样波动大可能原因图像数量太少或者姿态分布太集中。标定是一个优化过程数据量小或多样性不足会导致解算不稳定。解决方案严格按照3.2节的建议拍摄20组以上覆盖视野各个角落、各种姿态的图像。可以像拍电影打板一样固定摄像头手持标定板运动确保覆盖足够多的视角。坑二标定出的焦距fx, fy与摄像头数据手册相差甚远可能原因1最常见打印标定板时没有选择“实际大小”或者打印后没有用尺子精确测量方格尺寸而是想当然地输入了设计值。解决方案必须用游标卡尺或精确的尺子测量打印后多个方格的平均边长。这是整个标定过程中物理世界与数字世界唯一的桥梁绝对不能错。可能原因2输入的单位错了。如果你量的是厘米输入时却当成毫米结果会差10倍。可能原因3摄像头分辨率设置与标定时不一致。标定用的是640x480的图像但数据手册给的焦距可能是针对1920x1080分辨率的。焦距的单位是像素分辨率越高焦距的像素值自然越大。它们之间可以通过传感器像元尺寸进行换算但通常我们只关心在同一分辨率下的标定结果是否一致。坑三立体校正后图像边缘出现大量黑色无效区域原因这是正常现象。为了将两个图像平面投影到同一个共轭平面上算法需要对图像进行透视变换重投影这个变换类似于“拉伸”和“挤压”会导致原图的部分区域被映射到画布之外同时画布上也会出现一些原图没有的区域用黑色填充。解决方案在rectifyStereoImages函数中可以设置OutputView参数为valid这样函数会自动裁剪掉黑色的无效区域只返回两个图像有重叠的部分。缺点是视野会变小。你也可以在后续处理中用一个矩形ROI来界定有效区域。进阶思考何时需要重新标定摄像头标定不是一劳永逸的。以下情况需要重新标定物理状态改变摄像头被磕碰镜头焦距被调整如果是变焦镜头或者双目摄像头的相对位置因松动而发生改变。更换环境从室内到室外温度湿度剧烈变化可能导致镜头产生微小形变。追求极致精度当你的应用对精度要求极高时可以针对特定的工作距离和视野范围采集该范围内的图像进行标定这样得到的参数在该范围内会比通用标定更准。最后Matlab的标定工具虽然方便但它是一个“黑箱”。对于想深入理解双目视觉原理的朋友我建议在掌握这个流程后可以去研究一下OpenCV的标定源码或者自己用最小二乘法推导一下相机模型。知其然并知其所以然当你遇到Matlab也解决不了的奇异问题时底层的知识才能帮你找到突破口。至少现在你可以用这套流程快速、可靠地在Ubuntu上得到一组可用的双目标定参数让你的三维视觉项目跑起来了。