第4章:视觉传感器原理与建模

视觉传感器,说白了就是机器人的眼睛。但跟人眼不一样,它看到的是一堆像素点。怎么从这些像素里还原出三维世界?这就是本章要聊的事。

我最早接触视觉SLAM时,觉得相机模型就是个简单的针孔成像。后来在实际项目中才发现,这里面的坑比想象中多得多。嗯,咱们一步步来。

4.1 单目相机模型

单目相机是最常见的。你手机上的摄像头就是单目。它的核心是针孔模型——光线通过一个小孔,在成像平面上形成倒像。

数学上,一个三维点 P(X, Y, Z) 投影到像素坐标 (u, v) 的过程可以写成:

u = fx * X / Z + cx
v = fy * Y / Z + cy

这里的 fx, fy 是焦距(以像素为单位),cx, cy 是光心偏移。这4个参数就是相机内参。

关键点:单目相机丢失了深度信息。一个点在图像上的位置,只能告诉你它在哪条射线上,但不知道距离。这就是所谓的「尺度不确定性」。

我在做AR项目时遇到过一个问题:用单目相机估计物体位置,结果物体忽大忽小。后来才发现是尺度没固定。你想想看,单目SLAM如果没有其他传感器辅助,地图的尺度就是模糊的。

4.2 双目相机模型

双目相机模拟人的双眼。两个相机之间有个固定的基线距离 B。通过左右图像的视差 d,就能算出深度:

Z = B * f / d

其中 f 是焦距,d = u_left - u_right 是视差。

这里有个实际问题:视差 d 是整数像素,但真实视差是连续的。所以我们需要亚像素插值。我习惯用抛物线拟合来得到亚像素精度。

我的经验:双目相机的基线越长,深度测量精度越高,但视差搜索范围也越大。一般室内用5-10cm基线,室外可以用20-30cm。我曾经在无人机上用过15cm基线,效果不错。

4.3 深度相机(RGB-D)

深度相机直接输出每个像素的深度值。主流技术有两种:

  • 结构光:投射红外点阵,通过变形计算深度。代表:Kinect v1、Intel RealSense
  • ToF(飞行时间):发射红外脉冲,测量往返时间。代表:Kinect v2、Azure Kinect

深度相机的优点是直接得到深度,省去了立体匹配的计算。但缺点也很明显:

  • 容易受环境光干扰(尤其是室外)
  • 测量范围有限(一般0.5-5米)
  • 边缘处有空洞

避坑指南:我曾经在室外用Kinect做SLAM,结果深度数据全是噪声。后来换成双目+IMU才搞定。深度相机在室内弱光环境下表现最好,别指望它在阳光下正常工作。

4.4 内参标定

内参标定就是求 fx, fy, cx, cy 以及畸变参数。最经典的方法是张正友标定法——用棋盘格拍多张照片,然后优化求解。

标定流程:

  1. 打印棋盘格,贴在平面上
  2. 从不同角度拍10-20张照片
  3. 检测角点
  4. 用最小二乘法求解内参
  5. 优化畸变参数

我用OpenCV做过很多次标定,代码大致是这样:

import cv2
import numpy as np

# 棋盘格尺寸
pattern_size = (9, 6)
objp = np.zeros((pattern_size[0]*pattern_size[1], 3), np.float32)
objp[:,:2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1,2)

objpoints = []  # 世界坐标
imgpoints = []  # 图像坐标

for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
    if ret:
        objpoints.append(objp)
        imgpoints.append(corners)

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
    objpoints, imgpoints, gray.shape[::-1], None, None)

我的习惯:标定时让棋盘格占图像面积的1/3到1/2,角度变化要覆盖各个方向。我一般拍15张,重投影误差控制在0.3像素以内才算合格。

4.5 外参标定

外参描述相机在机器人坐标系中的位置和姿态。说白了就是知道相机装在哪、朝哪看。

外参包含旋转矩阵 R 和平移向量 t。标定方法通常是用已知尺寸的标定物,或者用手眼标定(Eye-in-Hand / Eye-to-Hand)。

手眼标定的核心方程是:

AX = XB

其中 A 是机械臂末端位姿变化,B 是相机观测到的标定板位姿变化,X 就是待求的手眼矩阵。

实际案例:我在做机械臂抓取项目时,外参标定花了整整两天。原因是标定板没固定好,每次移动都有微小偏移。后来用磁吸座固定标定板,一次就成功了。

4.6 特征点提取与匹配

特征点是图像中具有独特性的点。SLAM里最常用的就是ORB和SIFT。

ORB(Oriented FAST and Rotated BRIEF)

ORB是OpenCV的标配。它结合了FAST角点检测和BRIEF描述子,还加了旋转不变性。优点是速度快,适合实时SLAM。

ORB的提取步骤:

  • 构建图像金字塔(尺度不变性)
  • 用FAST检测角点
  • 计算灰度质心方向(旋转不变性)
  • 用rBRIEF生成二进制描述子

SIFT(Scale-Invariant Feature Transform)

SIFT是经典中的经典。它用DoG(高斯差分)检测极值点,生成128维浮点描述子。精度高,但速度慢,而且有专利限制。

我个人的建议:

  • 实时SLAM用ORB,速度快,够用
  • 三维重建或视觉定位用SIFT,精度更高
  • 如果做深度学习,可以用SuperPoint或D2-Net

特征匹配

匹配就是找两幅图像中对应的特征点。最常用的是暴力匹配(Brute-Force)和FLANN匹配。

匹配后一定要做筛选:

  • 比值测试:最近距离 / 次近距离 < 0.7
  • RANSAC:用单应矩阵或基础矩阵剔除误匹配
# ORB特征匹配示例
orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)

bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
matches = bf.knnMatch(des1, des2, k=2)

# 比值测试
good_matches = []
for m, n in matches:
    if m.distance < 0.7 * n.distance:
        good_matches.append(m)

避坑指南:我曾经在纹理稀疏的环境(比如白墙)做ORB匹配,结果匹配点全错了。后来加了光照补偿和自适应阈值才改善。记住,特征点提取的前提是图像有足够的纹理信息。

4.7 知识体系总览

下面这张图总结了本章的核心逻辑。从传感器原理到特征匹配,每一步都是后续SLAM和轨迹优化的基础。

视觉传感器原理与建模 · 知识体系 单目相机 双目相机 深度相机 针孔模型 + 畸变 立体视觉模型 ToF / 结构光 内参标定 外参标定 手眼标定 ORB 特征 SIFT 特征 特征匹配 → 视觉SLAM / 三维重建 / 轨迹优化

嗯,以上就是视觉传感器原理与建模的核心内容。从相机模型到标定,再到特征提取与匹配,每一步都有它的物理意义和工程技巧。你在实际项目中遇到问题时,可以回头看看这张图,理清思路。

可可精品资料库