【OpenCV-应用之路】-------单目相机标定

一。标定的目的:

   1.畸变矫正。

     因为相机成像大都依赖于感光芯片和透镜,现实的感光芯片和透镜,以及其安装并不可能达到理论的完美情况,因此需要矫正由此成像造成影响。

     畸变可分为以下几种因素:

                ①光轴中心与芯片中心不重合(感光芯片安装引起)。

                              需要求解光轴中心与感光芯片中心的位置偏移量 cx,cy;

                 ②x向和y向 成像投影距离不对等(感光芯片和透镜安装共同引起)。

                              需要求解各自的投影距离fx,fy;

                 ③径向畸变(透镜质量引起)。

                              通过泰勒级数展开式模拟矫正结果,需要求解展开式的各项系数k1,k2,k3...kn;

                 ④切向畸变(感光芯片和透镜安装共同引起)。

                            需要求解切向畸变量参数 p1,p2;

   2.求解像素当量,即像素量和物理世界的长度关系,如 每像素代表多少毫米。(有更简便的方法,以后会更新做法)

   3.求解相机相对姿态。即了解当前相机对于某个特定物体的特定姿态。

二。如何标定

    所有方法标定都大概可以分为以下几个步骤。

       1.获取多组图像与物理世界的对应点。

       2.求解标定参数。

       3.通过标定参数对图像进行矫正

Talk is cheap,show you the code,

以下是Opencv棋盘方式标定的代码示例。

#pragma once
#include

/*创建棋盘图片
Cols_n                    图片列数
Rows_n                    图片行数
WidthOfROI                棋盘宽度(像素)
pMat                      输出图片
*/
void f_get_ChessBoard(int Cols_n, int Rows_n, int WidthOfROI, cv::Mat* pMat)
{
    (*pMat) = cv::Mat(WidthOfROI*Rows_n, WidthOfROI*Cols_n, CV_8UC1, cv::Scalar(0));
    for (int i = 0; i < Cols_n; ++i)
    {
        for (int j = 0; j < Rows_n; ++j)
        {
            if ((i + j) % 2 == 1)
            {
                for (int x = 0; x < WidthOfROI; x++)
                {
                    for (int y = 0; y < WidthOfROI; y++)
                    {
                        int theX = i*WidthOfROI + x;
                        int theY = j*WidthOfROI + y;
                        (*pMat).at(theY, theX) = 255;
                    }
                }
            }

        }
    }
}
//1.使用生成图片函数,保存出图片
//2.打印出图片,并使用固定好的相机,拍摄多张摆放位置不一样的棋盘图片
//3.套入以下函数测试。

/*标定相机
rVecChessboardImages            多张棋盘格的图片
cols_n                          棋盘列数
rows_n                          棋盘行数
square_length                   棋盘边长(单位mm)
pCameraMatrix                   相机内参矩阵
pDistCoeffs                     透镜畸变多项式
*/
bool calibration_solve(const std::vector& rVecChessboardImagePath, const int cols_n, const int rows_n, const double square_length, cv::Mat* pCameraMatrix, cv::Mat*pDistCoeffs)
{
    //读取图像文件
    int ImgSize = rVecChessboardImagePath.size();
    std::vector rVecChessboardImages(ImgSize);
    for (int i = 0; i < ImgSize; i++)
    {
        cv::Mat& rObjImg = rVecChessboardImages[i];
        rObjImg = cv::imread(rVecChessboardImagePath[i]);
        if (rObjImg.empty())
        {
            return 0;//无法读取该文件
        }
    }

    //第一步:获取多组图像与物理世界的对应点
    //①获取图像点信息。
    //在棋盘法标定中, 一般以内角点作为特征点,即以下开始获取每个图片的内角点信息
        cv::Size Size_ChessboardCorners(cols_n - 1, rows_n - 1);//只检测内角点,因此需要行列数都减1,若背景为黑色,则行列都减3
    std::vector>vec_ChessboardCorners;
    for (int i = 0; i < rVecChessboardImages.size(); i++)
    {
        //开始找查某一个棋盘内角点
        std::vector newChessboardCorners;
        findChessboardCorners(rVecChessboardImages[i], Size_ChessboardCorners, newChessboardCorners, cv::CALIB_CB_NORMALIZE_IMAGE);
        //若对当前角点位置精度效果不满意可进行再迭代
        /*
        if (1)
        {
        //方法1:findChessboardCorners中调用的方法,可通过自己再次调用提高效果
        cv::TermCriteria criteria = cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 40, 0.001);//迭代参数
        cv::cornerSubPix(rVecChessboardImages[i], newChessboardCorners, cv::Size(9, 9), cv::Size(-1, -1), criteria);//findChessboardCorners内部已调用,再次调用,查找更精确位置
        }
        else
        {
        //方法2:(不建议此方法,由于对图像做了二值化处理,精度不佳,且部分点可能无法优化)
        //此方法具体工作流程为:找到角点。黑白底分别对原图二值化,腐蚀后取轮廓,找到最接近角点的两对轮廓,对每个轮廓简化,求角点,四个轮廓角点交叉交点。作为新角点。
        find4QuadCornerSubpix(rVecChessboardImages[i], newChessboardCorners, Size(10, 10));
        }
        */

        //如果需要查看每张图片的处理效果
        if (true)
        {
            
            cv::Mat DrawMap;//保证颜色为彩
            if (rVecChessboardImages[i].channels()==1)
                cv::cvtColor(rVecChessboardImages[i], DrawMap, CV_GRAY2BGR);
            else
                DrawMap = rVecChessboardImages[i].clone();

            cv::drawChessboardCorners(DrawMap, Size_ChessboardCorners, newChessboardCorners,1);
            cv::namedWindow("drawMap",0);
            cv::resizeWindow("drawMap", cv::Size(800, 600));
            cv::imshow("drawMap", DrawMap);
            
            cv::waitKey(1000);
        }

        vec_ChessboardCorners.push_back(newChessboardCorners);
    }

    //②获取物理世界信息。
    //因为棋盘每个格子是正方形的,且都在同一平面,假设边长为L,则棋盘内角点的坐标为 (L*当前列,L*当前行,0)
    int Size_getCornersSucceed = vec_ChessboardCorners.size();
    std::vector>vec_ObjPoints(Size_getCornersSucceed, std::vector(Size_ChessboardCorners.width*Size_ChessboardCorners.height));//现实点
    for (int y = 0; y < Size_ChessboardCorners.height; y++)
    {
        for (int x = 0; x < Size_ChessboardCorners.width; x++)
        {
            int index = y*Size_ChessboardCorners.width + x;
            vec_ObjPoints[0][index].x = square_length*x;
            vec_ObjPoints[0][index].y = square_length*y;
            vec_ObjPoints[0][index].z = 0;//以标定板平面为绝对平面
        }
    }
    for (int i = 1; i < Size_getCornersSucceed; i++)
    {
        vec_ObjPoints[i] = vec_ObjPoints[i - 1];
    }
    //第二步:求解标定参数。
    //opecvn提供了非常方便的接口
    cv::Mat CameraMatrix;
    cv::Mat DistCoeffs;
    std::vectorVecRotateMatrix;
    std::vectorVecMoveMatrix;
    double projectionError = cv::calibrateCamera(vec_ObjPoints, vec_ChessboardCorners, cv::Size(rVecChessboardImages[0].cols, rVecChessboardImages[0].rows), CameraMatrix, DistCoeffs, VecRotateMatrix, VecMoveMatrix, CV_CALIB_FIX_K3/*cv::CALIB_FIX_ASPECT_RATIO*/);
    //该函数详细解释参考: 单目相机标定-函数解析:calibrateCamera()——》https://blog.csdn.net/ms_cz_1995/article/details/100183853
    *pCameraMatrix = CameraMatrix;
    *pDistCoeffs = DistCoeffs;
    return true;
}

