【OpenCV实战】单目相机标定:从棋盘格拍摄到畸变校正

【OpenCV实战】单目相机标定:从棋盘格拍摄到畸变校正 前言在机器视觉、三维重建、机器人定位、AR 测量等任务中相机标定是一个非常基础但又很关键的步骤。对于单目相机来说标定的核心目标是求出相机的内部参数和畸变参数从而把图像中的畸变影响尽可能消除。本文使用 OpenCV 完成一次完整的单目相机标定流程包括棋盘格图片采集角点检测相机内参计算畸变参数求解图像去畸变重投影误差评估一、单目相机标定是什么单目相机标定主要是求解相机的内参矩阵和畸变系数。相机内参矩阵一般形式如下[ fx 0 cx ] [ 0 fy cy ] [ 0 0 1 ]其中fx、fy焦距在像素坐标系下的表示cx、cy主点坐标通常接近图像中心畸变参数用于描述镜头引起的径向畸变和切向畸变常见畸变包括桶形畸变枕形畸变切向畸变二、准备标定图片一般使用棋盘格标定板进行标定。拍摄时需要注意棋盘格要完整出现在图像中图片数量建议 15 张以上拍摄角度要丰富不能全部正对相机棋盘格应覆盖图像的不同区域图片要清晰避免运动模糊假设我们的棋盘格内角点数量为9 x 6注意这里指的是“内角点数量”不是棋盘格方块数量。三、Python 标定代码安装依赖pip install opencv-python numpy完整代码如下import cv2 import numpy as np import glob # 棋盘格内角点数量 CHECKERBOARD (9, 6) # 世界坐标中的棋盘格点例如 (0,0,0), (1,0,0), ... objp np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) objp[:, :2] np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) # 如果知道每个格子的真实尺寸例如 25mm可以乘上实际尺寸 # objp * 25.0 objpoints [] # 3D 点 imgpoints [] # 2D 点 images glob.glob(calib_images/*.jpg) for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, CHECKERBOARD, 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, CHECKERBOARD, corners2, ret) cv2.imshow(corners, img) cv2.waitKey(300) cv2.destroyAllWindows() ret, camera_matrix, dist_coeffs, rvecs, tvecs cv2.calibrateCamera( objpoints, imgpoints, gray.shape[::-1], None, None ) print(标定 RMS 误差:, ret) print(相机内参矩阵:) print(camera_matrix) print(畸变系数:) print(dist_coeffs)四、图像去畸变得到相机内参和畸变系数后可以对原图进行去畸变处理img cv2.imread(test.jpg) h, w img.shape[:2] new_camera_matrix, roi cv2.getOptimalNewCameraMatrix( camera_matrix, dist_coeffs, (w, h), 1, (w, h) ) undistorted cv2.undistort( img, camera_matrix, dist_coeffs, None, new_camera_matrix ) cv2.imwrite(undistorted.jpg, undistorted)参数alpha的含义alpha 0裁剪黑边保留有效图像区域alpha 1保留更多原始视野但可能出现黑边五、计算重投影误差重投影误差可以用来评估标定结果是否可靠total_error 0 for i in range(len(objpoints)): imgpoints2, _ cv2.projectPoints( objpoints[i], rvecs[i], tvecs[i], camera_matrix, dist_coeffs ) error cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2) total_error error mean_error total_error / len(objpoints) print(平均重投影误差:, mean_error)一般来说重投影误差越小标定效果越好。实际项目中如果误差过大可以检查棋盘格角点是否检测准确、图片是否模糊、拍摄角度是否太单一。六、C 版本标定代码如果项目使用 C 开发也可以直接调用 OpenCV 的calibrateCamera()完成单目相机标定。1. C 完整代码#include opencv2/opencv.hpp #include iostream #include vector int main() { // 棋盘格内角点数量宽 9高 6 cv::Size boardSize(9, 6); // 每个棋盘格的实际尺寸单位可以是 mm float squareSize 25.0f; std::vectorstd::vectorcv::Point3f objectPoints; std::vectorstd::vectorcv::Point2f imagePoints; std::vectorcv::Point3f objp; for (int i 0; i boardSize.height; i) { for (int j 0; j boardSize.width; j) { objp.emplace_back(j * squareSize, i * squareSize, 0.0f); } } std::vectorcv::String images; cv::glob(calib_images/*.jpg, images); cv::Size imageSize; for (const auto file : images) { cv::Mat img cv::imread(file); if (img.empty()) { std::cout 读取失败: file std::endl; continue; } imageSize img.size(); cv::Mat gray; cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY); std::vectorcv::Point2f corners; bool found cv::findChessboardCorners(gray, boardSize, corners); if (found) { cv::cornerSubPix( gray, corners, cv::Size(11, 11), cv::Size(-1, -1), cv::TermCriteria( cv::TermCriteria::EPS cv::TermCriteria::MAX_ITER, 30, 0.001 ) ); objectPoints.push_back(objp); imagePoints.push_back(corners); cv::drawChessboardCorners(img, boardSize, corners, found); cv::imshow(corners, img); cv::waitKey(300); } } cv::destroyAllWindows(); cv::Mat cameraMatrix; cv::Mat distCoeffs; std::vectorcv::Mat rvecs, tvecs; double rms cv::calibrateCamera( objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs ); std::cout 标定 RMS 误差: rms std::endl; std::cout 相机内参矩阵:\n cameraMatrix std::endl; std::cout 畸变系数:\n distCoeffs std::endl; return 0; }2. C 图像去畸变cv::Mat img cv::imread(test.jpg); cv::Mat newCameraMatrix cv::getOptimalNewCameraMatrix( cameraMatrix, distCoeffs, img.size(), 1, img.size() ); cv::Mat undistorted; cv::undistort( img, undistorted, cameraMatrix, distCoeffs, newCameraMatrix ); cv::imwrite(undistorted.jpg, undistorted);3. CMakeLists.txt 示例cmake_minimum_required(VERSION 3.10) project(CameraCalibration) set(CMAKE_CXX_STANDARD 11) find_package(OpenCV REQUIRED) add_executable(CameraCalibration main.cpp) target_link_libraries(CameraCalibration ${OpenCV_LIBS})4. 编译运行mkdir build cd build cmake .. make ./CameraCalibrationWindows 下如果使用 Visual Studio可以通过 CMake 生成工程或者直接在 VS 中配置 OpenCV 的 include、lib 和 dll 路径。C 版本和 Python 版本的整体流程是一样的都是先检测棋盘格角点再通过calibrateCamera()求解相机内参和畸变参数。实际项目中C 版本更适合集成到实时检测、机器人视觉、工业相机采集等工程里。七、常见问题1. 找不到棋盘格角点可能原因棋盘格内角点数量写错图片过暗或反光严重棋盘格没有完整出现在画面中棋盘格边缘模糊2. 标定误差很大建议重新采集图片保证棋盘格出现在图像中心、边缘、左上、右下等多个区域并且包含不同倾斜角度。3. 标定结果能否直接用于测距单目相机仅靠一张图片无法直接获得真实深度。如果要进行测距还需要结合已知尺寸、平面约束、深度估计模型或者使用双目相机。总结本文介绍了单目相机标定的基本流程。整体步骤可以概括为准备棋盘格标定板采集多张不同角度图片检测棋盘格角点使用cv2.calibrateCamera()求解内参和畸变参数使用cv2.undistort()完成图像去畸变通过重投影误差评估标定质量单目相机标定虽然流程不复杂但采集图片的质量会直接影响最终结果。实际项目中建议多拍、多角度、少模糊这样才能得到更稳定的标定参数。