OpenCV下的单目标定,双目标定与立体校正(calibrateCamera, stereoCalibrate and stereoRectify)

OpenCV下的单目标定,双目标定与立体校正(calibrateCamera, stereoCalibrate and stereoRectify)

文章目录

  • 1. 杂话
  • 2. 单目标定
    • 2.1 先看代码
    • 2.2 一点解释
    • 2.3 calibrateCamera参数
  • 3. 双目标定
    • 3.1 先看代码
    • 3.2 stereoCalibrate参数
  • 4. 立体校正
    • 4.1 先看代码
    • 4.2 一点解释
    • 4.3 stereoRectify参数
    • 4.4 initUndistortRectifyMap参数
    • 4.5 remap参数
  • 5. 绘制极线
    • 5.1 先看代码
    • 5.2 一点解释
    • 5.3 校正结果

1. 杂话

 大伙儿应该都用过OpenCV和相机吧,所以今天咱们就来说说怎么使用两个相机拍摄的照片和OpenCV来进行标定和立体校正。相机标定的理论解释其实有很多啦,我就随便找两个写得不错的帖子给大家参考一下哈:

 fengye2two的帖子-标定

 卍卐没想到的帖子-标定

 瞻邈-立体校正

 总而言之,我就不那个班门弄斧关公面前耍大刀了,我就简单说说代码层面的实现。
 其中,标定部分的代码部分参考了:Temuge Batpurev’s Blog
 绘制极线部分的代码参考了:逆光525的帖子-绘制极线
 对了,我使用的数据和完整的代码在这里:
Repo : Calibrate-and-Rectify

2. 单目标定

2.1 先看代码

chessboard_size = (9, 6)
frame_size = (640, 480)

# 设置棋盘格点的世界坐标
objp = np.zeros((chessboard_size[0]*chessboard_size[1], 3), np.float32)
objp[:,:2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].T.reshape(-1, 2)
square_size = 1
objp *= square_size

# 用于存储世界坐标和图像坐标
objpoints = [] # 3d points in real world space
imgpoints_main = [] # 2d points in image plane
images = glob.glob('demo/left*.jpg')
images = sorted(images)
print(f"Found {len(images)} images for calibration")

for idx, image_file in enumerate(images):
    img = cv2.imread(image_file)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 寻找棋盘格角点
    ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)

    if ret == True:
        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_main.append(corners2)
        
    else:
        print(f"Chessboard corners not found in image: {image_file}")

# 相机标定
ret_main, mtx_main, dist_main, rvecs_main, tvecs_main = cv2.calibrateCamera(objpoints, imgpoints_main, frame_size, None, None)
mtx_main, roiL = cv2.getOptimalNewCameraMatrix(mtx_main, dist_main,frame_size, 0)

print(ret_main)

imgpoints_side = [] # 2d points in image plane.
images = glob.glob('demo/right*.jpg')
images = sorted(images)
print(f"Found {len(images)} images for calibration")

for idx, image_file in enumerate(images):
    img = cv2.imread(image_file)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # 寻找棋盘格角点
    ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)

    if ret == True:

        # 亚像素级角点精确化
        criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
        corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
        imgpoints_side.append(corners2)
        
    else:
        print(f"Chessboard corners not found in image: {image_file}")

# 相机标定
ret_side, mtx_side, dist_side, rvecs_side, tvecs_side = cv2.calibrateCamera(objpoints, imgpoints_side, frame_size, None, None)
mtx_side, roiL = cv2.getOptimalNewCameraMatrix(mtx_side, dist_side,frame_size, 0)

print(ret_side)

2.2 一点解释

 首先是这个objp,这玩意说白了就是世界坐标系的坐标,但是这个世界坐标系不是咱们可以自己定义嘛对吧。方便起见,我就直接假设标定板的平面就是x-y平面,所以上面所有的焦点的z坐标不久都是0了嘛对吧哈哈哈。
 然后就是calibrateCamera这个函数,具体的输入参数是都写在上面了,需要注意的是,一般来说返回的误差也就是rmse在0.5以下会比较好,如果很大的话,那么你的标定图像就需要好好调整一下了,比如光照好一点,加入更多的角度之类的。

