用手机和A4纸玩转相机标定:零成本实践指南
想象一下,你手里只有一部智能手机和一台普通打印机,却想探索计算机视觉中最基础的相机标定技术。这听起来像天方夜谭?事实上,这正是我三年前在宿舍里完成的第一个视觉项目。当时作为穷学生的我,用打印的棋盘格和手机摄像头,不仅理解了相机内参的奥秘,还做出了一个能测量桌面物体距离的小工具。今天,我就把这份"穷人版"标定秘籍完整分享给你。
1. 准备工作:你的标定实验室
1.1 硬件准备清单
- 智能手机:任何能拍摄清晰照片的机型都可,建议关闭自动美颜功能
- A4纸和打印机:用于打印棋盘格图案
- 硬纸板或书本:确保标定板平整不弯曲
- 照明环境:均匀的自然光或室内灯光,避免强烈反光
1.2 生成标准棋盘格
使用Python生成可打印的棋盘格图案:
import cv2 import numpy as np def generate_chessboard(rows=6, cols=9, square_size=25, filename="chessboard.png"): width = cols * square_size height = rows * square_size image = np.ones((height, width), dtype=np.uint8) * 255 for i in range(rows): for j in range(cols): if (i + j) % 2 == 0: start_x = j * square_size start_y = i * square_size image[start_y:start_y+square_size, start_x:start_x+square_size] = 0 cv2.imwrite(filename, image) return image # 生成6x9的棋盘格,每个方格25mm generate_chessboard()提示:实际打印后请用尺子测量方格尺寸,确保打印缩放比例准确
2. 拍摄技巧:获取高质量标定图像
2.1 拍摄姿势大全
- 多角度覆盖:从不同视角拍摄15-20张照片,包括:
- 棋盘格位于画面中心
- 棋盘格靠近四边和角落
- 棋盘格有明显倾斜角度(30°-60°)
- 距离变化:近景(占画面80%)和远景(占画面30%)都要包含
- 避免常见错误:
- 反光导致棋盘格过曝
- 棋盘格弯曲变形
- 手指遮挡部分图案
2.2 手机相机设置建议
| 设置项 | 推荐值 | 说明 |
|---|---|---|
| 分辨率 | 最高可用 | 确保角点检测精度 |
| 对焦模式 | 手动对焦 | 锁定在棋盘格平面 |
| ISO | 自动 | 避免图像噪点过多 |
| HDR | 关闭 | 防止自动图像处理干扰 |
3. Python实战:从图像到内参矩阵
3.1 角点检测全流程
import cv2 import glob import numpy as np # 准备对象点:假设棋盘格在3D空间中的Z坐标为0 objp = np.zeros((6*9,3), np.float32) objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2) * 25 # 25mm方格尺寸 # 存储所有图像的对象点和图像点 objpoints = [] # 3D点 imgpoints = [] # 2D点 images = glob.glob('calib_photos/*.jpg') for fname in images: img = cv2.imread(fname) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 查找角点 ret, corners = cv2.findChessboardCorners(gray, (9,6), None) if ret: objpoints.append(objp) # 亚像素级精确化 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria) imgpoints.append(corners2) # 可视化(可选) cv2.drawChessboardCorners(img, (9,6), corners2, ret) cv2.imshow('Corners', img) cv2.waitKey(500) cv2.destroyAllWindows()3.2 相机标定与结果解读
# 执行相机标定 ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera( objpoints, imgpoints, gray.shape[::-1], None, None) print("相机矩阵:\n", mtx) print("\n畸变系数:", dist.ravel()) # 评估重投影误差 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("\n平均重投影误差: {} 像素".format(mean_error/len(objpoints)))典型输出解析:
相机矩阵(mtx):fx,fy:焦距(像素单位),反映手机摄像头的放大能力cx,cy:主点坐标,通常接近图像中心
畸变系数(dist):k1,k2:径向畸变系数,桶形畸变为负值,枕形畸变为正值p1,p2:切向畸变系数,反映镜头安装偏差
重投影误差:理想值应小于0.5像素,大于1像素需检查拍摄质量
4. 趣味应用:打造手机测距仪
4.1 单目测距原理
利用已知物体尺寸(如棋盘格方格)和相机内参,可以估算物体到相机的距离。基本公式:
实际物体高度 = (焦距 × 物体像素高度 × 传感器高度) / (图像高度 × 物体实际高度)4.2 实战代码实现
def distance_to_camera(known_width, focal_length, per_width): """计算到相机的距离""" return (known_width * focal_length) / per_width # 假设已知棋盘格方格宽度为25mm square_width = 25 # mm focal_length = mtx[0,0] # 从相机矩阵获取焦距 # 检测当前图像中的棋盘格 ret, corners = cv2.findChessboardCorners(gray, (9,6), None) if ret: # 计算最左侧和最右侧角点的像素距离 leftmost = corners[0][0] rightmost = corners[8][0] pixel_width = abs(rightmost[0] - leftmost[0]) # 计算距离 distance = distance_to_camera(square_width * 8, focal_length, pixel_width) print(f"相机到棋盘格的距离: {distance:.1f} mm")4.3 精度提升技巧
- 多方格平均法:使用多个方格的平均尺寸减少误差
- 动态校准:在不同距离拍摄已知尺寸物体,建立误差补偿模型
- 姿态补偿:当棋盘格不平行于成像平面时,使用PNP算法修正
# 使用PNP算法进行精确位姿估计 ret, rvec, tvec = cv2.solvePnP(objp, corners2, mtx, dist) distance = np.linalg.norm(tvec) # 计算相机到棋盘格的距离 print(f"精确距离估计: {distance:.1f} mm")5. 进阶探索:从标定到AR应用
掌握了这些基础后,你可以尝试:
- 镜头质量评估:比较不同手机的畸变系数
- 简单AR标记:在棋盘格上叠加虚拟物体
- 自制扫描仪:用标定结果进行3D重建
- 视觉SLAM入门:理解相机运动估计的基础
注意:实际应用中,环境光照、标定板平整度等因素都会影响最终精度。建议在正式项目中使用专业标定板,本方法更适合教学和原型验证。