【opencv 450 samples】calibration

#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"

#include <cctype>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <iostream>

using namespace cv;
using namespace std;

const char * usage =
" \nexample command line for calibration from a live feed用于从实时源进行校准的示例命令行 .\n"
"   calibration  -w=4 -h=5 -s=0.025 -o=camera.yml -op -oe\n"
" \n"
" 从存储图像列表进行校准的示例命令行:\n"
"   imagelist_creator image_list.xml *.png\n"
"   calibration -w=4 -h=5 -s=0.025 -o=camera.yml -op -oe image_list.xml\n"
" where image_list.xml is the standard OpenCV XML/YAML\n"
" use imagelist_creator to create the xml or yaml list\n"
" file consisting of the list of strings, e.g.:\n"
" \n"
"<?xml version=\"1.0\"?>\n"
"<opencv_storage>\n"
"<images>\n"
"view000.png\n"
"view001.png\n"
"<!-- view002.png -->\n"
"view003.png\n"
"view010.png\n"
"one_extra_view.jpg\n"
"</images>\n"
"</opencv_storage>\n";



const char* liveCaptureHelp =
    "当使用来自摄像机的实时视频作为输入时,可以使用以下热键:\n"
        "  <ESC>, 'q' - quit the program\n"
        "  'g' - start capturing images\n"
        "  'u' - switch undistortion on/off\n";

static void help(char** argv)
{
    printf( "这是一个相机校准示例.\n"
        "Usage: %s\n"
        "     -w=<board_width>         # the number of inner corners per one of board dimension\n"
        "     -h=<board_height>        # the number of inner corners per another board dimension\n"
        "     [-pt=<pattern>]          # 图案类型:棋盘或圆圈the type of pattern: chessboard or circles' grid\n"
        "     [-n=<number_of_frames>]  # 用于校准的帧数the number of frames to use for calibration\n"
        "                              # (if not specified, it will be set to the number\n"
        "                              #  of board views actually available如果未指定,它将设置为实际可用的板视图数)\n"
        "     [-d=<delay>]             # a minimum delay in ms between subsequent attempts to capture a next view后续尝试捕获下一个视图之间的最小延迟(毫秒)\n"
        "                              # (used only for video capturing)\n"
        "     [-s=<squareSize>]        # 一些用户定义单位的正方形大小square size in some user-defined units (1 by default)\n"
        "     [-o=<out_camera_params>] # 内部 [和外部] 参数的输出文件名the output filename for intrinsic [and extrinsic] parameters\n"
        "     [-op]                    # 写入检测到的特征点 write detected feature points\n"
        "     [-oe]                    # write extrinsic parameters   写外部参数\n"
        "     [-oo]                    # write refined 3D object points  写精提取的 3D 对象点\n"
        "     [-zt]                    # assume zero tangential distortion 假设零切向畸变\n"
        "     [-a=<aspectRatio>]       # fix aspect ratio 固定纵横比(fx/fy)\n"
        "     [-p]                     # fix the principal point at the center 将主点固定在中心\n"
        "     [-v]                     # flip the captured images around the horizontal axis 围绕水平轴翻转捕获的图像\n"
        "     [-V]                     # 使用视频文件,而不是图像列表,使用 [input_data] 字符串作为视频文件名use a video file, and not an image list, uses\n"
        "                              # [input_data] string for the video file name\n"
        "     [-su]                    # 校准后显示未失真的图像show undistorted images after calibration\n"
        "     [-ws=<number_of_pixel>]  # cornerSubPix 搜索窗口的一半(默认为 11)  Half of search window for cornerSubPix (11 by default)\n"
        "     [-dt=<distance>]         # actual distance between top-left and top-right corners of\n"
        "                              # the calibration grid. If this parameter is specified, a more\n"
        "                              # accurate calibration method will be used which may be better\n"
        "                              # with inaccurate, roughly planar target.校准网格的左上角和右上角之间的实际距离。 \n"		        
		"                              # 如果指定了此参数,将使用更准确的校准方法,这对于不准确的、大致平面的目标可能会更好。\n"
        "     [input_data]             # input data, one of the following:\n"
        "                              #  - 带有网格板图像列表的文本文件text file with a list of the images of the board\n"
        "                              #    可以使用 imagelist_creator 生成文本文件 the text file can be generated with imagelist_creator\n"
        "                              #  - 带有网格板视频的视频文件的名称name of video file with a video of the board\n"
        "                              #  如果未指定 input_data,则使用来自相机的实时取景if input_data not specified, a live view from the camera is used\n"
        "\n", argv[0] );
    printf("\n%s",usage);
    printf( "\n%s", liveCaptureHelp );
}

enum { DETECTION = 0, CAPTURING = 1, CALIBRATED = 2 }; //检测,捕获,校准
enum Pattern { CHESSBOARD, CIRCLES_GRID, ASYMMETRIC_CIRCLES_GRID };//棋盘、圆形网格、不对称圆形网格

static double computeReprojectionErrors(
        const vector<vector<Point3f> >& objectPoints,
        const vector<vector<Point2f> >& imagePoints,
        const vector<Mat>& rvecs, const vector<Mat>& tvecs,
        const Mat& cameraMatrix, const Mat& distCoeffs,
        vector<float>& perViewErrors )//计算重投影误差 
{
    vector<Point2f> imagePoints2;//图像角点坐标
    int i, totalPoints = 0;
    double totalErr = 0, err;
    perViewErrors.resize(objectPoints.size());//每张视图误差向量

    for( i = 0; i < (int)objectPoints.size(); i++ )
    {
        projectPoints(Mat(objectPoints[i]), rvecs[i], tvecs[i],
                      cameraMatrix, distCoeffs, imagePoints2);//根据理论坐标点和 相机内参矩阵和畸变系数 计算映射像素点坐标
        err = norm(Mat(imagePoints[i]), Mat(imagePoints2), NORM_L2);//计算图像识别点矢量与图像重映射点矢量误差
        int n = (int)objectPoints[i].size();
        perViewErrors[i] = (float)std::sqrt(err*err/n);//计算每张图残差
        totalErr += err*err;//总残差
        totalPoints += n;//总点数
    }

    return std::sqrt(totalErr/totalPoints);//总的平均残差
}
//计算网格板角点  在世界坐标系中表示
static void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners, Pattern patternType = CHESSBOARD)
{
    corners.resize(0);//计算角点

    switch(patternType)
    {
      case CHESSBOARD://网格板
      case CIRCLES_GRID://圆形网格
        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;

      case ASYMMETRIC_CIRCLES_GRID://不对称圆形网格
        for( int i = 0; i < boardSize.height; i++ )
            for( int j = 0; j < boardSize.width; j++ )
                corners.push_back(Point3f(float((2*j + i % 2)*squareSize),
                                          float(i*squareSize), 0));
        break;

      default:
        CV_Error(Error::StsBadArg, "Unknown pattern type\n");
    }
}
//运行校准 计算残差 
static bool runCalibration( vector<vector<Point2f> > imagePoints, //识别的图像角点
                    Size imageSize, Size boardSize, Pattern patternType, //图像尺寸,网格板尺寸,类型
                    float squareSize, float aspectRatio,//网格尺寸,纵横比
                    float grid_width, bool release_object,//网格总宽度,释放对象资源
                    int flags, Mat& cameraMatrix, Mat& distCoeffs,//标志位,相机内参矩阵,畸变系数
                    vector<Mat>& rvecs, vector<Mat>& tvecs,//旋转矩阵向量,平移矩阵向量
                    vector<float>& reprojErrs, //重映射误差向量
                    vector<Point3f>& newObjPoints,//新的对象点坐标
                    double& totalAvgErr)//总的平均误差
{
    cameraMatrix = Mat::eye(3, 3, CV_64F);//3X3单位矩阵,初始化相机内参 
    if( flags & CALIB_FIX_ASPECT_RATIO )//
        cameraMatrix.at<double>(0,0) = aspectRatio;//纵横比

    distCoeffs = Mat::zeros(8, 1, CV_64F);//畸变系数矩阵 8x1

    vector<vector<Point3f> > objectPoints(1);//网格板 角点坐标向量,初始化长度1,世界坐标系计算得到
    calcChessboardCorners(boardSize, squareSize, objectPoints[0], patternType);//计算网格板角点
    objectPoints[0][boardSize.width - 1].x = objectPoints[0][0].x + grid_width;//第一行最右侧(最后一个)角点x坐标  设置为固定值  
    newObjPoints = objectPoints[0];//第一行角点理论坐标矢量

    objectPoints.resize(imagePoints.size(),objectPoints[0]);//重置图像角点坐标矢量尺寸与识别的角点矢量一致,不够的话补充第一行角点向量

    double rms;//残差
    int iFixedPoint = -1;
    if (release_object)
        iFixedPoint = boardSize.width - 1;//固定点索引
    rms = calibrateCameraRO(objectPoints, imagePoints, imageSize, iFixedPoint,
                            cameraMatrix, distCoeffs, rvecs, tvecs, newObjPoints,
                            flags | CALIB_FIX_K3 | CALIB_USE_LU);//校准相机 计算残差
    printf("RMS error reported by calibrateCamera: %g\n", rms);//输出残差

    bool ok = checkRange(cameraMatrix) && checkRange(distCoeffs);//检查范围

    if (release_object) {
        cout << "New board corners: " << endl;
        cout << newObjPoints[0] << endl;// 第一行第一个角点坐标
        cout << newObjPoints[boardSize.width - 1] << endl;//第一行最后一个角点坐标
        cout << newObjPoints[boardSize.width * (boardSize.height - 1)] << endl;//最后一行,第一个角点坐标
        cout << newObjPoints.back() << endl;//最后一行最后一个角点坐标
    }

    objectPoints.clear();
    objectPoints.resize(imagePoints.size(), newObjPoints);
    totalAvgErr = computeReprojectionErrors(objectPoints, imagePoints,
                rvecs, tvecs, cameraMatrix, distCoeffs, reprojErrs);//重映射 总的平均误差

    return ok;
}

