相机和检测-02-相机标定(棋盘格、圆网格)

1. 遍历文件目录
//1. 读取所有照片
vector<cv::String> file_paths;
cv::glob("./data/image_*.jpg", file_paths);
//遍历
for(cv::String& path : filePath)
{
	cout<<path<<endl;
}
2. 查找角点
vector<cv::Point2f> corners;  //角点存储容器
cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
// 2. 遍历查找每个照片的角点,要求输入灰度图片
bool patternfound = cv::findChessboardCorners(gray, patternSize, corners);

if (patternfound) {
// 3. 通过亚像素优化角点的位置 (绘制角点)  (11,11)为滑动窗口大小  TermCriteria终止条件 30 迭代次数  0.1 误差值
cv::cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1),
                 cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
}

// 绘制角点
cv::drawChessboardCorners(image, patternSize, corners, patternfound);
cv::imshow("img", image);
3. 存储二维和三维角点坐标
// 每张图片的所有角点
vector<vector<cv::Point2f>> imagesPoints;
vector<vector<cv::Point3f>> objectPoints;
if (patternfound) {
// 4. 创建对应的物体3d坐标
    vector<cv::Point3f> objPoints;
    calcObjPoints(patternSize, square_size, objPoints);

    imagesPoints.push_back(corners);
    objectPoints.push_back(objPoints);
}
4. 手动填入角点坐标的真实值
for (int i = 0; i < patternSize.height; ++i) {
    for (int j = 0; j < patternSize.width; ++j) {
        // 打印每一行的所有内容,6个
        // printf("(%f,%f,0)", j * square_size, i * square_size);
        objPoints.emplace_back(j * square_size, i * square_size, 0);
    }
}
//每一张图片  作为一个整体 放到容器中去
imagesPoints.push_back(corners);
objectPoints.push_back(objPoints);
5. 执行标定
// 每张图片的所有角点
vector<vector<cv::Point2f>> imagesPoints; //角点容器
vector<vector<cv::Point3f>> objectPoints; //真实点容器
cv::Mat cameraMatrix; // 相机内参 fx,fy,cx,cy
cv::Mat distCoeffs;   // 畸变参数 distortion coefficients
vector<cv::Mat> rvecs; // 每张图片的旋转矩阵 rotation
vector<cv::Mat> tvecs; // 每张图片的平移向量 transform
cv::calibrateCamera(objectPoints, imagesPoints, imageSize,
        cameraMatrix, distCoeffs, rvecs, tvecs);
6. 保存标定结果到文件
cv::FileStorage fs("./calib_chessboard.yml", cv::FileStorage::WRITE);
// 获取当前时间
time_t tm;
time(&tm);
struct tm *t2 = localtime(&tm);
char buf[1024];
strftime(buf, sizeof(buf), "%c", t2);

fs << "calib_time" << buf;
fs << "imageSize" << imageSize;
fs << "cameraMatrix" << cameraMatrix;
fs << "distCoeffs" << distCoeffs;
fs.release();

完整代码如下:

/**
 * @BelongsProject: CameraCalibration
 * @Author: ty
 * @CreateTime: 2019-10-31
 * @Description: 使用棋盘格进行相机标定
 *
 * 直接从已有照片目录中读取图片,进行标定。
 *
 * 1. 读取所有照片
 * 2. 遍历查找每个照片的角点
 * 3. 通过亚像素优化角点的位置 (绘制角点)
 * 4. 创建对应的物体3d坐标
 * 5. 执行标定操作 *
 * 6. 保存标定结果到文件
 */
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
/**
 * 根据图片,标定板查找角点
 * @param image         图片
 * @param patternSize   标定板尺寸
 * @param corners       输出的角点
 * @return patternfound 是否在图像中找到了角点
 */
