相机标定实战指南:从原理到OpenCV实现,解决视觉测量不准问题

📅 2026/8/2 5:54:35 👁️ 阅读次数 📝 编程学习
相机标定实战指南:从原理到OpenCV实现,解决视觉测量不准问题

1. 项目概述:从“拍不准”到“算得准”的基石

如果你玩过3D建模、做过机器人视觉,或者捣鼓过自动驾驶小车,大概率都遇到过同一个问题:为什么我的相机拍出来的图像,用来测距、建模或者拼接时,总感觉“差那么一点”?明明算法看起来没问题,但结果就是飘忽不定,精度死活上不去。这个问题的根源,十有八九出在“相机标定”这个环节上。今天,我们就来彻底拆解这个看似基础、实则至关重要的技术——相机标定。

简单来说,相机标定就是给相机做一次“体检”和“身份认证”。它通过一系列数学计算,确定相机自身的“内部特性”(内参)和它在三维世界中的“姿势与位置”(外参)。没有准确的标定参数,相机就像一把刻度不准的尺子,用它去测量任何东西,结果都是不可信的。无论是单目视觉的测距、双目立体视觉的三维重建,还是激光雷达与相机的数据融合(即“16线雷达和相机标定”),标定都是第一步,也是最关键的一步。它决定了后续所有视觉算法性能的天花板。

这篇文章,我会从一个实践者的角度,带你走一遍相机标定的完整流程。我们不只讲“怎么做”,更重点剖析“为什么这么做”,以及我在实际项目中踩过的那些坑。无论你是刚入门的学生,还是需要快速上手的工程师,这篇内容都能给你一套可直接复现、且知其所以然的方法论。

2. 核心原理:相机如何把3D世界“压扁”成2D照片

在动手标定之前,我们必须先理解相机成像的数学模型。这是所有标定工作的理论基础,搞懂了它,你才能明白我们在计算什么,以及误差从何而来。

2.1 从针孔模型说起:一个完美的简化

目前最主流、最基础的相机模型是针孔相机模型。你可以把它想象成一个封闭的暗箱,只在箱壁开了一个小孔。外界的光线穿过这个小孔,在箱内对面的壁上形成上下颠倒的影像。这个模型忽略了透镜的存在,但它完美地描述了光线从三维世界到二维成像平面的中心投影关系。

在这个模型里,有几个关键的空间和坐标系需要理清:

  1. 世界坐标系 (X_w, Y_w, Z_w):这是我们定义的真实三维空间,比如一个房间,棋盘格标定板就放在这个空间里。
  2. 相机坐标系 (X_c, Y_c, Z_c):以相机的光心(小孔位置)为原点,Z_c轴指向相机正前方(光轴),X_c和Y_c轴平行于成像平面。
  3. 图像物理坐标系 (x, y):以成像平面的中心(理论上与光轴的交点,即主点)为原点,单位是毫米(mm)。一个三维点 (X_c, Y_c, Z_c) 投影到这里,遵循相似三角形原理:x = f * X_c / Z_c,y = f * Y_c / Z_c。这里的f就是焦距,是相机的一个核心内参。
  4. 图像像素坐标系 (u, v):这是我们最终看到的数字图像,原点在左上角,单位是像素。我们需要将物理坐标 (x, y) 转换到像素坐标。这个转换涉及两个部分:主点坐标 (cx, cy)像素尺寸 (dx, dy)

注意:这里容易混淆。焦距 f 的单位是毫米,它描述的是光心到成像平面的物理距离。而我们在标定结果中常看到的 fx, fy,其单位是像素。它们的关系是:fx = f / dx,fy = f / dy。因为现代相机的像素不一定是正方形,所以 dx 和 dy 可能不同,导致 fx 和 fy 也不同。标定直接求解的就是 fx, fy, cx, cy 这些以像素为单位的参数。

2.2 内参与外参:相机的“身份证”和“定位器”