//保存相机参数
static void saveCameraParams( const string& filename,
                       Size imageSize, Size boardSize,
                       float squareSize, float aspectRatio, int flags,
                       const Mat& cameraMatrix, const Mat& distCoeffs,
                       const vector<Mat>& rvecs, const vector<Mat>& tvecs,
                       const vector<float>& reprojErrs,
                       const vector<vector<Point2f> >& imagePoints,
                       const vector<Point3f>& newObjPoints,
                       double totalAvgErr )
{
    FileStorage fs( filename, FileStorage::WRITE );//写入文件 

    time_t tt;
    time( &tt );//获取时间
    struct tm *t2 = localtime( &tt );//本地时间
    char buf[1024];
    strftime( buf, sizeof(buf)-1, "%c", t2 );//%c	日期和时间表示法	Sun Aug 19 02:56:02 2012

    fs << "calibration_time" << buf;//校准时间

    if( !rvecs.empty() || !reprojErrs.empty() )//旋转矩阵矢量非空,重映射误差向量非空
        fs << "nframes" << (int)std::max(rvecs.size(), reprojErrs.size());//保存 帧数
    fs << "image_width" << imageSize.width;//保存图像宽度
    fs << "image_height" << imageSize.height;//图像高度
    fs << "board_width" << boardSize.width;//网格板宽度
    fs << "board_height" << boardSize.height;//网格板高度
    fs << "square_size" << squareSize;//小网格尺寸

    if( flags & CALIB_FIX_ASPECT_RATIO )
        fs << "aspectRatio" << aspectRatio;//纵横比

    if( flags != 0 )
    {
        sprintf( buf, "flags: %s%s%s%s",
            flags & CALIB_USE_INTRINSIC_GUESS ? "+use_intrinsic_guess" : "",//使用内在猜测 
            flags & CALIB_FIX_ASPECT_RATIO ? "+fix_aspectRatio" : "",//固定纵横比
            flags & CALIB_FIX_PRINCIPAL_POINT ? "+fix_principal_point" : "",//固定主点
            flags & CALIB_ZERO_TANGENT_DIST ? "+zero_tangent_dist" : "" );//零切线距离 
        //cvWriteComment( *fs, buf, 0 );
    }

    fs << "flags" << flags;

    fs << "camera_matrix" << cameraMatrix;//相机内参
    fs << "distortion_coefficients" << distCoeffs;//畸变系数

    fs << "avg_reprojection_error" << totalAvgErr;//平均重映射误差
    if( !reprojErrs.empty() )
        fs << "per_view_reprojection_errors" << Mat(reprojErrs);//每张图片的重映射误差

    if( !rvecs.empty() && !tvecs.empty() )//旋转矩阵向量和平移矩阵向量非空
    {
        CV_Assert(rvecs[0].type() == tvecs[0].type());//断言旋转矩阵和平移矩阵都是Mat格式
        Mat bigmat((int)rvecs.size(), 6, rvecs[0].type());//组合矩阵
        for( int i = 0; i < (int)rvecs.size(); i++ )//遍历旋转矩阵数量
        {
            Mat r = bigmat(Range(i, i+1), Range(0,3));//每行前三列 旋转矩阵 
            Mat t = bigmat(Range(i, i+1), Range(3,6));//每行后三列 平移矩阵

            CV_Assert(rvecs[i].rows == 3 && rvecs[i].cols == 1);//3X1旋转矩阵
            CV_Assert(tvecs[i].rows == 3 && tvecs[i].cols == 1);//3X1平移矩阵
            //*.t() is MatExpr (not Mat) so we can use assignment operator
            r = rvecs[i].t();
            t = tvecs[i].t();
        }
        //cvWriteComment( *fs, "a set of 6-tuples (rotation vector + translation vector) for each view", 0 );
        fs << "extrinsic_parameters" << bigmat;//外参
    }

    if( !imagePoints.empty() )//图像角点向量非空
    {
        Mat imagePtMat((int)imagePoints.size(), (int)imagePoints[0].size(), CV_32FC2);//图像角点坐标矩阵
        for( int i = 0; i < (int)imagePoints.size(); i++ )//遍历每行角点
        {
            Mat r = imagePtMat.row(i).reshape(2, imagePtMat.cols);//imagePtMat一行变两行=r
            Mat imgpti(imagePoints[i]);//第i行角点向量矩阵形式
            imgpti.copyTo(r);//复制到imagePtMat第i行
        }
        fs << "image_points" << imagePtMat;//图像点矩阵
    }

    if( !newObjPoints.empty() )//
    {
        fs << "grid_points" << newObjPoints;//理论网格点坐标
    }
}
//读取图片路径列表
static bool readStringList( const string& filename, vector<string>& l )
{
    l.resize(0);
    FileStorage fs(filename, FileStorage::READ);
    if( !fs.isOpened() )
        return false;
    size_t dir_pos = filename.rfind('/');//反向查找 /   
    if (dir_pos == string::npos)
        dir_pos = filename.rfind('\\');//反向查找 //
    FileNode n = fs.getFirstTopLevelNode();//获取顶层节点
    if( n.type() != FileNode::SEQ )
        return false;
    FileNodeIterator it = n.begin(), it_end = n.end();
    for( ; it != it_end; ++it )
    {
        string fname = (string)*it;// 文件名
        if (dir_pos != string::npos)// 未找到 // 
        {
            string fpath = samples::findFile(filename.substr(0, dir_pos + 1) + fname, false);//查找文件路径:目录位置+文件名
            if (fpath.empty())//
            {
                fpath = samples::findFile(fname);//查找示例文件
            }
            fname = fpath;//文件路径
        }
        else
        {
            fname = samples::findFile(fname);//查找示例文件名得到路径
        }
        l.push_back(fname);//路径列表
    }
    return true;
}