bool findCorners(const cv::Mat &image, const cv::Size &patternSize, vector<cv::Point2f> &corners) {
    cv::Mat gray;
    cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
    // 2. 遍历查找每个照片的角点
    bool patternfound = cv::findChessboardCorners(gray, patternSize, corners);

    if (patternfound) {
        // 3. 通过亚像素优化角点的位置 (绘制角点)
        cv::cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1),
                         cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1));
    }

    // 绘制角点
    cv::drawChessboardCorners(image, patternSize, corners, patternfound);
    cv::imshow("img", image);

    return patternfound;
}

void saveParams(cv::Size imageSize, const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs) {
    //    cv::FileStorage fs("./calib_chessboard.xml", cv::FileStorage::WRITE);
    cv::FileStorage fs("./calib_chessboard.yml", cv::FileStorage::WRITE);
    // 获取当前时间
    time_t tm;
    time(&tm);
    struct tm *t2 = localtime(&tm);
    char buf[1024];
    strftime(buf, sizeof(buf), "%c", t2);

    fs << "calib_time" << buf;
    fs << "imageSize" << imageSize;
    fs << "cameraMatrix" << cameraMatrix;
    fs << "distCoeffs" << distCoeffs;
    fs.release();
}

void calcObjPoints(const cv::Size &patternSize, float square_size, vector<cv::Point3f> &objPoints) {
    for (int i = 0; i < patternSize.height; ++i) {
        for (int j = 0; j < patternSize.width; ++j) {
            // 打印每一行的所有内容,6个
//                    printf("(%f,%f,0)", j * square_size, i * square_size);
            objPoints.emplace_back(j * square_size, i * square_size, 0);
        }
    }
}

int main(int argc, char **argv) {
    std::cout << "Hello" << std::endl;

    // 棋盘格尺寸
    cv::Size patternSize(6, 9);
    float square_size = 20.0;

    // 每张图片的所有角点
    vector<vector<cv::Point2f>> imagesPoints;
    vector<vector<cv::Point3f>> objectPoints;
    // 图片的像素尺寸
    cv::Size imageSize;

    // 1. 读取所有照片
    vector<cv::String> file_paths;
    cv::glob("./data/image_*.jpg", file_paths);
    for (cv::String& path : file_paths) {
        // 读取图片,获取图片尺寸
        cv::Mat img = cv::imread(path, cv::IMREAD_COLOR);
        cv::Mat image;
        img.copyTo(image);

        imageSize = image.size();
        std::cout << path << " size: " << imageSize << std::endl;

        // 定义角点坐标的列表
        vector<cv::Point2f> corners;

        bool patternfound = findCorners(image, patternSize, corners);

        // 如果找到了角点, 把角点和对应的3d坐标加到列表中
        if (patternfound) {

            // 4. 创建对应的物体3d坐标
            /**
             *  [0,0,0] [1,0,0] [2,0,0]
             *  [0,1,0] [1,1,0] [2,1,0]
             *  [0,2,0] [1,2,0] [2,2,0]
             */
            vector<cv::Point3f> objPoints;
             (patternSize, square_size, objPoints);

            imagesPoints.push_back(corners);
            objectPoints.push_back(objPoints);
        }
        
    }
    cv::Mat cameraMatrix; // 相机内参 fx,fy,cx,cy
    cv::Mat distCoeffs;   // 畸变参数 distortion coefficients

    vector<cv::Mat> rvecs; // 每张图片的旋转矩阵 rotation
    vector<cv::Mat> tvecs; // 每张图片的平移向量 transform

//    5. 执行标定操作 *
    cv::calibrateCamera(objectPoints, imagesPoints, imageSize,
            cameraMatrix, distCoeffs, rvecs, tvecs);

//    6. 打印并保存标定结果到文件
    std::cout << "cameraMatrix: " << std::endl;
    std::cout << cameraMatrix << std::endl;

    std::cout << "distCoeffs: " << std::endl;
    std::cout << distCoeffs << std::endl;

    saveParams(imageSize, cameraMatrix, distCoeffs);

    while (cv::waitKey(30) != 27) {
    }

    cv::destroyAllWindows();
    return 0;
}

