• 针孔相机模型和变形 这一节里的函数都使用针孔摄像机模型,这就是说,一幅视图是通过透视变换将三维空间中的点投影到图像平面。投影公式如下: 或者 这里(X, Y, Z)是一个点的世界坐标,(u, v)是点投影在...

    针孔相机模型和变形

    这一节里的函数都使用针孔摄像机模型,这就是说,一幅视图是通过透视变换将三维空间中的点投影到图像平面。投影公式如下:

    s \cdot m' = A\cdot[R|t] \cdot M'或者

    s\cdot  \begin{bmatrix} u \\ v \\ 1 \end{bmatrix} = \begin{bmatrix} fx & 0 & cx \\ 0 & fy & cy \\ 0 & 0 & 1 \end{bmatrix} \cdot \begin{bmatrix} r_{11} & r_{12} & r_{13} & t_{1} \\ r_{21} & r_{22} & r_{23} & t_{2} \\ r_{31} & r_{32} & r_{33} & t_{3} \end{bmatrix} \cdot \begin{bmatrix} X \\ Y \\ Z \\ 1 \end{bmatrix}

    这里(X, Y, Z)是一个点的世界坐标,(u, v)是点投影在图像平面的坐标,以像素为单位。A被称作摄像机矩阵,或者内参数矩阵。(cx, cy)是基准点(通常在图像的中心),fx, fy是以像素为单位的焦距。所以如果因为某些因素对来自于摄像机的一幅图像升采样或者降采样,所有这些参数(fx, fy, cx和cy)都将被缩放(乘或者除)同样的尺度。内参数矩阵不依赖场景的视图,一旦计算出,可以被重复使用(只要焦距固定)。旋转-平移矩阵[R|t]被称作外参数矩阵,它用来描述相机相对于一个固定场景的运动,或者相反,物体围绕相机的的刚性运动。也就是[R|t]将点(X, Y, Z)的坐标变换到某个坐标系,这个坐标系相对于摄像机来说是固定不变的。上面的变换等价与下面的形式(z≠0):

    \begin{bmatrix}x \\ y \\z \end{bmatrix} = R \cdot \begin{bmatrix}X \\ Y \\Z \end{bmatrix} + t

    x' = x / z

    y' = y / z

    u=fx \cdot x' + cx

    v=fy \cdot y' + cy

    真正的镜头通常有一些形变,主要的变形为径向形变,也会有轻微的切向形变。所以上面的模型可以扩展为:

    \begin{bmatrix}x \\ y \\z \end{bmatrix} = R \cdot \begin{bmatrix}X \\ Y \\Z \end{bmatrix} + t

    x' = x / z

    y' = y / z

    x'' = x' \cdot (1 + k_1 \cdot r^2 + k_2 \cdot r^4) + 2 \cdot p_1 \cdot x'\cdot y' + p_2 \cdot (r^2+2x'^2)

    y'' = y' \cdot (1 + k_1 \cdot r^2 + k_2 \cdot r^4) + p_1 \cdot (r^2+2 \cdot y'^2) + 2 \cdot p_2 \cdot x'\cdot y'

    这里 r2 = x'2 + y'2

    u = fx \cdot x'' + cx

    v = fy \cdot y'' + cy

    k1k2是径向形变系数,p1p1是切向形变系数。OpenCV中没有考虑高阶系数。形变系数跟拍摄的场景无关,因此它们是内参数,而且与拍摄图像的分辨率无关。

    后面的函数使用上面提到的模型来做如下事情:

    • 给定内参数和外参数,投影三维点到图像平面。
    • 给定内参数、几个三维点坐标和其对应的图像坐标,来计算外参数。
    • 根据已知的定标模式,从几个角度(每个角度都有几个对应好的3D-2D点对)的照片来计算相机的外参数和内参数。

    照相机定标

    ProjectPoints2

    投影三维点到图像平面

    void cvProjectPoints2( const CvMat* object_points, const CvMat* rotation_vector,
                           const CvMat* translation_vector, const CvMat* intrinsic_matrix,
                           const CvMat* distortion_coeffs, CvMat* image_points,
                           CvMat* dpdrot=NULL, CvMat* dpdt=NULL, CvMat* dpdf=NULL,
                           CvMat* dpdc=NULL, CvMat* dpddist=NULL );
    
    object_points
    物体点的坐标,为3xN或者Nx3的矩阵,这儿N是视图中的所有所有点的数目。
    rotation_vector
    旋转向量,1x3或者3x1。
    translation_vector
    平移向量,1x3或者3x1。
    intrinsic_matrix
    摄像机内参数矩阵A:\begin{bmatrix}fx & 0 & cx\\ 0 & fy & cy\\ 0&0&1\end{bmatrix}
    distortion_coeffs
    形变参数向量,4x1或者1x4,为[k1,k2,p1,p2]。如果是NULL,所有形变系数都设为0。
    image_points
    输出数组,存储图像点坐标。大小为2xN或者Nx2,这儿N是视图中的所有点的数目。
    dpdrot
    可选参数,关于旋转向量部分的图像上点的导数,Nx3矩阵。
    dpdt
    可选参数,关于平移向量部分的图像上点的导数,Nx3矩阵。
    dpdf
    可选参数,关于fx和fy的图像上点的导数,Nx2矩阵。
    dpdc
    可选参数,关于cx和cy的图像上点的导数,Nx2矩阵。
    dpddist
    可选参数,关于形变系数的图像上点的导数,Nx4矩阵。

    函数cvProjectPoints2通过给定的内参数和外参数计算三维点投影到二维图像平面上的坐标。另外,这个函数可以计算关于投影参数的图像点偏导数的雅可比矩阵。雅可比矩阵可以用在cvCalibrateCamera2和cvFindExtrinsicCameraParams2函数的全局优化中。这个函数也可以用来计算内参数和外参数的反投影误差。注意,将内参数和(或)外参数设置为特定值,这个函数可以用来计算外变换(或内变换)。

    FindHomography

    计算两个平面之间的透视变换

    void cvFindHomography( const CvMat* src_points,
                           const CvMat* dst_points,
                           CvMat* homography );
    
    src_points
    原始平面的点坐标,大小为2xN,Nx2,3xN或者 Nx3矩阵(后两个表示齐次坐标),这儿N表示点的数目。
    dst_points
    目标平面的点坐标大小为2xN,Nx2,3xN或者 Nx3矩阵(后两个表示齐次坐标)。
    homography
    输出的3x3的homography矩阵。

    函数cvFindHomography计算源平面和目标平面之间的透视变换H=\begin{bmatrix}h_{ij}\end{bmatrix}_{i,j}.

    s_i \begin{bmatrix}x'_i \\ y'_i \\ 1\end{bmatrix}  \approx  H  \begin{bmatrix}x_i \\ y_i \\ 1\end{bmatrix}

    使得反投影错误最小:

    \sum_i((x'_i-\frac{h_{11}x_i + h_{12}y_i + h_{13}}{h_{31}x_i + h_{32}y_i + h_{33}})^2+          (y'_i-\frac{h_{21}x_i + h_{22}y_i + h_{23}}{h_{31}x_i + h_{32}y_i + h_{33}})^2)

    这个函数可以用来计算初始的内参数和外参数矩阵。由于Homography矩阵的尺度可变,所以它被规一化使得h33 = 1

    CalibrateCamera2

    利用定标来计算摄像机的内参数和外参数

    void cvCalibrateCamera2( const CvMat* object_points, const CvMat* image_points,
                             const CvMat* point_counts, CvSize image_size,
                             CvMat* intrinsic_matrix, CvMat* distortion_coeffs,
                             CvMat* rotation_vectors=NULL,
                             CvMat* translation_vectors=NULL,
                             int flags=0 );
    
    object_points
    定标点的世界坐标,为3xN或者Nx3的矩阵,这里N是所有视图中点的总数。
    image_points
    定标点的图像坐标,为2xN或者Nx2的矩阵,这里N是所有视图中点的总数。
    point_counts
    向量,指定不同视图里点的数目,1xM或者Mx1向量,M是视图数目。
    image_size
    图像大小,只用在初始化内参数时。
    intrinsic_matrix
    输出内参矩阵(A) \begin{bmatrix}fx & 0 & cx\\ 0 & fy & cy \\ 0&0&1\end{bmatrix},如果指定CV_CALIB_USE_INTRINSIC_GUESS和(或)CV_CALIB_FIX_ASPECT_RATION,fx、 fy、 cx和cy部分或者全部必须被初始化。
    distortion_coeffs
    输出大小为4x1或者1x4的向量,里面为形变参数[k1, k2, p1, p2]。
    rotation_vectors
    输出大小为3xM或者Mx3的矩阵,里面为旋转向量(旋转矩阵的紧凑表示方式,具体参考函数cvRodrigues2)
    translation_vectors
    输出大小为3xM或Mx3的矩阵,里面为平移向量。
    flags
    不同的标志,可以是0,或者下面值的组合:
    • CV_CALIB_USE_INTRINSIC_GUESS - 内参数矩阵包含fx,fy,cx和cy的初始值。否则,(cx, cy)被初始化到图像中心(这儿用到图像大小),焦距用最小平方差方式计算得到。注意,如果内部参数已知,没有必要使用这个函数,使用cvFindExtrinsicCameraParams2则可。
    • CV_CALIB_FIX_PRINCIPAL_POINT - 主点在全局优化过程中不变,一直在中心位置或者在其他指定的位置(当CV_CALIB_USE_INTRINSIC_GUESS设置的时候)。
    • CV_CALIB_FIX_ASPECT_RATIO - 优化过程中认为fx和fy中只有一个独立变量,保持比例fx/fy不变,fx/fy的值跟内参数矩阵初始化时的值一样。在这种情况下, (fx, fy)的实际初始值或者从输入内存矩阵中读取(当CV_CALIB_USE_INTRINSIC_GUESS被指定时),或者采用估计值(后者情况中fx和fy可能被设置为任意值,只有比值被使用)。
    • CV_CALIB_ZERO_TANGENT_DIST – 切向形变参数(p1, p2)被设置为0,其值在优化过程中保持为0。

    函数cvCalibrateCamera2从每个视图中估计相机的内参数和外参数。3维物体上的点和它们对应的在每个视图的2维投影必须被指定。这些可以通过使用一个已知几何形状且具有容易检测的特征点的物体来实现。这样的一个物体被称作定标设备或者定标模式,OpenCV有内建的把棋盘当作定标设备方法(参考cvFindChessboardCorners)。目前,传入初始化的内参数(当CV_CALIB_USE_INTRINSIC_GUESS不被设置时)只支持平面定标设备(物体点的Z坐标必须为全0或者全1)。不过3维定标设备依然可以用在提供初始内参数矩阵情况。在内参数和外参数矩阵的初始值都计算出之后,它们会被优化用来减小反投影误差(图像上的实际坐标跟cvProjectPoints2计算出的图像坐标的差的平方和)。

    FindExtrinsicCameraParams2

    计算指定视图的摄像机外参数

    void cvFindExtrinsicCameraParams2( const CvMat* object_points,
                                       const CvMat* image_points,
                                       const CvMat* intrinsic_matrix,
                                       const CvMat* distortion_coeffs,
                                       CvMat* rotation_vector,
                                       CvMat* translation_vector );
    
    object_points
    定标点的坐标,为3xN或者Nx3的矩阵,这里N是视图中的个数。
    image_points
    定标点在图像内的坐标,为2xN或者Nx2的矩阵,这里N是视图中的个数。
    intrinsic_matrix
    内参矩阵(A) \begin{bmatrix}fx & 0 & cx\\ 0 & fy & cy \\ 0&0&1\end{bmatrix}
    distortion_coeffs
    大小为4x1或者1x4的向量,里面为形变参数[k1,k2,p1,p2]。如果是NULL,所有的形变系数都为0。
    rotation_vector
    输出大小为3x1或者1x3的矩阵,里面为旋转向量(旋转矩阵的紧凑表示方式,具体参考函数cvRodrigues2)。
    translation_vector
    大小为3x1或1x3的矩阵,里面为平移向量。

    函数cvFindExtrinsicCameraParams2使用已知的内参数和某个视图的外参数来估计相机的外参数。3维物体上的点坐标和相应的2维投影必须被指定。这个函数也可以用来最小化反投影误差。

    Rodrigues2

    进行旋转矩阵和旋转向量间的转换

    int  cvRodrigues2( const CvMat* src, CvMat* dst, CvMat* jacobian=0 );
    
    src
    输入的旋转向量(3x1或者1x3)或者旋转矩阵(3x3)。
    dst
    输出的旋转矩阵(3x3)或者旋转向量(3x1或者1x3)
    jacobian
    可选的输出雅可比矩阵(3x9或者9x3),关于输入部分的输出数组的偏导数。

    函数转换旋转向量到旋转矩阵,或者相反。旋转向量是旋转矩阵的紧凑表示形式。旋转向量的方向是旋转轴,向量的长度是围绕旋转轴的旋转角。旋转矩阵R,与其对应的旋转向量r,通过下面公式转换:

    \theta \leftarrow norm(r)

    r \leftarrow r/\theta

    R = \cos(\theta)I + (1-\cos(\theta))rr^T + \sin(\theta) \begin{bmatrix}0&-r_z&r_y\\ r_z&0&-r_x\\ -r_y&r_x&0\end{bmatrix}

    反变换也可以很容易的通过如下公式实现:

    \sin(\theta) \begin{bmatrix}0&-r_z&r_y\\ r_z&0&-r_x\\ -r_y&r_x&0\end{bmatrix} = \frac{R-R^T}{2}

    旋转向量是只有3个自由度的旋转矩阵一个方便的表示,这种表示方式被用在函数cvFindExtrinsicCameraParams2和cvCalibrateCamera2内部的全局最优化中。

    Undistort2

    校正图像因相机镜头引起的变形

    void cvUndistort2( const CvArr* src, CvArr* dst,
                       const CvMat* intrinsic_matrix,
                       const CvMat* distortion_coeffs );
    
    src
    原始图像(已经变形的图像)。只能变换32fC1的图像。
    dst
    结果图像(已经校正的图像)。
    intrinsic_matrix
    相机内参数矩阵,格式为 \begin{bmatrix}fx & 0 & cx\\ 0 & fy & cy\\ 0&0&1\end{bmatrix}
    distortion_coeffs
    四个变形系数组成的向量,大小为4x1或者1x4,格式为[k1,k2,p1,p2]

    函数cvUndistort2对图像进行变换来抵消径向和切向镜头变形。相机参数和变形参数可以通过函数cvCalibrateCamera2取得。使用本节开始时提到的公式,对每个输出图像像素计算其在输入图像中的位置,然后输出图像的像素值通过双线性插值来计算。如果图像得分辨率跟定标时用得图像分辨率不一样,fx、fy、cx和cy需要相应调整,因为形变并没有变化。

    InitUndistortMap

    计算形变和非形变图像的对应(map)

    void cvInitUndistortMap( const CvMat* intrinsic_matrix,
                             const CvMat* distortion_coeffs,
                             CvArr* mapx, CvArr* mapy );
    
    intrinsic_matrix
    摄像机内参数矩阵(A) [fx 0 cx; 0 fy cy; 0 0 1].
    distortion_coeffs
    形变系数向量[k1, k2, p1, p2],大小为4x1或者1x4。
    mapx
    x坐标的对应矩阵。
    mapy
    y坐标的对应矩阵。

    函数cvInitUndistortMap预先计算非形变对应-正确图像的每个像素在形变图像里的坐标。这个对应可以传递给cvRemap函数(跟输入和输出图像一起)。

    FindChessboardCorners

    寻找棋盘图的内角点位置

    int cvFindChessboardCorners( const void* image, CvSize pattern_size,
                                 CvPoint2D32f* corners, int* corner_count=NULL,
                                 int flags=CV_CALIB_CB_ADAPTIVE_THRESH );
    
    image
    输入的棋盘图,必须是8位的灰度或者彩色图像。
    pattern_size
    棋盘图中每行和每列角点的个数。
    corners
    检测到的角点
    corner_count
    输出,角点的个数。如果不是NULL,函数将检测到的角点的个数存储于此变量。
    flags
    各种操作标志,可以是0或者下面值的组合:
    • CV_CALIB_CB_ADAPTIVE_THRESH - 使用自适应阈值(通过平均图像亮度计算得到)将图像转换为黑白图,而不是一个固定的阈值。
    • CV_CALIB_CB_NORMALIZE_IMAGE - 在利用固定阈值或者自适应的阈值进行二值化之前,先使用cvNormalizeHist来均衡化图像亮度。
    • CV_CALIB_CB_FILTER_QUADS - 使用其他的准则(如轮廓面积,周长,方形形状)来去除在轮廓检测阶段检测到的错误方块。

    函数cvFindChessboardCorners试图确定输入图像是否是棋盘模式,并确定角点的位置。如果所有角点都被检测到且它们都被以一定顺序排布(一行一行地,每行从左到右),函数返回非零值,否则在函数不能发现所有角点或者记录它们地情况下,函数返回0。例如一个正常地棋盘图右8x8个方块和7x7个内角点,内角点是黑色方块相互联通地位置。这个函数检测到地坐标只是一个大约地值,如果要精确地确定它们的位置,可以使用函数cvFindCornerSubPix。

    DrawChessBoardCorners

    绘制检测到的棋盘角点

    void cvDrawChessboardCorners( CvArr* image, CvSize pattern_size,
                                  CvPoint2D32f* corners, int count,
                                  int pattern_was_found );
    
    image
    结果图像,必须是8位彩色图像。
    pattern_size
    每行和每列地内角点数目。
    corners
    检测到地角点数组。
    count
    角点数目。
    pattern_was_found
    指示完整地棋盘被发现(≠0)还是没有发现(=0)。可以传输cvFindChessboardCorners函数的返回值。

    当棋盘没有完全检测出时,函数cvDrawChessboardCorners以红色圆圈绘制检测到的棋盘角点;如果整个棋盘都检测到,则用直线连接所有的角点。

    姿态估计

    CreatePOSITObject

    初始化包含对象信息的结构

    CvPOSITObject* cvCreatePOSITObject( CvPoint3D32f* points, int point_count );
    
    points
    指向三维对象模型的指针
    point_count
    对象的点数

    函数 cvCreatePOSITObject 为对象结构分配内存并计算对象的逆矩阵。

    预处理的对象数据存储在结构CvPOSITObject中,只能在OpenCV内部被调用,即用户不能直接读写数据结构。用户只可以创建这个结构并将指针传递给函数。

    对象是在某坐标系内的一系列点的集合,函数 cvPOSIT计算从照相机坐标系中心到目标点points[0] 之间的向量。

    一旦完成对给定对象的所有操作,必须使用函数cvReleasePOSITObject释放内存。

    POSIT

    执行POSIT算法

    void cvPOSIT( CvPOSITObject* posit_object, CvPoint2D32f* image_points, 
                  double focal_length,
                  CvTermCriteria criteria, CvMatr32f rotation_matrix, 
                  CvVect32f translation_vector );
    
    posit_object
    指向对象结构的指针
    image_points
    指针,指向目标像素点在二维平面图上的投影。
    focal_length
    使用的摄像机的焦距
    criteria
    POSIT迭代算法程序终止的条件
    rotation_matrix
    旋转矩阵
    translation_vector
    平移矩阵.

    函数 cvPOSIT 执行POSIT算法。图像坐标在摄像机坐标系统中给出。焦距可以通过摄像机标定得到。算法每一次迭代都会重新计算在估计位置的透视投影。

    两次投影之间的范式差值是对应点中的最大距离。如果差值过小,参数criteria.epsilon就会终止程序。

    ReleasePOSITObject

    释放3D对象结构

    void cvReleasePOSITObject( CvPOSITObject** posit_object );
    
    posit_object
    指向 CvPOSIT 结构指针的指针。

    函数 cvReleasePOSITObject 释放函数 cvCreatePOSITObject分配的内存。

    CalcImageHomography

    计算长方形或椭圆形平面对象(例如胳膊)的Homography矩阵

    void cvCalcImageHomography( float* line, CvPoint3D32f* center,
                                float* intrinsic, float* homography );
    
    line
    对象的主要轴方向,为向量(dx,dy,dz).
    center
    对象坐标中心 ((cx,cy,cz)).
    intrinsic
    摄像机内参数 (3x3 matrix).
    homography
    输出的Homography矩阵(3x3).

    函数 cvCalcImageHomography 为从图像平面到图像平面的初始图像变化(defined by 3D oblong object line)计算Homography矩阵。

    对极几何(双视几何)

    FindFundamentalMat

    由两幅图像中对应点计算出基本矩阵

    int cvFindFundamentalMat( const CvMat* points1,
                              const CvMat* points2,
                              CvMat* fundamental_matrix,
                              int    method=CV_FM_RANSAC,
                              double param1=1.,
                              double param2=0.99,
                              CvMat* status=NULL);
    
    points1
    第一幅图像点的数组,大小为2xN/Nx2 或 3xN/Nx3 (N 点的个数),多通道的1xN或Nx1也可以。点坐标应该是浮点数(双精度或单精度)。:
    points2
    第二副图像的点的数组,格式、大小与第一幅图像相同。
    fundamental_matrix
    输出的基本矩阵。大小是 3x3 或者 9x3 ,(7-点法最多可返回三个矩阵).
    method
    计算基本矩阵的方法
    • CV_FM_7POINT – 7-点算法,点数目= 7
    • CV_FM_8POINT – 8-点算法,点数目 >= 8
    • CV_FM_RANSAC – RANSAC 算法,点数目 >= 8
    • CV_FM_LMEDS - LMedS 算法,点数目 >= 8
    param1
    这个参数只用于方法RANSAC 或 LMedS 。它是点到对极线的最大距离,超过这个值的点将被舍弃,不用于后面的计算。通常这个值的设定是0.5 or 1.0 。
    param2
    这个参数只用于方法RANSAC 或 LMedS 。 它表示矩阵正确的可信度。例如可以被设为0.99 。
    status
    具有N个元素的输出数组,在计算过程中没有被舍弃的点,元素被被置为1;否则置为0。这个数组只可以在方法RANSAC and LMedS 情况下使用;在其它方法的情况下,status一律被置为1。这个参数是可选参数。

    对极几何可以用下面的等式描述:

    p_2^T \cdot F \cdot p_1=0

    其中 F 是基本矩阵,p1 p2 分别是两幅图上的对应点。

    函数 FindFundamentalMat 利用上面列出的四种方法之一计算基本矩阵,并返回基本矩阵的值:没有找到矩阵,返回0,找到一个矩阵返回1,多个矩阵返回3。 计算出的基本矩阵可以传递给函数cvComputeCorrespondEpilines来计算指定点的对极线。

    例子1:使用 RANSAC 算法估算基本矩阵。
    int    numPoints = 100;
    CvMat* points1;
    CvMat* points2;
    CvMat* status;
    CvMat* fundMatr;
    points1 = cvCreateMat(2,numPoints,CV_32F);
    points2 = cvCreateMat(2,numPoints,CV_32F);
    status  = cvCreateMat(1,numPoints,CV_32F);
    
    /* 在这里装入对应点的数据... */
    
    fundMatr = cvCreateMat(3,3,CV_32F);
    int num = cvFindFundamentalMat(points1,points2,fundMatr,CV_FM_RANSAC,1.0,0.99,status);
    if( num == 1 )
         printf("Fundamental matrix was found\n");
    else
         printf("Fundamental matrix was not found\n");
    
    
    例子2:7点算法(3个矩阵)的情况。
    CvMat* points1;
    CvMat* points2;
    CvMat* fundMatr;
    points1 = cvCreateMat(2,7,CV_32F);
    points2 = cvCreateMat(2,7,CV_32F);
    
    /* 在这里装入对应点的数据... */
    
    fundMatr = cvCreateMat(9,3,CV_32F);
    int num = cvFindFundamentalMat(points1,points2,fundMatr,CV_FM_7POINT,0,0,0);
    printf("Found %d matrixes\n",num);
    

    ComputeCorrespondEpilines

    为一幅图像中的点计算其在另一幅图像中对应的对极线。

    void cvComputeCorrespondEpilines( const CvMat* points,
                                      int which_image,
                                      const CvMat* fundamental_matrix,
                                      CvMat* correspondent_lines);
    
    points
    输入点,是2xN 或者 3xN 数组 (N为点的个数)
    which_image
    包含点的图像指数(1 or 2)
    fundamental_matrix
    基本矩阵
    correspondent_lines
    计算对极点, 3xN数组

    函数 ComputeCorrespondEpilines 根据外级线几何的基本方程计算每个输入点的对应外级线。如果点位于第一幅图像(which_image=1),对应的对极线可以如下计算 :

    l_2=F \cdot p_1

    其中F是基本矩阵,p1 是第一幅图像中的点, l2 - 是与第二幅对应的对极线。如果点位于第二副图像中 which_image=2),计算如下:

    l_1=F^T \cdot p_2

    其中p2 是第二幅图像中的点,l1 是对应于第一幅图像的对极线,每条对极线都可以用三个系数表示 a, b, c:

    a\cdot x + b\cdot y + c = 0

    归一化后的对极线系数存储在correspondent_lines 中。

    ConvertPointsHomogenious

    Convert points to/from homogenious coordinates

    void cvConvertPointsHomogenious( const CvMat* src, CvMat* dst );
    
    src
    The input point array, 2xN, Nx2, 3xN, Nx3, 4xN or Nx4 (where N is the number of points). Multi-channel 1xN or Nx1 array is also acceptable.
    dst
    The output point array, must contain the same number of points as the input; The dimensionality must be the same, 1 less or 1 more than the input, and also within 2..4.

    The function cvConvertPointsHomogenious converts 2D or 3D points from/to homogenious coordinates, or simply copies or transposes the array. In case if the input array dimensionality is larger than the output, each point coordinates are divided by the last coordinate:

    (x,y[,z],w) -> (x',y'[,z'])
    其中
    x' = x/w
    y' = y/w
    z' = z/w (if output is 3D)

    If the output array dimensionality is larger, an extra 1 is appended to each point.

    (x,y[,z]) -> (x,y[,z],1)

    Otherwise, the input array is simply copied (with optional tranposition) to the output. Note that, because the function accepts a large variety of array layouts, it may report an error when input/output array dimensionality is ambiguous. It is always safe to use the function with number of points N>=5, or to use multi-channel Nx1 or 1xN arrays.

    展开全文
  • 本文分为两部分,一部分是介绍鱼眼相机畸变校正的原理,一部分是手撕OpenCV相机矫正代码。 文章主要结构如下图所示: 介绍鱼眼相机的原理 什么是鱼眼相机 相机内参标定 相机内外参标定 畸变矫正 径向畸变 枕型畸变 ...

    首先展示一下实现的效果:
    校正前:
    在这里插入图片描述
    校正后:
    在这里插入图片描述

    本文分为两部分,一部分是介绍鱼眼相机畸变校正的原理,一部分是手撕OpenCV相机矫正代码。
    文章主要结构如下图所示:

    在这里插入图片描述

    一、介绍鱼眼相机的原理

    1、什么是鱼眼镜头

    鱼眼镜头是具有超广视角的镜头。与一般的广角镜头、超广角镜头相比,在设计规格上,鱼眼镜头的焦距更短、视角更广。相同画幅和像素尺寸下,鱼眼镜头能容纳更多景物信息。

    相机需要标定的参数通常分为内参和外参两部分。外参确定了相机在某个三维空间中的位置和朝向,至于内参,可以说是相机内部的参数

    2、相机标定

    一句话就是世界坐标到像素坐标的映射,当然这个世界坐标是我们人为去定义的,标定就是已知标定控制点的世界坐标和像素坐标我们去解算这个映射关系,一旦这个关系解算出来了我们就可以由点的像素坐标去反推它的世界坐标,当然有了这个世界坐标,我们就可以进行测量等其他后续操作了~上述标定又被称作隐参数标定,因为它没有单独求出相机的内部参数,如相机焦虑,相机畸变系数等~一般来说如果你仅仅只是利用相机标定来进行一些比较简单的视觉测量的话,那么就没有必要单独标定出相机的内部参数了~至于相机内部参数如何解算,相关论文讲的很多~

    在图像测量过程以及机器视觉应用中,为确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系,必须建立相机成像的几何模型,这些几何模型参数就是相机参数。在大多数条件下这些参数必须通过实验与计算才能得到,这个求解参数的过程就称之为相机标定(或摄像机标定)

    相机标定的目的是确定相机的一些参数的值。

    2.1、相机内参

    相机内参数是与相机自身特性相关的参数,比如相机的焦距、像素大小等;

    • 理想情况下,镜头的光轴(就是通过镜头中心垂直于传感器平面的直线)应该是穿过图像的正中间的,但是,实际由于安装精度的问题,总是存在误差,这种误差需要用内参来描述
    • 理想情况下,相机对x方向和y方向的尺寸的缩小比例是一样的,但实际上,镜头如果不是完美的圆,传感器上的像素如果不是完美的紧密排列的正方形,都可能会导致这两个方向的缩小比例不一致。内参中包含两个参数可以描述这两个方向的缩放比例,不仅可以将用像素数量来衡量的长度转换成三维空间中的用其它单位(比如米)来衡量的长度,也可以表示在x和y方向的尺度变换的不一致性
    • 理想情况下,镜头会将一个三维空间中的直线也映射成直线(即射影变换),但实际上,镜头无法这么完美,通过镜头映射之后,直线会变弯,所以需要相机的畸变参数来描述这种变形效果。
      内参:下面给出了内参矩阵,需要注意的是,真实的镜头还会有径向和切向畸变,而这些畸变是属于相机的内参的。
      摄像机内参矩阵
      K=[fxsx00fyy0001] K = \left[ \begin{matrix} f_x & s & x_0 \\ 0 & f_y & y_0\\ 0 & 0 & 1\\ \end{matrix} \right]
    • fx,fy为焦距,一般情况下,二者相等
    • x0y0x_0、y_0为主点坐标(相对于成像平面)
    • s为坐标轴倾斜参数,理想情况下为0

    2.2、相机外参

    相机外参数是在世界坐标系中的参数,比如相机的位置、旋转方向等。
    在opencv的3D重建中(opencv中文网站中:照相机定标与三维场景重建),对摄像机的内参外参有讲解:
    外参:摄像机的旋转平移属于外参,用于描述相机在静态场景下相机的运动,或者在相机固定时,运动物体的刚性运动。因此,在图像拼接或者三维重建中,就需要使用外参来求几幅图像之间的相对运动,从而将其注册到同一个坐标系下面来

    摄像机外参矩阵:包括旋转矩阵和平移矩阵
    旋转矩阵和平移矩阵共同描述了如何把点从世界坐标系转换到摄像机坐标系

    • 旋转矩阵:描述了世界坐标系的坐标轴相对于摄像机坐标轴的方向
    • 平移矩阵:描述了在摄像机坐标系下,空间原点的位置
      例如:
    <extrinsic_parameters type_id="opencv-matrix">
      <rows>12</rows>
      <cols>6</cols>
      <dt>d</dt>
      <data>
        -2.5980408402571618e-02 1.1815251764239771e-02
        -2.1369682066260900e-02 -2.6259172744227482e+02
        -1.5013314270422163e+02 6.5076114096030335e+02
        1.8783574438932601e-01 3.4080574788432614e-03
        -4.4819225470020402e-02 -2.6249825531835506e+02
        -1.4332020633438907e+02 6.1417086164375849e+02
        -4.3948274340549737e-02 -1.5901241590628848e-01
        -3.8070877456305267e-02 -2.9192567949352303e+02
        -1.4523400761499192e+02 6.1248631156753356e+02
        -5.4180921683236474e-02 3.8204632641184355e-01
        1.4654409619692103e-02 -2.8342586424046874e+02
        -1.5195698577413373e+02 7.4503196408056158e+02
        -4.5574902681178746e-02 1.3175472526942796e-02
        -1.9316588833120132e-01 -2.9165913225243480e+02
        -1.2569187127204076e+02 6.3575994821752818e+02
        -2.1596684373807951e-01 -4.2158251451727510e-02
        -1.4250602265305079e-01 -1.5172004342333557e+02
        -1.0711546660350552e+02 6.5294093975274677e+02
        -1.8339477927677919e-01 -2.5276018993027234e-02
        -9.7860967431765103e-02 -1.8603080905622281e+02
        -1.4066246347070623e+02 6.3968745725872861e+02
        -1.7675445550053226e-01 3.6291116791742351e-02
        -5.7415047682237985e-02 -3.5842711857221030e+02
        -1.5015910813345465e+02 6.6496844041651650e+02
        -7.0610096263342581e-02 8.7348526184524447e-02
        -7.1550047486137608e-02 -2.5689394340378067e+02
        -1.5243522999020644e+02 6.5248719695970749e+02
        -4.8431448610766675e-01 -4.5116123706362796e-02
        -7.3223677555524391e-02 -1.6946287338934377e+02
        -1.3605175642753815e+02 6.4909482484246496e+02
        -1.2883187834587770e-01 4.8080549840749598e-01
        -7.1390631045400624e-02 -1.8691743164161142e+02
        -1.2880102625032143e+02 7.7423326914508300e+02
        -7.1443094165163687e-02 -1.4045350103334617e-01
        -1.3881443299063451e-01 -2.3481792226980730e+02
        -1.1796085620421017e+02 6.1266568621131535e+02
      </data>
    </extrinsic_parameters>
    

    3、参数矩阵

    矩阵名称 解释
    外参数矩阵 告诉你现实世界点(世界坐标)是怎样经过旋转(R)和平移(T),然后落到另一个现实世界点(摄像机坐标)上。
    内参数矩阵 告诉你上述那个点在摄像机坐标的基础上,是如何继续经过摄像机的镜头、并通过针孔成像和电子转化而成为像素点的。
    畸变矩阵 告诉你为什么上面那个像素点并没有落在理论计算该落在的位置上,还产生了偏移和变形

    4、畸变矫正

    4.1、径向畸变
    4.1.1、枕型畸变
    4.1.2桶型畸变
    4.2、切向畸变
    4.2.1、薄透镜畸变
    4.2.2、离心畸变

    参考文章:https://blog.csdn.net/liulina603/article/details/52953414

    二、OpenCV代码实现

    1、编译环境

    名称 版本
    操作系统 Ubuntu18.04TLS
    OpenCV 4.3
    Clion 2020.1
    编译语言 C++

    1.1、安装OpenCV

    首先是下载OpenCV库:

    1. 添加依赖
    $ sudo apt-get install build-essential
    $ sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev
    $ sudo apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev # 处理图像所需的包
    $ sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev liblapacke-dev
    $ sudo apt-get install libxvidcore-dev libx264-dev # 处理视频所需的包
    $ sudo apt-get install libatlas-base-dev gfortran # 优化opencv功能
    $ sudo apt-get install ffmpeg
    
    1. 下载opencv:https://github.com/opencv/opencv/releases
      我下载的是4.3.0
      在这里插入图片描述

    2. 将下载的opencv进行解压,进入 opencv的目录 新建文件夹(build), 在新建的文件夹(build)里面构建opencv 进行编译,这样不容易出错

    $ mkdir build
    $ cd build
    $ cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local ..
    
    1. 编译 sudo make -j4可以喝茶去了
    2. 然后安装 sudo make install
    3. 添加库的路径 和环境变量
    $ sudo gedit /etc/ld.so.conf.d/opencv.conf
    

    写入/usr/local/lib
    sudo ldconfig 生效
    sudo gedit /etc/profile添加环境变量

    PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig  
    export PKG_CONFIG_PATH 
    
    1. 进行测试
      进入opecvsamples/cpp/example_cmake 执行如下
    $ cmake .
    $ make
    $ ./opencv_example
    

    参考文章地址:https://blog.csdn.net/qq_30951423/article/details/80914319

    1.2、安装Clion

    1. 打开Clion官网:https://www.jetbrains.com/clion/
      在这里插入图片描述
    2. 进入到下载的文件夹下,打开Terminal,输入命令tar -zxvf CLion-2020.1.2.tar.gz 进行解压
    3. 进入Bin目录下,运行clion.sh
    cd clion-2020.1.2/bin/  
    ./clion.sh  
    

    1.3 配置Clion

    1. 新建工程。
      我新建工程名字为opencv
    2. 修改CMakeLists.txt文件
      在CMakeLists里导入OpenCV相关的包,配置如下:
    cmake_minimum_required(VERSION 3.10)
    project(opencv)
    find_package(OpenCV REQUIRED)
    include_directories(${OpenCV_INCLUDE_DIRS})
    set(CMAKE_CXX_STANDARD 14)
    set(SOURCE_FILES main.cpp)
    add_executable(opencv main.cpp)
    target_link_libraries(opencv ${OpenCV_LIBS})
    

    最后两行是告诉Cmake,把main.cpp编译成了一个可执行文件opencv,然后让这个可以执行的文件去连接OpenCV的动态库。
    3. 测试示例程序
    在main.cpp里添加下面的代码

    #include <iostream>
    #include <vector>
    #include "opencv2/core/core.hpp"
    #include "opencv2/opencv.hpp"
    #include "opencv2/highgui/highgui.hpp"
    using namespace std;
    using namespace cv;
    int main() {
        Mat img = imread("../cat.jpg");
        namedWindow("DisplayImage");
        imshow("Cat", img);
        waitKey();
        return 0;
    }
    

    记得下载一个喜欢的猫咪图片放在刚才创建的project目录下,或者把路径换成绝对路径。运行就能看到一个萌萌的猫咪图片了,这时配置就完成了。
    参考文章:https://marvae.github.io/2018-09-06/opencv-install/

    2、代码解析

    1. 创建工程
      首先在Clion创建一个工程,这里我的工程名字叫Camera_calibration
    2. 关联OpenCV
      然后在 CMakeLists.txt 文件中添加如下信息。其中紫色部分为项目名称

    在这里插入图片描述

    cmake_minimum_required(VERSION 3.10)
    project(Camera_calibration)
    
    find_package(OpenCV REQUIRED)
    include_directories(${OpenCV_INCLUDE_DIRS})
    
    set(CMAKE_CXX_STANDARD 14)
    set(SOURCE_FILES main.cpp)
    
    add_executable(Camera_calibration main.cpp)
    target_link_libraries(Camera_calibration ${OpenCV_LIBS})
    
    1. 标定相机外参,确定相机内参
      我们是通过标定相机的外部参数来确定相机内参。
      外部文件是CameraPara.xml
    <?xml version="1.0"?>
    <opencv_storage>
        <Settings>
            <!-- Number of inner corners per a item row and column. (square, circle) -->
            <BoardSize_Width>10</BoardSize_Width>
            <BoardSize_Height>7</BoardSize_Height>
    
            <!-- The size of a square in some user defined metric system (pixel, millimeter)像素大小 -->
            <Square_Size>50</Square_Size>
    
            <!-- The type of input used for camera calibration. One of: CHESSBOARD CIRCLES_GRID ASYMMETRIC_CIRCLES_GRID 标定图案类型-->
            <Calibrate_Pattern>"CHESSBOARD"</Calibrate_Pattern>
    
            <!-- The input to use for calibration.
                  To use an input camera -> give the ID of the camera, like "1"
                  To use an input video  -> give the path of the input video, like "/tmp/x.avi"
                  To use an image list   -> give the path to the XML or YAML file containing the list of the images, like "/tmp/circles_list.xml"
                  -->
            <Input>"VID5.xml"</Input>
            <!--  If true (non-zero) we flip the input images around the horizontal axis. 为1,输入图像就围绕水平轴翻转-->
            <Input_FlipAroundHorizontalAxis>0</Input_FlipAroundHorizontalAxis>
    
            <!-- Time delay between frames in case of camera. 相机每帧之间的延时-->
            <Input_Delay>100</Input_Delay>
    
            <!-- How many frames to use, for calibration. 使用图片的数量-->
            <Calibrate_NrOfFrameToUse>18</Calibrate_NrOfFrameToUse>
            <!-- Consider only fy as a free parameter, the ratio fx/fy stays the same as in the input cameraMatrix.
                 Use or not setting. 0 - False Non-Zero - True 固定长宽比-->
            <Calibrate_FixAspectRatio> 1 </Calibrate_FixAspectRatio>
            <!-- If true (non-zero) tangential distortion coefficients  are set to zeros and stay zero.为1,切向失真系数为0-->
            <Calibrate_AssumeZeroTangentialDistortion>1</Calibrate_AssumeZeroTangentialDistortion>
            <!-- If true (non-zero) the principal point is not changed during the global optimization.将主要点固定在中心-->
            <Calibrate_FixPrincipalPointAtTheCenter> 1 </Calibrate_FixPrincipalPointAtTheCenter>
    
            <!-- The name of the output log file. 我理解的标定参数的文件-->
            <Write_outputFileName>"out_camera_data.xml"</Write_outputFileName>
            <!-- If true (non-zero) we write to the output file the feature points.-->
            <Write_DetectedFeaturePoints>1</Write_DetectedFeaturePoints>
            <!-- If true (non-zero) we write to the output file the extrinsic camera parameters.-->
            <Write_extrinsicParameters>1</Write_extrinsicParameters>
            <!-- If true (non-zero) we show after calibration the undistorted images.显示标定之后的不失真的图像-->
            <Show_UndistortedImage>1</Show_UndistortedImage>
            <!-- If true (non-zero) will be used fisheye camera model. 标定使用鱼眼模型-->
            <Calibrate_UseFisheyeModel>1</Calibrate_UseFisheyeModel>
            <!-- If true (non-zero) distortion coefficient k1 will be equals to zero.-->
            <Fix_K1>0</Fix_K1>
            <!-- If true (non-zero) distortion coefficient k2 will be equals to zero.-->
            <Fix_K2>0</Fix_K2>
            <!-- If true (non-zero) distortion coefficient k3 will be equals to zero.-->
            <Fix_K3>0</Fix_K3>
            <!-- If true (non-zero) distortion coefficient k4 will be equals to zero.-->
            <Fix_K4>1</Fix_K4>
            <!-- If true (non-zero) distortion coefficient k5 will be equals to zero.-->
            <Fix_K5>1</Fix_K5>
        </Settings>
    </opencv_storage>
    
    • BoardSize_Width,BoardSize_Height: 要拍摄的期盼的宽度和高度
    • Square_Size :一个像素的大小
    • Calibrate_Pattern:标定的图案 ,有CHESSBOARD CIRCLES_GRID ASYMMETRIC_CIRCLES_GRID三个类型可以选择
    • Input 输入的数据地址
    • Input_Delay 每一帧图片的延时
    • Calibrate_FixAspectRatio 固定长宽比
    • Calibrate_AssumeZeroTangentialDistortion 切向没有发生畸变
    • Calibrate_FixPrincipalPointAtTheCenter 将主要点固定在中心
    • Write_outputFileName 到处内参文件的路径
    • Write_DetectedFeaturePoints 写入检测到的特征点
    • Write_extrinsicParameters 写入外部参数
    • Show_UndistortedImage 显示矫正后的图像
    • Fix_K1,Fix_K2,Fix_K3,Fix_K4,Fix_K5 畸变系数是否为0
      其中Input结点是输入的路径,可以使camera的id 可以使视频 ,也可以是包含图片名称的列表的xml或者yml文件,
      在这里我们看到的时候VID.xml文件,我们会在下面讲到这个文件,请继续往下读吧~
    1. 编写主程序引入OpenCV库`
    #include <iostream>
    #include <sstream>
    #include <string>
    #include <ctime>
    #include <cstdio>
    // opencv 库
    #include <opencv2/core.hpp>
    #include <opencv2/core/utility.hpp>
    #include <opencv2/imgproc.hpp>
    #include <opencv2/calib3d.hpp>
    #include <opencv2/imgcodecs.hpp>
    #include <opencv2/videoio.hpp>
    #include <opencv2/highgui.hpp>
    
    using namespace cv;
    using namespace std;
    
    1. 创建Setting
      Setting类主要方法如下。
      在这里插入图片描述
      5.1. 首先来声明一下共有成员和私有成员:
    class Settings
    {
    public:
        Size boardSize;              // The size of the board -> Number of items by width and height
        Pattern calibrationPattern;  // One of the Chessboard, circles, or asymmetric circle pattern
        float squareSize;            // The size of a square in your defined unit (point, millimeter,etc).
        int nrFrames;                // The number of frames to use from the input for calibration
        float aspectRatio;           // The aspect ratio
        int delay;                   // In case of a video input
        bool writePoints;            // Write detected feature points
        bool writeExtrinsics;        // Write extrinsic parameters
        bool calibZeroTangentDist;   // Assume zero tangential distortion
        bool calibFixPrincipalPoint; // Fix the principal point at the center
        bool flipVertical;           // Flip the captured images around the horizontal axis
        string outputFileName;       // The name of the file where to write
        bool showUndistorsed;        // Show undistorted images after calibration
        string input;                // The input ->
        bool useFisheye;             // use fisheye camera model for calibration
        bool fixK1;                  // fix K1 distortion coefficient
        bool fixK2;                  // fix K2 distortion coefficient
        bool fixK3;                  // fix K3 distortion coefficient
        bool fixK4;                  // fix K4 distortion coefficient
        bool fixK5;                  // fix K5 distortion coefficient
    
        int cameraID;
        vector<string> imageList;
        size_t atImageList;
        VideoCapture inputCapture;
        InputType inputType;
        bool goodInput;
        int flag;
    
    private:
        string patternToUse;
    };
    

    5.2. 其中:
    enum 为枚举类型,Pattern为枚举图案类型,InputType为输入数据的类型,相关代码如下:

    class Settings
    {
    public:
        ......
        //枚举图案类型
         enum Pattern { NOT_EXISTING, CHESSBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID };
        //枚举输入数据的类型 无效  相机编号 视频列表 图像列表
         enum InputType { INVALID, CAMERA, VIDEO_FILE, IMAGE_LIST };
         }
    

    5.3. **函数write()**为将函数写入到fs(FileStorage)中,代码如下。
    Setting自带的相关共有成员写入到fs中。

    class Settings
    {
    public:
    ......
     // 将数据写入到fs中
        void write(FileStorage& fs) const                        //Write serialization for this class
        {
            fs << "{"
               << "BoardSize_Width"  << boardSize.width
               << "BoardSize_Height" << boardSize.height
               << "Square_Size"         << squareSize
               << "Calibrate_Pattern" << patternToUse
               << "Calibrate_NrOfFrameToUse" << nrFrames
               << "Calibrate_FixAspectRatio" << aspectRatio
               << "Calibrate_AssumeZeroTangentialDistortion" << calibZeroTangentDist
               << "Calibrate_FixPrincipalPointAtTheCenter" << calibFixPrincipalPoint
               << "Write_DetectedFeaturePoints" << writePoints
               << "Write_extrinsicParameters"   << writeExtrinsics
               << "Write_outputFileName"  << outputFileName
               << "Show_UndistortedImage" << showUndistorsed
               << "Input_FlipAroundHorizontalAxis" << flipVertical
               << "Input_Delay" << delay
               << "Input" << input
               << "}";
        }
    }
    

    5.4. 函数read() 将FileNode 中的内容赋值给this 的相关属性(相机的内参 外参),相关代码如下:

    class Settings
    {
    public:
    ......
     void read(const FileNode& node)                          //Read serialization for this class
        {
            node["BoardSize_Width" ] >> boardSize.width;
            node["BoardSize_Height"] >> boardSize.height;
            node["Calibrate_Pattern"] >> patternToUse;
            node["Square_Size"]  >> squareSize;
            node["Calibrate_NrOfFrameToUse"] >> nrFrames;
            node["Calibrate_FixAspectRatio"] >> aspectRatio;
            node["Write_DetectedFeaturePoints"] >> writePoints;
            node["Write_extrinsicParameters"] >> writeExtrinsics;
            node["Write_outputFileName"] >> outputFileName;
            node["Calibrate_AssumeZeroTangentialDistortion"] >> calibZeroTangentDist;
            node["Calibrate_FixPrincipalPointAtTheCenter"] >> calibFixPrincipalPoint;
            node["Calibrate_UseFisheyeModel"] >> useFisheye;
            node["Input_FlipAroundHorizontalAxis"] >> flipVertical;
            node["Show_UndistortedImage"] >> showUndistorsed;
            node["Input"] >> input;
            node["Input_Delay"] >> delay;
            node["Fix_K1"] >> fixK1;
            node["Fix_K2"] >> fixK2;
            node["Fix_K3"] >> fixK3;
            node["Fix_K4"] >> fixK4;
            node["Fix_K5"] >> fixK5;
    
            validate();
        }
    

    5.5. 函数readStringList() 读取文件filename,并返回列表l

     static bool readStringList( const string& filename, vector<string>& l )
        {
            l.clear();
            FileStorage fs(filename, FileStorage::READ);
            if( !fs.isOpened() ) //传入的文件打不开,就退出
                return false;
            FileNode n = fs.getFirstTopLevelNode();
            if( n.type() != FileNode::SEQ ) //传入的文件不是队列就退出
                return false;
            FileNodeIterator it = n.begin(), it_end = n.end(); //it是队列开头,it_end是队列结尾
            for( ; it != it_end; ++it )
                l.push_back((string)*it); //一直想l中添加图片路径
            return true;
        }
    

    5.6. **lisListOfImages()**函数判断列表中是否有图像

     static bool isListOfImages( const string& filename)
        {
            string s(filename);
            // Look for file extension
            if( s.find(".xml") == string::npos && s.find(".yaml") == string::npos && s.find(".yml") == string::npos )
                return false;
            else
                return true;
        }
    

    5.7. 函数validate(),开始先验证共有成员的数值是否异常,如果异常就说明输入异常,就将goodInput设置为false,一般默认input是在CameraPara.xml中的结点 "VID5.xml"对应的Value,这里指的是VID5.xml,里面存放的是图片的路径,接下来我们看一下VID.xml的内容吧~

    <?xml version="1.0"?>
    <opencv_storage>
    <images>
    images/1.jpg
    images/2.jpg
    images/3.jpg
    images/4.jpg
    images/5.jpg
    images/6.jpg
    images/7.jpg
    images/8.jpg
    images/9.jpg
    images/10.jpg
    images/11.jpg
    images/12.jpg
    images/13.jpg
    images/14.jpg
    images/15.jpg
    images/16.jpg
    images/17.jpg
    images/18.jpg
    </images>
    </opencv_storage>
    

    还有一个关于相机的内参的文件,我们先放到最后再讲吧
    5.8. 再来讲回validate()函数

      void validate()
        {
            goodInput = true;
            //属性 非 常值,报错
            if (boardSize.width <= 0 || boardSize.height <= 0)
            {
                cerr << "Invalid Board size: " << boardSize.width << " " << boardSize.height << endl;
                goodInput = false;
            }
            if (squareSize <= 10e-6) //判断像素是否小于接近0的数
            {
                cerr << "Invalid square size " << squareSize << endl;
                goodInput = false;
            }
            if (nrFrames <= 0)  // 判断图片是否小于0
            {
                cerr << "Invalid number of frames " << nrFrames << endl;
                goodInput = false;
            }
    
            if (input.empty())      // Check for valid input
                inputType = INVALID; //无效输入
            else
            {  // 有效输入
                if (input[0] >= '0' && input[0] <= '9')  //判断是否是数字,如果是数字,那么就认为输入类型是相机
                {
                    stringstream ss(input);
                    ss >> cameraID;
                    inputType = CAMERA;
                }
                else
                {  //输入 不是相机
                    if (isListOfImages(input) && readStringList(input, imageList)) //判断是否是图片列表,如果是图片列表,将图片列表读取到imageList中
                    {
                        inputType = IMAGE_LIST;
                        nrFrames = (nrFrames < (int)imageList.size()) ? nrFrames : (int)imageList.size();  //两者之间取最小值
                    }
                    else
                        inputType = VIDEO_FILE; //输入类型为是视频文件。
                }
                if (inputType == CAMERA) //如果输入类型为相机ID
                    inputCapture.open(cameraID);  //那就打开相机
                if (inputType == VIDEO_FILE) //如果输入类型是视频文件,那么就打开视频
                    inputCapture.open(input);
                if (inputType != IMAGE_LIST && !inputCapture.isOpened()) //如果输入的类型是不是图片列表,同时inputCapture也不是打开状态,那么输入类型无效
                    inputType = INVALID;
            }
    
            if (inputType == INVALID) //如果输入类型无效
            {
                cerr << " Input does not exist: " << input;
                goodInput = false;
            }
    
            flag = 0; //建立标签
            if(calibFixPrincipalPoint) flag |= CALIB_FIX_PRINCIPAL_POINT;
            if(calibZeroTangentDist)   flag |= CALIB_ZERO_TANGENT_DIST;
            if(aspectRatio)            flag |= CALIB_FIX_ASPECT_RATIO;
            if(fixK1)                  flag |= CALIB_FIX_K1;
            if(fixK2)                  flag |= CALIB_FIX_K2;
            if(fixK3)                  flag |= CALIB_FIX_K3;
            if(fixK4)                  flag |= CALIB_FIX_K4;
            if(fixK5)                  flag |= CALIB_FIX_K5;
    
            if (useFisheye) { //使用鱼眼相机
                // the fisheye model has its own enum, so overwrite the flags
                flag = fisheye::CALIB_FIX_SKEW | fisheye::CALIB_RECOMPUTE_EXTRINSIC;
                if(fixK1)                   flag |= fisheye::CALIB_FIX_K1;
                if(fixK2)                   flag |= fisheye::CALIB_FIX_K2;
                if(fixK3)                   flag |= fisheye::CALIB_FIX_K3;
                if(fixK4)                   flag |= fisheye::CALIB_FIX_K4;
                if (calibFixPrincipalPoint) flag |= fisheye::CALIB_FIX_PRINCIPAL_POINT;
            }
    
            calibrationPattern = NOT_EXISTING;  //标定文件图案不存在
            if (!patternToUse.compare("CHESSBOARD")) calibrationPattern = CHESSBOARD;
            if (!patternToUse.compare("CIRCLES_GRID")) calibrationPattern = CIRCLES_GRID;
            if (!patternToUse.compare("ASYMMETRIC_CIRCLES_GRID")) calibrationPattern = ASYMMETRIC_CIRCLES_GRID;
            if (calibrationPattern == NOT_EXISTING)
            {
                cerr << " Camera calibration mode does not exist: " << patternToUse << endl;
                goodInput = false;
            }
            atImageList = 0;
    
        }
    

    5.9. 函数nextImage() 获取图片并返回

     Mat nextImage()
        {
            Mat result;
            if( inputCapture.isOpened() )
            {
                Mat view0;
                inputCapture >> view0;
                view0.copyTo(result);
            }
            else if( atImageList < imageList.size() )
                result = imread(imageList[atImageList++], IMREAD_COLOR);
    
            return result;
        }
    

    只转载不声明来源的行为是弟弟行为~
    6. Settings类结束了,稍微有点长呀,下面再介绍一下别的,读取FileNode的函数read()

    static inline void read(const FileNode& node, Settings& x, const Settings& default_value = Settings())
    {
        if(node.empty())
            x = default_value;
        else
            x.read(node);
    }
    
    
    1. 定义程序状态的匿名枚举类,enum { DETECTION = 0, CAPTURING = 1, CALIBRATED = 2 };
    enum { DETECTION = 0, CAPTURING = 1, CALIBRATED = 2 };
    
    1. 标定并进行保存的函数runCalibrationAndSave()
    bool runCalibrationAndSave(Settings& s, Size imageSize, Mat& cameraMatrix, Mat& distCoeffs,
                               vector<vector<Point2f> > imagePoints)
    {
        vector<Mat> rvecs, tvecs; //矩阵R ,矩阵T
        vector<float> reprojErrs; 
        double totalAvgErr = 0;
    
        bool ok = runCalibration(s, imageSize, cameraMatrix, distCoeffs, imagePoints, rvecs, tvecs, reprojErrs,
                                 totalAvgErr);
        cout << (ok ? "Calibration succeeded" : "Calibration failed")
             << ". avg re projection error = " << totalAvgErr << endl;
    
        if (ok)
            saveCameraParams(s, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs, reprojErrs, imagePoints,
                             totalAvgErr);
        return ok;
    }
    
    1. 计算盘角位置和误差的函数calcBoardCornerPositions()
    static void calcBoardCornerPositions(Size boardSize, float squareSize, vector<Point3f>& corners,
                                         Settings::Pattern patternType /*= Settings::CHESSBOARD*/)
    {
        corners.clear();
    
        switch(patternType)
        {
            case Settings::CHESSBOARD:
            case Settings::CIRCLES_GRID:
                for( int i = 0; i < boardSize.height; ++i )
                    for( int j = 0; j < boardSize.width; ++j )
                        corners.push_back(Point3f(j*squareSize, i*squareSize, 0));
                break;
    
            case Settings::ASYMMETRIC_CIRCLES_GRID:
                for( int i = 0; i < boardSize.height; i++ )
                    for( int j = 0; j < boardSize.width; j++ )
                        corners.push_back(Point3f((2*j + i % 2)*squareSize, i*squareSize, 0));
                break;
            default:
                break;
        }
    }
    
    1. 打印相机参数到输出文件中的函数saveCameraParams()
    static void saveCameraParams( Settings& s, Size& imageSize, Mat& cameraMatrix, Mat& distCoeffs,
                                  const vector<Mat>& rvecs, const vector<Mat>& tvecs,
                                  const vector<float>& reprojErrs, const vector<vector<Point2f> >& imagePoints,
                                  double totalAvgErr )
    {
        FileStorage fs( s.outputFileName, FileStorage::WRITE );
    
        time_t tm;
        time( &tm );
        struct tm *t2 = localtime( &tm );
        char buf[1024];
        strftime( buf, sizeof(buf), "%c", t2 );
    
        fs << "calibration_time" << buf;
    
        if( !rvecs.empty() || !reprojErrs.empty() )
            fs << "nr_of_frames" << (int)std::max(rvecs.size(), reprojErrs.size());
        fs << "image_width" << imageSize.width;
        fs << "image_height" << imageSize.height;
        fs << "board_width" << s.boardSize.width;
        fs << "board_height" << s.boardSize.height;
        fs << "square_size" << s.squareSize;
    
        if( s.flag & CALIB_FIX_ASPECT_RATIO )
            fs << "fix_aspect_ratio" << s.aspectRatio;
    
        if (s.flag)
        {
            std::stringstream flagsStringStream;
            if (s.useFisheye)
            {
                flagsStringStream << "flags:"
                                  << (s.flag & fisheye::CALIB_FIX_SKEW ? " +fix_skew" : "")
                                  << (s.flag & fisheye::CALIB_FIX_K1 ? " +fix_k1" : "")
                                  << (s.flag & fisheye::CALIB_FIX_K2 ? " +fix_k2" : "")
                                  << (s.flag & fisheye::CALIB_FIX_K3 ? " +fix_k3" : "")
                                  << (s.flag & fisheye::CALIB_FIX_K4 ? " +fix_k4" : "")
                                  << (s.flag & fisheye::CALIB_RECOMPUTE_EXTRINSIC ? " +recompute_extrinsic" : "");
            }
            else
            {
                flagsStringStream << "flags:"
                                  << (s.flag & CALIB_USE_INTRINSIC_GUESS ? " +use_intrinsic_guess" : "")
                                  << (s.flag & CALIB_FIX_ASPECT_RATIO ? " +fix_aspectRatio" : "")
                                  << (s.flag & CALIB_FIX_PRINCIPAL_POINT ? " +fix_principal_point" : "")
                                  << (s.flag & CALIB_ZERO_TANGENT_DIST ? " +zero_tangent_dist" : "")
                                  << (s.flag & CALIB_FIX_K1 ? " +fix_k1" : "")
                                  << (s.flag & CALIB_FIX_K2 ? " +fix_k2" : "")
                                  << (s.flag & CALIB_FIX_K3 ? " +fix_k3" : "")
                                  << (s.flag & CALIB_FIX_K4 ? " +fix_k4" : "")
                                  << (s.flag & CALIB_FIX_K5 ? " +fix_k5" : "");
            }
            fs.writeComment(flagsStringStream.str());
        }
    
        fs << "flags" << s.flag;
    
        fs << "fisheye_model" << s.useFisheye;
    
        fs << "camera_matrix" << cameraMatrix;
        fs << "distortion_coefficients" << distCoeffs;
    
        fs << "avg_reprojection_error" << totalAvgErr;
        if (s.writeExtrinsics && !reprojErrs.empty())
            fs << "per_view_reprojection_errors" << Mat(reprojErrs);
    
        if(s.writeExtrinsics && !rvecs.empty() && !tvecs.empty() )
        {
            CV_Assert(rvecs[0].type() == tvecs[0].type());
            Mat bigmat((int)rvecs.size(), 6, CV_MAKETYPE(rvecs[0].type(), 1));
            bool needReshapeR = rvecs[0].depth() != 1 ? true : false;
            bool needReshapeT = tvecs[0].depth() != 1 ? true : false;
    
            for( size_t i = 0; i < rvecs.size(); i++ )
            {
                Mat r = bigmat(Range(int(i), int(i+1)), Range(0,3));
                Mat t = bigmat(Range(int(i), int(i+1)), Range(3,6));
    
                if(needReshapeR)
                    rvecs[i].reshape(1, 1).copyTo(r);
                else
                {
                    //*.t() is MatExpr (not Mat) so we can use assignment operator
                    CV_Assert(rvecs[i].rows == 3 && rvecs[i].cols == 1);
                    r = rvecs[i].t();
                }
    
                if(needReshapeT)
                    tvecs[i].reshape(1, 1).copyTo(t);
                else
                {
                    CV_Assert(tvecs[i].rows == 3 && tvecs[i].cols == 1);
                    t = tvecs[i].t();
                }
            }
            fs.writeComment("a set of 6-tuples (rotation vector + translation vector) for each view");
            fs << "extrinsic_parameters" << bigmat;
        }
    
        if(s.writePoints && !imagePoints.empty() )
        {
            Mat imagePtMat((int)imagePoints.size(), (int)imagePoints[0].size(), CV_32FC2);
            for( size_t i = 0; i < imagePoints.size(); i++ )
            {
                Mat r = imagePtMat.row(int(i)).reshape(2, imagePtMat.cols);
                Mat imgpti(imagePoints[i]);
                imgpti.copyTo(r);
            }
            fs << "image_points" << imagePtMat;
        }
    }
    
    
    1. 运行并进行标定的函数runCalibration()
    static bool runCalibration( Settings& s, Size& imageSize, Mat& cameraMatrix, Mat& distCoeffs,
                                vector<vector<Point2f> > imagePoints, vector<Mat>& rvecs, vector<Mat>& tvecs,
                                vector<float>& reprojErrs,  double& totalAvgErr)
    {
        //! [fixed_aspect]
        cameraMatrix = Mat::eye(3, 3, CV_64F);
        if( s.flag & CALIB_FIX_ASPECT_RATIO )
            cameraMatrix.at<double>(0,0) = s.aspectRatio;
        //! [fixed_aspect]
        if (s.useFisheye) {
            distCoeffs = Mat::zeros(4, 1, CV_64F);
        } else {
            distCoeffs = Mat::zeros(8, 1, CV_64F);
        }
    
        vector<vector<Point3f> > objectPoints(1);
        calcBoardCornerPositions(s.boardSize, s.squareSize, objectPoints[0], s.calibrationPattern);
    
        objectPoints.resize(imagePoints.size(),objectPoints[0]);
    
        //Find intrinsic and extrinsic camera parameters
        double rms;
    
        if (s.useFisheye) {
            Mat _rvecs, _tvecs;
            rms = fisheye::calibrate(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, _rvecs,
                                     _tvecs, s.flag);
    
            rvecs.reserve(_rvecs.rows);
            tvecs.reserve(_tvecs.rows);
            for(int i = 0; i < int(objectPoints.size()); i++){
                rvecs.push_back(_rvecs.row(i));
                tvecs.push_back(_tvecs.row(i));
            }
        } else {
            rms = calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs,
                                  s.flag);
        }
    
        cout << "Re-projection error reported by calibrateCamera: "<< rms << endl;
    
        bool ok = checkRange(cameraMatrix) && checkRange(distCoeffs);
    
        totalAvgErr = computeReprojectionErrors(objectPoints, imagePoints, rvecs, tvecs, cameraMatrix,
                                                distCoeffs, reprojErrs, s.useFisheye);
    
        return ok;
    }
    
    1. 函数calcBoardCornerPositions() ,计算边角位置和误差
    static void calcBoardCornerPositions(Size boardSize, float squareSize, vector<Point3f>& corners,
                                         Settings::Pattern patternType /*= Settings::CHESSBOARD*/)
    {
        corners.clear();
    
        switch(patternType)
        {
            case Settings::CHESSBOARD:
            case Settings::CIRCLES_GRID:
                for( int i = 0; i < boardSize.height; ++i )
                    for( int j = 0; j < boardSize.width; ++j )
                        corners.push_back(Point3f(j*squareSize, i*squareSize, 0));
                break;
    
            case Settings::ASYMMETRIC_CIRCLES_GRID:
                for( int i = 0; i < boardSize.height; i++ )
                    for( int j = 0; j < boardSize.width; j++ )
                        corners.push_back(Point3f((2*j + i % 2)*squareSize, i*squareSize, 0));
                break;
            default:
                break;
        }
    }
    
    1. 函数computeReprojectionErrors(),计算误差
    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, bool fisheye)
    {
        vector<Point2f> imagePoints2;
        size_t totalPoints = 0;
        double totalErr = 0, err;
        perViewErrors.resize(objectPoints.size());
    
        for(size_t i = 0; i < objectPoints.size(); ++i )
        {
            if (fisheye)
            {
                fisheye::projectPoints(objectPoints[i], imagePoints2, rvecs[i], tvecs[i], cameraMatrix,
                                       distCoeffs);
            }
            else
            {
                projectPoints(objectPoints[i], rvecs[i], tvecs[i], cameraMatrix, distCoeffs, imagePoints2);
            }
            err = norm(imagePoints[i], imagePoints2, NORM_L2);
    
            size_t n = objectPoints[i].size();
            perViewErrors[i] = (float) std::sqrt(err*err/n);
            totalErr        += err*err;
            totalPoints     += n;
        }
    
        return std::sqrt(totalErr/totalPoints);
    }
    
    1. main 函数()
    int main(int argc, char* argv[])
    {
        //提示
        help();
    
        //! [file_read]
        Settings s;
        const string inputSettingsFile = argc > 1 ? argv[1] : "CameraPara.xml"; //判断参数个数,如果执行main函数没有传入参数,inputSettingsFile默认为default.xml
        cout<<"The inputSettingsFile is the "<<inputSettingsFile<<endl;
        FileStorage fs(inputSettingsFile, FileStorage::READ); // Read the settings 读取inputSettingsFile代表的(xml文件)
        if (!fs.isOpened()) //如果打不开该xml文件,就报异常
        {
            cout << "Could not open the configuration file: \"" << inputSettingsFile << "\"" << endl;
            return -1;
        }
        fs["Settings"] >> s;
        fs.release();                                         // close Settings file
        //! [file_read]
    
    //    FileStorage fout("settings.yml", FileStorage::WRITE); // write config as YAML
    //    fout << "Settings" << s;
    
        if (!s.goodInput)
        {
            cout << "Invalid input detected. Application stopping. " << endl;
            return -1;
        }
    
        vector<vector<Point2f> > imagePoints;
        Mat cameraMatrix, distCoeffs;
        Size imageSize;
        // 如果输入类型是图像, 那么就进行捕捉,否则就开始检测
        int mode = s.inputType == Settings::IMAGE_LIST ? CAPTURING : DETECTION;
        int showMeTheCode = Settings::IMAGE_LIST?3:2;
    
        clock_t prevTimestamp = 0;
        const Scalar RED(0,0,255), GREEN(0,255,0);
        const char ESC_KEY = 27;
    
        //! [get_input]
        for(;;)
        {
            Mat view;
            bool blinkOutput = false;
    
            view = s.nextImage();
    
            //-----  If no more image, or got enough, then stop calibration and show result -------------
            if( mode == CAPTURING && imagePoints.size() >= (size_t)s.nrFrames )
            {
                if( runCalibrationAndSave(s, imageSize,  cameraMatrix, distCoeffs, imagePoints))
                    mode = CALIBRATED;
                else
                    mode = DETECTION;
            }
            if(view.empty())          // If there are no more images stop the loop
            {
                // if calibration threshold was not reached yet, calibrate now
                if( mode != CALIBRATED && !imagePoints.empty() )
                    runCalibrationAndSave(s, imageSize,  cameraMatrix, distCoeffs, imagePoints);
                break;
            }
            //! [get_input]
    
            imageSize = view.size();  // Format input image.
            if( s.flipVertical )    flip( view, view, 0 );
    
            //! [find_pattern]
            vector<Point2f> pointBuf;
    
            bool found;
    
            int chessBoardFlags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE;
    
            if(!s.useFisheye) {
                // fast check erroneously fails with high distortions like fisheye
                chessBoardFlags |= CALIB_CB_FAST_CHECK;
            }
    
            switch( s.calibrationPattern ) // Find feature points on the input format
            {
                case Settings::CHESSBOARD:
                    found = findChessboardCorners( view, s.boardSize, pointBuf, chessBoardFlags);
                    break;
                case Settings::CIRCLES_GRID:
                    found = findCirclesGrid( view, s.boardSize, pointBuf );
                    break;
                case Settings::ASYMMETRIC_CIRCLES_GRID:
                    found = findCirclesGrid( view, s.boardSize, pointBuf, CALIB_CB_ASYMMETRIC_GRID );
                    break;
                default:
                    found = false;
                    break;
            }
            //! [find_pattern]
            //! [pattern_found]
            if ( found)                // If done with success,
            {
                // improve the found corners' coordinate accuracy for chessboard
                if( s.calibrationPattern == Settings::CHESSBOARD)
                {
                    Mat viewGray;
                    cvtColor(view, viewGray, COLOR_BGR2GRAY);
                    cornerSubPix( viewGray, pointBuf, Size(11,11),
                                  Size(-1,-1), TermCriteria( TermCriteria::EPS+TermCriteria::COUNT, 30, 0.1 ));
                }
    
                if( mode == CAPTURING &&  // For camera only take new samples after delay time
                    (!s.inputCapture.isOpened() || clock() - prevTimestamp > s.delay*1e-3*CLOCKS_PER_SEC) )
                {
                    imagePoints.push_back(pointBuf);
                    prevTimestamp = clock();
                    blinkOutput = s.inputCapture.isOpened();
                }
    
                // Draw the corners.
                drawChessboardCorners( view, s.boardSize, Mat(pointBuf), found );
            }
            //! [pattern_found]
            //----------------------------- Output Text ------------------------------------------------
            //! [output_text]
            string msg = (mode == CAPTURING) ? "100/100" :
                         mode == CALIBRATED ? "Calibrated" : "Press 'g' to start";
            int baseLine = 0;
            Size textSize = getTextSize(msg, 1, 1, 1, &baseLine);
            Point textOrigin(view.cols - 2*textSize.width - 10, view.rows - 2*baseLine - 10);
    
            if( mode == CAPTURING )
            {
                if(s.showUndistorsed)
                    msg = format( "%d/%d Undist", (int)imagePoints.size(), s.nrFrames );
                else
                    msg = format( "%d/%d", (int)imagePoints.size(), s.nrFrames );
            }
    
            putText( view, msg, textOrigin, 1, 1, mode == CALIBRATED ?  GREEN : RED);
    
            if( blinkOutput )
                bitwise_not(view, view);
            //! [output_text]
            //------------------------- Video capture  output  undistorted ------------------------------
            //! [output_undistorted]
            if( mode == CALIBRATED && s.showUndistorsed )
            {
                Mat temp = view.clone();
                if (s.useFisheye)
                    cv::fisheye::undistortImage(temp, view, cameraMatrix, distCoeffs);
                else
                    undistort(temp, view, cameraMatrix, distCoeffs);
            }
            //! [output_undistorted]
            //------------------------------ Show image and check for input commands -------------------
            //! [await_input]
            imshow("Image View", view);
            char key = (char)waitKey(s.inputCapture.isOpened() ? 50 : s.delay);
    
            if( key  == ESC_KEY )
                break;
    
            if( key == 'u' && mode == CALIBRATED )
                s.showUndistorsed = !s.showUndistorsed;
    
            if( s.inputCapture.isOpened() && key == 'g' )
            {
                mode = CAPTURING;
                imagePoints.clear();
            }
            //! [await_input]
        }
    
        // -----------------------Show the undistorted image for the image list ------------------------
        //! [show_results]
        if( s.inputType == Settings::IMAGE_LIST && s.showUndistorsed )
        {
            Mat view, rview, map1, map2;
    
            if (s.useFisheye)
            {
                Mat newCamMat;
                fisheye::estimateNewCameraMatrixForUndistortRectify(cameraMatrix, distCoeffs, imageSize,
                                                                    Matx33d::eye(), newCamMat, 1);
                fisheye::initUndistortRectifyMap(cameraMatrix, distCoeffs, Matx33d::eye(), newCamMat, imageSize,
                                                 CV_16SC2, map1, map2);
            }
            else
            {
                initUndistortRectifyMap(
                        cameraMatrix, distCoeffs, Mat(),
    
                        getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, 0), imageSize,
                        CV_16SC2, map1, map2);
            }
    
            for(size_t i = 0; i < s.imageList.size(); i++ )
            {
                view = imread(s.imageList[i], IMREAD_COLOR);
                if(view.empty())
                    continue;
                remap(view, rview, map1, map2, INTER_LINEAR);
                imshow("Image View", rview);
                char c = (char)waitKey();
                if( c  == ESC_KEY || c == 'q' || c == 'Q' )
                    break;
            }
        }
        //! [show_results]
    
        return 0;
    }
    

    源代码链接:https://github.com/philtell/CameraCalibration

    精简版:https://github.com/philtell/fisheye_opencv
    记得加一个星~

    展开全文
  • 转载:... #include "opencv2/core/core.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/calib3d/calib3d.hpp" #include "opencv2/highgui/highgui

    转载:http://blog.csdn.net/dcrmg/article/details/52929669

    #include "opencv2/core/core.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/calib3d/calib3d.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include <iostream>
    #include <fstream>
    
    using namespace cv;
    using namespace std;
    
    void main() 
    {
    	ifstream fin("calibdata.txt"); /* 标定所用图像文件的路径 */
    	ofstream fout("caliberation_result.txt");  /* 保存标定结果的文件 */	
    	//读取每一幅图像,从中提取出角点,然后对角点进行亚像素精确化	
    	cout<<"开始提取角点………………";
    	int image_count=0;  /* 图像数量 */
    	Size image_size;  /* 图像的尺寸 */
    	Size board_size = Size(4,6);    /* 标定板上每行、列的角点数 */
    	vector<Point2f> image_points_buf;  /* 缓存每幅图像上检测到的角点 */
    	vector<vector<Point2f>> image_points_seq; /* 保存检测到的所有角点 */
    	string filename;
    	int count= -1 ;//用于存储角点个数。
    	while (getline(fin,filename))
    	{
    		image_count++;		
    		// 用于观察检验输出
    		cout<<"image_count = "<<image_count<<endl;		
    		/* 输出检验*/
    		cout<<"-->count = "<<count;		
    		Mat imageInput=imread(filename);
    		if (image_count == 1)  //读入第一张图片时获取图像宽高信息
    		{
    			image_size.width = imageInput.cols;
    			image_size.height =imageInput.rows;			
    			cout<<"image_size.width = "<<image_size.width<<endl;
    			cout<<"image_size.height = "<<image_size.height<<endl;
    		}
    
    		/* 提取角点 */
    		if (0 == findChessboardCorners(imageInput,board_size,image_points_buf))
    		{			
    			cout<<"can not find chessboard corners!\n"; //找不到角点
    			exit(1);
    		} 
    		else 
    		{
    			Mat view_gray;
    			cvtColor(imageInput,view_gray,CV_RGB2GRAY);
    			/* 亚像素精确化 */
    			find4QuadCornerSubpix(view_gray,image_points_buf,Size(11,11)); //对粗提取的角点进行精确化
    			image_points_seq.push_back(image_points_buf);  //保存亚像素角点
    			/* 在图像上显示角点位置 */
    			drawChessboardCorners(view_gray,board_size,image_points_buf,true); //用于在图片中标记角点
    			imshow("Camera Calibration",view_gray);//显示图片
    			waitKey(500);//暂停0.5S		
    		}
    	}
    	int total = image_points_seq.size();
    	cout<<"total = "<<total<<endl;
    	int CornerNum=board_size.width*board_size.height;  //每张图片上总的角点数
    	for (int ii=0 ; ii<total ;ii++)
    	{
    		if (0 == ii%CornerNum)// 24 是每幅图片的角点个数。此判断语句是为了输出 图片号,便于控制台观看 
    		{	
    			int i = -1;
    			i = ii/CornerNum;
    			int j=i+1;
    			cout<<"--> 第 "<<j <<"图片的数据 --> : "<<endl;
    		}
    		if (0 == ii%3)	// 此判断语句,格式化输出,便于控制台查看
    		{
    			cout<<endl;
    		}
    		else
    		{
    			cout.width(10);
    		}
    		//输出所有的角点
    		cout<<" -->"<<image_points_seq[ii][0].x;
    		cout<<" -->"<<image_points_seq[ii][0].y;
    	}	
    	cout<<"角点提取完成!\n";
    
    	//以下是摄像机标定
    	cout<<"开始标定………………";
    	/*棋盘三维信息*/
    	Size square_size = Size(10,10);  /* 实际测量得到的标定板上每个棋盘格的大小 */
    	vector<vector<Point3f>> object_points; /* 保存标定板上角点的三维坐标 */
    	/*内外参数*/
    	Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0)); /* 摄像机内参数矩阵 */
    	vector<int> point_counts;  // 每幅图像中角点的数量
    	Mat distCoeffs=Mat(1,5,CV_32FC1,Scalar::all(0)); /* 摄像机的5个畸变系数:k1,k2,p1,p2,k3 */
    	vector<Mat> tvecsMat;  /* 每幅图像的旋转向量 */
    	vector<Mat> rvecsMat; /* 每幅图像的平移向量 */
    	/* 初始化标定板上角点的三维坐标 */
    	int i,j,t;
    	for (t=0;t<image_count;t++) 
    	{
    		vector<Point3f> tempPointSet;
    		for (i=0;i<board_size.height;i++) 
    		{
    			for (j=0;j<board_size.width;j++) 
    			{
    				Point3f realPoint;
    				/* 假设标定板放在世界坐标系中z=0的平面上 */
    				realPoint.x = i*square_size.width;
    				realPoint.y = j*square_size.height;
    				realPoint.z = 0;
    				tempPointSet.push_back(realPoint);
    			}
    		}
    		object_points.push_back(tempPointSet);
    	}
    	/* 初始化每幅图像中的角点数量,假定每幅图像中都可以看到完整的标定板 */
    	for (i=0;i<image_count;i++)
    	{
    		point_counts.push_back(board_size.width*board_size.height);
    	}	
    	/* 开始标定 */
    	calibrateCamera(object_points,image_points_seq,image_size,cameraMatrix,distCoeffs,rvecsMat,tvecsMat,0);
    	cout<<"标定完成!\n";
    	//对标定结果进行评价
    	cout<<"开始评价标定结果………………\n";
    	double total_err = 0.0; /* 所有图像的平均误差的总和 */
    	double err = 0.0; /* 每幅图像的平均误差 */
    	vector<Point2f> image_points2; /* 保存重新计算得到的投影点 */
    	cout<<"\t每幅图像的标定误差:\n";
    	fout<<"每幅图像的标定误差:\n";
    	for (i=0;i<image_count;i++)
    	{
    		vector<Point3f> tempPointSet=object_points[i];
    		/* 通过得到的摄像机内外参数,对空间的三维点进行重新投影计算,得到新的投影点 */
    		projectPoints(tempPointSet,rvecsMat[i],tvecsMat[i],cameraMatrix,distCoeffs,image_points2);
    		/* 计算新的投影点和旧的投影点之间的误差*/
    		vector<Point2f> tempImagePoint = image_points_seq[i];
    		Mat tempImagePointMat = Mat(1,tempImagePoint.size(),CV_32FC2);
    		Mat image_points2Mat = Mat(1,image_points2.size(), CV_32FC2);
    		for (int j = 0 ; j < tempImagePoint.size(); j++)
    		{
    			image_points2Mat.at<Vec2f>(0,j) = Vec2f(image_points2[j].x, image_points2[j].y);
    			tempImagePointMat.at<Vec2f>(0,j) = Vec2f(tempImagePoint[j].x, tempImagePoint[j].y);
    		}
    		err = norm(image_points2Mat, tempImagePointMat, NORM_L2);
    		total_err += err/=  point_counts[i];   
    		std::cout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;   
    		fout<<"第"<<i+1<<"幅图像的平均误差:"<<err<<"像素"<<endl;   
    	}   
    	std::cout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl;   
    	fout<<"总体平均误差:"<<total_err/image_count<<"像素"<<endl<<endl;   
    	std::cout<<"评价完成!"<<endl;  
    	//保存定标结果  	
    	std::cout<<"开始保存定标结果………………"<<endl;       
    	Mat rotation_matrix = Mat(3,3,CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */
    	fout<<"相机内参数矩阵:"<<endl;   
    	fout<<cameraMatrix<<endl<<endl;   
    	fout<<"畸变系数:\n";   
    	fout<<distCoeffs<<endl<<endl<<endl;   
    	for (int i=0; i<image_count; i++) 
    	{ 
    		fout<<"第"<<i+1<<"幅图像的旋转向量:"<<endl;   
    		fout<<tvecsMat[i]<<endl;   
    		/* 将旋转向量转换为相对应的旋转矩阵 */   
    		Rodrigues(tvecsMat[i],rotation_matrix);   
    		fout<<"第"<<i+1<<"幅图像的旋转矩阵:"<<endl;   
    		fout<<rotation_matrix<<endl;   
    		fout<<"第"<<i+1<<"幅图像的平移向量:"<<endl;   
    		fout<<rvecsMat[i]<<endl<<endl;   
    	}   
    	std::cout<<"完成保存"<<endl; 
    	fout<<endl;
    	system("pause");	
    	return ;
    }


    展开全文
  • AR-摄像机标定

    2017-02-03 18:27:22
    AR-摄像机标定

    使用OpenCV进行摄像机标定,在OpenCV例程中有程序可以实现,
    /samples/cpp/tutorial_code/calib3d/camera_calibration里的程序就可以进行单目相机标定。
    http://download.csdn.net/detail/chuhang_zhqr/9293665有我上传的,内含测试图片和文件,想直接用的就去下载吧。
    接下来分析下摄像机标定的过程:
    1:这个程序中把初始化参数放在了一个文件(in_VID5.xml)中,在程序的开始首先读入文件中的参数:

    const string inputSettingsFile = argc > 1 ? argv[1] : "in_VID5.xml";
        FileStorage fs(inputSettingsFile, FileStorage::READ); // Read the settings
        if (!fs.isOpened())
        {
            cout << "Could not open the configuration file: \"" << inputSettingsFile << "\"" << endl;
            return -1;
        }
        fs["Settings"] >> s;
        fs.release();                                         // close Settings file
    
        if (!s.goodInput)
        {
            cout << "Invalid input detected. Application stopping. " << endl;
            return -1;
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这个in_VID5.xml可以设置图像大小,棋盘格大小,还可以设置图像源输入:相机输入(“0”),视频输入,图像序列输入(“/home/zhu/program_c/opencv_study/test_down/camera_calibration/VID5.xml”),相机输入时,在运行程序时,按g开始采集图像并进行计算标定系数,期间可以移动棋盘格或相机,获得不同方位的棋盘格图像,使标定系数尽可能精确。

    2:读入相机初始参数后,判断这些数据是否符合,就是验证数据的正确性。

    void interprate()
        {
            goodInput = true;
            if (boardSize.width <= 0 || boardSize.height <= 0)
            {
                cerr << "Invalid Board size: " << boardSize.width << " " << boardSize.height << endl;
                goodInput = false;
            }
            if (squareSize <= 10e-6)
            {
                cerr << "Invalid square size " << squareSize << endl;
                goodInput = false;
            }
            if (nrFrames <= 0)
            {
                cerr << "Invalid number of frames " << nrFrames << endl;
                goodInput = false;
            }
    
            if (input.empty())      // Check for valid input
                    inputType = INVALID;
            else
            {
                if (input[0] >= '0' && input[0] <= '9')
                {
                    stringstream ss(input);
                    ss >> cameraID;
                    inputType = CAMERA;
                }
                else
                {
                    if (readStringList(input, imageList))
                        {
                            inputType = IMAGE_LIST;
                            nrFrames = (nrFrames < (int)imageList.size()) ? nrFrames : (int)imageList.size();
                        }
                    else
                        inputType = VIDEO_FILE;
                }
                if (inputType == CAMERA)
                    inputCapture.open(cameraID);
                if (inputType == VIDEO_FILE)
                    inputCapture.open(input);
                if (inputType != IMAGE_LIST && !inputCapture.isOpened())
                        inputType = INVALID;
            }
            if (inputType == INVALID)
            {
                cerr << " Inexistent input: " << input;
                goodInput = false;
            }
    
            flag = 0;
            if(calibFixPrincipalPoint) flag |= CV_CALIB_FIX_PRINCIPAL_POINT;
            if(calibZeroTangentDist)   flag |= CV_CALIB_ZERO_TANGENT_DIST;
            if(aspectRatio)            flag |= CV_CALIB_FIX_ASPECT_RATIO;
    
    
            calibrationPattern = NOT_EXISTING;
            if (!patternToUse.compare("CHESSBOARD")) calibrationPattern = CHESSBOARD;
            if (!patternToUse.compare("CIRCLES_GRID")) calibrationPattern = CIRCLES_GRID;
            if (!patternToUse.compare("ASYMMETRIC_CIRCLES_GRID")) calibrationPattern = ASYMMETRIC_CIRCLES_GRID;
            if (calibrationPattern == NOT_EXISTING)
                {
                    cerr << " Inexistent camera calibration mode: " << patternToUse << endl;
                    goodInput = false;
                }
            atImageList = 0;
    
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    3:读入一帧图像,开始寻找特征点,就是棋盘的黑白格角点,

    found = findChessboardCorners( view, s.boardSize, pointBuf,
                    CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE);
    • 1
    • 2
    • 1
    • 2

    寻找棋盘图的内角点位置
    int cvFindChessboardCorners(const void* image,CvSize pattern_size,
    CvPoint2D32f* coeners,int* corner_count=NULL,
    int flags=CV_CALIB_CB_ADAPTIVE_THRESH);
    image 输入的棋盘图,必须是8位的灰度或者彩色图像。
    pattern_size 棋盘图中每行和每列角点的个数。
    corners 检测到的角点
    corner_count 输出,角点的个数。如果不是NULL,函数将检测到的角点的个数存储于此变量。
    flags 各种操作标志,可以是0或者下面值的组合:
    CV_CALIB_CB_ADAPTIVE_THRESH - 使用自适应阈值(通过平均图像亮度计算得到)将图像转换为黑白图,而不是一 个固定的阈值。
    CV_CALIB_CB_NORMALIZE_IMAGE - 在利用固定阈值或者自适应的阈值进行二值化之前,先使用cvNormalizeHist来 均衡化图像亮度。
    CV_CALIB_CB_FILTERR_QUADS - 使用其他的准测(如轮廓面积,周长,方形形状)来去除在轮廓检测阶段检测到的 错误方块。

        函数cvFindChessboardCorners试图确定输入图像是否是棋盘模式,并确定角点的位置。
        如果所有角点都被检测到且它们都被以一定顺序排布(一行一行地,每行从左到右),
        函数返回非零值,否则在函数不能发现所有角点或者记录他们的情况下,函数返回0.
        例如一个正常地棋盘图有8x8个方块和7x7个内角点,内角点是黑色方块相互连通的位置。
        这个函数检测到地坐标只有一个大约地值,如果要精确地确定它们的位置,可以使用函数cvFindCornerSubPix。
    

    接下来确定角点的精确位置:

    cvtColor(view, viewGray, COLOR_BGR2GRAY);
    cornerSubPix( viewGray, pointBuf, Size(11,11),
    Size(-1,-1), TermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER, 30, 0.1 ));//这里是迭代次数,达到精度或者达到迭代次数就可以结束了。
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3

    最后在图像上显示检测到的角点位置,用彩色点和线清楚显示:

    drawChessboardCorners( view, s.boardSize, Mat(pointBuf), found );
    • 1
    • 1

    void cvDrawChessboardCorners(CvArr* image,CvSize pattern_size,
    CvPoint2D32f* corners,int count,
    int pattern_was_found);
    image 结果图像,必须是八位彩色图像。
    pattern_size 每行和每列地内角点数目。
    corners 检测到地角点数组。
    count 角点数目。
    pattern_was_found 指示完整地棋盘被发现(!=0)还是没有发现(=0)。
    可以传输cvFindChessboardCorner函数的返回值。
    当棋盘没有完全检测出时,函数cvDrawChessboardCorners以红色圆圈绘制检测到的棋盘角点。
    如果整个棋盘都检测到,则用直线连接所有的角点。

    4:当所有图片都检测完了,或者图片足够多了,得到每帧图像中所有角点的精确位置,就开始计算校正系数:

    runCalibrationAndSave(s, imageSize,  cameraMatrix, distCoeffs, imagePoints)
    • 1
    • 1

    要计算校正系数,必须先求这些棋盘点在真实世界坐标系的三维坐标。

    for( int i = 0; i < boardSize.height; ++i )
                for( int j = 0; j < boardSize.width; ++j )
                    corners.push_back(Point3f(float( j*squareSize ), float( i*squareSize ), 0));
            break;
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

    根据求得的真实世界坐标系的三维坐标和之前在相机坐标系找到的二维的角点位置的转换关系,就可以求得相机内参数,失真系数,相机外参数等:

    calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix,distCoeffs, rvecs, tvecs, s.flag|CV_CALIB_FIX_K4|CV_CALIB_FIX_K5);
    • 1
    • 1

    利用定标来计算摄像机的内参数和外参数
    void cvCalibrateCamera2(const CvMat* object_points,const CvMat* image_points,
    const CvMat* point_counts,CvSize image_size,
    CvMat* intrinsic_matrix,CvMat* distortion_coeffs,
    CvMat* rotation_vectors=NULL,
    CvMat* translation_vectors=NULL,
    int flags=0);
    object_points 定标点的世界坐标,为3xN或者Nx3的矩阵,这里N是所有视图中点的总数。
    image_Points 定标点的图像坐标,为2xN或者Nx2的矩阵,这里N是所有视图中点的总数。
    point_counts 向量,指定不同视图里点的数目,1xM或者Mx1向量,M是视图数目。
    image_size 图像大小,只用在初始化参数时。
    intrinsic_matrix 输出参数矩阵(A),如果指定CV_CALIB_USE_INTRINSIC_GUESS和(或)CV_CALIB_FIX_ASPECT_RATI ON,fx,fy,cx和cy部分或者全部必须被初始化。
    distortion_coeffs 输出大小为4x1或者1x4的向量,里面为形变参数(旋转矩阵的紧凑表达方式,具体参考函数cvRodr igues2)
    translation_vectors 输出大小为3xM或Mx3的矩阵,里面为平移向量。
    flags 不同的标志,可以是0,或者下面值的组合:
    CV_CALIB_USE_INTRINSIC_GUESS - 内参数矩阵包含fx,fy,cx和cy的初始值。否则,(cx,cy)被初始化到图像中心 (这儿用到图像大小),焦距用最小平方差公式计算得到。注意,如果内部参数已知,没有必要使用这个参数,使用cvF indExtrinsicCameraParams2则可。
    CV_CALIB_FIX_PRINCIPAL_POINT - 主点在全局优化过程中不变,一直在中心位置或者在其他指定的位置(当CV_CALIB_ USE_INTRINSIC_GUESS设置的时候)。
    CV_CALIB_FIX_ASPECT_PATIO - 优化过程中认为fx和fy中只有一个独立变量,保持比例fx/fy不变,fx/fy的值跟内参数矩阵初始化时的值一样。在这种情况下,(fx,fy)的实际初始值或者从输入内存矩阵中读取(当CV_CALIB_USE_INTRINSIC_GUESS被指定时),或者采用估计值(后者情况中fx和fy可能被设置为任意值,只有比值被使用)。
    CV_CALIB_ZERO_TANGENT_DIST - 切向形变参数(p1,p2)被设置为0,其值在优化过程中保持为0.

    函数cvCalibrateCamera2从每个视图中估计相机的内参数和外参数。3维物体的点和它们对应的在每个视图的2维投影必须被指定。
    这些可以通过使用一个已知几何形状且具有容易检测的特征点的物体来实现。这样的一个物体被称作定标设备或者定标模式。
    opencv有内建的把棋盘当作定标设备方法(参考cvFindChessboardCorners)。
    目前,传入初始化的内参数(当CV_CALIB_USE_INTRINSIC_GUESS不被设置时)只支持平面定标设备(物体点的z坐标必须全0或者全1)。
    不过3维定标设备依然可以用在提供初始化内参数矩阵情况。在内参数和外参数矩阵的初始值都计算出之后,它们会被优化用来减小反投影误差(图像上的实际坐标跟cvProjectPoints2计算出的图像坐标的差的平方和)。
    

    5:通过计算真实坐标系中的点与标定系数的关系,重新把真实坐标系的点投射到屏幕上,与拍摄到的图片中角点对比,求误差:

    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), CV_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);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    6:把想保存的数据保存到一个输出.yml中,以便在AR中导入相机参数时使用。

     FileStorage fs( s.outputFileName, FileStorage::WRITE );
    • 1
    • 1

    7:之后便可以在AR中进行调用了,关于xml和yml文件的读写请自行百度。

    下篇文章分析下AR如何计算虚拟目标在相机坐标系中的转换矩阵。

    展开全文
  • 双目测距一

    2014-01-21 17:27:15
    双目测距的基本原理 如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差 )与目标点到成像平面...“@scyscyao :在OpenCV中,f的量纲是像素点,T的量纲由定标板棋

    觉得不错,留作记录,原文:

    http://blog.csdn.net/chenyusiyuan/article/details/5961769


    双目测距的基本原理

    clip_image002[10]

    如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差clip_image004 )与目标点到成像平面的距离Z存在着反比例的关系:Z=fT/d。“@scyscyao :在OpenCV中,f的量纲是像素点,T的量纲由定标板棋盘格的实际尺寸和用户输入值确定,一般是以毫米为单位(当然为了精度提高也可以设置为0.1毫米量级),d=xl-xr的量纲也是像素点。因此分子分母约去,Z的量纲与T相同。 

    clip_image006

    假设目标点在左视图中的坐标为(x,y),在左右视图上形成的视差为d,目标点在以左摄像头光心为原点的世界坐标系中的坐标为(X,Y,Z),则存在上图所示的变换矩阵Q,使得 Q*[x y d 1]’ = [X Y Z W]’。

    @scyscyao :为了精确地求得某个点在三维空间里的距离Z,我们需要获得的参数有焦距f、视差d、摄像头中心距Tx。如果还需要获得X坐标和Y坐标的话,那么还需要额外知道左右像平面的坐标系与立体坐标系中原点的偏移cx和cy。其中f, Tx, cx和cy可以通过立体标定获得初始值,并通过立体校准优化,使得两个摄像头在数学上完全平行放置,并且左右摄像头的cx, cy和f相同(也就是实现图2中左右视图完全平行对准的理想形式)。而立体匹配所做的工作,就是在之前的基础上,求取最后一个变量:视差d(这个d一般需要达到亚像素精度)。从而最终完成求一个点三维坐标所需要的准备工作。在清楚了上述原理之后,我们也就知道了,所有的这几步:标定、校准和匹配,都是围绕着如何更精确地获得 f, d, Tx, cx 和cy 而设计的 。 ”

     

    一、图像的获取

    1. 如何打开两个或多个摄像头?

    可以通过OpenCV的capture类函数或者结合DirectShow来实现双摄像头的捕获,具体可见我的读书笔记《OpenCV学习笔记(6)基于 VC+OpenCV+DirectShow 的多个摄像头同步工作 》。文中曾提及不能用cvCreateCameraCapture 同时读取两个摄像头,不过后来一位研友来信讨论说只要把摄像头指针的创建代码按照摄像头序号降序执行,就可以顺利打开多个摄像头 ,例如:

     

    [c-sharp] view plaincopy
    1. CvCapture* capture2 = cvCreateCameraCapture( 1 );  
    2. CvCapture* capture1 = cvCreateCameraCapture( 0 );  

    采用DirectShow的方式读入时:

     

    [c-sharp] view plaincopy
    1. camera2.OpenCamera(1, false, 640,480);  
    2. camera1.OpenCamera(0, false, 640,480);  

     

    这样就可以同时采集两个摄像头。我也验证过这种方法确实有效,而且还解决了我遇到的cvSetCaptureProperty调整帧画面大小速度过慢的问题。当摄像头的打开或创建代码按照摄像头序号从0开始以升序编写执行时,使用cvSetCaptureProperty就会出现第一个摄像头(序号为0)的显示窗口为灰色(即无图像)、且程序运行速度缓慢的现象。而改为降序编写执行后,则能正常、实时地显示各摄像头的画面。具体原因有待分析讨论。

     

    2. 如何实现多个摄像头帧画面的同步抓取?

    在单摄像头情况下用 cvQueryFrame 即可抓取一帧画面,实际上这个函数是由两个routine组成的:cvGrabFrame和cvRetrieveFrame(详见Learning OpenCV第103页)。cvGrabFrame将摄像头帧画面即时复制到内部缓存中,然后通过cvRetrieveFrame把我们预定义的一个IplImage型空指针指向缓存内的帧数据。注意这时我们并没有真正把帧数据取出来,它还保存在OpenCV的内部缓存中,下一次读取操作就会被覆盖掉。所以一般我们要另外定义一个IplImage来复制所抓取的帧数据,然后对这个新IplImage进行操作。

    由上面的解释也可以看出,cvGrabFrame的作用就是尽可能快的将摄像头画面数据复制到计算机缓存,这个功能就方便我们实现对多个摄像头的同步抓取,即首先用cvGrabFrame依次抓取各个CvCapture*,然后再用cvRetrieveFrame把帧数据取出来。例如:

     

    [c-sharp] view plaincopy
    1. cvGrabFrame( lfCam );  
    2. cvGrabFrame( riCam );  
    3. frame1 = cvRetrieveFrame( lfCam );  
    4. frame2 = cvRetrieveFrame( riCam );  
    5. if( !frame1|| !frame2) break;  
    6. cvCopyImage(frame1, image1);   
    7. cvCopyImage(frame2, image2);  

    二、摄像头定标

    摄像头定标一般都需要一个放在摄像头前的特制的标定参照物(棋盘纸),摄像头获取该物体的图像,并由此计算摄像头的内外参数。标定参照物上的每一个特征点相对于世界坐标系的位置在制作时应精确测定,世界坐标系可选为参照物的物体坐标系。在得到这些已知点在图像上的投影位置后,可计算出摄像头的内外参数。

    clip_image008

    如上图所示,摄像头由于光学透镜的特性使得成像存在着径向畸变,可由三个参数k1,k2,k3确定;由于装配方面的误差,传感器与光学镜头之间并非完全平行,因此成像存在切向畸变,可由两个参数p1,p2确定。单个摄像头的定标主要是计算出摄像头的内参(焦距f和成像原点cx,cy、五个畸变参数(一般只需要计算出k1,k2,p1,p2,对于鱼眼镜头等径向畸变特别大的才需要计算k3))以及外参(标定物的世界坐标)。 OpenCV 中使用的求解焦距和成像原点的算法是基于张正友的方法( pdf ),而求解畸变参数是基于 Brown 的方法( pdf )。


    1. 图像坐标系、摄像头坐标系和世界坐标系的关系

    clip_image010 clip_image012

    摄像头成像几何关系,其中Oc 点称为摄像头(透镜)的光心,Xc 轴和Yc 轴与图像的x轴和Y轴平行,Zc 轴为摄像头的光轴,它与图像平面垂直。光轴与图像平面的交点O1 ,即为图像坐标系的原点。由点Oc 与Xc 、Yc 、Zc 轴组成的坐标系称为摄像头坐标系,Oc O1 的距离为摄像头焦距,用f表示。

    图像坐标系是一个二维平面,又称为像平面,“@scyscyao :实际上就是摄像头的CCD传感器的表面。每个CCD传感器都有一定的尺寸,也有一定的分辨率,这个就确定了毫米与像素点之间的转换关系。举个例子,CCD的尺寸是8mm X 6mm,帧画面的分辨率设置为640X480,那么毫米与像素点之间的转换关系就是80pixel/mm。”设CCD传感器每个像素点的物理大小为dx*dy,相应地,就有 dx=dy=1/80。


    2. 进行摄像头定标时,棋盘方格的实际大小 square_size (默认为 1.0f )的设置对定标参数是否有影响?

    @scyscyao :当然有。在标定时,需要指定一个棋盘方格的长度,这个长度(一般以毫米为单位,如果需要更精确可以设为0.1毫米量级)与实际长度相同,标定得出的结果才能用于实际距离测量。一般如果尺寸设定准确的话,通过立体标定得出的Translation向量的第一个分量Tx的绝对值就是左右摄像头的中心距。一般可以用这个来验证立体标定的准确度。比如我设定的棋盘格大小为270 (27mm),最终得出的Tx大小就是602.8 (60.28mm),相当精确。”

     

    3. 定标所得的摄像头内参数,即焦距和原点坐标,其数值单位都是一致的吗?怎么把焦距数值换算为实际的物理量?

    @wobject :是的,都是以像素为单位。假设像素点的大小为k x l,单位为mm,则fx = f / k, fy = f / (l * sinA), A一般假设为 90°,是指摄像头坐标系的偏斜度(就是镜头坐标和CCD是否垂直)。摄像头矩阵(内参)的目的是把图像的点从图像坐标转换成实际物理的三维坐标。因此其中的fx, fy, cx, cy 都是使用类似上面的纲量。同样,Q 中的变量 f,cx, cy 也应该是一样的。”

     

    4. 棋盘图像数目应该取多少对摄像头定标比较适宜?

    OpenCV中文论坛上piao的帖子《在OpenCV中用cvCalibrateCamera2进行相机标定(附程序) 》中指出影响摄像头定标结果的准确性和稳定性的因素主要有三个:

    (1) 标定板所在平面与成像平面(image plane)之间的夹角;

    (2) 标定时拍摄的图片数目(棋盘图像数目);

    (3) 图像上角点提取的不准确。

    感觉OpenCV1.2以后对图像角点的提取准确度是比较高的,cvFindChessboardCorners 和 cvFindCornerSubPix结合可以获得很好的角点检测效果(hqhuang1在《[HQ]角点检测(Corner Detection) cvFindCornerSubPix 使用范例 》中给出了相关的应用范例)。因此,影响定标结果较大的就是标定板与镜头的夹角和棋盘图像数目,在实际定标过程中,我感觉棋盘图像数目应该大于20张,每成功检测一次完整的棋盘角点就要变换一下标定板的姿态(包括角度、距离) 

     

    5. 单目定标函数cvCalibrateCamera2采用怎样的 flags 比较合适?

    由于一般镜头只需要计算k1,k2,p1,p2四个参数,所以我们首先要设置 CV_CALIB_FIX_K3;其次,如果所用的摄像头不是高端的、切向畸变系数非常少的,则不要设置 CV_CALIB_ZERO_TANGENT_DIST,否则单目校正误差会很大;如果事先知道摄像头内参的大概数值,并且cvCalibrateCamera2函数的第五个参数intrinsic_matrix非空,则也可设置 CV_CALIB_USE_INTRINSIC_GUESS ,以输入的intrinsic_matrix为初始估计值来加快内参的计算;其它的 flag 一般都不需要设置,对单目定标的影响不大。

     

    P.S. 使用OpenCV进行摄像机定标虽然方便,但是定标结果往往不够准确和稳定,最好是使用 Matlab标定工具箱 来进行定标,再将定标结果取回来用于立体匹配和视差计算。工具箱的使用官方主页 有图文并茂的详细说明,此外,有两篇博文也进行了不错的总结,推荐阅读:

    (1)分享一些OpenCV实现立体视觉的经验

    (2)Matlab标定工具箱使用的一些注意事项


    展开全文
  • OpenCV的示例程序,个人感觉对初学者很有用。 代码位置:\opencv\sources\samples\ parter 1: No1. adaptiveskindetector.cpp 利用HSV空间的色调信息的皮肤检测,背景不能有太多与肤色相似的颜色。
  • parter 1: No1. adaptiveskindetector.cpp 利用HSV空间的色调信息的皮肤检测,背景不能有太多与肤色相似的颜色。效果不是特别好。 No2. bagofwords_classification.cpp ...好大一串……目前还看不懂。...No3....No4....
  • opencv下自带例子总结

    2013-07-04 14:21:20
    parter 1: No1. drawing.cpp 简单的画点、线、文字等。不解释。 No2....人脸检测。根据已训练好的分类器对人脸图像进行检测,用不同颜色的圆形框或矩形框标记出检测出的五官。...相机定标。根据自带的函数提取角点
  • 三维立体测距

    2019-06-10 15:14:12
    4.ARCore SDK for Android 5.菜鸟全球科技挑战赛「智能体积测量」 6.基于vSLAM的方法 7.基于SFM的MVSNet 1.PSMNet 最新的研究表明,利用一对立体图像来估算深度信息可以格式化为一个有监督的学习任务,进而...
  • 双目测距的基本原理

    2016-03-03 16:57:19
    觉得不错,留作记录,原文: ... ...如上图所示,双目测距主要是利用了目标点在左右两幅视图上成像的横向坐标直接存在的差异(即视差 )与目标点到成像平面的距离Z存在着反比例的关系:Z=fT/d。...
  • opencv(一)

    2019-06-27 18:00:59
    OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言...
  • OpenCV 自带例程总结

    2017-03-11 12:31:30
    OpenCV 自带例程总结原文出处opencv 2.4.4版本共100个自带例子。parter 1: No1. adaptiveskindetector.cpp 利用HSV空间的色调信息的皮肤检测,背景不能有太多与肤色相似的颜色。效果不是特别好。...
  • opencv4.1.1自带demo解析

    2019-10-11 17:29:25
    转自... 网上学习opencv的资源有很多,例子也不少,然而很多人却不知道,我们下载好的opencv自带了许多简单易学的demo,对于新手来说特别适用。此篇针对opencv4.1.1版本,在原文基础上有补充。 ...
  • [收藏]opencv 2.4.4版…

    2017-06-29 10:06:50
    原文地址:2.4.4版本示例程序说明">[收藏]opencv 2.4.4版本示例程序说明作者:互信小朴 parter 1: No1.  adaptiveskindetector.cpp 利用HSV空间的色调信息的皮肤检测,背景不能有太多与肤色相似的颜色。效果不是...
  • Buildroot笔记

    2019-11-20 11:33:43
    CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn ... ... 整理Buildroot笔记,包含配置选项注释、目录结构分析、常用命令、构建示例、 使用技巧。 ...1.Buildroot基本介绍 ...Buildroot是Linu...
  • 在Fedora22下移植opencv-2.4.10 首先到官网或其他地方获取opencv-2.4.10。在opencv-2.4.10里面已经包含了cmake了,等会直接用就可以。 在Fedora22下安装编译环境,因为这些操作我已经做完,所以下面都显示跳过...
  • OpenCV 自带例程总结 ... opencv 2.4.4版本共100个自带例子。 parter 1: No1. adaptiveskindetector.cpp ...利用HSV空间的色调信息的皮肤检测,背景不能有太多与肤色相似的颜色。效果不是特别好
1 2
收藏数 30
精华内容 12