//运行和保存
static bool runAndSave(const string& outputFilename,          //输出文件名
                const vector<vector<Point2f> >& imagePoints,  //图像角点
                Size imageSize, Size boardSize, Pattern patternType, float squareSize,//图像尺寸,标定板尺寸,模式类型,网格尺寸
                float grid_width, bool release_object,//网格总宽度,释放对象
                float aspectRatio, int flags, Mat& cameraMatrix,//纵横比,标志位,相机内参矩阵
                Mat& distCoeffs, bool writeExtrinsics, bool writePoints, bool writeGrid )//畸变系数,写外参,写坐标点,写网格
{
    vector<Mat> rvecs, tvecs;//旋转矩阵向量,平移矩阵向量
    vector<float> reprojErrs;//重映射误差向量
    double totalAvgErr = 0;//总的平均误差
    vector<Point3f> newObjPoints;//新的点对象坐标
	//运行相机校准
    bool ok = runCalibration(imagePoints, imageSize, boardSize, patternType, squareSize,
                   aspectRatio, grid_width, release_object, flags, cameraMatrix, distCoeffs,
                   rvecs, tvecs, reprojErrs, newObjPoints, totalAvgErr);//
    printf("%s. avg reprojection error = %.7f\n",
           ok ? "Calibration succeeded" : "Calibration failed",
           totalAvgErr);//输出重映射总的平均误差

    if( ok )//校准成功
        saveCameraParams( outputFilename, imageSize,//
                         boardSize, squareSize, aspectRatio,
                         flags, cameraMatrix, distCoeffs,
                         writeExtrinsics ? rvecs : vector<Mat>(),
                         writeExtrinsics ? tvecs : vector<Mat>(),
                         writeExtrinsics ? reprojErrs : vector<float>(),
                         writePoints ? imagePoints : vector<vector<Point2f> >(),
                         writeGrid ? newObjPoints : vector<Point3f>(),
                         totalAvgErr );//保存参数
    return ok;
}

//主循环
int main( int argc, char** argv )
{
    Size boardSize, imageSize;//网格板尺寸,图像尺寸
    float squareSize, aspectRatio = 1;//网格大小,纵横比
    Mat cameraMatrix, distCoeffs;//相机内参矩阵,畸变系数
    string outputFilename;//输出文件名
    string inputFilename = "";

    int i, nframes;// i,帧数
    bool writeExtrinsics, writePoints;//写外参,写点
    bool undistortImage = false;//无畸变图像
    int flags = 0;
    VideoCapture capture;//视频采集
    bool flipVertical;//垂直翻转 
    bool showUndistorted;//显示无畸变
    bool videofile;//视频文件
    int delay;//延迟ms
    clock_t prevTimestamp = 0;//上一时间戳
    int mode = DETECTION;//检测模式
    int cameraId = 0;//相机ID
    vector<vector<Point2f> > imagePoints;//图像角点 向量
    vector<string> imageList;//图像列表
    Pattern pattern = CHESSBOARD;//棋盘格

    cv::CommandLineParser parser(argc, argv,
        "{help ||}{w||}{h||}{pt|chessboard|}{n|10|}{d|1000|}{s|1|}{o|out_camera_data.yml|}"
        "{op||}{oe||}{zt||}{a||}{p||}{v||}{V||}{su||}"
        "{oo||}{ws|11|}{dt||}"
        "{@input_data|0|}");
    if (parser.has("help"))
    {
        help(argv);
        return 0;
    }
    boardSize.width = parser.get<int>( "w" );//网格宽度方向数量
    boardSize.height = parser.get<int>( "h" );//棋盘格高度方向数量
    if ( parser.has("pt") )
    {
        string val = parser.get<string>("pt");
        if( val == "circles" )
            pattern = CIRCLES_GRID;//圆形网格
        else if( val == "acircles" )
            pattern = ASYMMETRIC_CIRCLES_GRID;//不对称圆形网格
        else if( val == "chessboard" )
            pattern = CHESSBOARD;//棋盘格
        else
            return fprintf( stderr, "Invalid pattern type: must be chessboard or circles\n" ), -1;
    }
    squareSize = parser.get<float>("s");//网格大小
    nframes = parser.get<int>("n");//帧数
    delay = parser.get<int>("d");//延迟
    writePoints = parser.has("op");//写点
    writeExtrinsics = parser.has("oe");//写外参
    bool writeGrid = parser.has("oo");//写网格
    if (parser.has("a")) {
        flags |= CALIB_FIX_ASPECT_RATIO;//固定纵横比
        aspectRatio = parser.get<float>("a");//解析纵横比
    }
    if ( parser.has("zt") )
        flags |= CALIB_ZERO_TANGENT_DIST;// 设定切向畸变参数(p1,p2)为零。
    if ( parser.has("p") )
        flags |= CALIB_FIX_PRINCIPAL_POINT;//在进行优化时会固定光轴点。当CV_CALIB_USE_INTRINSIC_GUESS参数被设置,光轴点将保持在中心或者某个输入的值。
    flipVertical = parser.has("v");//垂直翻转 
    videofile = parser.has("V");//视频文件
    if ( parser.has("o") )
        outputFilename = parser.get<string>("o");//输出文件名
    showUndistorted = parser.has("su");//显示无畸变
    if ( isdigit(parser.get<string>("@input_data")[0]) )//数字相机
        cameraId = parser.get<int>("@input_data");//相机ID
    else
        inputFilename = parser.get<string>("@input_data");//输入视频文件路径
    int winSize = parser.get<int>("ws");//窗口大小
    float grid_width = squareSize * (boardSize.width - 1);//网格总宽度
    bool release_object = false;
    if (parser.has("dt")) {
        grid_width = parser.get<float>("dt");//网格宽
        release_object = true;//释放对象
    }
    if (!parser.check())//解析错误
    {
        help(argv);//显示帮助信息
        parser.printErrors();//打印错误
        return -1;
    }
    if ( squareSize <= 0 )//小网格尺寸应大于零
        return fprintf( stderr, "Invalid board square width\n" ), -1;
    if ( nframes <= 3 ) //无效的图片数量  4张以上
        return printf("Invalid number of images\n" ), -1;
    if ( aspectRatio <= 0 )//无效的纵横比 应非负
        return printf( "Invalid aspect ratio\n" ), -1;
    if ( delay <= 0 )//无效延迟
        return printf( "Invalid delay\n" ), -1;
    if ( boardSize.width <= 0 )//宽度方向网格数量 
        return fprintf( stderr, "Invalid board width\n" ), -1;
    if ( boardSize.height <= 0 )//高度方向网格数量
        return fprintf( stderr, "Invalid board height\n" ), -1;

    if( !inputFilename.empty() )//输入文件名非空:里面保存有图片路径列表
    {
        if( !videofile && readStringList(samples::findFile(inputFilename), imageList) )//读取视频文件到图像列表失败或者没有视频文件
            mode = CAPTURING;//捕获模式
        else
            capture.open(samples::findFileOrKeep(inputFilename));//打开视频文件
    }
    else
        capture.open(cameraId);//打开相机

    if( !capture.isOpened() && imageList.empty() ) //视频流没打开或者图像列表为空
        return fprintf( stderr, "Could not initialize video (%d) capture\n",cameraId ), -2;//无法初始化视频捕获

    if( !imageList.empty() )//图像列表非空
        nframes = (int)imageList.size();//图像数量,帧数。

    if( capture.isOpened() )//捕获打开
        printf( "%s", liveCaptureHelp );//实时捕获帮助

    namedWindow( "Image View", 1 );//图像视图窗口

    for(i = 0;;i++) //遍历所有帧
    {
        Mat view, viewGray;//图像, 灰度图像
        bool blink = false;

        if( capture.isOpened() )//视频流打开
        {
            Mat view0;
            capture >> view0;
            view0.copyTo(view);//获取一帧
        }
        else if( i < (int)imageList.size() )//遍历图片
            view = imread(imageList[i], 1);//读取一张图片

        if(view.empty())//图像遍历完毕
        {
            if( imagePoints.size() > 0 )//图像角点数大于零
                runAndSave(outputFilename, imagePoints, imageSize,
                           boardSize, pattern, squareSize, grid_width, release_object, aspectRatio,
                           flags, cameraMatrix, distCoeffs,
                           writeExtrinsics, writePoints, writeGrid);//校准相机 保存结果
            break;
        }

        imageSize = view.size();//图像尺寸

        if( flipVertical )//垂直翻转
            flip( view, view, 0 );//沿x-轴翻转

        vector<Point2f> pointbuf;//角点向量
        cvtColor(view, viewGray, COLOR_BGR2GRAY);//灰度图

        bool found;
        switch( pattern )
        {
            case CHESSBOARD:
                found = findChessboardCorners( view, boardSize, pointbuf,
                    CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_FAST_CHECK | CALIB_CB_NORMALIZE_IMAGE);//搜索棋盘格角点
                break;
            case CIRCLES_GRID:
                found = findCirclesGrid( view, boardSize, pointbuf );//搜索 圆形网格角点
                break;
            case ASYMMETRIC_CIRCLES_GRID:
                found = findCirclesGrid( view, boardSize, pointbuf, CALIB_CB_ASYMMETRIC_GRID );//搜索不对称圆形网格角点
                break;
            default:
                return fprintf( stderr, "Unknown pattern type\n" ), -1;
        }

       // 提高找到角点的坐标精度 improve the found corners' coordinate accuracy
        if( pattern == CHESSBOARD && found) cornerSubPix( viewGray, pointbuf, Size(winSize,winSize),
            Size(-1,-1), TermCriteria( TermCriteria::EPS+TermCriteria::COUNT, 30, 0.0001 ));//亚像素角点检测

        if( mode == CAPTURING && found &&
           (!capture.isOpened() || clock() - prevTimestamp > delay*1e-3*CLOCKS_PER_SEC) )//捕获模式,且搜索到角点,且 (没打开视频流或者打开了视频流捕获时间到)
        {
            imagePoints.push_back(pointbuf);//该添加到图像角点向量中
            prevTimestamp = clock();//更新上一时间戳
            blink = capture.isOpened();//视频捕获打开状态
        }

        if(found)
            drawChessboardCorners( view, boardSize, Mat(pointbuf), found );//绘制角点

        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(undistortImage)//无畸变图像
                msg = format( "%d/%d Undist", (int)imagePoints.size(), nframes );
            else
                msg = format( "%d/%d", (int)imagePoints.size(), nframes );  //
        }

        putText( view, msg, textOrigin, 1, 1,
                 mode != CALIBRATED ? Scalar(0,0,255) : Scalar(0,255,0));//绘制字符串

        if( blink )
            bitwise_not(view, view);//图像取反操作

        if( mode == CALIBRATED && undistortImage )//校准模式 无畸变图像
        {
            Mat temp = view.clone();
            undistort(temp, view, cameraMatrix, distCoeffs);//除畸变
        }

        imshow("Image View", view);//显示
        char key = (char)waitKey(capture.isOpened() ? 50 : 500);//等待按键

        if( key == 27 )
            break;

        if( key == 'u' && mode == CALIBRATED )
            undistortImage = !undistortImage;//切换无畸变模式

        if( capture.isOpened() && key == 'g' )
        {
            mode = CAPTURING;//捕获模式
            imagePoints.clear();
        }

        if( mode == CAPTURING && imagePoints.size() >= (unsigned)nframes )//捕获模式,图像角点数量超过帧数
        {
            if( runAndSave(outputFilename, imagePoints, imageSize,
                       boardSize, pattern, squareSize, grid_width, release_object, aspectRatio,
                       flags, cameraMatrix, distCoeffs,
                       writeExtrinsics, writePoints, writeGrid))//运行校准和保存 校准参数
                mode = CALIBRATED;//校准完毕
            else
                mode = DETECTION;//检测
            if( !capture.isOpened() )//捕获未打开
                break;
        }
    }

    if( !capture.isOpened() && showUndistorted ) //捕获已关闭 且 显示无畸变
    {
        Mat view, rview, map1, map2; 
        initUndistortRectifyMap(cameraMatrix, distCoeffs, Mat(),
                                getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, 0),
                                imageSize, CV_16SC2, map1, map2);//计算无畸变和修正转换映射

        for( i = 0; i < (int)imageList.size(); i++ ) 
        {
            view = imread(imageList[i], 1);
            if(view.empty())
                continue;
            //undistort( view, rview, cameraMatrix, distCoeffs, cameraMatrix );
            remap(view, rview, map1, map2, INTER_LINEAR);//除畸变
            imshow("Image View", rview);
            char c = (char)waitKey();
            if( c == 27 || c == 'q' || c == 'Q' )
                break;
        }
    }

    return 0;
}