2.3 calibrateCamera参数

    # 单目标定 calibrateCamera
    # ret, mtx, dist, rvecs, tvecs = 
    # cv2.calibrateCamera(objpoints, imgpoints, imageSize, cameraMatrix, distCoeffs)
    #  输入参数:
    # objpoints: 物体点坐标的列表。这些是 3D 世界坐标,通常是棋盘格的角点坐标。
    # imgpoints: 图像点坐标的列表。这些是 2D 图像坐标,通常是从图像中检测到的棋盘格角点的坐标。
    # imageSize: 图像的大小,格式为 (width, height)。
    # cameraMatrix (可选): 初始的相机内参矩阵。如果传入 None,则函数会计算一个初始值。
    # distCoeffs (可选): 初始的畸变系数。如果传入 None,则函数会计算一个初始值。
    # 输出参数:
    # ret: 平均重投影误差(root mean square error, RMSE),表示校准结果的精度。
    # mtx: 相机内参矩阵(camera matrix)。
    # dist: 畸变系数(distortion coefficients)。
    # rvecs: 旋转向量列表,表示每个视角的旋转。
    # tvecs: 平移向量列表,表示每个视角的平移。

3. 双目标定

3.1 先看代码

flags = cv2.CALIB_FIX_INTRINSIC
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 100, 1e-6)
ret, mtx_main, dist_main, mtx_side, dist_side, R, T, E, F = cv2.stereoCalibrate(
    objpoints, imgpoints_main, imgpoints_side,
    mtx_main, dist_main, mtx_side, dist_side,
    frame_size, criteria=criteria, flags=flags)

3.2 stereoCalibrate参数

    # 双目标定函数 
    # ret, cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, R, T, E, F = 
    # cv2.stereoCalibrate(objectPoints, imagePoints1, imagePoints2, 
    #                       cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, imageSize,
    #                       criteria=criteria, flags=flags)
    
    # 输入参数:
    # objectPoints: 物体点坐标的列表,类似 calibrateCamera 函数中的 objpoints。
    # imagePoints1: 左相机的图像点坐标列表。
    # imagePoints2: 右相机的图像点坐标列表。
    # cameraMatrix1: 左相机的初始内参矩阵。
    # distCoeffs1: 左相机的初始畸变系数。
    # cameraMatrix2: 右相机的初始内参矩阵。
    # distCoeffs2: 右相机的初始畸变系数。
    # imageSize: 图像的大小,格式为 (width, height)。
    
    # criteria (可选): 终止条件,用于优化算法的迭代过程。
        # cv2.TERM_CRITERIA_MAX_ITER:当达到最大迭代次数时停止。
        # cv2.TERM_CRITERIA_EPS:当参数变化小于设定的精度时停止。
        # 可以组合使用,例如cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS,表示当满足任意一个条件时停止。
        # 最大迭代次数:一个整数,指定最大迭代次数。例如,100表示最多迭代100次。
        # 精度阈值:一个浮点数,指定参数变化小于该值时停止迭代。例如,1e-5表示当参数变化小于0.00001时停止迭代。
    
    # flags (可选): 标志位,用于指定某些参数的固定或自由度。
        # cv2.CALIB_FIX_INTRINSIC:在标定过程中保持两个相机的内参数矩阵不变。这意味着在双目标定过程中不会重新估计每个相机的内参数矩阵(包括焦距、光轴中心等),而是使用单个相机标定结果中得到的内参数。
        # cv2.CALIB_USE_INTRINSIC_GUESS:使用传入的内参数作为初始猜测值,并在标定过程中对其进行优化。这对提高标定精度很有帮助,特别是在内参数已经较准确的情况下。
        # cv2.CALIB_FIX_PRINCIPAL_POINT:保持主点(光轴中心)固定不变。
        # cv2.CALIB_FIX_FOCAL_LENGTH:保持焦距不变。
        # cv2.CALIB_FIX_ASPECT_RATIO:保持焦距的长宽比不变。
        # cv2.CALIB_ZERO_TANGENT_DIST:假设切向畸变参数为零并保持不变。
        # cv2.CALIB_RATIONAL_MODEL:使标定函数使用一个带有6个畸变系数的合理模型。
        # cv2.CALIB_SAME_FOCAL_LENGTH:假设两个摄像头具有相同的焦距。
    
    # 输出参数:
    # ret: 平均重投影误差。
    # cameraMatrix1: 校准后的左相机内参矩阵。
    # distCoeffs1: 校准后的左相机畸变系数。
    # cameraMatrix2: 校准后的右相机内参矩阵。
    # distCoeffs2: 校准后的右相机畸变系数。
    # R: 旋转矩阵,将右相机坐标系转换到左相机坐标系。
    # T: 平移向量,将右相机坐标系转换到左相机坐标系。
    # E: 基础矩阵。
    # F: 本质矩阵。