基于上面的模型,我们可以用数学公式来描述整个投影过程:

  1. 世界坐标到相机坐标(刚体变换):这需要外参——旋转矩阵 R (3x3) 和平移向量 t (3x1)。[X_c; Y_c; Z_c; 1] = [R | t] * [X_w; Y_w; Z_w; 1]。外参描述了相机在世界坐标系下的姿态和位置。对于单目标定,我们每拍一张标定板图片,就对应一组不同的外参(因为相机或标定板移动了)。

  2. 相机坐标到图像物理坐标(透视投影)x = X_c / Z_c,y = Y_c / Z_c(这里隐含了归一化,焦距 f 的乘法被合并到内参矩阵中)。

  3. 图像物理坐标到像素坐标(仿射变换):这需要内参矩阵 K[u; v; 1] = K * [x; y; 1]

    内参矩阵 K 是一个 3x3 的上三角矩阵:

    K = [ fx, s, cx; 0, fy, cy; 0, 0, 1 ]
    • fx, fy: 以像素为单位的焦距。
    • cx, cy: 主点坐标,即光轴与成像平面的交点(通常接近图像中心)。
    • s (skew): 倾斜系数,描述图像坐标轴不垂直的程度。对于绝大多数现代数码相机,s 非常接近于0,可以忽略。

所以,完整的投影方程是:s * [u; v; 1] = K * [R | t] * [X_w; Y_w; Z_w; 1]。这里的 s 是一个任意的尺度因子(就是 Z_c)。标定的任务,就是通过已知的若干组[X_w, Y_w, Z_w](标定板角点的世界坐标)和它们对应的[u, v](图像像素坐标),来求解出相机的内参矩阵 K 和多组外参[R|t]

2.3 透镜的“不完美”:畸变模型

针孔模型是理想的,但真实的相机镜头由于光学设计、制造和组装工艺,会引入畸变,导致直线在图像中变弯。主要畸变有两种:

  • 径向畸变:由透镜形状缺陷引起,成像点沿径向方向偏离理想位置。它又分为:
    • 桶形畸变:图像边缘向内弯曲,像通过一个球面看东西。
    • 枕形畸变:图像边缘向外弯曲。 通常用多项式模型来校正:x_corrected = x * (1 + k1*r^2 + k2*r^4 + k3*r^6),y 方向同理。r^2 = x^2 + y^2。k1, k2, k3 是径向畸变系数。
  • 切向畸变:由透镜安装与成像平面不平行引起。校正公式为:x_corrected = x + [2*p1*x*y + p2*(r^2+2*x^2)],y 方向有对应项。p1, p2 是切向畸变系数。

因此,完整的相机标定,其输出参数包括:

  • 内参(Intrinsics): fx, fy, cx, cy, (s)。
  • 畸变系数(Distortion Coefficients): k1, k2, p1, p2, (k3, ...)。
  • 外参(Extrinsics): 对于每组标定图像,对应一组旋转向量(Rodrigues旋转向量,3x1,紧凑表示)和平移向量 t。

3. 实操准备:工具、标定板与拍摄技巧

理论清楚了,我们开始准备实战。工欲善其事,必先利其器。

3.1 工具链选择:为什么是OpenCV?

对于绝大多数应用场景,OpenCV的相机标定模块是首选,甚至是事实上的工业标准。原因如下:

  • 成熟稳定:算法经过几十年迭代,鲁棒性和精度极高。
  • 开源免费:无需支付高昂的授权费用。
  • 接口丰富:支持棋盘格、圆网格、不对称圆网格等多种标定板。
  • 社区强大:遇到问题几乎都能找到解决方案。

我们将使用 OpenCV 的findChessboardCornerscornerSubPixcalibrateCamera等一系列函数来完成标定。语言上,Python(OpenCV-Python)和 C++ 皆可,本文示例以 Python 为主,因其上手更快。

3.2 标定板设计与制作:棋盘格的奥秘

