白学立体视觉(3): 单目相机标定

这篇具有很好参考价值的文章主要介绍了白学立体视觉(3): 单目相机标定。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

小伙伴们,第一个理论加实践的小结来啦。本小节将会在白学立体视觉(2): 相机内外参数与坐标系的基础上,介绍一下鼎鼎有名的张正友标定法。


一、为什么需要相机标定?

我们如果想重建出一台相机的成像过程的数学模型,相机的参数是最基本的。相机参数又分为内参和外参。那么内参和外参就是我们标定的最终目的。
1、有了内参中的畸变系数:[k1,k2,k3, …,p1,p2,…],我们就可以对带畸变的图像进行矫正,避免采集到的图像产生桶形畸变和枕形畸变。
2、有了相机内外参数,我们就可以构建相机成像的几何模型,从而把世界坐标系中的三维物体投影到二维平面上。
3、计算多台相机中间的映射关系,本节不详细介绍,后边有的是机会。(这个很重要,这将是双目视觉乃至多目立体视觉基础)。

相机内参:相机的内参(dx,dy,r,u0,v0,f);
     一个像素在水平和垂直方向上的物理尺寸为dx和dy,单位为毫米(或者米);
     焦距f,单位为毫米(或者米);
     图像物理坐标的扭曲因子r,通常为0,忽略不计;
     u0,v0为主点在图像像素坐标系下的坐标,单位为像素;主点为过光心垂直于成像面的垂线,该垂线与成像面的交点。
相机外参:[R|T];
     世界坐标系转换到相机坐标系的旋转R和平移T矩阵。
畸变系数:[k1,k2,k3, …,p1,p2,…]。
     我们只关心相机的径向畸变系数k1,k2,k3…,和相机的切向畸变系数p1,p2,~。

二、张正友标定法

1.标定板

张正友标定法应用比较广泛,操作也比较简单易实现。只需要一个角点特征明显,且易于提取的平面板。如下图所示:白学立体视觉(3): 单目相机标定
标定板有以下几个制作要求:
1、标定板是一个二维平面。
2、标定板不反光,不透视,不易变形。
3、角点之间的物理距离是固定的,根据需要可以设为7cm,11cm等。

2.公式命名

  本节公式主要来源于张正友论文,感兴趣的可以阅读一下原论文。
  2D图像点坐标及其齐次坐标:白学立体视觉(3): 单目相机标定
  3D空间点坐标及其齐次坐标:白学立体视觉(3): 单目相机标定
  3D空间点到图像像素坐标的映射关系用下式表示:
白学立体视觉(3): 单目相机标定
s:表示3D空间坐标系到图像坐标系的尺度因子
A:相机内参矩阵
(u0,v0):像主点坐标,
α , β:分别为图像u轴和v轴方向的焦距值(像素单位),也就是上小结中提到的ax、ay(ax 和 ay 被称为等效焦距,有时候也用Fx 和 Fy表示)
γ:为像元轴的倾斜因子

3.理论介绍

标定板是一个平面,所以角点都位于一个空间平面上,我们可以让标定板上左上角的第一个角点设为世界坐标系的原点(0, 0, 0),让Z轴垂直于标定板平面,所以标定板上的所有角点的Z坐标都为0,即角点的空间三维坐标可以用下式表示:
白学立体视觉(3): 单目相机标定
同时,旋转矩阵R的第i列可以用 ri 表示,[r1,r2,r3]和t分别是相机坐标系相对于世界坐标系的旋转矩阵和平移向量。其中角点Z坐标全部为0,所以:
白学立体视觉(3): 单目相机标定
这里涉及一个很重要的概念:单应性矩阵H,是一个3 × 3的矩阵,简单提及一下这个矩阵,只要记住它包含了相机内参和外参即可,后续小结会详细介绍。于是,空间到图像的映射关系就可以用下式表示:
白学立体视觉(3): 单目相机标定
其中单应性矩阵H可以用下式表示,λ 是一个任意标量(λ 表示齐次坐标的尺度不变性,也可以认为λ = 1):
白学立体视觉(3): 单目相机标定
单应性矩阵是一个3 × 3的矩阵,也可以用下式表示:
白学立体视觉(3): 单目相机标定
根据旋转矩阵的性质,即R为单位正交阵,所以r1和r2是标准正交基,相互垂直且模长均为1(旋转向量的模为1,旋转不改变尺度)。

