上一篇我们讲述了如何用python生成出棋盘格,接下来我们来讲讲如何用这个棋盘格进行相机标定。
相机标定要解决的核心问题就是:把三维世界中的点投影到图像像素坐标系时,获取其中的“成像变换”。一个相机从理想的小孔模型出发,像素点的位置主要由两部分决定,一是相机自身的“成像比例与主点位置”等内部属性(内参),二是镜头带来的非线性弯曲(畸变)。当相机姿态变化,还会引入相机相对于标定板的旋转和平移(外参)。
棋盘格之所以常用,是因为它提供了大量几何结构规则、角点易检测的特征点。我们知道这些角点在真实世界的平面坐标(例如方格边长是20mm,则角点网络坐标可以用(i*s,j*s,0)表示,i,j就是横竖的方格位置,s就是方格固定的边长),同时又能在图像上检测出对应的像素坐标,通过多张不同姿态的棋盘格照片建立“世界点—-像素点”的匹配,OpenCV就可以求解出相机内参矩阵K和畸变系数dist,并给出每张图对应的外参(rvec,tvec)。
其中K通常写作[[fx,0,cx],[0,fy,cy],[0,0,1]],fx,fy表示水平方向/竖直方向的等效焦距(以像素为单位),cx,cy是主点(通常接近图像中心),而畸变dist常见为5个或更多参数,最常用的模型包含径向畸变k1,k2,k3(越往边缘越“鼓”)和切向畸变p1,p2(镜头装配不完美导致的斜切变形)。
当你完成标定获取如上的数据后,你就可以将这些参数用于后续的三维重建、测距、SLAM、姿态估计等任务。
下面是我提供的一个OpenCV标定脚本,需要你先采集同一张棋盘格在不同角度、不同位置、不同距离下大约15~30张图片,要保证画面中棋盘格覆盖不同区域(尤其是四角边缘),避免每张都居中且姿态单一。图片要清晰,不能过曝,角点要可见(不能倾斜到角点检测失败)。运行脚本后,程序会输出相机内参矩阵、畸变参数,并给出重投影误差(越小越好,通常0.2~0.8像素属于比较常见的范围,取决于分辨率与拍摄质量)。
import os, glob
import numpy as np
import cv2
# ========= 你需要修改的参数 =========
images_dir = "./calib_images"
pattern = "*.jpg" # 或 *.png
inner_cols = 9 # 内角点列数
inner_rows = 6 # 内角点行数
square_size = 20.0 # 方格边长(单位自定:mm 或 m,但要一致)
# ===================================
chessboard_size = (inner_cols, inner_rows)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-6)
# 世界坐标系下的棋盘格角点(Z=0 平面)
objp = np.zeros((inner_rows * inner_cols, 3), np.float32)
objp[:, :2] = np.mgrid[0:inner_cols, 0:inner_rows].T.reshape(-1, 2)
objp *= square_size
objpoints, imgpoints = [], []
image_paths = sorted(glob.glob(os.path.join(images_dir, pattern)))
if not image_paths:
raise FileNotFoundError(f"找不到图片:{images_dir}/{pattern}")
img_size = None
used = 0
for p in image_paths:
img = cv2.imread(p)
if img is None:
continue
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
if img_size is None:
img_size = (gray.shape[1], gray.shape[0]) # (w,h)
found, corners = cv2.findChessboardCorners(gray, chessboard_size)
if not found:
print(f"[跳过] 未检测到角点: {os.path.basename(p)}")
continue
corners = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
objpoints.append(objp)
imgpoints.append(corners)
used += 1
print(f"\n总图片: {len(image_paths)},有效用于标定: {used}")
if used < 8:
print("提示:有效图片偏少,建议 15~30 张,且棋盘覆盖画面各区域(尤其四角)。")
# 标定:返回 RMS 重投影误差、相机内参 K、畸变参数 dist、每张图外参 rvec/tvec
ret, K, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, img_size, None, None
)
print("\n===== 标定结果 =====")
print("图像尺寸 (w,h):", img_size)
print("RMS 重投影误差 ret:", ret)
print("\n内参矩阵 K:\n", K)
print("\n畸变参数 dist (k1,k2,p1,p2,k3[,...]):\n", dist.ravel())
# 计算“平均每点重投影误差”(像素),比 ret 更直观
total_sq = 0.0
total_n = 0
for i in range(used):
projected, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], K, dist)
diff = imgpoints[i].reshape(-1, 2) - projected.reshape(-1, 2)
total_sq += np.sum(diff ** 2)
total_n += len(projected)
mean_error = np.sqrt(total_sq / total_n)
print("\n平均每点重投影误差(像素):", mean_error)
# 保存结果(npz 简单实用)
np.savez("camera_calib.npz",
camera_matrix=K,
dist_coeffs=dist,
image_size=np.array(img_size),
rms=np.array(ret),
mean_reproj_error=np.array(mean_error))
print("\n已保存: camera_calib.npz")
这是角点检测成功的图:

参考文献:
https://docs.opencv.org/master/dc/dbb/tutorial_py_calibration.html