OpenCV 最常用的是棋盘格标定板。它的角点(黑白方格相交的点)易于被算法精确检测。制作标定板有几个关键点:

  1. 方格数量:不宜太少也不宜太多。常见的有 9x6(内角点8x5)、7x10 等。角点太少,提供的约束方程不足;角点太多,边缘角点检测容易不准确,且标定板需要做得很大或相机分辨率要很高。我推荐从9x6开始。
  2. 方格尺寸:这是唯一需要知道的物理尺寸。你需要精确测量一个方格的实际边长(例如 25.0 mm)。这个值的测量精度直接影响到标定结果中平移向量 t 的尺度(即真实世界的大小)。对于 fx, fy 等内参的相对值影响不大,但如果你需要做真实的测距,这个值必须测准。
  3. 打印质量:必须使用高精度打印机(如激光打印机)打印在平整、刚性的材料上(如亚克力板、铝板)。贴在泡沫板或软质材料上会因弯曲引入误差。确保黑白对比度高,边缘清晰。
  4. 平整度:这是最重要的要求。标定板必须绝对平整。你可以将打印好的纸贴在厚重的玻璃或专业的光学平晶上。我踩过最大的坑就是用了便宜的泡沫板,夏天受热轻微变形,导致标定结果在不同温度下重复性极差。

实操心得:可以去网上下载一个 PDF 格式的棋盘格,但务必确认打印时选择了“实际大小”或“100%缩放”。更好的方法是自己用代码(如 OpenCV 的drawChessboardCorners反向生成)或 Illustrator 等矢量绘图软件生成,确保像素级精确。

3.3 图像采集:决定标定质量的“七分功夫”

标定图像的质量比算法本身更重要。采集时请遵循以下原则:

  1. 数量要够:至少15-20 张有效图像。太少会导致标定欠拟合,太多(超过50张)可能收益不大且增加计算量。
  2. 姿态要丰富:这是核心中的核心。你需要让标定板以各种不同的角度和位置出现在画面中。
    • 覆盖整个视野:标定板应出现在图像的左上、右上、左下、右下、中心等各个区域。
    • 姿态多样化:既要有关注度较高的“正视”角度(标定板大致平行于成像平面),也要有大幅度的倾斜、旋转和俯仰。特别是要包含一些“极端”姿态,比如标定板几乎与光轴平行(此时板子看起来像一条窄带),这对于约束焦距和畸变参数至关重要。很多人标定结果不好,就是因为姿态太单一。
    • 距离变化:拍摄一些近景和远景,让标定板在图像中占据不同的大小(例如从占满画面的1/3到占满整个画面)。
  3. 对焦要清晰:使用相机的手动对焦模式,确保标定板图案清晰。自动对焦可能导致不同图片焦距轻微变化,引入误差。
  4. 光照要均匀:避免强烈的反光、阴影或局部过曝/过暗。均匀的漫射光是最理想的。
  5. 保持静止:拍摄时,相机和标定板应保持稳定,避免模糊。

一个高效的采集流程:固定相机,手持标定板在镜头前缓慢移动、旋转,同时连续拍摄。这样可以快速获得大量不同姿态的图像。事后剔除模糊、对焦不准或姿态重复的图片。

4. 标定流程步步拆解:代码与解读

假设我们已经准备好了20张高质量的棋盘格图像(calib_01.jpg...calib_20.jpg),方格尺寸为 25.0 mm。下面我们一步步用代码实现标定。

4.1 步骤一:提取角点坐标

首先,我们需要从每张图像中找到棋盘格角点的像素坐标

import numpy as np import cv2 import glob # 定义棋盘格的尺寸(内角点数量,即行数-1,列数-1) pattern_size = (8, 5) # 对应9x6的棋盘格,内角点是8行5列 # 准备物体点(世界坐标系中的3D点) # 假设棋盘格在Z=0的平面上,X和Y坐标就是方格索引乘以方格尺寸 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) objp *= 25.0 # 乘以方格的物理尺寸(单位:mm) # 用于存储所有图像的对象点和图像点 objpoints = [] # 3D点(世界坐标系) imgpoints = [] # 2D点(图像像素坐标系) # 获取所有标定图像路径 images = glob.glob('./calibration_images/*.jpg') 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) # 角点亚像素精细化 - 这是提升精度的关键一步! criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners_refined = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) imgpoints.append(corners_refined) # 可视化(可选) cv2.drawChessboardCorners(img, pattern_size, corners_refined, ret) cv2.imshow('Corners Found', img) cv2.waitKey(500) # 显示500毫秒 else: print(f"未在 {fname} 中找到角点,已跳过。") cv2.destroyAllWindows() print(f"成功处理了 {len(objpoints)} 张图像。")

