新闻详情

新闻详情

首页 / 资讯中心 / 详情

Python+OpenCV相机标定实战:单双目内参、畸变系数与极线校正

发布时间:2026/10/2 4:04:21来源:尧图网络
Python+OpenCV相机标定实战:单双目内参、畸变系数与极线校正
简介这份资源面向计算机、人工智能、通信、物联网等专业的在校学生与教师提供基于Python和OpenCV的单目与双目相机标定完整源码可用于课程设计、毕业设计、大作业或项目立项演示也适合零基础入门与进阶学习。压缩包共8个文件以4个py脚本为核心分别实现单目标定与双目立体标定流程另附2个txt与2个md说明文档交代运行环境、依赖与使用注意事项整体约25KB轻量易部署。目前已有436人学习下载具备一定参考热度。代码经实际运行验证稳定可靠读者可据此理解相机内参、畸变系数、棋盘格角点检测及双目标定参数求解等关键环节并在此基础上二次开发扩展测距、三维重建等DIY功能。需注意解压后路径与项目名不要含中文建议重命名为英文再运行遇到问题可私信作者沟通。1. 从一张畸变棋盘格说起这套 Python OpenCV 标定源码到底能干什么很多人第一次做视觉项目都会遇到一个反直觉的现象明明用的是标称 6mm 焦距的工业镜头测出来的距离却总是差那么几个像素直线在画面边缘还会弯成弧线。这不是镜头坏了而是相机内参和畸变系数没标定。这套基于 Python 和 OpenCV 的相机标定程序解决的正是这个问题——它把单目相机标定和双目相机标定两套流程都封装好了输入一组棋盘格图片输出内参矩阵、畸变系数双目还额外给出旋转矩阵、平移向量和极线校正映射。适合正在做毕业设计、课程设计或者刚接手一个视觉测量、三维重建、双目测距项目的同学。你不需要从零推导张正友标定法的公式但需要知道每张棋盘格该拍成什么样、参数怎么设、结果怎么判断合不合格。下面我按实际拆包复现的顺序把这份源码的用法和坑一次讲清。2. 单目标定从棋盘格采集到内参矩阵输出单目相机标定是整个流程的地基。双目标定本质上是在两个单目各自标定完成后再求两个相机之间的相对位姿。所以先把单目跑通后面双目的结果才可信。这一章从采集、代码结构到参数含义把单目这条链路走完。2.1 棋盘格采集为什么你拍的图总是标定失败标定失败十有八九不是代码问题而是采集的图片不合格。OpenCV 的findChessboardCorners对棋盘格的完整性、清晰度、角度都有要求。常见做法是打印一张 9x6 或 11x8 的棋盘格贴在硬质平板上避免纸张弯曲。采集时注意几点棋盘格要占画面三分之一以上太小角点检测精度差至少覆盖画面四个角和中心区域只在中间拍十几张边缘畸变系数会估不准每张图棋盘格要有不同角度倾斜 20 到 45 度之间比较理想完全正对会让某些参数不可观测。我一般会拍 20 到 30 张宁多勿少因为标定函数会自动筛选但图太少一旦有几张检测失败就不够了。光照要均匀避免反光和阴影压在角点上。如果是双目左右相机要同时采集保证同一时刻的棋盘格位姿一致否则后面立体标定的外参就是错的。提示棋盘格内角点数量是 (列数-1) x (行数-1)。比如 10x7 的方格内角点是 9x6代码里 pattern_size 要填 (9,6)填错直接检测不到。2.2 源码结构与单目标定核心代码这份源码通常包含单目和双目两个入口脚本外加一个标定工具模块。目录结构大致是mono_calibration.py、stereo_calibration.py、calibration_utils.py以及存放图片的images/left、images/right文件夹。先看单目标定的核心逻辑下面这段是我按源码结构整理的可复现版本import cv2 import numpy as np import glob # 棋盘格内角点数按实际打印的格子减一填写 pattern_size (9, 6) # 方格实际物理尺寸单位毫米用于输出平移向量的真实尺度 square_size 25.0 # 生成棋盘格在世界坐标系下的三维点Z0平面 objp np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) objp * square_size objpoints [] # 世界坐标点 imgpoints [] # 图像像素点 images glob.glob(images/left/*.jpg) for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 查找角点第二个参数为亚像素优化窗口 ret, corners cv2.findChessboardCorners(gray, pattern_size, None) 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) print(内参矩阵:\n, mtx) print(畸变系数:, dist.ravel()) print(重投影误差:, ret)逻辑上分三步先构造棋盘格在世界坐标系下的理想三维点Z 全部为 0再对每张图检测角点并做亚像素优化把图像点和世界点一一对应最后交给calibrateCamera求解。参数里square_size决定平移向量的物理单位如果你只关心像素级精度可以填 1.0但要做真实测距就必须填实际毫米数。cornerSubPix的窗口 (11,11) 是常见值图像分辨率高可以适当加大。返回的ret是重投影误差单位像素一般要求小于 0.5超过 1.0 说明采集或角点检测有问题。2.3 内参矩阵和畸变系数怎么读标定输出的内参矩阵是三行三列形式如下参数含义典型判断fx, fy焦距的像素表示两者接近差异大说明像素非方形或标定异常cx, cy主点坐标应接近图像中心偏离过大要检查k1, k2, p1, p2, k3径向和切向畸变系数k1 通常为负绝对值过大说明镜头畸变强fx 和 fy 理论上应该相等实际会有几个像素差异属于正常。cx、cy 如果离图像中心超过图像宽高的 10%多半是棋盘格覆盖区域太偏。畸变系数里 k1 是主导项广角镜头 k1 绝对值可能到 -0.3 甚至更大普通镜头在 -0.1 左右。p1、p2 是切向畸变正常应该很小如果很大说明镜头和传感器不平行或者图片有问题。注意畸变系数只有在标定图像覆盖了画面边缘时才有意义。如果你只在画面中心拍k2、k3 会接近 0但拿去校正边缘图像就会翻车。3. 双目标定外参求解与极线校正的完整链路双目比单目多出来的部分是求两个相机之间的旋转和平移关系以及做极线校正让左右图像的对应点在同一水平线上。这一步做不好后面做双目测距或者三维重建时视差图会一塌糊涂。这一章把双目标定的输入要求、代码实现和结果验证讲透。3.1 双目采集的同步要求双目标定的前提是左右相机在同一时刻拍摄同一块棋盘格。如果两个相机是分开触发、手动按快门那基本没法用因为棋盘格在两次拍摄之间动了外参就错了。常见做法是用一根同步线连接两个相机做硬件触发或者用支持双目的采集卡。如果是 USB 相机至少要用软件触发尽量同时抓帧但精度会差一些。采集时棋盘格要同时完整出现在左右画面里且尽量覆盖两幅图的公共视野。左右相机各自的内参可以先用单目标定分别求出来也可以直接交给双目标定函数一起优化。我一般会先分别单目标定检查各自重投影误差合格后再跑双目标定这样出问题好定位是哪个相机的问题。3.2 双目标定核心代码与参数双目标定在单目的基础上多了左右角点配对和stereoCalibrate调用。下面这段是核心逻辑import cv2 import numpy as np import glob pattern_size (9, 6) square_size 25.0 objp np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) objp * square_size objpoints [] imgpoints_left [] imgpoints_right [] left_images sorted(glob.glob(images/left/*.jpg)) right_images sorted(glob.glob(images/right/*.jpg)) criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) for lf, rf in zip(left_images, right_images): imgL cv2.imread(lf) imgR cv2.imread(rf) grayL cv2.cvtColor(imgL, cv2.COLOR_BGR2GRAY) grayR cv2.cvtColor(imgR, cv2.COLOR_BGR2GRAY) retL, cornersL cv2.findChessboardCorners(grayL, pattern_size, None) retR, cornersR cv2.findChessboardCorners(grayR, pattern_size, None) # 只有左右都检测到才配对使用 if retL and retR: cornersL cv2.cornerSubPix(grayL, cornersL, (11, 11), (-1, -1), criteria) cornersR cv2.cornerSubPix(grayR, cornersR, (11, 11), (-1, -1), criteria) objpoints.append(objp) imgpoints_left.append(cornersL) imgpoints_right.append(cornersR) # 先分别单目标定得到初始内参 retL, mtxL, distL, _, _ cv2.calibrateCamera( objpoints, imgpoints_left, grayL.shape[::-1], None, None) retR, mtxR, distR, _, _ cv2.calibrateCamera( objpoints, imgpoints_right, grayR.shape[::-1], None, None) # 立体标定求解R和T ret, mtxL, distL, mtxR, distR, R, T, E, F cv2.stereoCalibrate( objpoints, imgpoints_left, imgpoints_right, mtxL, distL, mtxR, distR, grayL.shape[::-1], flagscv2.CALIB_FIX_INTRINSIC) print(旋转矩阵 R:\n, R) print(平移向量 T:\n, T.ravel()) print(立体标定重投影误差:, ret)关键点在stereoCalibrate的flags参数。CALIB_FIX_INTRINSIC表示固定前面单目标定得到的内参只优化外参适合内参已经标得比较准的情况。如果内参不确定可以用默认 flags 让函数一起优化但计算量大、对初值敏感。返回的 R 和 T 描述的是右相机相对于左相机的位姿T 的模长就是基线距离单位跟square_size一致。基线是双目测距的核心参数T 的数值要跟实际卷尺量的基线对比差太多说明标定有问题。3.3 极线校正与结果验证拿到 R 和 T 之后还要做极线校正让左右图像的极线水平对齐。OpenCV 提供stereoRectify和initUndistortRectifyMap两个函数完成这件事# 极线校正计算校正后的投影矩阵和映射表 R1, R2, P1, P2, Q, roi1, roi2 cv2.stereoRectify( mtxL, distL, mtxR, distR, grayL.shape[::-1], R, T, flagscv2.CALIB_ZERO_DISPARITY, alpha0) # 生成校正映射后续对每帧图像做remap mapLx, mapLy cv2.initUndistortRectifyMap( mtxL, distL, R1, P1, grayL.shape[::-1], cv2.CV_32FC1) mapRx, mapRy cv2.initUndistortRectifyMap( mtxR, distR, R2, P2, grayR.shape[::-1], cv2.CV_32FC1) # 对测试图做校正检查极线是否水平 imgL cv2.imread(images/left/1.jpg) imgR cv2.imread(images/right/1.jpg) rectL cv2.remap(imgL, mapLx, mapLy, cv2.INTER_LINEAR) rectR cv2.remap(imgR, mapRx, mapRy, cv2.INTER_LINEAR) cv2.imwrite(rect_left.jpg, rectL) cv2.imwrite(rect_right.jpg, rectR)alpha0表示校正后只保留有效像素边缘会被裁掉alpha1保留全部像素但会有黑边。Q是视差到三维的重投影矩阵后面做测距会用到。验证方法是把校正后的左右图并排画几条水平线看同一物体在左右图中的对应点是否在同一水平线上。如果偏差明显说明 R、T 不准或者采集时同步没做好。验证项合格标准不合格的常见原因单目重投影误差 0.5 像素棋盘格太小、角点模糊双目重投影误差 1.0 像素左右不同步、配对错误基线 T 模长与实测接近square_size 填错极线水平度对应点同一行R、T 求解异常4. 避坑与排查标定跑不通时先看这几条标定这件事代码往往不是瓶颈坑都在数据和参数上。下面这几条是我和身边人踩过的按现象、原因、解决整理遇到问题按顺序排查。4.1 角点检测返回 False现象是findChessboardCorners一直返回 False一张图都检测不到。原因通常是pattern_size填错把外角点数量当成了内角点或者棋盘格打印比例不对导致方格不是正方形。解决方法是先用一张清晰的正面图单独测试把pattern_size逐个试同时确认棋盘格是黑白相间、方格等大。如果图片有反光或者棋盘格边缘被裁掉也会检测失败换图即可。4.2 重投影误差过大现象是标定能跑完但ret超过 1.0 甚至好几。原因是采集图片质量差比如棋盘格倾斜角度太单一、画面模糊、角点被遮挡。解决方法是剔除误差贡献最大的那几张图重新采集覆盖更多角度和位置的图片。可以在代码里打印每张图的重投影误差把明显偏大的删掉再标。4.3 双目基线数值离谱现象是输出的 T 向量模长跟实际基线差几倍甚至符号相反。原因是左右图片没有按顺序配对或者square_size单位填错。解决方法是确认left_images和right_images排序后一一对应检查square_size是否跟实际方格尺寸一致。如果 T 的符号反了说明左右相机定义搞反了交换左右图重新标定。4.4 校正后图像黑边严重现象是极线校正后图像边缘出现大片黑边有效区域很小。原因是alpha0裁掉了无效像素加上畸变系数大校正后有效区域本来就小。解决方法是把alpha改成 1 保留全部像素或者调整stereoRectify的newImageSize参数放大输出尺寸。如果黑边大到影响使用说明镜头畸变太强考虑换镜头或者接受裁剪。4.5 换分辨率后结果失效现象是同一套相机换了分辨率重新标定内参完全对不上。原因是内参跟分辨率绑定fx、fy、cx、cy 都是像素单位分辨率变了必须重新标定。解决方法是固定项目使用的分辨率标定和后续处理保持一致。如果必须换分辨率可以按比例缩放内参作为初值但精度会下降最好重新标。5. 进阶技巧用 Q 矩阵做双目测距与精度验证标定的最终目的通常是测量。拿到校正映射和 Q 矩阵后就可以做双目测距了。这一步能反过来验证标定质量如果测距误差大说明前面标定有问题。下面这段是完整的测距验证流程import cv2 import numpy as np # 读取校正映射和Q矩阵前面标定保存下来的 # 这里假设已经通过np.save保存了mapLx等参数 imgL cv2.imread(rect_left.jpg) imgR cv2.imread(rect_right.jpg) grayL cv2.cvtColor(imgL, cv2.COLOR_BGR2GRAY) grayR cv2.cvtColor(imgR, cv2.COLOR_BGR2GRAY) # SGBM立体匹配参数需按图像调整 stereo cv2.StereoSGBM_create( minDisparity0, numDisparities128, # 必须是16的倍数 blockSize5, P18 * 3 * 5 ** 2, P232 * 3 * 5 ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32 ) disparity stereo.compute(grayL, grayR).astype(np.float32) / 16.0 # 用Q矩阵把视差图转成三维点云 points_3d cv2.reprojectImageTo3D(disparity, Q) # 取画面中心区域某点的深度做验证 h, w disparity.shape cx, cy w // 2, h // 2 depth points_3d[cy, cx, 2] print(中心点深度(mm):, depth)numDisparities决定视差搜索范围太小测不了近处太大会增加计算量一般设 64 或 128。blockSize是匹配块大小奇数越大越平滑但边缘越糊。reprojectImageTo3D用 Q 矩阵把每个像素的视差转成三维坐标Z 就是深度。验证方法是把棋盘格放在已知距离比如 500mm看输出的深度是否接近。如果误差在 1% 到 2% 以内说明标定合格误差超过 5%回去检查基线和内参。我一般会做一组不同距离的验证从 300mm 到 1500mm 各测一次画一条误差曲线。如果近距离准、远距离偏大多半是基线标小了如果整体偏移可能是主点不准。这套流程走下来标定结果到底能不能用心里就有数了。从那以后我每次标定完都强制走一遍已知距离验证不再只看重投影误差那个数字。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