使用摄像头或视频进行标定
/**
 * @BelongsProject: CameraCalibration
 * @Author: ty
 * @CreateTime: 2019-10-31
 * @Description: 使用棋盘格进行相机标定
 *
 * 直接从已有照片目录中读取图片,进行标定。
 *
 * 1. 开启摄像头,实时获取照片
 * 2. 遍历查找每个照片的角点
 * 3. 通过亚像素优化角点的位置 (绘制角点)
 * 4. 创建对应的物体3d坐标
 * 5. 执行标定操作 *
 * 6. 保存标定结果到文件
 */
#include <iostream>
#include <opencv2/opencv.hpp>

const int ACTION_ESC = 27;
const int ACTION_SPACE = 32;

void saveParams(cv::String saveFileName, const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs, double rms);

using namespace std;
/**
 * 根据图片,标定板查找角点
 * @param image         图片
 * @param patternSize   标定板尺寸
 * @param corners       输出的角点
 * @return patternfound 是否在图像中找到了角点
 */
bool findCorners(const cv::Mat &image, const cv::Size &patternSize, vector<cv::Point2f> &corners) {
    cv::Mat gray;
    cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
    // 2. 遍历查找每个圆网格的中心
    bool patternfound = cv::findCirclesGrid(gray, patternSize, corners);
    // 绘制角点
    cv::drawChessboardCorners(image, patternSize, corners, patternfound);
    cv::imshow("img", image);

    return patternfound;
}

void calcObjPoints(const cv::Size &patternSize, float square_size, vector<cv::Point3f> &objPoints) {

    /**
     *  [0,0,0] [1,0,0] [2,0,0]
     *  [0,1,0] [1,1,0] [2,1,0]
     *  [0,2,0] [1,2,0] [2,2,0]
     */
    for (int i = 0; i < patternSize.height; ++i) {
        for (int j = 0; j < patternSize.width; ++j) {
            // 打印每一行的所有内容,6个
//                    printf("(%f,%f,0)", j * square_size, i * square_size);
            objPoints.emplace_back(j * square_size, i * square_size, 0);
        }
    }
}

int main(int argc, char **argv) {
    std::cout << "开始采集图片信息,按[空格键]采集图片。" << std::endl;

    // 棋盘格尺寸
    cv::Size patternSize(7, 10);
    float square_size = 20.0;
    double scale_factor = 1.0;
    // 采集15张图片
    int num_boards = 15;

    // 每张图片的所有角点
    vector<vector<cv::Point2f>> imagesPoints;
    // 图片的像素尺寸
    cv::Size imageSize;

    cv::VideoCapture capture(0);

    // 判断相机是否正确打开
    if (!capture.isOpened()) {
        std::cerr << "相机无法打开!请检查后重试。" << std::endl;
        return -1;
    }

    while (true) {
        cv::Mat image0, image;
        capture >> image0;

        image0.copyTo(image);

        // 水平翻转图片
        cv::flip(image, image, 1);

        // 执行缩放
        if (scale_factor != 1.0) {
            cv::resize(image, image, cv::Size(), scale_factor, scale_factor, cv::INTER_LINEAR);
        }

        imageSize = image.size();

        // 定义角点坐标的列表
        vector<cv::Point2f> corners;

        bool patternfound = findCorners(image, patternSize, corners);

        int action = cv::waitKey(30) & 0xFF;
        if (action == ACTION_ESC || action == 'q') {
            printf("标定程序已终止!\n");
            return -1;
        } else if(action == ACTION_SPACE){  //按空格保存数据
            // 如果找到了角点, 把角点和对应的3d坐标加到列表中
            if (patternfound) {

                imagesPoints.push_back(corners);

                printf("保存照片及参数 %d/%d \n", (int)imagesPoints.size(), num_boards);

                if ((int) imagesPoints.size() == num_boards) {
                    break;
                }
            }else {
                printf("未发现角点\n");
            }

        }
    }
    printf("照片及角点参数已采集完毕,开始执行标定!\n");

    // 物体对象的3d坐标列表
    vector<vector<cv::Point3f>> objectPoints;
    // 4. 创建对应的物体3d坐标
    vector<cv::Point3f> objPoints;
    calcObjPoints(patternSize, square_size, objPoints);
    objectPoints.resize(num_boards, objPoints);

    cv::Mat cameraMatrix; // 相机内参 fx,fy,cx,cy
    cv::Mat distCoeffs;   // 畸变参数 distortion coefficients

    vector<cv::Mat> rvecs; // 每张图片的旋转矩阵 rotation
    vector<cv::Mat> tvecs; // 每张图片的平移向量 transform

//    5. 执行标定操作 * RMS error    |  RMSE
    double rms = cv::calibrateCamera(objectPoints, imagesPoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs);

    printf("RMS error report by calibrateCamera: %f\n", rms);

//    6. 打印并保存标定结果到文件
    std::cout << "cameraMatrix: " << std::endl;
    std::cout << cameraMatrix << std::endl;

    std::cout << "distCoeffs: " << std::endl;
    std::cout << distCoeffs << std::endl;

    saveParams("./calib_circle.yml", cameraMatrix, distCoeffs, rms);

    cv::destroyAllWindows();
    return 0;
}