笔记:


#一、 相机标定原理讲解 https://www.cnblogs.com/haoxing990/p/4588566.html
其中,k1,k2,k3,k4,k5和k6为径向畸变,p1和p2为轴向畸变。在opencv中,畸变矩阵的参数为(k1,k2,p1,p2[,k3[,k4,k5,k6]]])。  
Opencv中的标定模块常用的标定函数:
double calibrateCamera(InputArrayOfArrays objectPoints, InputArrayOfArrays imagePoints,Size imageSize, InputOutputArray cameraMatrix, InputOutputArray distCoeffs, OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs, int flags=0)

其中
objectPoints 为世界坐标系中的点。在使用时,应该输入一个三维点的vector的vector,即vector<vector<Point3f> > (注要记得括号的区别)objectPoints。

imagePoints 为其对应的图像点。和objectPoints一样,应该输入std::vector<std::vector<cv::Point2f> > imagePoints 型的变量。

imageSize 为图像的大小,在计算相机的内参数和畸变矩阵需要用到;

cameraMatrix 为内参数矩阵(相机矩阵 cameraMatrix)。输入一个 cv::Mat cameraMatrix 即可。

distCoeffs 为畸变矩阵。输入一个 cv::Mat distCoeffs 即可。

rvecs 为旋转向量;应该输入一个cv::Mat的vector,即vector<cv::Mat> rvecs 因为每个 vector<Point3f> 会得到一个rvecs。

tvecs 为位移向量;和 rvecs 一样,也应该为 vector<cv::Mat> tvecs 。
标定时所采用的算法可如下某个或者某几个参数:
CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,在cameraMatrix矩阵中应该有fx,fy,cx,cy的估计值。否则的话,将初始化(cx,cy)图像的中心点,使用最小二乘估算出fx,fy。如果内参数矩阵和畸变居中已知的时候,应该标定模块中的solvePnP()函数计算外参数矩阵。
CV_CALIB_FIX_PRINCIPAL_POINT:在进行优化时会固定光轴点。当CV_CALIB_USE_INTRINSIC_GUESS参数被设置,光轴点将保持在中心或者某个输入的值。
CV_CALIB_FIX_ASPECT_RATIO:固定fx/fy的比值,只将fy作为可变量,进行优化计算。当CV_CALIB_USE_INTRINSIC_GUESS没有被设置,fx和fy将会被忽略。只有fx/fy的比值在计算中会被用到。
CV_CALIB_ZERO_TANGENT_DIST :设定切向畸变参数(p1,p2)为零。
CV_CALIB_FIX_K1,...,CV_CALIB_FIX_K6 :对应的径向畸变在优化中保持不变。如果设置了 CV_CALIB_USE_INTRINSIC_GUESS 参数,
CV_CALIB_RATIONAL_MODEL :计算 k4,k5,k6 三个畸变参数。如果没有设置,则只计算其它5个畸变参数。


