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

news2024/11/13 9:32:45

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 校正结果
  • 6. 注

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,基本符合预期哦~

6. 注

 在相机之间的转角很大的时候,双目标定+立体校正的流程可能会失效,所以我们在下一篇帖子一起来看一下如何使用未标定的的矫正函数:无标定校正

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

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

相关文章

U盘RAW状态深度解析与高效恢复策略

一、U盘RAW状态探秘:数据访问的隐形壁垒 在数字化时代,U盘作为我们日常生活中不可或缺的数据存储工具,承载着大量的重要信息与文件。然而,当U盘突然变为RAW格式时,这些宝贵的数据便仿佛被无形的屏障所隔离&#xff0c…

前端面试题(CSS篇八)

一、letter-spacing 与字符间距? letter-spacing可以用来控制字符之间的间距,这里说的“字符”包括英文字母、汉字以及空格等。 letter-spacing具有以下一些特性。 (1)继承性。 (2)默认值是normal而不是0。…

Go语言中GC(垃圾回收回收机制)三色标记与混合写屏障

5、Golang三色标记混合写屏障GC模式全分析 (yuque.com) 第1讲-课程目标_哔哩哔哩_bilibili Golang三色标记GC混合写屏障 Go V1.3之前的标记清除(mark and sweep) 垃圾回收、内存管理、自动适放、三色标记法、STW (stop the world) 图的遍历?可达性分…

轴端命名 及多器件编号

1.轴端命名 轴如果单端动力输入时,动力输入一侧,可以称为: 输入端,驱动端 另一侧可能的称谓是: 支撑端、自由端、从动端、未驱动端、静态支撑端。 另外,在查阅相关称谓时,看到了关于卷扬机…

<Rust>egui部件学习:如何在窗口及部件显示中文字符?

前言 本专栏是关于Rust的GUI库egui的部件讲解及应用实例分析,主要讲解egui的源代码、部件属性、如何应用。 环境配置 系统:windows 平台:visual studio code 语言:rust 库:egui、eframe 概述 本文是本专栏的第一篇博…

简约唯美的404HTML源码

源码介绍 简约唯美的404HTML源码,很适合做网站错误页,将下面的源码放到一个空白的html里面,然后上传到服务器里面即可使用 效果预览 完整源码 <!DOCTYPE html> <html><head><meta charset="utf-8"><title>404 Error Example<…

配置服务器

参考博客 1. https://blog.csdn.net/qq_31278903/article/details/83146031 2. https://blog.csdn.net/u014374826/article/details/134093409 3. https://blog.csdn.net/weixin_42728126/article/details/88887350 4. https://blog.csdn.net/Dreamhai/article/details/109…

Linux主机添加ipv6地址

一、添加网卡ipv6地址 通过命令行添加 ip add add 2001:db8:0:1::102/64 dev ens160 通过编辑/etc/sysconfig/network-scripts/目录下的ifcfg-配置文件添加 TYPEEthernet BOOTPROTOdhcp # 或者指定为 "static" 如果你想要静态配置 DEFROUTEyes IPV4_FAILURE_FAT…

多元统计分析概述

目录 1. 多元回归分析 2. 主成分分析&#xff08;PCA&#xff09; 3. 因子分析 4. 判别分析 5. 聚类分析 6. 多维尺度分析&#xff08;MDS&#xff09; 结论 多元统计分析是一组用于分析多个变量之间关系的统计方法。它广泛应用于各个领域&#xff0c;如市场研究、生物医…

C++中的语句详细介绍:简单语句、条件、循环迭代语句、跳转语句、异常处理语句、try语句等

文章目录 C中的语句(1)简单语句A.空语句B.复合语句 (2)条件语句(3)迭代语句A.常规for循环B.范围for循环C.while和do...while (4)跳转语句A.break语句B.continue语句C.goto语句 (5)异常处理语句A.标准异常B.throw抛出异常 (6)try语句 C中的语句 (1)简单语句 简单语句包括&#…

探索 Python 的宝藏:深入理解 NumPy库