关键点解读

  • findChessboardCorners:这个函数使用基于梯度的算法初步定位角点。ret为True表示成功找到。
  • cornerSubPix这一步至关重要。初步找到的角点是整数像素位置。cornerSubPix算法通过迭代,寻找局部的最大梯度交汇点,可以将角点定位精度提升到亚像素级别(如0.1像素),极大提高标定精度。参数(11,11)定义了搜索窗口大小。
  • objp:我们定义了标定板的世界坐标。这里假设棋盘格在Z=0平面上,这是一个常见的设定。标定求解出的平移向量 t 就是相机相对于这个棋盘格坐标原点的位置。

4.2 步骤二:执行标定计算

有了所有图像对应的3D-2D点对,就可以调用核心的标定函数了。

# 获取图像尺寸(以第一张图像为准) img_shape = gray.shape[::-1] # (width, height) 注意OpenCV是(width, height) # 执行相机标定 ret, camera_matrix, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera( objpoints, imgpoints, img_shape, None, None ) print("标定是否成功:", ret) print("\n相机内参矩阵 K:") print(camera_matrix) print("\n畸变系数 (k1, k2, p1, p2, k3):") print(dist_coeffs.ravel()) # 通常输出顺序是 [k1, k2, p1, p2, k3]

函数输出解析

  • ret:重投影误差的RMS(均方根)值。这是衡量标定精度的核心指标。单位是像素。这个值越小越好,对于普通USB相机,小于0.5像素可以认为是很好的结果;工业相机或经过精心采集的图像,可以做到0.1-0.3像素。如果大于1像素,就需要检查图像质量或标定板姿态了。
  • camera_matrix:3x3的内参矩阵 K。
  • dist_coeffs:畸变系数向量,默认包含5个参数[k1, k2, p1, p2, k3]。有时OpenCV会根据标志位返回4或8个系数。
  • rvecs,tvecs:每张标定图像对应的旋转向量和平移向量(外参)。注意,这里的平移向量 t 的单位与你提供的objp单位一致(本例中是mm)。

4.3 步骤三:评估标定结果与可视化

标定完了不能只看内参,必须进行评估和验证。

1. 计算每张图的重投影误差:

mean_error = 0 for i in range(len(objpoints)): # 使用标定得到的参数,将世界坐标点重投影到图像上 imgpoints_reprojected, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], camera_matrix, dist_coeffs) # 计算原始图像点与重投影点之间的误差 error = cv2.norm(imgpoints[i], imgpoints_reprojected, cv2.NORM_L2) / len(imgpoints_reprojected) mean_error += error print(f"图像 {i} 的重投影误差: {error:.3f} 像素") print(f"\n总体平均重投影误差: {mean_error/len(objpoints):.3f} 像素")

逐张检查误差,如果某张图的误差显著高于其他图(例如2倍以上),可以考虑将其从数据集中剔除,重新标定,这往往能提升整体精度。

2. 可视化畸变校正效果:

# 选取一张图像进行校正测试 test_img = cv2.imread('./calibration_images/calib_01.jpg') h, w = test_img.shape[:2] # 获取最优的新相机矩阵(可以选择性地裁剪掉校正后图像的黑边) new_camera_matrix, roi = cv2.getOptimalNewCameraMatrix(camera_matrix, dist_coeffs, (w, h), 1, (w, h)) # 方法1:使用 undistort 函数 undistorted_img = cv2.undistort(test_img, camera_matrix, dist_coeffs, None, new_camera_matrix) # 方法2:使用 remap 函数(对于视频流等需要实时处理的场景,先计算映射,后复用) # mapx, mapy = cv2.initUndistortRectifyMap(camera_matrix, dist_coeffs, None, new_camera_matrix, (w,h), cv2.CV_32FC1) # undistorted_img = cv2.remap(test_img, mapx, mapy, cv2.INTER_LINEAR) # 裁剪黑边(根据roi) # x, y, w_new, h_new = roi # undistorted_img = undistorted_img[y:y+h_new, x:x+w_new] # 并排显示 combined = np.hstack((test_img, undistorted_img)) cv2.imshow('Original vs Undistorted', combined) cv2.waitKey(0) cv2.destroyAllWindows()