# 二、 opencv学习---VideoCapture 类基础知识
方法:  cv::VideoCapture capture(const string& filename);  // 从视频文件读取 
例程:  cv::VideoCapture capture("C:/Users/DADA/DATA/gogo.avi");  // 从视频文件读取 
 在opencv中关于视频的读操作是通过VideoCapture类来完成的;关于视频的写操作是通过VideoWriter类来实现的。
 cv::VideoCapture capture(int device );  //视频捕捉设备 id ---笔记本电脑的用0表示 

cv::VideoCapture VideoCapture;  这里的第二个VideoCapture是一个对象名
VideoCapture.open( "C:/Users/DADA/DATA/gogo.avi" );  
将视频帧读取到cv::Mat矩阵中,有两种方式:一种是read()操作;另一种是 “>>”操作。
cv::Mat frame;  
cap.read(frame); //读取方式一  
cap >> frame; //读取方式二 

VideoCapture类的构造函数:
C++: VideoCapture::VideoCapture()
C++: VideoCapture::VideoCapture(const string& filename)
C++: VideoCapture::VideoCapture(int device)
功能:创建一个VideoCapture类的实例,如果传入对应的参数,可以直接打开视频文件或者要调用的摄像头。
参数:
filename – 打开的视频文件名。
device – 打开的视频捕获设备id ,如果只有一个摄像头可以填0,表示打开默认的摄像头。 
2.VideoCapture::open
功能:打开一个视频文件或者打开一个捕获视频的设备(也就是摄像头)
C++: bool VideoCapture::open(const string& filename)
C++: bool VideoCapture::open(int device)
参数: 
filename – 打开的视频文件名。
device – 打开的视频捕获设备id ,如果只有一个摄像头可以填0,表示打开默认的摄像头。
    通过对VideoCapture类的构造函数和open函数分析,可以发现opencv读入视频的方法一般有如下两种。比如读取当前目录下名为"dog.avi"的视频文件,那么这两种写法分别如下。
(1)先实例化再初始化:
VideoCapture capture;
capture.open("dog.avi");
(2)在实例化的同时进行初始化:
VideoCapture("dog.avi");
3.VideoCapture::isOpened
C++: bool VideoCapture::isOpened()
功能:判断视频读取或者摄像头调用是否成功,成功则返回true。
4.VideoCapture::release
C++: void VideoCapture::release()
功能:关闭视频文件或者摄像头。
5.VideoCapture::grab
C++: bool VideoCapture::grab()
功能:从视频文件或捕获设备中抓取下一个帧,假如调用成功返回true。(细节请参考opencv文档说明)
6.VideoCapture::retrieve
C++: bool VideoCapture::retrieve(Mat& image, int channel=0)
功能:解码并且返回刚刚抓取的视频帧,假如没有视频帧被捕获(相机没有连接或者视频文件中没有更多的帧)将返回false。
7.VideoCapture::read
C++: VideoCapture& VideoCapture::operator>>(Mat& image)
C++: bool VideoCapture::read(Mat& image)
功能:该函数结合VideoCapture::grab()和VideoCapture::retrieve()其中之一被调用,用于捕获、解码和返回下一个视频帧这是一个最方便的函数对于读取视频文件或者捕获数据从解码和返回刚刚捕获的帧,假如没有视频帧被捕获(相机没有连接或者视频文件中没有更多的帧)将返回false。
    从上面的API中我们会发现获取视频帧可以有多种方法 :
// 方法一 
capture.read(frame); 
// 方法二 
capture.grab(); 
// 方法三
capture.retrieve(frame); 
// 方法四
capture >> frame;
8.VideoCapture::get
C++: double VideoCapture::get(int propId)
功能:一个视频有很多属性,比如:帧率、总帧数、尺寸、格式等,VideoCapture的get方法可以获取这些属性。
参数:属性的ID。
属性的ID可以是下面的之一:
CV_CAP_PROP_POS_MSEC Current position of the video file in milliseconds or video capture timestamp.
CV_CAP_PROP_POS_FRAMES 0-based index of the frame to be decoded/captured next.
CV_CAP_PROP_POS_AVI_RATIO Relative position of the video file: 0 - start of the film, 1 - end of the film.
CV_CAP_PROP_FRAME_WIDTH Width of the frames in the video stream.
CV_CAP_PROP_FRAME_HEIGHT Height of the frames in the video stream.
CV_CAP_PROP_FPS Frame rate.
CV_CAP_PROP_FOURCC 4-character code of codec.
CV_CAP_PROP_FRAME_COUNT Number of frames in the video file.
CV_CAP_PROP_FORMAT Format of the Mat objects returned by retrieve() .
CV_CAP_PROP_MODE Backend-specific value indicating the current capture mode.
CV_CAP_PROP_BRIGHTNESS Brightness of the image (only for cameras).
CV_CAP_PROP_CONTRAST Contrast of the image (only for cameras).
CV_CAP_PROP_SATURATION Saturation of the image (only for cameras).
CV_CAP_PROP_HUE Hue of the image (only for cameras).
CV_CAP_PROP_GAIN Gain of the image (only for cameras).
CV_CAP_PROP_EXPOSURE Exposure (only for cameras).
CV_CAP_PROP_CONVERT_RGB Boolean flags indicating whether images should be converted to RGB.
CV_CAP_PROP_WHITE_BALANCE Currently not supported
CV_CAP_PROP_RECTIFICATION Rectification flag for stereo cameras (note: only supported by DC1394 v 2.x backend currently)
Note: 如果查询的视频属性是VideoCapture类不支持的,将会返回0。
9.VideoCapture::set
C++: bool VideoCapture::set(int propertyId, double value)
功能:设置VideoCapture类的属性,设置成功返回ture,失败返回false。
参数:第一个是属性ID,第二个是该属性要设置的值。
属性ID如下:
CV_CAP_PROP_POS_MSEC Current position of the video file in milliseconds.
CV_CAP_PROP_POS_FRAMES 0-based index of the frame to be decoded/captured next.
CV_CAP_PROP_POS_AVI_RATIO Relative position of the video file: 0 - start of the film, 1 - end of the film.
CV_CAP_PROP_FRAME_WIDTH Width of the frames in the video stream.
CV_CAP_PROP_FRAME_HEIGHT Height of the frames in the video stream.
CV_CAP_PROP_FPS Frame rate.
CV_CAP_PROP_FOURCC 4-character code of codec.
CV_CAP_PROP_FRAME_COUNT Number of frames in the video file.
CV_CAP_PROP_FORMAT Format of the Mat objects returned by retrieve() .
CV_CAP_PROP_MODE Backend-specific value indicating the current capture mode.
CV_CAP_PROP_BRIGHTNESS Brightness of the image (only for cameras).
CV_CAP_PROP_CONTRAST Contrast of the image (only for cameras).
CV_CAP_PROP_SATURATION Saturation of the image (only for cameras).
CV_CAP_PROP_HUE Hue of the image (only for cameras).
CV_CAP_PROP_GAIN Gain of the image (only for cameras).
CV_CAP_PROP_EXPOSURE Exposure (only for cameras).
CV_CAP_PROP_CONVERT_RGB Boolean flags indicating whether images should be converted to RGB.
CV_CAP_PROP_WHITE_BALANCE Currently unsupported
CV_CAP_PROP_RECTIFICATION Rectification flag for stereo cameras (note: only supported by DC1394 v 2.x backend currently)


#三、 【OpenCV3】图像翻转——cv::flip()详解
https://blog.csdn.net/guduruyu/article/details/68942211
在opencv2和opencv中,cv::flip()支持图像的翻转(上下翻转、左右翻转,以及同时均可)。
具体调用形式如下:
void cv::flip(
		cv::InputArray src, // 输入图像
		cv::OutputArray dst, // 输出
		int flipCode = 0 // >0: 沿y-轴翻转, 0: 沿x-轴翻转, <0: x、y轴同时翻转
	);
