【摄像头标定】双目摄像头标定及矫正-opencv(python)

双目摄像头标定及矫正

    • 棋盘格标定板
    • 标定
    • 矫正

棋盘格标定板

本文使用棋盘格标定板,可以到这篇博客中下载:https://blog.csdn.net/qq_39330520/article/details/107864568
在这里插入图片描述

标定

要进行标定首先需要双目拍的棋盘格图片,20张左右,由于本文的双目摄像头嵌入在开发板底板中,并且使用的是ros进行开发,所以对于大部分人拍照这里是没有参考价值的,对于也是使用ros开发的小伙伴,需要写一个节点发布双目摄像头的图像数据,然后再写一个节点订阅双目摄像头数据进行拍照保存。本文重点也不在拍照,对于其他小伙伴可以直接搜索一些适用的拍照方法,只要能获得到图片即可。
左摄像头图片如下:
在这里插入图片描述
右摄像头图片如下:
在这里插入图片描述
由于摄像头底层代码有问题,所以图像很暗,但不影响标定。
标定代码如下:

import cv2
import os
import numpy as np
import itertools
import yaml# 定义文件夹路径
left_folder = "C:/new_pycharm_project/yolov10-main/shuangmu_left_pic"
right_folder = "C:/new_pycharm_project/yolov10-main/shuangmu_right_pic"# 获取图像文件列表并排序
left_images = sorted(os.listdir(left_folder))
right_images = sorted(os.listdir(right_folder))# 确保左右相机图像数量一致
assert len(left_images) == len(right_images), "左右相机图像数量不一致"# 加载两个摄像头图片文件夹并将里面的彩图转换为灰度图
def load_images(folder, images):img_list = []for img_name in images:img_path = os.path.join(folder, img_name)frame = cv2.imread(img_path)if frame is not None:gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)img_list.append((frame, gray))else:print(f"无法读取图像: {img_path}")return img_list# 检测棋盘格角点
def get_corners(imgs, pattern_size):corners = []for frame, gray in imgs:ret, c = cv2.findChessboardCorners(gray, pattern_size)     #ret 表示是否成功找到棋盘格角点,c 是一个数组,包含了检测到的角点的坐标if not ret:print("未能检测到棋盘格角点")continuec = cv2.cornerSubPix(gray, c, (5, 5), (-1, -1),(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))     #cv2.cornerSubPix 函数用于提高棋盘格角点的精确度,对初始检测到的角点坐标 c 进行优化corners.append(c)      #将优化后的角点坐标 c 添加到 corners 列表中# 绘制角点并显示vis = frame.copy()cv2.drawChessboardCorners(vis, pattern_size, c, ret)new_size = (1280, 800)resized_img = cv2.resize(vis, new_size)cv2.imshow('Corners', resized_img)cv2.waitKey(150)return corners# 相机标定
def calibrate_camera(object_points, corners, imgsize):cm_input = np.eye(3, dtype=np.float32)ret = cv2.calibrateCamera(object_points, corners, imgsize, cm_input, None)return retdef save_calibration_to_yaml(file_path, cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, R, T, E, F):data = {'camera_matrix_left': {'rows': 3,'cols': 3,'dt': 'd','data': cameraMatrix_l.flatten().tolist()},'dist_coeff_left': {'rows': 1,'cols': 5,'dt': 'd','data': distCoeffs_l.flatten().tolist()},'camera_matrix_right': {'rows': 3,'cols': 3,'dt': 'd','data': cameraMatrix_r.flatten().tolist()},'dist_coeff_right': {'rows': 1,'cols': 5,'dt': 'd','data': distCoeffs_r.flatten().tolist()},'R': {'rows': 3,'cols': 3,'dt': 'd','data': R.flatten().tolist()},'T': {'rows': 3,'cols': 1,'dt': 'd','data': T.flatten().tolist()},'E': {'rows': 3,'cols': 3,'dt': 'd','data': E.flatten().tolist()},'F': {'rows': 3,'cols': 3,'dt': 'd','data': F.flatten().tolist()}}with open(file_path, 'w') as file:yaml.dump(data, file, default_flow_style=False)print(f"Calibration parameters saved to {file_path}")img_left = load_images(left_folder, left_images)      #img_left是个列表,存放左摄像头所有的灰度图片。
img_right = load_images(right_folder, right_images)
pattern_size = (8, 5)
corners_left = get_corners(img_left, pattern_size)       #corners_left的长度表示检测到棋盘格角点的图像数量。corners_left[i] 和 corners_right[i] 中存储了第 i 张图像检测到的棋盘格角点的二维坐标。
corners_right = get_corners(img_right, pattern_size)
cv2.destroyAllWindows()# 断言,确保所有图像都检测到角点
assert len(corners_left) == len(img_left), "有图像未检测到左相机的角点"
assert len(corners_right) == len(img_right), "有图像未检测到右相机的角点"# 准备标定所需数据
points = np.zeros((8 * 5, 3), dtype=np.float32)   #创建40 行 3 列的零矩阵,用于存储棋盘格的三维坐标点。棋盘格的大小是 8 行 5 列,40 个角点。数据类型为 np.float32,这是一张图的,因为一个角点对应一个三维坐标
points[:, :2] = np.mgrid[0:8, 0:5].T.reshape(-1, 2) * 21  #给这些点赋予实际的物理坐标,* 21 是因为每个棋盘格的大小为 21mmobject_points = [points] * len(corners_left)     #包含了所有图像中棋盘格的三维物理坐标点 points。这里假设所有图像中棋盘格的物理坐标是相同的,因此用 points 复制 len(corners_left) 次。
imgsize = img_left[0][1].shape[::-1]     #img_left[0] 是左相机图像列表中的第一张图像。img_left[0][1] 是该图像的灰度图像。shape[::-1] 取灰度图像的宽度和高度,并反转顺序,以符合 calibrateCamera 函数的要求。print('开始左相机标定')
ret_l = calibrate_camera(object_points, corners_left, imgsize)    #object_points表示标定板上检测到的棋盘格角点的三维坐标;corners_left[i]表示棋盘格角点在图像中的二维坐标;imgsize表示图像大小
retval_l, cameraMatrix_l, distCoeffs_l, rvecs_l, tvecs_l = ret_l[:5]    #返回值里就包含了标定的参数print('开始右相机标定')
ret_r = calibrate_camera(object_points, corners_right, imgsize)
retval_r, cameraMatrix_r, distCoeffs_r, rvecs_r, tvecs_r = ret_r[:5]# 立体标定,得到左右相机的外参:旋转矩阵、平移矩阵、本质矩阵、基本矩阵
print('开始立体标定')
criteria_stereo = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-5)
ret_stereo = cv2.stereoCalibrate(object_points, corners_left, corners_right,cameraMatrix_l, distCoeffs_l,cameraMatrix_r, distCoeffs_r,imgsize, criteria=criteria_stereo,flags=cv2.CALIB_FIX_INTRINSIC)
ret, _, _, _, _, R, T, E, F = ret_stereo# 输出结果
print("左相机内参:\n", cameraMatrix_l)
print("左相机畸变系数:\n", distCoeffs_l)
print("右相机内参:\n", cameraMatrix_r)
print("右相机畸变系数:\n", distCoeffs_r)
print("旋转矩阵 R:\n", R)
print("平移向量 T:\n", T)
print("本质矩阵 E:\n", E)
print("基本矩阵 F:\n", F)
print("标定完成")# 保存标定结果
save_calibration_to_yaml('calibration_parameters.yaml', cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, R, T, E, F)# 计算重投影误差
def compute_reprojection_errors(objpoints, imgpoints, rvecs, tvecs, mtx, dist):total_error = 0total_points = 0for 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)total_error += errortotal_points += len(imgpoints2)mean_error = total_error / total_pointsreturn mean_error# 计算并打印左相机和右相机的重投影误差
print("左相机重投影误差: ", compute_reprojection_errors(object_points, corners_left, rvecs_l, tvecs_l, cameraMatrix_l, distCoeffs_l))
print("右相机重投影误差: ", compute_reprojection_errors(object_points, corners_right, rvecs_r, tvecs_r, cameraMatrix_r, distCoeffs_r))# 立体矫正和显示
def stereo_rectify_and_display(img_l, img_r, cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, R, T):img_size = img_l.shape[:2][::-1]# 立体校正R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, img_size, R, T)map1x, map1y = cv2.initUndistortRectifyMap(cameraMatrix_l, distCoeffs_l, R1, P1, img_size, cv2.CV_32FC1)map2x, map2y = cv2.initUndistortRectifyMap(cameraMatrix_r, distCoeffs_r, R2, P2, img_size, cv2.CV_32FC1)# 图像矫正rectified_img_l = cv2.remap(img_l, map1x, map1y, cv2.INTER_LINEAR)rectified_img_r = cv2.remap(img_r, map2x, map2y, cv2.INTER_LINEAR)# 显示矫正后的图像combined_img = np.hstack((rectified_img_l, rectified_img_r))cv2.imshow('Rectified Images', combined_img)cv2.imwrite("stereo_jiaozheng.png",combined_img)cv2.waitKey(0)cv2.destroyAllWindows()# 加载并矫正示例图像
example_idx = 0
img_l = img_left[example_idx][0]
img_r = img_right[example_idx][0]
stereo_rectify_and_display(img_l, img_r, cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, R, T)