4. 立体校正

4.1 先看代码

# 立体校正
# 立体校正
R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify(mtx_main, dist_main, mtx_side, dist_side, frame_size, R, T)

# 对测试图像进行校正
def rectify_image(img, mtx, dist, R, P):
    h, w = img.shape[:2]
    mapx, mapy = cv2.initUndistortRectifyMap(mtx, dist, R, P, (w, h), cv2.CV_32FC1)
    return cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)

# 读取测试图片
img_main = cv2.imread('demo/left03.jpg')
img_side = cv2.imread('demo/right03.jpg')

# 校正图像
rectified_main = rectify_image(img_main, mtx_main, dist_main, R1, P1)
rectified_side = rectify_image(img_side, mtx_side, dist_side, R2, P2)

# 保存校正后的图像
cv2.imwrite('rectified_main.png', rectified_main)
cv2.imwrite('rectified_side.png', rectified_side)

4.2 一点解释

 其实也没啥好解释的吧,可能就是需要说一下这个initUndistortRectifyMap和这个remap函数是啥子。简单来说哈,第一个函数就是根据相机自己的内参数和两个相机之间的外参数,生成一个映射来消除图片的畸变(为啥要消除可以看上面的理论贴)。第二个函数就是一个映射过程,根据上一个函数生成的映射执行这个映射过程,得到矫正之后的图片。具体的参数我在下面也解释一下吧。

4.3 stereoRectify参数

# R1, R2, P1, P2, Q, roi1, roi2 = 
# cv2.stereoRectify(cameraMatrix1, distCoeffs1, cameraMatrix2, distCoeffs2, imageSize, 
#                   R, T, flags=cv2.CALIB_ZERO_DISPARITY, alpha=0, newImageSize=(0, 0))
# 输入参数:
# cameraMatrix1: 左相机内参矩阵。
# distCoeffs1: 左相机畸变系数。
# cameraMatrix2: 右相机内参矩阵。
# distCoeffs2: 右相机畸变系数。
# imageSize: 图像的大小,格式为 (width, height)。
# R: 旋转矩阵,将右相机坐标系转换到左相机坐标系。
# T: 平移向量,将右相机坐标系转换到左相机坐标系。
# flags (可选): 校正类型标志。
# alpha (可选): 自由参数,范围为 [0, 1],决定图像边缘区域的裁剪程度。
# newImageSize (可选): 新图像的大小。
# 输出参数:
# R1: 左相机的校正变换(旋转矩阵)。
# R2: 右相机的校正变换(旋转矩阵)。
# P1: 左相机的新投影矩阵。
# P2: 右相机的新投影矩阵。
# Q: 视差-深度映射矩阵。
# roi1: 左相机图像的有效区域。
# roi2: 右相机图像的有效区域。

4.4 initUndistortRectifyMap参数

# 计算畸变和矫正的映射 initUndistortRectifyMap
# map1, map2 = 
# cv2.initUndistortRectifyMap(cameraMatrix, distCoeffs, R, newCameraMatrix, size, m1type)
# 输入参数:
# cameraMatrix: 相机内参矩阵。
# distCoeffs: 相机畸变系数。
# R: 校正变换(旋转矩阵)。
# newCameraMatrix: 新的相机矩阵。
# size: 图像的大小,格式为 (width, height)。
# m1type: 输出映射的类型,可以是 cv2.CV_32FC1 或 cv2.CV_16SC2 等。
# 输出参数:
# map1: 第一张映射表,用于 remap 函数。
# map2: 第二张映射表,用于 remap 函数。

4.5 remap参数