白学立体视觉(3): 单目相机标定
结合上式可以得到:

白学立体视觉(3): 单目相机标定
根据旋转矩阵R的列向量正交和向量模为1这两个约束条件,经过一下数据变化可以得到:
白学立体视觉(3): 单目相机标定
通过上面的式子,很容易看出,单应性矩阵H和内参矩阵A的元素之间满足两个线性方程约束。h1和h2是通过单应性矩阵求解出来的,所以我们要求解的参数就变成A矩阵中未知的5个参数。我们可以通过三个单应性矩阵来求解这5个参数,利用三个单应性矩阵在两个约束下可以生成6个方程。其中,三个单应性矩阵又可以通过三张标定板图像获得,但是这三张图像是对同一标定板在不同角度和高度采集得到。

4.相机参数求解过程

我们令
白学立体视觉(3): 单目相机标定
B是一个对称阵,所以B的有效元素就剩下6个,即:

白学立体视觉(3): 单目相机标定
设H的第i列为hi:
白学立体视觉(3): 单目相机标定
根据b的定义,进一步化简得到:
白学立体视觉(3): 单目相机标定
通过计算,可以得到:
白学立体视觉(3): 单目相机标定
利用上面提到的两个约束条件,我们可以得到下面的方程组:

白学立体视觉(3): 单目相机标定
这是一个标准的线性方程组V b = 0 ,大学时线性代数老师教我们的线性方程组的求解方法还记得吗?哈哈哈。
如果有n组观察图像,则V 是 2n x 6 的矩阵,根据最小二乘定义,V b = 0 的解是VTV 最小特征值对应的特征向量。

因此, 可以直接估算出 b,后续可以通过b求解内参。因为B中的未知量为6个,所以当观测平面 n ≥ 3 时,可以得到b的唯一解
当 n = 2时, 一般可令畸变参数γ = 0
当 n = 1时, 仅能估算出α 与 β, 此时一般可假定像主点坐标 u0 与 v0 为0
内部参数可通过如下公式计算(cholesky分解):
白学立体视觉(3): 单目相机标定
有了内部参数矩阵A,再根据
白学立体视觉(3): 单目相机标定
化简计算得到以下外部参数:
白学立体视觉(3): 单目相机标定
由于图像噪声等因素的影响,上述过程求得的 CCD 相机参数与真实值之间还存在着差距。使用最大似然估计进行优化。
因此,需要对所有的相机参数进行最优化求解,优化目标函数为:
白学立体视觉(3): 单目相机标定
其中n为标定板图像数量,m为标定板上角点数量。
mij 是通过角点检测得到的像素坐标点。
m~为世界坐标系一点M通过相机成像模型投影得到的二维点坐标。
这是一个非线性表达式,所以这是一个非线性最小化求解问题,可以用Levenberg-Marquardt算法来求解。

5.相机畸变

以上的公式推导都没有考虑相机的畸变问题,现实情况下,很难有相机厂商能做出没有畸变的相机。上个小结已经对相机畸变进行了详细介绍。我们主要考虑两种畸变:径向畸变切向畸变。一般的相机只要计算径向畸变的前三项:k1,k2,k3和切向畸变的前两项:p1,p2就可以得到很好的畸变矫正效果。而在一些畸变比较大的相机中,比如鱼眼相机,要想比较好的去处畸变,就需要计算更多项参数。
张正友标定法只考虑了最大的径向畸变:k1,k2。数学表达式为:
白学立体视觉(3): 单目相机标定
其中,(u,v)是理想的无畸变的像素坐标,(^u, ^v)是实际畸变后的像素坐标,在这里就是角点检测得到的角点坐标,(u0,v0)代表主点。
(x,y)是理想无畸变的连续图像坐标,(^x, ^y)是实际畸变后的连续图像坐标。k1和k2为前两阶的畸变参数。
白学立体视觉(3): 单目相机标定
从而得到畸变公式的矩阵形式:
白学立体视觉(3): 单目相机标定
记做:Dk=d
白学立体视觉(3): 单目相机标定
将上面计算的内参数代入公式:
白学立体视觉(3): 单目相机标定
得到近似理想条件下的角点坐标( u , v ) 。然后根据畸变公式计算近似的畸变系数k1和k2。