STM32 OLED调试面板实战:I2C驱动与实时状态可视化 2026/10/2 7:28:57

STM32 OLED调试面板实战:I2C驱动与实时状态可视化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Windows CMD命令行实战手册:从文件操作到网络排查 2026/10/2 7:28:51

Windows CMD命令行实战手册:从文件操作到网络排查

我用CMD用了十几年,从XP时代一直到现在的Windows 11,这个黑底白字的窗口始终是我日常工作中绕不开的工具。很多人觉得都图形界面了,谁还稀罕命令行?但真到了排查网络、批量处理文件、清理系统盘、管理Windows服务这些场景&#xf…

阅读更多 →
吉大编译原理实验代码:可调试可延展的编译器工程脚手架 2026/10/2 7:28:44

吉大编译原理实验代码:可调试可延展的编译器工程脚手架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
PIC16F15355开发板UART实战:从MCC配置到串口通信排错全攻略 2026/10/2 7:28:44

PIC16F15355开发板UART实战:从MCC配置到串口通信排错全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
8kHz PWM频率在FOC电机控制中的物理意义与实现原理 2026/10/2 7:28:43

8kHz PWM频率在FOC电机控制中的物理意义与实现原理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
STM32F103裸机开发实战:从寄存器到USB设备的硬核入门 2026/10/2 7:28:43

STM32F103裸机开发实战:从寄存器到USB设备的硬核入门

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