观察校正后的图像,原本弯曲的直线(如标定板边缘、房间墙角线)应该变得笔直。这是检验畸变系数是否准确的最直观方法。

5. 参数解读、保存与应用

5.1 内参矩阵与畸变系数的意义

拿到camera_matrixdist_coeffs后,我们需要理解每个数字的含义。

假设输出为:

K = [[ 1.02331415e+03, 0.00000000e+00, 9.59503534e+02], [ 0.00000000e+00, 1.02098391e+03, 5.44923336e+02], [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]] Dist = [ -0.25680112, 0.09123415, -0.00051234, 0.00089412, -0.01845678]
  • fx=1023.3, fy=1020.98:两者接近,说明像素接近正方形。如果相差较大,可能是相机传感器像素非正方形,或标定板姿态不够丰富导致的误差。
  • cx=959.5, cy=544.9:主点坐标。图像分辨率若是1920x1080,中心点是(960, 540)。我们的结果与之非常接近,这是合理的。
  • 畸变系数k1=-0.2568为负值,表示存在桶形畸变(广角镜头常见)。k2=0.0912为正,用于修正高阶畸变。p1, p2非常小,说明切向畸变很小。k3通常用于描述更边缘的畸变,值较小。

5.2 保存与加载标定参数

标定参数需要保存下来供后续程序使用。推荐使用yamljson格式。

import yaml import json # 保存为YAML data = { 'camera_matrix': camera_matrix.tolist(), 'dist_coeffs': dist_coeffs.tolist(), 'image_width': img_shape[0], 'image_height': img_shape[1] } with open('camera_calibration.yaml', 'w') as f: yaml.dump(data, f, default_flow_style=False) # 保存为JSON with open('camera_calibration.json', 'w') as f: json.dump(data, f, indent=4) print("标定参数已保存。") # 加载示例 with open('camera_calibration.yaml', 'r') as f: loaded_data = yaml.safe_load(f) K_loaded = np.array(loaded_data['camera_matrix']) D_loaded = np.array(loaded_data['dist_coeffs'])

5.3 应用:图像去畸变与像素坐标反投影

去畸变:上面已经演示过cv2.undistort。这是最直接的应用,确保后续视觉算法处理的是无畸变的“理想”图像。

像素坐标反投影到射线方向:这是许多三维视觉算法的第一步。给定一个像素坐标[u, v],如何得到它在相机坐标系下的方向向量

def pixel_to_ray(u, v, camera_matrix, dist_coeffs=None): """ 将像素坐标转换为相机坐标系下的归一化射线方向向量。 如果提供畸变系数,会先进行去畸变。 """ # 1. 将像素坐标转换为归一化平面坐标(去畸变后) pts_pixel = np.array([[[u, v]]], dtype=np.float32) if dist_coeffs is not None: # 去畸变 pts_undistorted = cv2.undistortPoints(pts_pixel, camera_matrix, dist_coeffs, P=camera_matrix) else: pts_undistorted = cv2.undistortPoints(pts_pixel, camera_matrix, None, P=camera_matrix) x, y = pts_undistorted.ravel() # 2. 归一化平面坐标 (x, y) 对应的是 (X_c / Z_c, Y_c / Z_c) # 因此,在相机坐标系下,该像素对应的射线方向向量为 [x, y, 1](需要归一化) ray_dir = np.array([x, y, 1.0]) ray_dir /= np.linalg.norm(ray_dir) # 归一化为单位向量 return ray_dir # 示例:将图像中心点反投影 u_center, v_center = camera_matrix[0, 2], camera_matrix[1, 2] ray = pixel_to_ray(u_center, v_center, camera_matrix, dist_coeffs) print(f"图像中心点对应的射线方向向量: {ray}")

这个方向向量就是光心指向该像素对应的三维空间点的射线。有了它,结合双目视觉或深度传感器提供的深度信息,就能计算出真实的三维坐标。

6. 常见问题、避坑指南与高阶技巧

在实际项目中,你会遇到各种各样的问题。下面是我总结的一些典型坑点和解决方案。

6.1 标定结果不稳定或误差大