标定完成后会显示一张矫正后的图像。代码重要的地方都给出了注释,主要流程就是分别对左右相机进行标定,然后对两个相机进行联合标定(立体标定),最后得到的参数会保存到yaml文件中:

---
camera_matrix_left:rows: 3cols: 3dt: ddata:- 531.7200210313852- 0- 642.0170539101581- 0- 533.6471323984354- 420.4033045027399- 0- 0- 1
dist_coeff_left:rows: 1cols: 5dt: ddata:- -0.1670007968198256- 0.04560028196221921- 0.0011938487550718078- -0.000866537907860316- -0.00805042100882671
camera_matrix_right:rows: 3cols: 3dt: ddata:- 525.9058345430292- 0- 628.7761214904813- 0- 528.2078922687268- 381.8575789135264- 0- 0- 1
dist_coeff_right:rows: 1cols: 5dt: ddata:- -0.15320688387351564- 0.03439886104586617- -0.0003732170677440928- -0.0024909528446780153- -0.005138400994014348
R:rows: 3cols: 3dt: ddata:- 0.9999847004116569- -0.00041406631566505544- 0.005516112008926496- 0.0003183979929468572- 0.9998497209492369- 0.017333036100216304- -0.005522460079247196- -0.017331014592906722- 0.9998345554979852
T:rows: 3cols: 1dt: ddata:- -55.849260376265015- 2.1715925432988743- 0.46949841441903933
E:rows: 3cols: 3dt: ddata:- -0.012142020481601675- -0.5070637607007459- 2.1630954322858496- 0.1610659204031652- -0.9681187500627653- 55.84261022903612- -2.189341611238282- -55.83996821910631- -0.9800159939787676
F:rows: 3cols: 3dt: ddata:- -2.4239149875305048e-8- -0.0000010085973649868748- 0.0027356495714066175- 3.2013501988129346e-7- -0.0000019172863951399893- 0.05961765359743852- -0.002405523166325036- -0.057046539240958545- 1