最后就可以建立新的最大似然估计表达式:
白学立体视觉(3): 单目相机标定
通过非线性求解方法来求解所有内外参数和畸变系数。

到这里,张正友标定法就计算出来我们想要的相机内参、相机外参和畸变系数。

三、OpenCV实践张氏标定法

1.标定步骤

1、准备一个标定板,按照上文提出的要求制作。为了简便也可以用A4纸打印,粘贴在一个平板上。
2、采集棋盘格标定板若干张图片,一般10至20张就够。但是每次拍摄,标定板角度和位置要改变。
3、检测图片中角点。
4、利用解析解估算方法计算出5个内部参数,以及6个外部参数
5、使用畸变公式的线性方程组求解近似的畸变系数(或者直接使用0也可以)
6、使用非线性优化法计算精确的内外参数和畸变系数。

2.标定实践

白学立体视觉(3): 单目相机标定

代码如下(示例):

# -*- coding:utf-8 -*-
import os
import cv2
import numpy as np
import glob
import configparser

class MonocularCalibration(object):
    '''单目标定和矫正类'''
    def __init__(self, data_root, cali_file="MonoCalib_Param_720p.ini", board_size=(7,11), img_shape=(720, 1280), suffix="png"):
        self.cali_file = os.path.join(data_root, cali_file)
        self.H, self.W = img_shape
        if os.path.exists(self.cali_file):
            print("\n===> Read Calibration file from {}...".format(self.cali_file))
            self.read_cali_file(self.cali_file)
        else:
            print("\n===> Start Calibration...")
            # 步骤1:初始化
            self.board_size = board_size
            # 步骤2:标定
            self.Mono_Calibration(corner_h=self.board_size[0], corner_w=self.board_size[1], source_path=data_root, suffix=suffix)
            # 步骤3:生成标定文件
            self.write_cali_file(cali_file=self.cali_file)

    # 计算单目相机内参、畸变系数
    def Mono_Calibration(self, corner_h, corner_w, source_path, suffix="png"):
        # 设置寻找亚像素角点的参数,采用的停止准则是最大循环次数30和最大误差容限0.001
        criteria = (cv2.TERM_CRITERIA_MAX_ITER | cv2.TERM_CRITERIA_EPS, 30, 0.001)
        # 获取标定板角点的位置
        # 世界坐标系中的棋盘格点,例如(0,0,0), (1,0,0), (2,0,0) ....,(8,5,0),去掉Z坐标,记为二维矩阵
        objp = np.zeros((corner_h * corner_w, 3), np.float32)
        # 将世界坐标系建在标定板上,所有点的Z坐标全部为0,所以只需要赋值x和y
        objp[:, :2] = np.mgrid[0:corner_w, 0:corner_h].T.reshape(-1, 2)
        square_size = 50.0  # 18.1  # 18.1 mm
        objp = objp * square_size

        # 储存棋盘格角点的世界坐标和图像坐标对
        obj_points = []  # 存储3D点  # 在世界坐标系中的三维点
        img_points = []  # 存储2D点  # 在图像平面的二维点

        images = glob.glob(os.path.join(source_path, "*." + suffix))
        for index, fname in enumerate(images):
            print("=== ", index)
            img = cv2.imread(fname)
            h_, w_, _ = img.shape
            if h_ != self.H or w_ != self.W:
                img = cv2.resize(img, (self.W, self.H), interpolation=cv2.INTER_CUBIC)

            self.gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            size = self.gray.shape[::-1]
            ret, corners = cv2.findChessboardCorners(self.gray, (corner_w, corner_h), None)

            if (corners[0, 0, 0] < corners[-1, 0, 0]):
                print("*" * 5 + " order of {} is inverse! ".format(index) + "*" * 5)
                corners = np.flip(corners, axis=0).copy()

            if ret:
                obj_points.append(objp)
                corners2 = cv2.cornerSubPix(self.gray, corners, (11, 11), (-1, -1), criteria)  # 在原角点的基础上寻找亚像素角点
                img_points.append(corners2)

                cv2.drawChessboardCorners(img, (corner_w, corner_h), corners2, ret)  # 记住,OpenCV的绘制函数一般无返回值
                cv2.imshow('img', img)
                cv2.waitKey(50)

        cv2.destroyAllWindows()

        # print("img_points: ", img_points.shape)
        # print("obj_points: ", obj_points.shape)

        # 标定
        ret, self.mtx, self.dist, self.rvecs, self.tvecs = cv2.calibrateCamera(obj_points, img_points, self.gray.shape[::-1], None, None)

        print("ret:", ret)
        print("mtx:\n", self.mtx)  # 内参数矩阵
        print("dist:\n", self.dist)  # 畸变系数   distortion cofficients = (k_1,k_2,p_1,p_2,k_3)

        # 并且通过实验表明,distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
        # 三个参数的时候由于k3所对应的非线性较为剧烈。估计的不好,容易产生极大的扭曲,所以k_3强制设置为0.0
        # self.dist[0, 4] = 0.0

        return self.mtx, self.dist, self.rvecs, self.tvecs

    def Mono_Undisort_list(self, source_path, suffix="png"):
        # 对所有图片进行去畸变,有两种方法实现分别为: undistort()和remap()
        images = glob.glob(os.path.join(source_path, "*." + suffix))
        for fname in images:
            fname = '/'.join(fname.split('\\'))
            print(fname)
            prefix = fname.split('/')[-1].replace(".", "_undistort.")
            # print(prefix)
            img = cv2.imread(fname)
            h_, w_, _ = img.shape
            if h_ != self.H or w_ != self.W:
                img = cv2.resize(img, (self.W, self.H), interpolation=cv2.INTER_CUBIC)


            dst = self.Mono_Rectify(img, flag=True)


            print("img: ", img.shape)
            print("dst: ", dst.shape)
            # cv2.imshow('img', img)
            # cv2.imshow('dst', dst)
            #
            # cv2.waitKey(500)

            if not os.path.isdir(os.path.join(source_path, "mono_rec")):
                os.mkdir(os.path.join(source_path, "mono_rec"))
            cv2.imwrite(os.path.join(source_path, "mono_rec", prefix), dst)

    def Mono_Rectify(self, image, flag=True):
        image = cv2.resize(image, (self.W, self.H))

        # 使用 cv.undistort()进行畸变校正
        if flag:
            # 矫正单目图像:直接使用计算的相机内参矩阵
            image_rec = cv2.undistort(image, self.mtx, self.dist, None, self.mtx)

        else:
            # 矫正单目图像:使用计算的相机内参矩阵计算新的内参矩阵
            '''
            1、默认情况下,我们通常不会求取新的CameraMatrix,这样代码中会默认使用标定得到的CameraMatrix。
                而这个摄像机矩阵是在理想情况下没有考虑畸变得到的,所以并不准确,重要的是fx和fy的值会比考虑畸变情况下的偏大,
                会损失很多有效像素。我们可以通过这个函数getOptimalNewCameraMatrix ()求取一个新的摄像机内参矩阵,
            2、cv2.getOptimalNewCameraMatrix()。如果参数alpha = 0, 它返回含有最小不需要像素的非扭曲图像,
                所以它可能移除一些图像角点。如果alpha = 1, 所有像素都返回。还会返回一个 ROI 图像,我们可以用来对结果进行裁剪。
            '''
            h, w = self.H, self.W
            newcameramtx, roi = cv2.getOptimalNewCameraMatrix(self.mtx, self.dist, (w, h), 0, (w, h))
            image_rec = cv2.undistort(image, self.mtx, self.dist, None, newcameramtx)
            # crop and save the image
            # x, y, w, h = roi
            # image_rec = image_rec[y:y + h, x:x + w]

        return image_rec

    def read_cali_file(self, cali_file):
        con = configparser.ConfigParser()
        con.read(cali_file, encoding='utf-8')
        sections = con.sections()
        # print(sections.items)
        calib = con.items('Calib')
        rectify = con.items('Rectify')
        calib = dict(calib)
        rectify = dict(rectify)

        self.u0 = calib['u0']
        self.v0 = calib['v0']
        self.fx = calib['fx']
        self.fy = calib['fy']

        self.mtx = np.array([[rectify['mtx_0'], rectify['mtx_1'], rectify['mtx_2']],
                            [rectify['mtx_3'], rectify['mtx_4'], rectify['mtx_5']],
                            [rectify['mtx_6'], rectify['mtx_7'], rectify['mtx_8']]]).astype('float32')

        self.dist = np.array([[rectify['dist_k1'],
                               rectify['dist_k2'],
                               rectify['dist_p1'],
                               rectify['dist_p2'],
                               rectify['dist_k3']]]).astype('float32')

    def write_cali_file(self, cali_file="./MonoCalib_Param.ini"):
        self.u0 = self.mtx[0, 2]
        self.v0 = self.mtx[1, 2]
        self.fx = self.mtx[0, 0]
        self.fy = self.mtx[1, 1]
        self.Calib = {"fx": self.fx,
                      "fy": self.fy,
                      "u0": self.u0,
                      "v0": self.v0,
                     }
        # 相机内参矩阵
        # fx  s   x0
        # 0   fy  y0
        # 0   0    1
        Rectify = {}
        for i in range(3):
            for j in range(3):
                Rectify['mtx_{}'.format(i*3+j)] = self.mtx[i, j]

        # 相机畸变系数
        # 畸变系数   distortion cofficients = (k_1,k_2,p_1,p_2,k_3)
        Rectify['dist_k1'] = self.dist[0, 0]
        Rectify['dist_k2'] = self.dist[0, 1]
        Rectify['dist_p1'] = self.dist[0, 2]
        Rectify['dist_p2'] = self.dist[0, 3]
        Rectify['dist_k3'] = self.dist[0, 4]

        self.Rectify = Rectify
        # Write calibration param to file.
        config = configparser.ConfigParser()
        config["Calib"] = self.Calib
        config["Rectify"] = self.Rectify
        # with open("Calib_Param_4.ini", 'w') as configfile:
        with open(cali_file, 'w') as configfile:
            config.write(configfile)


