作为一名计算机视觉工程师,我经常需要处理镜头畸变带来的各种问题。上周团队新到的鱼眼镜头拍出来的建筑照片全都变成了"哈哈镜"效果,直线全变成了曲线。今天就带大家完整走一遍图像去畸变的实战流程,包含Matlab和Python两种实现方式。
镜头畸变主要分为两种类型:径向畸变和切向畸变。径向畸变会使图像呈现"水波纹"效果,表现为图像边缘的直线向内(桶形畸变)或向外(枕形畸变)弯曲;切向畸变则像是把图像放在平行四边形中拉扯,通常是由于镜头安装时与成像平面不平行造成的。要校正这些畸变,我们需要先通过相机标定获取相机的内参矩阵和畸变系数。
棋盘格是最常用的标定工具,因为它规则的黑白方格图案便于计算机自动检测角点。根据我的经验,标定效果与以下几个因素密切相关:
提示:如果条件有限,可以使用OpenCV自带的标定图像数据集,但实际拍摄的效果通常更好。
在标定过程中,我们需要定义棋盘格的三维世界坐标。通常将棋盘格平面设为Z=0平面,X和Y轴沿着棋盘格方向。每个角点的坐标可以表示为(0,0,0)、(1,0,0)、(2,0,0)...(6,8,0)这样的形式。
python复制import numpy as np
# 创建世界坐标系中的角点坐标 (7x9棋盘格)
objp = np.zeros((7*9,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:9].T.reshape(-1,2) * square_size # square_size为棋盘格实际尺寸(mm)
Matlab的Computer Vision Toolbox提供了完整的相机标定流程。下面代码展示了如何自动检测棋盘格角点并进行标定:
matlab复制% 创建图像数据存储
images = imageDatastore('calibration_photos/');
% 检测棋盘格角点
[imagePoints, boardSize] = detectCheckerboardPoints(images.Files);
% 生成世界坐标 (假设棋盘格方格边长30mm)
squareSize = 30;
worldPoints = generateCheckerboardPoints(boardSize, squareSize);
% 执行相机标定
params = estimateCameraParameters(imagePoints, worldPoints);
% 分析标定结果
showReprojectionErrors(params); % 显示重投影误差
showExtrinsics(params); % 显示相机外参
标定完成后,params对象包含以下关键参数:
对于更灵活的解决方案,我推荐使用Python+OpenCV组合。下面是完整的标定代码:
python复制import cv2
import numpy as np
import glob
# 准备世界坐标点
chessboard_size = (7,9)
square_size = 30 # mm
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
# 存储3D和2D点
objpoints = [] # 世界坐标系中的3D点
imgpoints = [] # 图像坐标系中的2D点
# 读取标定图像
images = glob.glob('calibration_*.jpg')
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 查找棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, chessboard_size,
cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_NORMALIZE_IMAGE)
if ret:
# 亚像素级精确化
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria)
objpoints.append(objp)
imgpoints.append(corners2)
# 相机标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None)
OpenCV使用的畸变模型包含5个参数:(k1,k2,p1,p2,k3)
畸变校正公式为:
x' = x(1 + k1r² + k2r⁴ + k3r⁶) + 2p1xy + p2(r² + 2x²)
y' = y(1 + k1r² + k2r⁴ + k3r⁶) + p1(r² + 2y²) + 2p2xy
其中r² = x² + y²,(x,y)是归一化图像坐标。
matlab复制% 读取待校正图像
srcImg = imread('distorted_image.jpg');
% 去畸变
undistortedImg = undistortImage(srcImg, params);
% 显示结果
figure;
imshowpair(srcImg, undistortedImg, 'montage');
title('原始图像(左) vs 去畸变图像(右)');
python复制# 读取图像
img = cv2.imread('distorted.jpg')
# 去畸变
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h))
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
# 裁剪图像
x, y, w, h = roi
dst = dst[y:y+h, x:x+w]
# 可视化对比
import matplotlib.pyplot as plt
plt.figure(figsize=(12,6))
plt.subplot(121), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('原始图像')
plt.subplot(122), plt.imshow(cv2.cvtColor(dst, cv2.COLOR_BGR2RGB)), plt.title('去畸变图像')
plt.show()
标定完成后,我们需要评估标定质量:
python复制# 计算重投影误差
mean_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2)/len(imgpoints2)
mean_error += error
print(f"总重投影误差: {mean_error/len(objpoints):.3f} 像素")
print(f"内参矩阵:\n{mtx}")
print(f"畸变系数: {dist.ravel()}")
良好的标定结果重投影误差通常小于0.5像素。如果误差过大,可能需要检查标定图像质量或增加标定图像数量。
棋盘格角点检测失败
标定误差过大
去畸变后图像出现黑边
鱼眼镜头的特殊处理
对于鱼眼镜头,需要使用不同的畸变模型:
python复制# 鱼眼镜头标定
K = np.zeros((3,3))
D = np.zeros((4,1))
rms, _, _, _, _ = cv2.fisheye.calibrate(
objpoints, imgpoints, gray.shape[::-1], K, D,
flags=cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC)
多相机系统标定
对于立体视觉系统,可以使用stereoCalibrate函数:
python复制ret, _, _, _, _, R, T, E, F = cv2.stereoCalibrate(
objpoints, imgpoints1, imgpoints2, mtx1, dist1, mtx2, dist2,
image_size)
在线标定优化
对于需要持续优化的场景,可以实现增量式标定:
python复制# 初始化标定器
calibrator = cv2.CalibrateCamera()
# 增量添加标定图像
for img in calibration_images:
ret, corners = find_chessboard(img)
if ret:
calibrator.add(imgpoints, objpoints)
# 获取最终标定结果
ret, mtx, dist = calibrator.calibrate()
在实际工程应用中,我们需要考虑算法的实时性和资源消耗。以下是一些优化建议:
GPU加速
OpenCV的CUDA模块可以显著加速去畸变过程:
python复制gpu_img = cv2.cuda_GpuMat()
gpu_img.upload(img)
gpu_undist = cv2.cuda.undistort(gpu_img, mtx, dist)
dst = gpu_undist.download()
查找表(LUT)优化
对于固定参数的相机,可以预计算畸变映射表:
python复制map1, map2 = cv2.initUndistortRectifyMap(mtx, dist, None, newcameramtx, (w,h), cv2.CV_32FC1)
dst = cv2.remap(img, map1, map2, cv2.INTER_LINEAR)
移动端优化
在资源受限的设备上,可以考虑:
经过多年的实战,我发现良好的标定是计算机视觉应用的基石。特别是在SLAM、三维重建等应用中,精确的去畸变直接影响最终结果的准确性。建议定期检查相机标定参数,特别是当相机受到震动或温度变化较大时。