1. 相机标定与图像畸变校正
任务目标
在Jetson平台上,使用棋盘格标定板对USB相机完成内参标定,获取相机矩阵与畸变系数,并利用标定结果对实时图像进行畸变校正,消除镜头引起的桶形/枕形畸变。
环境与依赖
- 硬件:Jetson Nano/Xavier NX/Orin系列开发板 + USB相机(或CSI相机,需对应驱动)
- 系统:Ubuntu 18.04/20.04(JetPack 4.x/5.x)
- Python库:
opencv-python(含cv2)、numpy、glob(标准库)
安装命令:pip3 install opencv-python numpy
实现方案
完整流程分为三步:采集标定图像 → 计算内参与畸变系数 → 加载参数并实时校正。以下代码将三步整合为两个可独立运行的脚本,先运行采集脚本生成标定数据,再运行标定与校正脚本完成闭环。
代码实现
第一步:采集棋盘格图像(保存至 calib_images/ 目录)
import cv2
import os
os.makedirs('calib_images', exist_ok=True)
cap = cv2.VideoCapture(0) # 0为默认相机,按需调整
count = 0
while True:
ret, frame = cap.read()
if not ret:
break
cv2.putText(frame, f'Captured: {count}/20 (SPACE to save, ESC to exit)',
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
cv2.imshow('Capture', frame)
key = cv2.waitKey(1) & 0xFF
if key == ord(' '):
cv2.imwrite(f'calib_images/img_{count:02d}.jpg', frame)
count += 1
print(f'Saved {count}/20')
elif key == 27: # ESC
break
if count >= 20:
break
cap.release()
cv2.destroyAllWindows()
print('Capture complete.')
第二步:标定并校正(核心完整代码)
import cv2
import numpy as np
import glob
# ========== 配置 ==========
CHESSBOARD_SIZE = (9, 6) # 内角点数,需与棋盘格实际规格一致
SQUARE_SIZE = 0.025 # 棋盘格方格边长(米),用于尺度恢复
CALIB_DIR = 'calib_images/'
# ========== 1. 提取角点 ==========
objp = np.zeros((CHESSBOARD_SIZE[0] * CHESSBOARD_SIZE[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:CHESSBOARD_SIZE[0], 0:CHESSBOARD_SIZE[1]].T.reshape(-1, 2) * SQUARE_SIZE
objpoints = [] # 世界坐标系中的3D点
imgpoints = [] # 图像中的2D点
images = glob.glob(CALIB_DIR + '*.jpg')
assert len(images) >= 5, f'至少需要5张有效标定图,当前仅{len(images)}张'
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, CHESSBOARD_SIZE, None)
if ret:
objpoints.append(objp)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1),
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))
imgpoints.append(corners2)
cv2.drawChessboardCorners(img, CHESSBOARD_SIZE, corners2, ret)
cv2.imshow('Detected', img)
cv2.waitKey(100)
cv2.destroyAllWindows()
print(f'成功提取角点的图像数: {len(objpoints)}/{len(images)}')
# ========== 2. 计算内参与畸变系数 ==========
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
print('相机矩阵 (mtx):\n', mtx)
print('畸变系数 (dist):\n', dist)
print('重投影误差:', ret)
# 保存标定结果
np.savez('calib_params.npz', mtx=mtx, dist=dist)
# ========== 3. 畸变校正(单张图像验证) ==========
test_img = cv2.imread(images[0])
h, w = test_img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
# 方法一:undistort(简单直接)
dst = cv2.undistort(test_img, mtx, dist, None, newcameramtx)
# 方法二:remap(适用于实时视频流,预计算映射表)
mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w, h), cv2.CV_32FC1)
dst_remap = cv2.remap(test_img, mapx, mapy, cv2.INTER_LINEAR)
# 裁剪ROI区域(去除黑色边框)
x, y, w_roi, h_roi = roi
dst_cropped = dst[y:y+h_roi, x:x+w_roi]
# 拼接对比图
compare = np.hstack((test_img, dst))
cv2.imwrite('calibration_result.jpg', compare)
cv2.imshow('Before | After', compare)
cv2.waitKey(0)
cv2.destroyAllWindows()
print('校正结果已保存为 calibration_result.jpg')
运行与验证
- 采集图像:运行第一步脚本,手持棋盘格在不同角度、距离(约30–80cm)下拍摄,确保棋盘格完整出现在画面中且光线均匀。按空格键保存,共采集15–20张后按ESC退出。
- 执行标定:运行第二步脚本。终端会打印相机矩阵、畸变系数和重投影误差。
- 判定通过标准:
- 角点检测阶段:成功提取角点的图像数 ≥ 5张(建议≥15张以保证精度);
- 重投影误差 < 0.5像素(若大于1.0,需重新采集更多角度图像);
- 弹出对比图中,棋盘格直线边缘由弯曲变为笔直,图像四周的拉伸变形明显消除。
实时视频流校正(可选扩展)
若需对实时视频流校正,只需在读取每帧后调用 cv2.remap(frame, mapx, mapy, cv2.INTER_LINEAR) 即可,映射表只需计算一次,性能开销极小。
常见问题
- 角点检测失败:确认棋盘格内角点数设置正确(如9×6表示内角点,而非格子数);避免强反光或阴影遮挡。
- 标定结果偏差大:图像数量不足或拍摄角度过于单一(如全部正对相机),应包含倾斜、旋转、远近不同姿态。
- 校正后图像边缘有黑边:使用
getOptimalNewCameraMatrix的ROI裁剪即可去除。