这是最常见的问题。请按以下清单排查:

  1. 标定板平整度:再强调一次,这是头号杀手。用手感觉不出来,但足以让重投影误差增加好几倍。务必使用刚性底板。
  2. 角点检测不准:检查findChessboardCorners的返回值,确保每张图都成功检测。对于光照不均或模糊的图片,可以尝试调整findChessboardCornersCALIB_CB_ADAPTIVE_THRESH等标志位,或先对图像进行直方图均衡化。
  3. 姿态多样性不足:回顾你的标定图像集。如果大多数图片都是标定板在画面中央且近乎正对相机,那标定结果,特别是焦距和畸变参数,会非常不可靠。必须包含大角度倾斜的图片
  4. 标定板方格尺寸错误:用游标卡尺精确测量,并确认代码中objp *= square_sizesquare_size单位与你测量的一致(mm或cm)。
  5. 图像数量不足或质量差:剔除模糊、对焦不准、有反光的图片。确保有效图片在15张以上。

6.2 内参矩阵中的“异常值”

  • fx 和 fy 相差很大:如果相差超过5%,首先检查标定板姿态是否缺乏绕X轴或Y轴的旋转。如果姿态没问题,那可能是相机传感器像素确实是矩形(非正方形),这是正常的。
  • cx, cy 远离图像中心:如果偏离中心很多(例如超过图像尺寸的10%),很可能是标定板在画面中的分布严重不均(例如全在右侧),导致优化算法“认为”主点偏了。确保标定板覆盖整个画面。
  • 畸变系数 k1, k2 绝对值过大(例如 > 1):这通常意味着标定失败。检查角点提取是否正确,或者标定板是否平整。也可能是镜头畸变过于严重,超出了OpenCV默认畸变模型的描述范围(可以考虑使用更高阶的模型,如cv2.CALIB_RATIONAL_MODEL标志)。

6.3 实时标定与在线标定

对于嵌入式系统或需要长期运行的应用,相机参数可能会因温度、机械应力等发生缓慢漂移。这时可以考虑在线标定策略:

  • 基于自然特征的标定:在场景中放置已知尺寸的物体(不一定是棋盘格),利用SLAM或SfM(运动恢复结构)技术,在运行过程中同时估计相机位姿和内参。这需要更复杂的算法,如Bundle Adjustment。
  • 基于 vanishing point(消失点)的标定:利用场景中的平行线(如建筑物边缘)在图像中交汇于消失点的特性,来约束相机内参。这种方法不需要特定标定物,但精度通常低于棋盘格标定。

6.4 双目与雷达-相机标定的前置步骤

本文是“基础(一)”,专注于单目内参标定。但它是所有更高级标定的前提:

  • 双目相机标定:在分别完成左右相机的单目标定(获得各自的内参和畸变)后,还需要进行立体标定,以计算左右相机之间的旋转矩阵 R 和平移向量 T(即双目系统的外参)。OpenCV 提供stereoCalibrate函数。
  • 雷达与相机联合标定:同样,必须先获得精确的相机内参。然后,通过寻找雷达点云和相机图像中对应同一物理点的特征(例如标定板的角点或特定靶标的中心),来求解雷达坐标系到相机坐标系的变换矩阵。这是一个更复杂的问题,通常需要特制的、能被两种传感器同时清晰观测的联合标定板。

6.5 标定参数的验证:不要只信一次结果

一个可靠的标定流程,必须包含验证环节。方法如下:

  1. 留出验证集:采集图像时,预留3-5张不参与标定。
  2. 计算验证误差:使用标定得到的参数,计算验证集图像的重投影误差。这个误差应该与训练集(用于标定的图像集)的误差处于同一量级。如果验证误差显著更大,说明标定过程可能存在过拟合,或者图像集本身的一致性有问题。
  3. 实地测试:用标定好的相机去测量已知尺寸的物体(例如一把尺子),看看测得的距离是否准确。这是最直接的验收方式。

相机标定是一个实践性极强的技术。最好的学习方式就是动手做一遍,分析结果,遇到问题,再回头理解理论。当你成功地将重投影误差降到0.3像素以下,并且看到畸变校正后横平竖直的世界时,你会对这个将物理世界与数字图像连接起来的数学模型,有更深的理解和掌控感。这份理解,是构建任何可靠视觉系统的第一块,也是最坚实的一块基石。