OpenCV 相机校正过程中,calibrateCamera函数projectPoints函数的重投影误差的分析

OpenCV 校正过程中,calibrateCamera函数的ret和重投影误差的分析


一、前言

本文主要是讲解Python利用OpenCV进行相机校正过程中,几个重点参数的分析。标定的过程就不再一一赘述了,很多博客和网站都在讲解怎么进行标定。本文章主要分析,标定过程中的误差计算公式,和对calibrateCamera( )projectPoints( ) 两个函数得到的误差不同的原因进行分析。

本文重点分析重投影误差的计算方式。准确描述OpenCV自带的函数,和各个文档,教程里面所说的重投影误差计算的流程进行分析和对比,描述官方的重投影误差和教程实现重投影误差的差别。 我认为OpenCV官方和其他大多数地方的教程,给的重新计算重投影误差的结果,其计算的表达式,也是不准确的。

正确的方法是相机校正的方法输出的重投影误差ret。使用的是RMS均方根误差。另外教程和博客,给的是MSE均方误差

二、calibrateCamera( )函数分析

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(x_world, y_voltage, (224, 224), None, None)

这里,ret表示的是重投影误差;mtx是相机的内参矩阵;dist表述的相机畸变参数;rvecs表示标定棋盘格世界坐标系到相机坐标系的旋转参数:rotation vectors,需要进行罗德里格斯转换;tvecs表示translation vectors,主要是平移参数。
其他几个参数,OpenCV的官网都进行了仔细分析,在这个网站上去仔细的看,每个参数对应的数学公式。https://docs.opencv.org/3.4/dc/dbb/tutorial_py_calibration.html

主要分析ret这个值,也就是retval,这个是重投影误差。

import numpy as np
import cv2 as cv2
import glob
# termination criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*7,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)
# Arrays to store object points and image points from all the images.
objpoints = [] # 3d point in real world space
imgpoints = [] # 2d points in image plane.
images = glob.glob('*.jpg')
for fname in images:
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv.COLOR_BGR2GRAY)
    # Find the chess board corners
    ret, corners = cv2.findChessboardCorners(gray, (7,6), None)
    # If found, add object points, image points (after refining them)
    if ret == True:
        objpoints.append(objp)
        corners2 = cv2.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria)
        imgpoints.append(corners)
        # Draw and display the corners
        cv2.drawChessboardCorners(img, (7,6), corners2, ret)
        cv2.imshow('img', img)
        cv2.waitKey(500)
cv2.destroyAllWindows()

# Calibration, Now that we have our object points and image points, we are ready to go for 
# calibration. We can use the function, cv.calibrateCamera() which returns the camera matrix, 
# distortion coefficients, rotation and translation vectors etc.
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)
print("ret:", ret)
print("Camera matrix : \n")
print(mtx)
print("dist : \n")
print(dist)
print("rvecs : \n")
print(rvecs)
print("tvecs : \n")
print(tvecs)

其中,这个ret也就是retval,表示的是重投影误差,是算法自动返回的,其表达式为:
R e p p o i n t s   x ′ = k [ R ∣ T ] X   . Reppoints \ x'= k [R|T]X \,. Reppoints x=k[RT]X.
其中,Reppoints x’是[Nx2],表示的图像像素平面的2维坐标点,X是[Nx3]表示的3D世界坐标系下的3维坐标点。也就是棋盘格所构造的3D坐标系的大小。
r e t = r e p r o _ e r r o r = ∣ ∣ x ′ − x ∣ ∣ 2 2 t o t a l _ p o i n t s = ∣ ∣ x ′ − x ∣ ∣ 2 t o t a l _ p o i n t s ret =repro \_error =\sqrt{\frac{ ||x'-x||_{2}^2}{total\_points}}={\frac{ ||x'-x||_{2}}{\sqrt{total\_points}}} ret=repro_error=total_points∣∣xx22 =total_points ∣∣xx2
这就是cv2.calibrateCamera()返回的值中,ret也就是重投影误差的计算公式。求的是RMS均方根误差,对所有的点,求二范数,然后求平均再开根号,得到RMS误差。