https://blog.csdn.net/guduruyu/article/details/68942211 


# 四、 计算机视觉--opencv圆点提取  findCirclesGrid
https://blog.csdn.net/u012348774/article/details/108607390 
https://blog.csdn.net/u012348774/article/details/108607390
bool cv::findCirclesGrid	(	
    InputArray 	image, #输入图片,可以是单通道也可以是三通道
    Size 	patternSize, # 棋盘格的大小,分别是每行有几个圆和每列有几个圆,后边会举例子
    OutputArray 	centers, # 输出的圆心列表
    int 	flags, # 共有三个参数,CALIB_CB_SYMMETRIC_GRID 表示棋盘格是对称的,CALIB_CB_ASYMMETRIC_GRID表示棋盘格是不对称的,CALIB_CB_CLUSTERING是一个特殊参数,在仿射变换比较严重时可以使用
    const Ptr< FeatureDetector > & 	blobDetector, # 圆提取方法
    CirclesGridFinderParameters 	parameters  # 一些额外参数,但是文档里未介绍如何使用
)	

首先展示两种常用的圆形格,如下图所示(来源于参考资料);其中第一种时非对称圆形格(对应CALIB_CB_ASYMMETRIC_GRID),图中也已经展示了如何读取其大小;第二种时对称圆形格(对应CALIB_CB_SYMMETRIC_GRID ),此时大小就很明显了,7*7。通常情况下,对称和非对称并没有太大的区别,但是明显发现对称圆形格旋转后可能还是一样的,此时可能会对后方交会等等操作带来一些误解,因此通常而言都推荐使用非对称;除非本身旋转角度不大,那就无所谓。

另外需要额外注意的时,findCirclesGrid最终需要输出的时grid。一般情况下,当影像的倾角比较大的时候,可能会难以寻找grid。此时可以利用CALIB_CB_CLUSTERING。
CALIB_CB_CLUSTERING 会以层次Kmean方式聚类检测值,并计算检测点围成的凸包角点,并排序外部角点。同时,会根据排序后的2D外部角点和理想估计点,计算单应性H,再计算出所有监测点的投影点,再根据Knn选取跟理想估计点近似最近点,作为实际输出的圆形中点。(来源参考文献)

圆形的提取方法
通常,如果不加以特殊说明,findCirclesGrid提取圆形的时候使用默认const Ptr<FeatureDetector> &blobDetector = SimpleBlobDetector::create(),其默认参数列表如下:

	// 参考:https://blog.csdn.net/app_12062011/article/details/51953030
    thresholdStep = 10;    //二值化的阈值步长
    minThreshold = 50;   //二值化的起始阈值
    maxThreshold = 220;    //二值化的终止阈值 
    
    //重复的最小次数,只有属于灰度图像斑点的那些二值图像斑点数量大于该值时,该灰度图像斑点才被认为是特征点  
    minRepeatability = 2;     
    //最小的斑点距离,不同二值图像的斑点间距离小于该值时,被认为是同一个位置的斑点,否则是不同位置上的斑点  
    minDistBetweenBlobs = 10;  
  
    filterByColor = true;    //斑点颜色的限制变量  
    blobColor = 0;    //表示只提取黑色斑点;如果该变量为255,表示只提取白色斑点  
  
    filterByArea = true;    //斑点面积的限制变量  
    minArea = 25;    //斑点的最小面积  
    maxArea = 5000;    //斑点的最大面积  
  
    filterByCircularity = false;    //斑点圆度的限制变量,默认是不限制  
    minCircularity = 0.8f;    //斑点的最小圆度  
    //斑点的最大圆度,所能表示的float类型的最大值  
    maxCircularity = std::numeric_limits<float>::max();  
  
    filterByInertia = true;    //斑点惯性率的限制变量  
    //minInertiaRatio = 0.6;  
    minInertiaRatio = 0.1f;    //斑点的最小惯性率  
    maxInertiaRatio = std::numeric_limits<float>::max();    //斑点的最大惯性率  
  
    filterByConvexity = true;    //斑点凸度的限制变量  
    //minConvexity = 0.8;  
    minConvexity = 0.95f;    //斑点的最小凸度  
    maxConvexity = std::numeric_limits<float>::max();    //斑点的最大凸度  


# 五、 cv::cornerSubPix()亚像素角点检测 
https://blog.csdn.net/originalcandy/article/details/84838549
cv::goodFeaturesToTrack()提取到的角点只能达到像素级别,在很多情况下并不能满足实际的需求,这时,我们则需要使用cv::cornerSubPix()对检测到的角点作进一步的优化计算,可使角点的精度达到亚像素级别。

具体调用形式如下:
    void cv::cornerSubPix(
        cv::InputArray image, // 输入图像
        cv::InputOutputArray corners, // 角点(既作为输入也作为输出)
        cv::Size winSize, // 区域大小为 NXN; N=(winSize*2+1)
        cv::Size zeroZone, // 类似于winSize,但是总具有较小的范围,Size(-1,-1)表示忽略
        cv::TermCriteria criteria // 停止优化的标准
    );

第一个参数是输入图像,和cv::goodFeaturesToTrack()中的输入图像是同一个图像。
第二个参数是检测到的角点,即是输入也是输出。

第三个参数是计算亚像素角点时考虑的区域的大小,大小为NXN; N=(winSize*2+1)。

第四个参数作用类似于winSize,但是总是具有较小的范围,通常忽略(即Size(-1, -1))。

第五个参数用于表示计算亚像素时停止迭代的标准,可选的值有cv::TermCriteria::MAX_ITER 、cv::TermCriteria::EPS(可以是两者其一,或两者均选),前者表示迭代次数达到了最大次数时停止,后者表示角点位置变化的最小值已经达到最小时停止迭代。二者均使用cv::TermCriteria()构造函数进行指定。

通过一个示例看看cv::cornerSubPix()亚像素角点检测的具体效果。


    cv::Mat image_color = cv::imread("image.jpg", cv::IMREAD_COLOR);
 
    //用于绘制亚像素角点
    cv::Mat image_copy = image_color.clone();
    //使用灰度图像进行角点检测
    cv::Mat image_gray;
    cv::cvtColor(image_color, image_gray, cv::COLOR_BGR2GRAY);
 
    //设置角点检测参数
    std::vector<cv::Point2f> corners;
    int max_corners = 100;
    double quality_level = 0.01;
    double min_distance = 10;
    int block_size = 3;
    bool use_harris = false;
    double k = 0.04;
 
    //角点检测
    cv::goodFeaturesToTrack(image_gray,
        corners,
        max_corners,
        quality_level,
        min_distance,
        cv::Mat(),
        block_size,
        use_harris,
        k);
 
    //将检测到的角点绘制到原图上
    for (int i = 0; i < corners.size(); i++)
    {undefined
        cv::circle(image_color, corners[i], 5, cv::Scalar(0, 0, 255), 2, 8, 0);
    }
 
    //指定亚像素计算迭代标注
    cv::TermCriteria criteria = cv::TermCriteria(
                    cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS,
                    40,
                    0.01);
 
    //亚像素检测
    cv::cornerSubPix(image_gray, corners, cv::Size(5, 5), cv::Size(-1, -1), criteria);
 
    //将检测到的亚像素角点绘制到原图上
    for (int i = 0; i < corners.size(); i++)
    {undefined
        cv::circle(image_copy, corners[i], 5, cv::Scalar(0, 255, 0), 2, 8, 0);
    }
 
    cv::imshow("corner", image_color);
    cv::imshow("sub pixel corner", image_copy);
 
    cv::imwrite("corner.jpg", image_color);
    cv::imwrite("corner_sub.jpg", image_copy);
    cv::waitKey(0);
    return;