/*矫正图片
rIn_Mat             输入图片
CameraMatrix        相机内参矩阵
DistCoeffs          透镜畸变多项式
pOut_Mat            校正后的图片
*/
bool f_calibration(const cv::Mat& rIn_Mat, const cv::Mat& CameraMatrix, const cv::Mat& DistCoeffs, cv::Mat&Out_Mat)
{
    //第三步:通过标定参数对图像进行矫正
    //Opencv提供两种矫正方法
    if (1)
    {
        //方法1,适合对单张图片的矫正
        undistort(rIn_Mat, Out_Mat, CameraMatrix, DistCoeffs);
    }
    else
    {
        //方法2,适合对多张图片,或视频流等,同一个相机的多张图片进行矫正。
        cv::Mat mapx;
        cv::Mat mapy;
        if (0)
        {
            //处理黑边版本
            double alpha = 0;//用于控制边缘黑边,取值范围为0-1,alpha=1时保留所有像素,不处理黑边。
            cv::Mat newCameraMatrix = cv::getOptimalNewCameraMatrix(CameraMatrix, DistCoeffs, cv::Size(rIn_Mat.cols, rIn_Mat.rows), alpha);
            initUndistortRectifyMap(CameraMatrix, DistCoeffs, cv::noArray(), newCameraMatrix, cv::Size(rIn_Mat.cols, rIn_Mat.rows), CV_32FC1, mapx, mapy);
        }
        else
        {
            //不处理黑边版本
            initUndistortRectifyMap(CameraMatrix, DistCoeffs, cv::noArray(), cv::noArray(), cv::Size(rIn_Mat.cols, rIn_Mat.rows), CV_32FC1, mapx, mapy);
        }
        remap(rIn_Mat, Out_Mat, mapx, mapy, cv::INTER_LINEAR);
    }
    return true;
}

int main(int argc, char** argv)
{
    cv::Size ChessBoardSize(10, 7);

    //cv::Mat ChessBoard;
    //f_get_ChessBoard(ChessBoardSize.width, ChessBoardSize.height, 100, &ChessBoard);
    //cv::imwrite("ChessBoard.bmp", ChessBoard);
    //打印出"ChessBoard.bmp"放到相机下拍照,获取"1.bmp"

    std::vectorVecChessboardImagePath;
    VecChessboardImagePath.push_back("1.bmp");
    cv::Mat CameraMatrix;
    cv::Mat DistCoeffs;
    calibration_solve(VecChessboardImagePath, ChessBoardSize.width, ChessBoardSize.height, 1, &CameraMatrix, &DistCoeffs);

    cv::Mat inMat = cv::imread("1.bmp");
    cv::Mat outMat;
    f_calibration(inMat, CameraMatrix, DistCoeffs, outMat);
    cv::imwrite("out0.bmp", outMat);

}

因为无大畸变镜头,原图为网图

【OpenCV-应用之路】-------单目相机标定_第1张图片

输出图片:

你可能感兴趣的:(Opencv,OpenCV-应用之路,opencv,计算机视觉)