其中,二范数用的是:cv2.norm(data1, data2, cv2.NORM_L2)计算得到:
n o r m ( x , x ′ ) = ∣ ∣ x − x ′ ∣ ∣ 2 = ∣ ∣ x − x ′ ∣ ∣ 2 2 = ( x 1 − x 1 ′ ) 2 + . . . + ( x n − x n ′ ) 2 norm(x,x')=||x-x'||_2=\sqrt{|| x-x'||_{2}^2}=\sqrt{(x_1-x'_1)^2+...+(x_n-x'_n)^2} norm(x,x)=∣∣xx2=∣∣xx22 =(x1x1)2+...+(xnxn)2

三、projectPoints()函数分析

imgpoints2, _ = cv.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
  1. 原始的计算方法,大多数教程和博客,以及OpenCV文档,给出的重投影误差计算方法。

对相机标定得到的参数,带回原始的投影公式,重新计算从3D世界坐标系投影到2D图像平面坐标系的新的图像像素值,然后计算重投影误差。其中,objpoints表示世界坐标系的坐标,也就是棋盘格的坐标系,rvecs,tvecs,mtx,dist就是相机标定得到的那些参数。

mean_error = 0
for i in range(len(objpoints)):
    imgpoints2, _ = cv.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
    error = cv.norm(imgpoints[i], imgpoints2, cv.NORM_L2)/len(imgpoints2)
    mean_error += error
print( "total error: {}".format(mean_error/len(objpoints)) )

这里计算误差的方式,我们将得到的值和上面相机校正返回的值ret,不一样。因此,需要考虑,将这个官方给定的计算方式,进行重新计算。如果你不在意准确的值,就不用进行优化。

本文是准确的探索,重投影误差的计算方式。按照上面的算法流程,我们可以得到以下的计算重投影误差的公式:
r e p r o _ e r r o r = ∣ ∣ x ′ − x ∣ ∣ 2 t o t a l _ p o i n t s repro\_error={\frac{ {||x'-x||_{2}}}{total\_points}} repro_error=total_points∣∣xx2
很明显,均方误差的计算,按照上述的表达式,是不准确的。误差开根号,然后除以点数多少,只能说是每个点的平均欧式误差。MSE误差。

  1. 校正之后的重投影误差计算方法或者代码。

因此,需要求得准确的均方根误差,我们应该是先求平方,求平均,在开根号。因此,需要将代码改为:

mean_error = 0
for i in range(len(objpoints)):
    imgpoints2, _ = cv.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
    error = cv.norm(imgpoints[i], imgpoints2, cv.NORM_L2)
    total_error += error*error
print( "rms error: {}".format(np.sqrt(total_error/(len(objpoints)*len(imgpoints2)))))

上述的流程才是:
r e p r o _ e r r o r = ( ∣ ∣ x − x ′ ∣ ∣ 2 2 t o t a l _ p o i n t s repro\_error=\sqrt{\frac{(||x-x'||_2^2}{total\_points}} repro_error=total_points(∣∣xx22

  • 与我们的cv2.calibrateCamera()返回的值是一样的。

四、具体实验和算法对比

  1. 相机校正返回的值
    相机校正返回的值

  2. 函数的返回值
    其中的返回的误差项,真实值:ret 为 ret: 0.008683449668964258
    返回值

  3. 使用大多数教程和博客的算法来计算的误差值,是有差别的:
    在这里插入图片描述

  4. 参考代码给出的输出误差结果
    total error: 0.0018512112930950482

  5. 按照自己修正的代码,来进行重投影误差计算:
    在这里插入图片描述
    输出的重投影误差为:total error: 0.008683459017710442

因此,和我们相机校正输出的=重投影误差是一样的。

五、参考附录

下面是原始C++函数,计算重投影误差的流程,我上面的公式就是按照这个算法来解释的:

static double computeReprojectionErrors(
        const vector<vector<Point3f> >& objectPoints,
        const vector<vector<Point2f> >& imagePoints,
        const vector<Mat>& rvecs, const vector<Mat>& tvecs,
        const Mat& cameraMatrix, const Mat& distCoeffs,
        vector<float>& perViewErrors )
{
    vector<Point2f> imagePoints2;
    int i, totalPoints = 0;
    double totalErr = 0, err;
    perViewErrors.resize(objectPoints.size());

    for( i = 0; i < (int)objectPoints.size(); i++ )
    {
        projectPoints(Mat(objectPoints[i]), rvecs[i], tvecs[i],
                      cameraMatrix, distCoeffs, imagePoints2);
        err = norm(Mat(imagePoints[i]), Mat(imagePoints2), NORM_L2);
        int n = (int)objectPoints[i].size();
        perViewErrors[i] = (float)std::sqrt(err*err/n);
        totalErr += err*err;
        totalPoints += n;
    }

    return std::sqrt(totalErr/totalPoints);
}

需要准确描述如何计算重投影误差的同学,需要仔细修改你的代码,因为RMS误差来描述的,是需要求平方差,然后求平均,再求平方根。

正确的方法是相机校正的方法输出的重投影误差,而不是大多数,包括OpenCV官方,给出的,都是不一样的,或者是不准确的重投影误差的计算函数。

参考网站:

  1. https://stackoverflow.com/questions/29628445/meaning-of-the-retval-return-value-in-cv2-calibratecamera
  2. https://docs.opencv.org/master/dc/dbb/tutorial_py_calibration.html

如果有用,记得点赞👍加收藏哦。!!!!

  • 94
    点赞
  • 327
    收藏
    觉得还不错? 一键收藏
  • 52
    评论
### 回答1: calibratecamera 函数的flags 可以通过设置 CALIB_USE_INTRINSIC_GUESS 或者 CALIB_FIX_ASPECT_RATIO 来实现。具体方法是:将CALIB_USE_INTRINSIC_GUESS设置为True,CALIB_FIX_ASPECT_RATIO设置为False,然后它会自动寻找针孔模型的参数。 ### 回答2: 标定OpenCV相机的针孔模型需要设置calibratecamera函数的flags参数。 calibrateCamera函数OpenCV用于相机标定的函数,它可以根据一组已知的三维物体点和对应的图像点来计算相机的内参矩阵和畸变系数。 flags参数是一个整数,用于指定标定过程的一些选项。根据不同的需求,可以设置不同的标定标志位来调整标定的精确度和速度。 常用的标定标志位包括: 1. CALIB_USE_INTRINSIC_GUESS:使用初始猜测的内参矩阵和畸变系数进行标定。这样可以加快标定的速度,但精度可能会降低。 2. CALIB_FIX_INTRINSIC:固定内参矩阵和畸变系数。如果已经有了一个较好的内参矩阵和畸变系数,可以使用这个标志位来固定它们,仅求解外参矩阵。 3. CALIB_FIX_PRINCIPAL_POINT:固定主点坐标。如果已经知道主点的大致位置,可以使用这个标志位来固定主点坐标,仅求解其他参数。 4. CALIB_FIX_FOCAL_LENGTH:固定焦距。如果已经知道焦距的大致值,可以使用这个标志位来固定焦距,仅求解其他参数。 5. CALIB_FIX_ASPECT_RATIO:固定纵横比。如果已经知道相机的纵横比,可以使用这个标志位来固定纵横比,仅求解其他参数。 6. CALIB_ZERO_TANGENT_DIST:固定切向畸变系数。如果认为切向畸变系数为0,可以使用这个标志位来固定切向畸变系数,仅求解其他参数。 通过设置不同的标志位,可以根据实际需求来调整相机标定的过程,以达到较好的标定效果。 ### 回答3: 标定OpenCV相机的针孔模型需要使用`calibrateCamera`函数,并且可以通过设置`flags`参数来进行不同的标定方式选择。`flags`参数是一个整数,用于设定标定的方法和其他细节。 常见的flags参数设置方法有以下几种: 1. `cv2.CALIB_USE_INTRINSIC_GUESS`:使用已知的初始相机矩阵进行标定,加速标定过程; 2. `cv2.CALIB_FIX_PRINCIPAL_POINT`:固定主点坐标,不进行优化; 3. `cv2.CALIB_FIX_ASPECT_RATIO`:固定像素宽高比,不进行优化; 4. `cv2.CALIB_FIX_FOCAL_LENGTH`:固定焦距,不进行优化; 5. `cv2.CALIB_ZERO_TANGENT_DIST`:切向畸变系数初始化为0; 6. `cv2.CALIB_FIX_K1`:固定径向畸变系数k1,不进行优化; 7. `cv2.CALIB_FIX_K2`:固定径向畸变系数k2,不进行优化; 8. `cv2.CALIB_FIX_K3`:固定径向畸变系数k3,不进行优化; 9. `cv2.CALIB_FIX_K4`:固定径向畸变系数k4,不进行优化; 10. `cv2.CALIB_FIX_K5`:固定径向畸变系数k5,不进行优化; 11. `cv2.CALIB_FIX_K6`:固定径向畸变系数k6,不进行优化; 12. `cv2.CALIB_RATIONAL_MODEL`:使用鱼眼模型进行标定。 除了以上常见的flags参数设置方法,还有一些其他可选的标定方式可以根据实际需求进行设置。在调用`calibrateCamera`函数时,将`flags`参数设置为所需的数值即可选择相应的标定方法。这些标定方法的选择将直接影响标定结果的准确性和效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 52
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值