void saveParams(cv::String saveFileName, const cv::Mat &cameraMatrix, const cv::Mat &distCoeffs, double rms) {
    //    cv::FileStorage fs("./calib_chessboard.xml", cv::FileStorage::WRITE);
    cv::FileStorage fs(saveFileName, cv::FileStorage::WRITE);
    // 获取当前时间
    time_t tm;
    time(&tm);
    struct tm *t2 = localtime(&tm);
    char buf[1024];
    strftime(buf, sizeof(buf), "%c", t2);

    fs << "calib_time" << buf;
    fs << "rms" << rms;
    fs << "cameraMatrix" << cameraMatrix;
    fs << "distCoeffs" << distCoeffs;
    fs.release();
}
标定的评价指标,使用cv::calibrateCamera()的返回值rms,值越小误差越小,效果越好
double rms = cv::calibrateCamera(objectPoints, imagesPoints, imageSize, cameraMatrix, distCoeffs, rvecs, tvecs);
圆网格标定

只是将上述寻找角点函数替换为圆网格的寻找角点,且亚像素优化只适用于棋盘格,其他不适用( cv::cornerSubPix() )

bool patternfound = cv::findCirclesGrid(gray, patternSize, corners);
//但是绘制的时候还是使用棋盘格法来绘制
//绘制角点
cv::drawChessboardCorners(image, patternSize, corners, patternfound);
cv::imshow("img", image);
非对称圆网格,要注意真实坐标矩阵的获取
for (int i = 0; i < patternSize.height; ++i) {
      for (int j = 0; j < patternSize.width; ++j) {
           // 打印每一行的所有内容,6个
           objPoints.emplace_back(
                   float((j * 2 + i % 2) * square_size),
                   float(i * square_size), 0);
       }
}

闪屏的添加,借助bitwise_not()函数

// 添加闪屏,告知用户获取成功
cv::bitwise_not(image, image, cv::noArray());

图片的保存

stringstream ss;
ss << "./calib_acircle_pic/image_" << (int) imagesPoints.size() << ".jpg";
// 保存照片
cv::imwrite(ss.str(), image0, vector<int>{CV_IMWRITE_JPEG_QUALITY, 100});
使用标准命令行封装参数
// ./CameraCalibrationACircleVideo 4 11 20 15 --scale=1.0 -f
// ./CameraCalibrationACircleVideo -h --help
// 解析用户输入的参数
cv::CommandLineParser parser(argc, argv, "{@arg1||}{@arg2||}{@arg3||}{@arg4|15|}"
                                         "{help h||}{scale s|1.0|}{f||}");

if (parser.has("h")) {
    print_help();
    return 0;
}

int board_width = parser.get<int>(0);
int board_height = parser.get<int>(1);
float square_size = parser.get<float>(2);
int num = parser.get<int>(3);
  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值