分别是左相机的内参矩阵、畸变系数,右相机的内参矩阵和畸变系数,两个相机之间的旋转矩阵、平移矩阵、本质矩阵、基本矩阵。

矫正

import cv2
import yaml
import numpy as np# 定义函数读取标定数据
def read_calibration_data(calibration_file):with open(calibration_file, 'r') as f:calib_data = yaml.safe_load(f)cameraMatrix_l = np.array(calib_data['camera_matrix_left']['data']).reshape(3, 3)distCoeffs_l = np.array(calib_data['dist_coeff_left']['data'])cameraMatrix_r = np.array(calib_data['camera_matrix_right']['data']).reshape(3, 3)distCoeffs_r = np.array(calib_data['dist_coeff_right']['data'])R = np.array(calib_data['R']['data']).reshape(3, 3)T = np.array(calib_data['T']['data']).reshape(3, 1)return cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, R, T# 定义函数对图像进行矫正
def rectify_images(left_image_path, right_image_path, calibration_file):# 读取标定数据cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, R, T = read_calibration_data(calibration_file)# 读取左右图像img_left = cv2.imread(left_image_path)img_right = cv2.imread(right_image_path)# 获取图像尺寸(假设左右图像尺寸相同)img_size = img_left.shape[:2][::-1]# 立体校正R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify(cameraMatrix_l, distCoeffs_l,cameraMatrix_r, distCoeffs_r,img_size, R, T)# 计算映射参数map1_l, map2_l = cv2.initUndistortRectifyMap(cameraMatrix_l, distCoeffs_l, R1, P1, img_size, cv2.CV_32FC1)map1_r, map2_r = cv2.initUndistortRectifyMap(cameraMatrix_r, distCoeffs_r, R2, P2, img_size, cv2.CV_32FC1)# 应用映射并显示结果rectified_img_l = cv2.remap(img_left, map1_l, map2_l, cv2.INTER_LINEAR)rectified_img_r = cv2.remap(img_right, map1_r, map2_r, cv2.INTER_LINEAR)# 合并图像显示combined_img = np.hstack((rectified_img_l, rectified_img_r))cv2.imshow('Rectified Images', combined_img)cv2.waitKey(0)cv2.destroyAllWindows()# 设置路径和文件名
left_image_path = "C:/new_pycharm_project/yolov10-main/shuangmu_left_pic/left_image0.png"
right_image_path = "C:/new_pycharm_project/yolov10-main/shuangmu_right_pic/right_image0.png"
calibration_file = "C:/new_pycharm_project/yolov10-main/calibration_parameters.yaml"# 调用函数进行图像矫正
rectify_images(left_image_path, right_image_path, calibration_file)