# 重映射函数 remap
# dst = cv2.remap(src, map1, map2, interpolation, borderMode, borderValue)
# 输入参数:
# src: 输入图像。
# map1: 第一张映射表。
# map2: 第二张映射表。
# interpolation: 插值方法,如 cv2.INTER_LINEAR 或 cv2.INTER_CUBIC。
# borderMode (可选): 边界模式,定义如何处理图像边界,如 cv2.BORDER_CONSTANT 或 cv2.BORDER_REPLICATE。
# borderValue (可选): 边界值,如果使用 cv2.BORDER_CONSTANT 时使用。
# 输出参数:
# dst: 校正后的图像。

5. 绘制极线

5.1 先看代码

plt.figure(figsize=(20, 20))

for i in range(0,1):  # 以第一对图片为例
    im_L=Image.fromarray(rectified_main) # numpy 转 image类
    im_R=Image.fromarray(rectified_side) # numpy 转 image 类

    width = im_L.size[0]*2
    height = im_L.size[1]

    img_compare = Image.new('RGBA',(width, height))
    img_compare.paste(im_L,box=(0,0))
    img_compare.paste(im_R,box=(640,0))
    
    #在已经极线对齐的图片上均匀画线
    for i in range(1,20):
        len=480/20
        plt.axhline(y=i*len, color='r', linestyle='-')
    plt.imshow(img_compare)
    plt.savefig('epipolar_lines.png', bbox_inches='tight', pad_inches=0)
    plt.show()

5.2 一点解释

 注意哈,因为有点怕麻烦,我这里不是去算了那个极线,然后画的线。我是直接画了水平线作为极线,然后手动去看在同一个水平线上的点是不是对应的。当然了对于标定图片,你也可以直接使用角点作为对应点,画一下极线来看一看。但是如果不是标定图片,可能就需要用SIFT关键点匹配了,这一点OpenCV官方有:官方教程

5.3 校正结果

在这里插入图片描述
 OK,基本符合预期哦~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Security 授权

