当前位置: 首页 > news >正文

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

前言

在机器视觉、三维重建、机器人定位、AR 测量等任务中,相机标定是一个非常基础但又很关键的步骤。
对于单目相机来说,标定的核心目标是求出相机的内部参数和畸变参数,从而把图像中的畸变影响尽可能消除。

本文使用 OpenCV 完成一次完整的单目相机标定流程,包括:

  • 棋盘格图片采集
  • 角点检测
  • 相机内参计算
  • 畸变参数求解
  • 图像去畸变
  • 重投影误差评估

一、单目相机标定是什么?

单目相机标定主要是求解相机的内参矩阵和畸变系数。

相机内参矩阵一般形式如下:

[ fx 0 cx ] [ 0 fy cy ] [ 0 0 1 ]

其中:

  • fx、fy:焦距在像素坐标系下的表示
  • cx、cy:主点坐标,通常接近图像中心
  • 畸变参数:用于描述镜头引起的径向畸变和切向畸变

常见畸变包括:

  • 桶形畸变
  • 枕形畸变
  • 切向畸变

二、准备标定图片

一般使用棋盘格标定板进行标定。拍摄时需要注意:

  1. 棋盘格要完整出现在图像中
  2. 图片数量建议 15 张以上
  3. 拍摄角度要丰富,不能全部正对相机
  4. 棋盘格应覆盖图像的不同区域
  5. 图片要清晰,避免运动模糊

假设我们的棋盘格内角点数量为:

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::vector<std::vector<cv::Point3f>> objectPoints; std::vector<std::vector<cv::Point2f>> imagePoints; std::vector<cv::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::vector<cv::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::vector<cv::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::vector<cv::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 ./CameraCalibration

Windows 下如果使用 Visual Studio,可以通过 CMake 生成工程,或者直接在 VS 中配置 OpenCV 的 include、lib 和 dll 路径。

C++ 版本和 Python 版本的整体流程是一样的,都是先检测棋盘格角点,再通过calibrateCamera()求解相机内参和畸变参数。实际项目中,C++ 版本更适合集成到实时检测、机器人视觉、工业相机采集等工程里。

七、常见问题

1. 找不到棋盘格角点

可能原因:

  • 棋盘格内角点数量写错
  • 图片过暗或反光严重
  • 棋盘格没有完整出现在画面中
  • 棋盘格边缘模糊

2. 标定误差很大

建议重新采集图片,保证棋盘格出现在图像中心、边缘、左上、右下等多个区域,并且包含不同倾斜角度。

3. 标定结果能否直接用于测距?

单目相机仅靠一张图片无法直接获得真实深度。
如果要进行测距,还需要结合已知尺寸、平面约束、深度估计模型,或者使用双目相机。

总结

本文介绍了单目相机标定的基本流程。整体步骤可以概括为:

  1. 准备棋盘格标定板
  2. 采集多张不同角度图片
  3. 检测棋盘格角点
  4. 使用cv2.calibrateCamera()求解内参和畸变参数
  5. 使用cv2.undistort()完成图像去畸变
  6. 通过重投影误差评估标定质量

单目相机标定虽然流程不复杂,但采集图片的质量会直接影响最终结果。实际项目中,建议多拍、多角度、少模糊,这样才能得到更稳定的标定参数。

http://www.jsqmd.com/news/1026963/

相关文章:

  • 海康威视iVMS-4200在银河麒麟系统部署全攻略:ARM/x86/龙芯架构适配与实战避坑
  • 2026年静力切割施工品牌官方甄选:西北地区专业加固公司实力对比 - 优质品牌商家
  • 海光异构卡dcu 64BW *2 ZeRO-2 异构卡2 16g*4 zero-3微调deepseekf1-qwen2-14b模型速度对比
  • 2026年当下广西比较好的干冰灭火器生产厂商有哪些?盘点与选型指南 - 品牌鉴赏官2026
  • 从零搭建CodeWarrior for StarCore/SDMA开发环境:编译、链接与模拟器调试全指南
  • 2026年太原面包培训推荐榜单:欧包/软欧包/日式面包/吐司/法棍/碱水面包/手工面包综合技能培训深度解析 - 品牌发掘
  • 2026年 江苏石墨换热器厂家推荐:石墨吸收器/盐酸石墨合成炉/石墨塔器,耐腐蚀性能与工艺精度标杆解析 - 品牌发掘
  • 2026年 雨水收集系统/模块/厂家TOP榜单:PP模块、海绵城市与市政道路雨水的专业实力与口碑推荐 - 品牌发掘
  • 企业级AI工作流编排实战:5大核心架构设计与实施策略
  • 2026年深圳保税区转厂报关服务商综合能力甄选指南 - 优质品牌商家
  • ExtractorSharp完全指南:零基础掌握游戏资源编辑与NPK文件处理
  • 深入理解Linux内核地址转换:从mynext变量剖析逻辑地址到物理地址映射
  • 2026年北京五粮液回收权威机构推荐指南:专业甄选与行业解析 - 优质品牌商家
  • 零基础PHP从零到一手写堆排序的庖丁解牛
  • Claude 旧模型退休后,接口迁移不要只改一个 model 字段
  • 2026税务申报服务机构甄选指南:跨境合规与本土化服务深度解析 - 优质品牌商家
  • 2026年江苏及周边地区方管租赁与盘扣脚手架服务商甄选指南 - 优质品牌商家
  • 清远漏水检测维修权威推荐:卫生间-厨房-阳台-屋顶天花板漏水维修:靠谱防水补漏公司团队TOP5推荐(2026最新深度调研实测榜单) - 即刻修防水
  • 电脑优化神器 WinTools.net v26.5.1 中文便携版下载和使用教程(系统清理 + 优化全能工具)
  • 从零搭建Java萌宠社交系统:WebSocket实时聊天+动态发布模块实现
  • Typst 0.15 版本发布:多维度升级,为学术与技术写作带来排版新变革!
  • 2026年云南省PMP培训机构哪家好?官方授权R.E.P.报考指南 - 众智商学院课程中心
  • C++命令模式与请求封装
  • K-means与Watershed图像分割实战:无监督、可解释、轻量级方案
  • MBA 论文查重反复标红?AI 写作工具一招双降重复率 + AI 疑似度
  • 嵌入式Hypervisor分区配置与系统健康监控实战指南
  • 山东防爆监控哪个品牌靠谱
  • 构建智能决策辅助系统:从Alpha因子挖掘到实战应用
  • 2026年GEO公司测评:五大服务商能力对比,为什么首推虎博科技?
  • C++ 内存模型详解