探索 Python 的宝藏&#xff1a;深入理解 NumPy 库 引言&#xff1a;为何选择 NumPy&#xff1f; NumPy 是 Python 中一个基础而强大的库&#xff0c;它为 Python 语言提供了高性能的多维数组对象和相应的操作。在科学计算、数据分析、机器学习等领域&#xff0c;NumPy 以其高…

Android获取当前屏幕显示的是哪个activity

在 Android 中&#xff0c;要获取当前屏幕显示的 Activity&#xff0c;可以使用以下几种方法&#xff1a; 方法一&#xff1a;使用 ActivityManager 获取当前运行的任务信息 这是一个常见的方法&#xff0c;尽管从 Android 5.0 (API 21) 开始&#xff0c;有些方法变得不太可靠…

ctfshow~VIP限免题目20道(保姆级解析)

奈何自己没有实力&#xff0c;看到免费的东西就想占点便宜&#xff0c;想着做都做了就出个wp吧&#xff0c;本人小白&#xff0c;不喜勿喷&#xff01; 一、源码泄露 题目提示&#xff1a;开发注释未及时删除 题目给出了源码泄露&#xff0c;那咱直接看源码&#xff08;右键点…

2.I/O口

文章目录 I/O输出(点灯)分析电路代码编写 I/O输入(电平检测)代码编写 I/O内部电路分析51单片机STM32单片机输入输出 I/O输出(点灯) 分析电路 看电路图&#xff0c;元器件形成电压差&#xff0c;即可点亮LED灯 代码编写 使用不同操作进行LED控制 #include "reg52.h&quo…

AI助手在企业虚拟展厅中的应用有哪些?

在AI人工智能发展的浪潮下&#xff0c;视创云展也在元宇宙展厅中创新的融入了「AI智能助手」&#xff0c;当用户在虚拟展厅内自由探索时&#xff0c;AI智能助手始终如影随形&#xff0c;为用户提供即时、精准的信息解答与互动体验&#xff0c;使参观过程更加智能化、便捷化和个…

吴恩达机器学习笔记 三十八 二进制标签 均值归一化

标签 0 和 1 可以有很多种意义。从回归到二分分类&#xff1a;之前定义 ,而对于二进制标签&#xff0c;通过给出y^(i,j)为 1 的概率&#xff0c;其中 损失函数 均值归一化 计算每个电影的平均得分&#xff0c;例如第一部电影的平均分为2.5&#xff0c;第五部电影的平均分为1.2…

HP iLO5服务器硬件监控指标解读

在现代化数据中心中&#xff0c;服务器的稳定运行对于保障业务的连续性至关重要。HP的iLO&#xff08;Integrated Lights-Out&#xff09;技术&#xff0c;尤其是iLO5版本&#xff0c;为HP服务器提供了强大的远程管理功能。监控易作为一款专业的监控软件&#xff0c;通过支持HP…

Artix7系列FPGA实现SDI视频编解码,基于GTP高速接口,提供3套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案在Xilinx--Kintex系列FPGA上的应用本方案在Xilinx--Zynq系列FPGA上的应用 3、详细设计方案设计原理框图SDI 输入设备Gv8601a 均衡器GTP 高速接口-->解串与串化SMPTE SD/HD/3G SDI IP核BT1120转…

【爬虫】滑块缺口识别

滑块示例 分为背景图 和 滑块图 主要目的 识别背景图滑块缺口 下载识别库 pip install opencvcode import numpy as np import cv2def identify_gap(bg, tp):bg1 np.asarray(bytearray(bg), dtypenp.uint8)tp1 np.asarray(bytearray(tp), dtypenp.uint8)# 灰度bg_img cv2…

MySQL 数据库 - SQL

SQL通用语法 SQL通用语法 SQL语句可以单行或者多行书写&#xff0c;以分号结尾。SQL语句可以使用空格/缩进来增强语句的可读性。 注意&#xff1a;空格和缩进的个数是没有限制的&#xff0c;可以是 “一个” 也可以是 “多个”。MySQL数据库的SQL语句不区分大小写&#xff0c;…