基于request的授权 HttpSecurity 权限配置 Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(authorize -> {authorize// 放行请求:针对含有 admin 权限的用户放行 /user/get 接口.requestMatchers("/us…

训练营第十一天 | 150. 逆波兰表达式求值

150. 逆波兰表达式求值 做题思路 遇到操作符,出栈,从栈口取出俩元素;遇到数字,入栈 栈的应用场景:相邻元素的消除 逆波兰表达式:即后缀表达式 来自二叉树的后序遍历:左右中 代码细节 class …

有限元中弱形式的一些数学基础

有限元方法在求解PED时,一般先将控制方程转化为等效的若积分形式,本文试图总结一下这一过程的一些数学基础,本文主要从工程的角度出发和理解,不探讨严谨的数学证明过程。 PDE强形式 强形式是PDE及其边界条件的原始形式。求解强…

Java巅峰之路---基础篇---综合练习(面向对象)

目录 文字版格斗游戏 基础版 souf输出语句 进阶版 键盘录入的说明 复杂对象数组练习 需求: 添加和遍历 删除和遍历 修改和遍历 文字版格斗游戏 基础版 格斗游戏,每个游戏角色的姓名,血量,都不相同,在选定人…

[BJDCTF2020]Mark loves cat

黑盒直接扫 dirsearch -u http://bba9a212-64d3-4a16-88b4-3605fe3ef749.node5.buuoj.cn:81/ -w /home/kali/Desktop/dirsearch/db/dicc.txt我们用GitHack拿一下源码 没有的去下载一下,开源代码 cd GitHackpython GitHack.py http://bba9a212-64d3-4a16-88b4-3605…

排序算法3_冒泡排序、快速排序

一、冒泡排序 1.1 冒泡排序定义和思路 冒泡排序的基本思想是:通过相邻两个元素之间的比较和交换,使较大的元素逐渐从前面移向后面(升序),就像水底下的气泡一样逐渐向上冒泡,所以被称为“冒泡”排序。  在…

垃圾收集篇

文章目录 垃圾收集算法垃圾的概念对象存活的判断引用计数器法可达性分析算法 算法标记清除算法复制算法标记压缩算法 垃圾收集的相关概念STW安全点安全区域 垃圾收集器重要指标吞吐量停顿时间 垃圾收集器的分类Serial 收集器:串行回收ParNew 收集器:并行…

【可视化大屏系列】Echarts之饼图绘制

本文为个人近期学习总结,若有错误之处,欢迎指出! Echarts之饼图绘制 前言1.需求2.实现效果3.大概思路4.代码实现子组件写法父组件写法 5.附加(1)圆环饼图的绘制(2)南丁格尔玫瑰饼图A.半径展示数…

新手小白的pytorch学习第三弹-------tensor的基本操作

reshape, view, stacking, squeeze(), unsqueeze(),permute()torch.tensor 和 numpy 的 array切片,张量里面获取元素值随机种子 1 导入torch import torch2 reshape() tensor_A torch.arange(1, 11) tensor_Atensor_A.reshape(2, 5) tensor_A.reshape(2, 5)tenso…

浮点数存储方法(float,double,long double)

前言: 浮点数家族包括float、double、long double 类型。 如果你打出3.14,编译器默认是double类型的。若想让他为float类型,则要在前面加f; 1E10是科学计数法,代表1.010^10 (1) 浮点型如何在内存中存放?…

husky 和 lint-staged 构建代码项目规范

目录 前言 最简单的方法 过 scripts 来解决如果检测工具多,需要多次处理 通过 husky(哈士奇)来解决容易遗忘的问题 1. 安装 2. husky init 3. 试一试​ lint-stadge 只 lint 改动的 1. 安装 2. 修改 package.json 配置 3. 添加 npm 脚本: 4.使用 Husky…

Linux的load(负载)

负载(load)是Linux机器的一个重要指标,直观了反应了机器当前的状态。 在Linux系统中,系统负载是对当前CPU工作量的度量,被定义为特定时间间隔内运行队列中的平均线程数。 Linux的负载高,主要是由于CPU使用、内存使用、10消…

【AI】目标检测算法【R-CNN:Regions with CNN features】

1. 常用目标检测算法介绍 目标检测是计算机视觉领域的一个重要分支,它旨在识别并定位图像中的各种对象。以下是一些流行的目标检测算法: 1.1 二阶段目标检测算法 R-CNN (Regions with CNN features): 通过选择性搜索算法选取候选区域,然后…

【C语言】详解结构体(上)

文章目录 前言1. 结构体类型的含义2.结构体的声明2.1 结构体声明的语法2.2 结构体变量的创建和初始化 3.结构体的特殊声明4. 结构体的自引用5.小结 前言 C语言的数据类型分为内置数据类型和自定义的数据类型。所谓的内置的数据类型可以认为是C语言自带的数据类型(c…

【网络安全】基于PHP study的DVWA靶场搭建教程

PHP study的安装本文略过 DVWA安装地址 https://github.com/digininja/DVWA?tabreadme-ov-file将zip文件安装至PHP study的www目录下: 解压,进入config目录中,将.dist后缀删除: 接着打开该php文件,将用户名、密码改为…

程序包不存在【java: 程序包org.springframework.boot不存在】

1、问题提示:java: 程序包org.springframework.boot不存在 注意:已经下载好了程序包,就是提示不存在 2、解决办法

Qt5离线安装包无法下载问题解决办法

Qt5离线安装包无法下载问题解决办法 文章目录 Qt5离线安装包无法下载问题解决办法1、前言2、Qt5安装包下载办法 更多精彩内容👉个人内容分类汇总 👈👉Qt开发经验 👈 1、前言 Qt安装包官方下载地址 Qt5离线安装包目前在国内已经被墙…

链表的回文结构(链表的中间节点+反转链表)

链表的回文结构 一.链表的中间节点思路1:暴力求解思路2:快慢指针 二.返回倒数第k个节点思路1:暴力求解思路2:快慢指针 三.反转链表思路1:头插法思路2:反转指针的指向 四.链表的回文结构思路1:利…

react + redux 状态管理操作

目录 1 概念2 Redux 安装3 创建子模块并导入4 中间件为 react 注入 store5 在组件中使用 store 数据6 修改 store 数据7 提交 action 传参8 异步状态操作9 redux 调试工具 1 概念 Redux 是一个全局状态管理的 JS 库 2 Redux 安装 在react中使用redux,官方要求安…

css设置弹性flex后,如果设置100vh高度不撑满的原因

问题 父元素设置height为100%,有两个子元素,第一个设置height:100vh,第二个设置flex:1,此时第一个高度无法撑满盒子 原因解决方式 当父元素设置display为flex,第一个div设置高度64px,剩一个div设置高度为flex:1,这时…