结果对比:
在这里插入图片描述
在这里插入图片描述
第一张是矫正前的左右相机图像,第二张是矫正后的。可以看到去除了畸变,并且两图像基本出于同一水平线。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/860339.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

企业微信内嵌H5项目接入聊天功能

产品需求是,在列表中把符合条件的列表接入聊天功能,以下是详细步骤: 1.引入企业微信 <script src"https://res.wx.qq.com/wwopen/js/jsapi/jweixin-1.0.0.js"></script> 2.获取wx签名(必须要) /*** 获取wx签名**/ export function getWxJsApi(data) {r…

pdf压缩,pdf压缩在线,pdf文件太大怎么变小

在数字化时代&#xff0c;PDF文档因其跨平台、保持原样、易于阅读和打印等特点&#xff0c;成为了我们日常工作和生活中不可或缺的一部分。然而&#xff0c;随着PDF文件的不断累积&#xff0c;存储空间逐渐变得紧张&#xff0c;特别是在处理大量大型PDF文件时&#xff0c;如何有…

若依前后端分离项目整合shardingjdbc分表(详细,分片字段订单id)

文章目录 1. 引入Maven依赖2.引入配置文件3.兼容之前的数据库源,使用现在的sharding数据库源&#xff08;shardingjdbc默认的数据源&#xff09;&#xff0c;但是配置好文件之后是没有生效的&#xff0c;需要加配置文件覆盖4. 检测是否成功5. 如何使用&#xff0c;在需要使用的…

qt开发-14_QListwidget 仿qq好友列表制作

QListWidget 继承 QListView。QListWidget 类提供了一个基于项的列表小部件。QListWidg et 是一个便捷的类&#xff0c;它提供了一个类似于 QListView&#xff08;下一小节将讲到&#xff09;提供的列表视图&#xff0c;但 是提供了一个用于添加和删除项目的基于项目的经典接口…

第10章 启动过程组 (启动过程组的重点工作)

第10章 启动过程组 10.3启动过程组的重点工作&#xff0c;在第三版教材第362~364页&#xff1b; 文字图片音频方式 第一个知识点&#xff1a;项目启动会议 1、作用 标志着对项目经理责权的定义结果的正式公布&#xff0c;通常由项目经理负责组织和召开。2、目的 使项目各…

分享:Wordpress插件-AI Image Pro v2.6.0中文版语言包

AI Image Pro是一个wordpress插件,可将您的 WordPress 网站直接与最佳图像生成 A模型(DALL.E和稳定扩散)集成&#xff0c;并允许您利用 AI 生成图像、编辑(内画)图像和创建图像变体。最重要的是&#xff0c;它配备了许多一键式滤镜和许多微调选项,您可以使用它们来增强和创建令…

黑匣子问题:大语言模型的内部工作原理

像 GPT-3 这样的大型语言模型 (LLM) 已经展示了令人印象深刻的自然语言能力&#xff0c;但它们的内部工作原理仍然知之甚少。这种“黑匣子”性质使得ChatGPT在敏感的现实应用程序中部署时可能会出现问题。 什么是LLM黑匣子问题&#xff1f; 语言学习模型 (LLM) 是强大的工具&…

要求全国70%中医院设置康复科!康复科门诊服务这样建设!

近日&#xff0c;国家中医药管理局印发《国家中医药管理局关于进一步加强中医医院康复科建设的通知》提出&#xff0c;中医医院应根据当地人口规模及中医药康复服务需求设置康复科并作为独立科室进行建设和管理。有条件的二级以上中医医院应当按照《中医医院康复科推荐配置标准…

软件质量保证与测试

目录 一、测试流程 二、测试用例 2.1概念 2.2用例编写格式 三、设计测试点 3.1等价类 3.1.1概念 3.1.2案例 3.1.3适用场景 3.1.4执行用例 3.2边界值 3.2.1概念 3.2.2案例 3.2.3使用场景 3.3判定表 3.3.1判定表使用原因 3.3.2概念 3.3.3案例 3.3.4使用场景 …

Day4: 两两交换链表中的节点 24 删除链表的倒数第N个节点 19 链表相交 02.07 环形链表II 142

题目24. 两两交换链表中的节点 - 力扣&#xff08;LeetCode&#xff09; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* …

新鲜出炉的信息化一机两用方案

在信息化日益发展的今天&#xff0c;网络安全问题愈发凸显其重要性。尤其是在政府和企事业单位中&#xff0c;如何在保证业务流畅和工作效率的同时&#xff0c;确保信息高安全性&#xff0c;成为了一个亟待解决的问题。而“一机两用”政策&#xff0c;正是针对这一需求而提出的…

QT布局管理(分割窗口QSplitter类、停靠窗口QDockWidget类、堆栈窗体QStackedWidget类、基本布局QLayout)

此片文章简单介绍布局管理的使用方法。通过实例先分别介绍分隔窗口QSplitter类、停靠窗口QDockWidget类及QStackedWidget类的使用&#xff0c;最后再通过一个实例介绍QLayout的使用。 分割窗口QSplitter类 分隔窗口可以灵活地布局窗口&#xff0c;可以用在文件资源管理器地窗…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 任务积分优化问题(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 📎在线评测链接 任务积分优化问题(100分) 🌍 评测功能需要 订阅专栏 后私信…

python数据可视化:在带有子图的绘图中添加总标题 matplotlib.pyplot.suptitle()

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 python数据可视化&#xff1a; 在带有子图的绘 图中 添加总标题 matplotlib.pyplot.suptitle() 请问关于以下代码表述正确的选项是&#xff1f; import matplotlib.pyplot as plt fig, (ax…

LiveMedia视频汇聚平台的设备管理功能

LiveMedia视频汇聚平台的设备管理功能是实现视频资源有效管理和控制的关键组成部分。以下是设备管理功能的详细介绍&#xff1a; 设备接入与管理&#xff1a; 设备添加与编辑&#xff1a;平台支持添加、编辑与删除设备&#xff0c;可编辑的信息包括设备接入的协议类型、服务节…

中小学校共用电脑通过安当SLA产品配置实现开机控制

中小学校公用电脑实现电脑开机控制的必要性主要体现在以下几个方面&#xff1a; 1. 增强安全性&#xff1a; 公用电脑由于使用频繁&#xff0c;容易被未经授权的用户访问&#xff0c;可能存在数据泄露或恶意软件植入的风险。通过实现电脑开机控制&#xff0c;学校可以确保只有…

游戏AI的创造思路-技术基础-深度学习(4)

下面的内容是让AI进行左右互博&#xff0c;这就是传说中的GAN对抗网络 当然&#xff0c;周伯通和GAN真的是难兄难弟&#xff0c;欲练神功&#xff0c;结果被黄药师&#xff08;欺骗&#xff09;坑了 目录 3.4. 生成对抗网络&#xff08;GAN&#xff09; 3.4.1. 定义 3.4.2.…

ThinkPad 进入BIOS推荐方法ThinkPad(ThinkCentre , ThinkStation)

ThinkPad 进入BIOS推荐方法ThinkPad&#xff08;ThinkCentre &#xff0c; ThinkStation&#xff09; 打开系统电源。在启动过程中&#xff0c;按Lenovo &#xff0c; ThinkPad &#xff0c; ThinkStation或ThinkCentre徽标上的F1 。 下图显示了示例BIOS屏幕。 注意&#xff…

PHPMailer发送的中文内容乱码如何解决

一&#xff1a; PHPMailer sdk 文件中有个设置默认编码的位置&#xff1a; vendor/phpmailer/phpmailer/src/PHPMailer.php 二&#xff1a; 实际业务代码中&#xff1a; require /sdk/PHPMailer/vendor/autoload.php;$mail new PHPMailer(true);try {//Server settings$mai…

免费!AI视频一键转绘,​哎哟不错哦~

前段时间给大家介绍过StreamV2V&#xff0c;它基于一种神奇的扩散模型&#xff0c;实现了视频到视频的一键转绘。今天带来StreamV2V视频一键转绘整合包&#xff0c;我只能“说哎哟不错哦”~ StreamV2V简介 你正在看一个视频&#xff0c;突然间&#xff0c;视频中的人物换了张脸…