直接角点检测和亚像素角点检测的结果分别如下,从检测的效果来看,使用亚像素角点检测后角点的精细度确实得到了显著的提升。


# 六、 OpenCV getTextSize函数中的baseLine参数
cv::Size cv::getTextSize(
		const string& text,
		int fontFace,
		double fontScale,
		int thickness,
		int* baseLine
	);
其中前四个参数都好理解:text为文本,fontFace为文本的字体类型,fontScale为文本大小的倍数(以字体库中的大小为基准而放大的倍数),thickness为文本的粗细。最后一个参数baseLine是指距离文本最低点对应的y坐标,乍听起来很乱,具体如图所示
我们对于四线本应该很熟悉,在小学英语时会经常用这种本子写英文,借助其中的三条线可以帮助我们了解该函数。函数返回的Size中的height其实是图中两个红线的距离,而baseLine即下面红线与蓝线直接的距离。


#七 、 cv2.bitwise_not(主要讲这个)
https://blog.csdn.net/m0_48095841/article/details/117913362
用法1:image = cv2.bitwise_not(src, dst=None, mask=None)
其中src表示要进行操作的图像,dst是输出的图像(其实就是得到的image),一般不写就好了
bitwise_not的作用是取反操作,例如:

import cv2
import numpy as np
 
 
img = cv2.imread("pic.jpg")
image = cv2.bitwise_not(src=img)
cv2.imshow("mask", image)
cv2.waitKey()
cv2.destroyAllWindows()


# 八、 initUndistortRectifyMap
CV_EXPORTS_W void initUndistortRectifyMap( InputArray cameraMatrix, InputArray distCoeffs,
                           InputArray R, InputArray newCameraMatrix,
                           Size size, int m1type, OutputArray map1, OutputArray map2 );

//! initializes maps for cv::remap() for wide-angle
CV_EXPORTS_W float initWideAngleProjMap( InputArray cameraMatrix, InputArray distCoeffs,
                                         Size imageSize, int destImageWidth,
                                         int m1type, OutputArray map1, OutputArray map2,
                                         int projType = PROJ_SPHERICAL_EQRECT, double alpha = 0);
https://blog.csdn.net/u013341645/article/details/78710740 
函数功能:计算无畸变和修正转换映射。


Opencv之initUndistortRectifyMap函数和 remap()函数
https://blog.csdn.net/weixin_44279335/article/details/109448583
initUndistortRectifyMap函数
函数说明:
这个函数使用于计算无畸变和修正转换关系。
函数原型:
C++:
void initUndistortRectifyMap( InputArray cameraMatrix, InputArray distCoeffs,
InputArray R, InputArray newCameraMatrix, Size size, int m1type, OutputArray map1, OutputArray map2 );
python:
map1, map2=cv.initUndistortRectifyMap(cameraMatrix, distCoeffs, R, newCameraMatrix, size, m1type[, map1[, map2]])

参数说明:
cameraMatrix——输入的摄像头内参数矩阵(3X3矩阵)
distCoeffs——输入的摄像头畸变系数矩阵(5X1矩阵)
R——输入的第一和第二摄像头坐标系之间的旋转矩阵
newCameraMatrix——输入的校正后的3X3摄像机矩阵
size——摄像头采集的无失真图像尺寸
m1type——map1的数据类型,可以是CV_32FC1或CV_16SC2
map1——输出的X坐标重映射参数
map2——输出的Y坐标重映射参数

remap()函数
函数说明:一幅图像中某位置的像素放置到另一个图片指定位置。
函数原型:
C++:
void remap(InputArray src, OutputArray dst, InputArray map1, InputArray map2,
int interpolation, intborderMode = BORDER_CONSTANT,
const Scalar& borderValue = Scalar())
python:
dst = cv.remap(src, map1, map2, intermap2polation, dst=None, borderMode=None, borderValue=None)
cv.remap(src, map1, map2, interpolation[, dst[, borderMode[, borderValue]]])
参数说明:
src——输入图像,即原图像,需要单通道8位或者浮点类型的图像
dst(c++)——输出图像,即目标图像,需和原图形一样的尺寸和类型
map1——它有两种可能表示的对象:(1)表示点(x,y)的第一个映射;(2)表示CV_16SC2,CV_32FC1等
map2——有两种可能表示的对象:(1)若map1表示点(x,y)时,这个参数不代表任何值;(2)表示 CV_16UC1,CV_32FC1类型的Y值
intermap2polation——插值方式,有四中插值方式:
(1)INTER_NEAREST——最近邻插值
(2)INTER_LINEAR——双线性插值(默认)
(3)INTER_CUBIC——双三样条插值(默认)
(4)INTER_LANCZOS4——lanczos插值(默认)

intborderMode——边界模式,默认BORDER_CONSTANT
borderValue——边界颜色,默认Scalar()黑色

程序实现:
#摄像头内参矩阵参数
self.intrinsic = np.array([[5.8629461226441333e+02, 0., 3.2307824498677365e+02],
[0., 5.8580366873441926e+02, 2.4267926102121419e+02],
[0., 0., 1.]])
#摄像头畸变矩阵参数
self.distortion_coeff = np.array([[-4.3728820025053183e-01], [2.4851525131136012e-01],
[-7.7560743133785464e-04], [-6.2320226939478036e-04],
[-8.3503368627783472e-02]])

map1, map2 = cv2.initUndistortRectifyMap(self.intrinsic, self.distortion_coeff, None, None,
(frame.shape[1], frame.shape[0]), cv2.CV_32FC1)
frame = cv2.remap(frame, map1, map2, cv2.INTER_LINEAR)
最后:
参考链接:https://docs.opencv.org/master/index.html


#九、 C++中的find()及rfind()
https://blog.csdn.net/u011600477/article/details/77164049
string中 find()的应用 (rfind() 类似,只是从反向查找)
原型如下:
(1)size_t find (const string& str, size_t pos = 0) const; //查找对象–string类对象
(2)size_t find (const char* s, size_t pos = 0) const; //查找对象–字符串
(3)size_t find (const char* s, size_t pos, size_t n) const; //查找对象–字符串的前n个字符
(4)size_t find (char c, size_t pos = 0) const; //查找对象–字符
结果:找到 – 返回 第一个字符的索引
没找到–返回 string::npos

string中 find()的应用 (rfind() 类似,只是从反向查找)
原型如下:
(1)size_t find (const string& str, size_t pos = 0) const; //查找对象–string类对象
(2)size_t find (const char* s, size_t pos = 0) const; //查找对象–字符串
(3)size_t find (const char* s, size_t pos, size_t n) const; //查找对象–字符串的前n个字符
(4)size_t find (char c, size_t pos = 0) const; //查找对象–字符
结果:找到 – 返回 第一个字符的索引
没找到–返回 string::npos

#十、 OpenCV —FileStorage类的数据读写操作与示例
https://daimajiaoliu.com/daima/60ea5ec20c9c807
OpenCV的许多应用都需要使用数据的存储于读取,例如经过3D校准后的相机,需要存储校准结果矩阵,以方便下次调用该数据;基于机器学习的应用,同样需要将学习得到的参数保存等。OpenCV通过XML/YAML格式实现数据持久化。本文简要梳理了使用 FileStorage类进行基本数据持久化操作,给出了示例代码。

主要内容包括:
FileStorage 类
构造函数
operator <<
FileStorage::open
FileStorage::isOpened
FileStorage::release
FileStorage::getFirstTopLevelNode
FileStorage::root
FileStorage::operator[]

示例代码
创建写入器、创建读取器
写入数值、写入矩阵、写入自定义数据结构、写入当前时间
读取数值、读取矩阵、读取自定义数据结构、读取当前时间
关闭写入器、关闭读取器

**FileStorage
类**
FileStorage类将各种OpenCV数据结构的数据存储为XML 或 YAML格式。同时,也可以将其他类型的数值数据存储为这两种格式。
 