def main():
    source_path = "path to your image"
    mono_calibration = MonocularCalibration(data_root=source_path,
                                            cali_file="MonoCalib_Param_720p.ini",
                                            board_size=(7, 11),
                                            img_shape=(720, 1280),
                                            suffix="png")
    print(mono_calibration.mtx)
    print(mono_calibration.dist)
    data_root = source_path
    mono_calibration.Mono_Undisort_list(data_root, suffix="png")


if __name__ == '__main__':
    main()

矫正效果:
白学立体视觉(3): 单目相机标定

这个代码我测试过,效果还可以。里面加了一些注释,相信大家可以理解。小伙伴们也可以调一下参数,以便得到自己想要的效果。


总结

本小节主要是对张正友标定法进行了相机的介绍,希望各位有所收获。当然了这只是比较基础的单目标定过程,是双目视觉的基础。后边小节我们将进一步学习双目视觉立体标定的相关知识。文章来源地址https://www.toymoban.com/news/detail-457072.html

到了这里,关于白学立体视觉(3): 单目相机标定的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 普通单目相机标定——准备工作

            这里我们还是以普通相机为例(非鱼眼相机)来进行后续的相关标定操作,再回顾下相机的成像模型如下所示。         已知相机内参(fx,fy,u0,v0),畸变系数[k1,k2,k3,p1,p2],相机外参[R|T]。世界坐标系中点Pw(Xw,Yw,Zw),投影至像素坐标系点p(u,v)的计算过程如下。 1)

    2024年02月15日
    浏览(37)
  • 相机标定小工具(单目+双目)技术分享

    以上就是软件的主要代码,相信大家都可以做出来了。加油!

    2024年02月14日
    浏览(57)
  • kalibr 进行相机单目、双目标定全流程

    参考链接: Installation · ethz-asl/kalibr Wiki · GitHub Docker 操作知识: Docker攻略:从安装到入门到进阶 | Dockerfile调优 | 镜像分层 | 容器生命周期/5种网络模式 |跨宿主机通信_血煞长虹的博客-CSDN博客 部署kalibr步骤如下: 1.拉取镜像 2.创建container 3.进入container 运行以后即可进入con

    2024年02月09日
    浏览(41)
  • 双目立体标定与极线校正【双目立体视觉几何】

       在这篇博文中,我们来了解一下立体标定的过程。双目相机各自经过相机标定后,该如何把两个相机统一起来,获得物体三维信息呢?    那么在双目立体视觉中,通过把左右相机平面旋转到一个平面内,且行对齐,旋转后的左右相机平面具有相同的焦距f,且具有相同

    2024年02月10日
    浏览(52)
  • Python OpenCV 单目相机标定、坐标转换相关代码(包括鱼眼相机)

      本文不讲原理,只关注代码,有很多博客是讲原理的,但是代码最多到畸变矫正就结束了,实际上就是到 OpenCV 官方示例涉及的部分。   在官方示例中使用黑白棋盘格求解了相机的内外参和畸变系数,并对图像做了畸变矫正,但在实际使用时还缺少很多功能,以下是本

    2024年02月02日
    浏览(33)
  • 基于OpenCV的单目相机标定与三维定位

           相机是产生图像数据的硬件,广泛应用于消费电子、汽车、安防等领域。围绕着相机衍生出一系列的研究与应用领域,包括传统的图像处理和基于深度学习的智能应用等。目前大火的自动驾驶中相机也是重要的硬件组成,如环视用鱼眼相机,adas用周视相机。    

    2024年02月09日
    浏览(34)
  • 使用opencv做双目测距(相机标定+立体匹配+测距)

    最近在做双目测距,觉得有必要记录点东西,所以我的第一篇博客就这么诞生啦~ 双目测距属于立体视觉这一块,我觉得应该有很多人踩过这个坑了,但网上的资料依旧是云里雾里的,要么是理论讲一大堆,最后发现还不知道怎么做,要么就是直接代码一贴,让你懵逼。 所以

    2024年01月20日
    浏览(35)
  • ubuntu20.04下的USB摄像头使用与标定(单目相机)一.使用

    A.查找摄像头接口 B.在该路径下修改launch文件,换成你自己的摄像头接口 C.运行摄像头节点(此时就能看到画面了) ⭐可能会出现的错误 1.终端会warning是因为还没有进行摄像头标定,后续会进行讲解 2.若用的是虚拟机但发出了error,看看你的usb是接入3.0还是2.0处,然后在如下

    2024年02月04日
    浏览(157)
  • 使用ROS功能包camera_calibration进行单目相机和双目相机的内参和外参标定

    本文总结使用ROS标定单目和双目相机的过程,同时提供生成棋盘格文件的方法。 参考链接: [1]使用ros标定相机的内参和外参 [2]ROS下采用camera_calibration进行双目相机标定 棋盘格可以自己买一个,或者打印一个粘在板子上,棋盘格电子版生成可以参考博客《使用kalibr标定工具进

    2024年02月11日
    浏览(45)
  • 视觉相机模型以及投影原理推导——(单目)

    参考文献:视觉SLAM十四讲、视觉惯性SLAM理论与源码分析、该博客、文中的公式直接引用上面的文章,如有侵权请联系本人删除 投影过程 三维世界中的物体(目标点)P反射光线,通过相机光心,投影到相机的感光平面(物理成像平面/像素成像平面),一个个的光线投影点汇

    2024年02月09日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包