OpenCV —数据持久化: FileStorage类的数据存取操作与示例
https://blog.csdn.net/iracer/article/details/51339377
构造函数
FileStorage类的构造函数为:
cv::FileStorage(const string& source, int flags, const string& encoding=string());

参数:
source –存储或读取数据的文件名(字符串),其扩展名(.xml 或 .yml/.yaml)决定文件格式。
flags – 操作模式,包括:
FileStorage::READ 打开文件进行读操作
FileStorage::WRITE 打开文件进行写操作
FileStorage::APPEND打开文件进行附加操作
FileStorage::MEMORY 从source读数据,或向内部缓存写入数据(由FileStorage::release返回)
encoding – 文件编码方式。目前不支持UTF-16 XML 编码,应使用 8-bit 编码。

写数据operator <<
向filestorage中写入数据
template<typename_Tp> FileStorage& operator<<(FileStorage& fs, const _Tp& value)
template<typename_Tp> FileStorage& operator<<(FileStorage& fs, const vector<_Tp>& vec)
写数据operator <<
向filestorage中写入数据
template<typename_Tp> FileStorage& operator<<(FileStorage& fs, const _Tp& value)
template<typename_Tp> FileStorage& operator<<(FileStorage& fs, const vector<_Tp>& vec)

以下代码分别演示写入数值、矩阵、多个变量、当前时间和关闭文件:
// 1.create our writter
	cv::FileStorage fs("test.yml", FileStorage::WRITE);
	
	// 2.Save an int
	int imageWidth= 5;
	int imageHeight= 10;
	fs << "imageWidth" << imageWidth;
	fs << "imageHeight" << imageHeight;
 
	// 3.Write a Mat
	cv::Mat m1= Mat::eye(3,3, CV_8U);
	cv::Mat m2= Mat::ones(3,3, CV_8U);
	cv::Mat resultMat= (m1+1).mul(m1+2);
	fs << "resultMat" << resultMat;
 
	// 4.Write multi-variables 
	cv::Mat cameraMatrix = (Mat_<double>(3,3) << 1000, 0, 320, 0, 1000, 240, 0, 0, 1);
    cv::Mat distCoeffs = (Mat_<double>(5,1) << 0.1, 0.01, -0.001, 0, 0);
    fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;
 
	// 5.Save local time
	time_t rawtime; time(&rawtime); //#include <time.h>
	fs << "calibrationDate" << asctime(localtime(&rawtime));
 
	// 6.close the file opened
	fs.release();
https://blog.csdn.net/iracer/article/details/51339377 


FileStorage::open
打开一个文件
boolFileStorage::open(const string& filename, int flags, const string&encoding=string()) 

参数:
filename – 待打开的文件名,其扩展名(.xml 或 .yml/.yaml) 决定文件格式(XML 或 YAML)
flags – 操作模式。见构造函数
encoding – 文件编码方式。

// open a file
	cv::FileStorage fs;
	fs.open("test.yml",FileStorage::WRITE);
// ... some process here
	fs.release();


FileStorage::isOpened
检查文件是否已经打开,调用:
boolFileStorage::isOpened()
返回:
ture – 如果对象关联了当前文件;
false – 其他情况。

尝试打开文件后调用此方法是个比较好的做法。
// Checks whether the file is opened
	cv::FileStorage fs;
	fs.open("test.yml",FileStorage::WRITE);
 
	bool flag = fs.isOpened();
	cout<<"flag = "<<flag<<endl<<endl;
// if failed to open a file
	if(!fs.isOpened()){
		cout<<"failed to open file test.yml "<<endl<<endl;
		return 1;
	}


FileStorage::release
存储或读取操作完成后,需要关闭文件并释放缓存,调用
void FileStorage::release()
cv::FileStorage fs("test.yml", fs::WRITE);
//... some processing here
fs.release();

FileStorage::getFirstTopLevelNode
返回映射(mapping)顶层的第一个元素:
FileStorage::getFirstTopLevelNode()
// open a file for reading
	fs.open("test.yml", FileStorage::READ);
// get the first top level node
	int firstNode = fs.getFirstTopLevelNode();
	cout<<"the First Top Level Node = "<<firstNode<<endl<<endl;


FileStorage::root
返回顶层映射(mapping)
FileNode FileStorage::root(int streamidx=0) 
参数:
streamidx – 从0开始的字符串索引。大部分情况文件中只有一个串,但YAML支持多个串,因此可以有多个。
Returns: 顶层映射

读数据:FileStorage::operator[]
返回指定的顶层映射元素
FileNode FileStorage::operator[](const string& nodename) const
FileNode FileStorage::operator[](const char* nodename) const
参数:
nodename – 文件节点名(见下文FileNode类)
返回:名称为nodename的节点数据


#十一、 calibrateCameraRO


http://qt.digitser.net/opencv/4.2.0/zh-CN/index.html




#十二、 C 库函数 - strftime()  https://www.runoob.com/cprogramming/c-function-strftime.html
size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr) 根据 format 中定义的格式化规则,格式化结构 timeptr 表示的时间,并把它存储在 str 中。

声明
下面是 strftime() 函数的声明。
size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr)
参数
str -- 这是指向目标数组的指针,用来复制产生的 C 字符串。
maxsize -- 这是被复制到 str 的最大字符数。
format -- 这是 C 字符串,包含了普通字符和特殊格式说明符的任何组合。这些格式说明符由函数替换为表示 tm 中所指定时间的相对应值。格式说明符是:
说明符	替换为	实例
%a	缩写的星期几名称	Sun
%A	完整的星期几名称	Sunday
%b	缩写的月份名称	Mar
%B	完整的月份名称	March
%c	日期和时间表示法	Sun Aug 19 02:56:02 2012
%d	一月中的第几天(01-31)	19
%H	24 小时格式的小时(00-23)	14
%I	12 小时格式的小时(01-12)	05
%j	一年中的第几天(001-366)	231
%m	十进制数表示的月份(01-12)	08
%M	分(00-59)	55
%p	AM 或 PM 名称	PM
%S	秒(00-61)	02
%U	一年中的第几周,以第一个星期日作为第一周的第一天(00-53)	33
%w	十进制数表示的星期几,星期日表示为 0(0-6)	4
%W	一年中的第几周,以第一个星期一作为第一周的第一天(00-53)	34
%x	日期表示法	08/19/12
%X	时间表示法	02:50:06
%y	年份,最后两个数字(00-99)	01
%Y	年份	2012
%Z	时区的名称或缩写	CDT
%%	一个 % 符号	%

timeptr -- 这是指向 tm 结构的指针,该结构包含了一个被分解为以下各部分的日历时间:
struct tm {
   int tm_sec;         /* 秒,范围从 0 到 59                */
   int tm_min;         /* 分,范围从 0 到 59                */
   int tm_hour;        /* 小时,范围从 0 到 23                */
   int tm_mday;        /* 一月中的第几天,范围从 1 到 31                    */
   int tm_mon;         /* 月份,范围从 0 到 11                */
   int tm_year;        /* 自 1900 起的年数                */
   int tm_wday;        /* 一周中的第几天,范围从 0 到 6                */
   int tm_yday;        /* 一年中的第几天,范围从 0 到 365                    */
   int tm_isdst;       /* 夏令时                        */    
};


下面的实例演示了 strftime() 函数的用法。

实例
#include <stdio.h>
#include <time.h>
 
int main ()
{
   time_t rawtime;
   struct tm *info;
   char buffer[80];
 
   time( &rawtime );
 
   info = localtime( &rawtime );
 
   strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", info);
   printf("格式化的日期 & 时间 : |%s|\n", buffer );
  
   return(0);
}
让我们编译并运行上面的程序,这将产生以下结果:
格式化的日期 & 时间 : |2018-09-19 08:59:07|



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值