【QT/OpenCV】QT实现张正友相机标定

相机标定

      • 01、相机标定
      • 02、OpenCV函数及其张正友标定法
        • 2.1、相机标定步骤
        • 2.2、相机标定相关函数
          • [2.2.1 提取角点--- findChessboardCorners](#2.2.1 提取角点--- findChessboardCorners)
          • [2.2.2 亚像素角点提取1--- find4QuadCornerSubpix](#2.2.2 亚像素角点提取1--- find4QuadCornerSubpix)
          • [2.2.3 亚像素角点提取2--- cornerSubPix](#2.2.3 亚像素角点提取2--- cornerSubPix)
          • [2.2.4 绘制内角点 --- drawChessboardCorners](#2.2.4 绘制内角点 --- drawChessboardCorners)
          • [2.2.5 相机标定 --- calibrateCamera](#2.2.5 相机标定 --- calibrateCamera)
          • [2.2.6 标定评价 --- projectPoints](#2.2.6 标定评价 --- projectPoints)
          • [2.2.7 查看标定结果(两种方法)](#2.2.7 查看标定结果(两种方法))
            • [2.2.7.1 initUndistortRectifyMap 与 remap](#2.2.7.1 initUndistortRectifyMap 与 remap)
            • [2.2.7.2 undistort](#2.2.7.2 undistort)
      • 03、Qt+OpenCV程序
      • 04、运行截图

01、相机标定

机器视觉是采用相机成像来实现对三维场景的测量、定位、重建等过程。也是一个利用二维图像进行三维反推的过程,我们所处的世界是三维的,而图像或者照片是二维的。我们可以把相机认为是一个函数,输入量是一个三维场景,输出量是一幅二维图像。 正常来说,三维到二维这个过程是不可逆的。

如果我们能够找到一个合适的数学模型,来近似以上这个三维到二维的过程,然后找到这个数学模型的反函数,就可以实现二维到三维的反过程。

即: 用简单的数学模型来表达复杂的成像过程,并且求出成像的反过程。

为什么要使用相机标定?

如上我们知道三维到二维是通过成像的原理,那么这个过程中就会因为相机的出厂参数、或者畸变参数导致成像的图像跟原始图像差距很大,标定就是为了确定这些参数,然后将要处理实际工作时,通过这些参数将图像校正OK。

标定会涉及到的参数:

内参矩阵 外参矩阵 畸变参数
f/dx,f/dy,u0,v0 相机位姿、平移、旋转 k1,k2,p1,p2,k3

畸变参数中,k1,k2,k3代表了径向畸变参数, p1,p2代表了切向畸变参数。

外参矩阵与相机的摆放等等很多因素有关,所以,在非特定应用不理会。

得出结论:相机标定就是确定相机的内外参数、畸变参数的过程。

相机标定常用方法:

标定方法 优点 缺点 常见方法
相机自标定法 灵活性强、可在线标定 精度低、鲁棒性差 分层逐步标定、基于Kruppa方程
主动视觉相机标定法 不需要标定物体、算法简单、鲁棒性高 成本高、设备昂贵 主动系统控制相机做特定运动
标定物标定法 可使用与任意的相机模型、精度高 需要标定物、算法复杂 Tsai两步法、张正友标定法(本文所有方法)

鲁棒性 :指控制系统在一定(结构,大小)的参数摄动下,维持某些性能的特性。

以上三种也有另外一种官方说法: 线性标定法、非线性优化标定法、两步法。

视觉的理论说实话我自己也是很迷糊的,看不懂就去查阅,进入代码环节吧。

02、OpenCV函数及其张正友标定法

2.1、相机标定步骤

使用OpenCV进行张正友标定,总结有以下几步:

  1. 准备标定图片
  2. 对每张图片提取角点信息
  3. 对每张图片进一步提取亚像素角点信息
  4. 在棋盘格上绘制内角点(显示,非必须)
  5. 相机标定
  6. 对标定结果进行评价
  7. 查看标定结果/使用校正结果对相机图片进行校正。

标定图片尽量使用棋盘格哈,使用OpenCV可以直接用包括的图片,路径在安装目录下面,如下:

如果没有,可以自己绘制一个,绘制程序如下:

cpp 复制代码
// 生成棋盘格(demo)
void CreateGridironPattern()
{
    // 单位转换
    int dot_per_inch = 108;
    /*
    * 这里以我惠普 光影精灵9的参数计算如下:
    *  公式: DPI = 1920 / sqrt(15.6 ^ 2 + (1920 / 1080 * 15.6)^2)
    *  sqrt(15.6 ^ 2 + (1920 / 1080 * 15.6)^2) ≈ 17.76
    */

    double cm_to_inch = 0.3937;   // 1cm = 0.3937inch  
    double inch_to_cm = 2.54;    //  1inch = 2.54cm( 1 英寸 = 2.54 厘米 是一个国际公认的单位)
    double inch_per_dot = 1.0 / 96.0;

    // 自定义标定板
    double blockSize_cm = 1.5;  // 方格尺寸: 边长1.5cm的正方形
    // 设置横列方框数目
    int blockcol = 10;
    int blockrow = 8;

    int blockSize = (int)(blockSize_cm / inch_to_cm * dot_per_inch);
    cout << "标定板尺寸: " << blockSize << endl;

    int imageSizeCol = blockSize * blockrow;
    int imageSizeRow = blockSize * blockcol;

    Mat chessBoard(imageSizeCol, imageSizeRow, CV_8UC3, Scalar::all(0));
    unsigned char color = 0;

    for (int i = 0; i < imageSizeRow; i = i + blockSize) {
        color = ~color; // 将颜色值取反,如果开始为0,取反后为255(即黑白互换)
        for (int j = 0; j < imageSizeCol; j = j + blockSize) {
            Mat ROI = chessBoard(Rect(i, j, blockSize, blockSize));
            ROI.setTo(Scalar::all(color));
            color = ~color;
        }
    }

    imshow("chess board", chessBoard);
    imwrite("chessBard.jpg", chessBoard);

    waitKey(0);
    return;
}

然后拍照,准备好10~20张照片为宜(使用校正相机拍照)。

2.2、相机标定相关函数

2.2.1 提取角点--- findChessboardCorners

函数原型:

cpp 复制代码
CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize, OutputArray corners,
                                         int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE );

参数解释:

  1. image: 传入拍摄的棋盘格Mat图像,必须是8位的灰度或者彩色图像
  2. patternSize: 每个棋盘格上内角点的行列数,一般情况下,行列数不要相同,便于后续待定程序识别标定板的 方向。
  3. corners: 用于存储检测到的内角点图像坐标为止,一般用元素是Point2f的向量来表示,如:vector<Point2f> image_points_buf
2.2.2 亚像素角点提取1--- find4QuadCornerSubpix

函数原型:

cpp 复制代码
CV_EXPORTS_W bool find4QuadCornerSubpix( InputArray img, InputOutputArray corners, Size region_size );

参数解释:

  1. img:输入的Mat矩阵,最好是8位灰度图像,检测效率更高。
  2. corners:初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要时浮点型数据,一般用元素是Point2f/Point2d的向量来表示,如: vector<Point2f> imagePointBuf
  3. region_size:角点搜索窗口的尺寸。
2.2.3 亚像素角点提取2--- cornerSubPix

函数原型:

cpp 复制代码
CV_EXPORTS_W void cornerSubPix( InputArray image, InputOutputArray corners,
                                Size winSize, Size zeroZone,
                                TermCriteria criteria );

参数解释:

  1. image:输入的Mat矩阵,最好是8位灰度图像,检测效率更高。
  2. corners:初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据,一般用元素是Point2f/Point2d的向量来表示,如:vector<Point2f> imagePointBuf
  3. winSize:大小位搜索窗口的一半。
  4. zeroZone:死区的一半尺寸,死区为不对搜索区的中央位置做求和运算的区域。它是用来避免自相关矩阵出现某些可能的奇异性。当值为(-1,-1)时表示没有死区。
  5. criteria:定义求角点的迭代过程的终止条件,可以为迭代次数和角点精度两者的组合。

网上看大佬们说两个函数测出来的结果,偏差基本都控制在0.5个像素之内。

2.2.4 绘制内角点 --- drawChessboardCorners

函数原型:

cpp 复制代码
CV_EXPORTS_W void drawChessboardCorners( InputOutputArray image, Size patternSize,
                                         InputArray corners, bool patternWasFound );

参数解释:

  1. image:8位灰度或者彩色图像。
  2. patternSize:每张标定棋盘上内角点的行列数。
  3. corners:初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据,一般用元素是Pointf2f/Point2d的向量来表示:vector<Point2f> iamgePointsBuf
  4. patternWasFound:标志位,用来指示定义的棋盘内角点是否被完整的探测到,true表示被完整的探测到,函数会用直线依次连接所有的内角点,作为一个整体,false表示有未被探测到的内角点,这时候函数会以(红色)圆圈标记处检测到的内角点。
2.2.5 相机标定 --- calibrateCamera

函数原型:

cpp 复制代码
CV_EXPORTS_W double calibrateCamera( InputArrayOfArrays objectPoints,
                                     InputArrayOfArrays imagePoints, Size imageSize,
                                     InputOutputArray cameraMatrix, InputOutputArray distCoeffs,
                                     OutputArrayOfArrays rvecs, OutputArrayOfArrays tvecs,
                                     int flags = 0, TermCriteria criteria = TermCriteria(
                                        TermCriteria::COUNT + TermCriteria::EPS, 30, DBL_EPSILON) );

参数解释:

  1. objectPoints:为世界坐标系中的三维点,在使用时,应输入一个三维坐标点的向量的向量,即 vector<vector<Point3f>> object_points. 需要依据棋盘格上单个黑色矩阵的大小,计算(初始化)没一个内角点的世界坐标。
  2. imagePoints:为每一个内角点对应的图像坐标点,和objectPoints一样,应该输入 vector<vector<Point2f>> image_points_seq形式的变量。
  3. imageSize:为图像的像素尺寸大小,在计算相机的内参和畸变矩阵时需要使用到该参数。
  4. cameraMatrix:为相机的内参矩阵。 输入一个Mat cameraMatrix即可,如: Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0))
  5. distCoeffs:为畸变矩阵。输入一个 Mat distCoeffs = Mat(1,5CV_32FC1,Scalar::all(0))
  6. rvercs:为旋转向量,应该输入一个Mat类型的vector,即vector<Mat> rvecs
  7. tvecs: 为平移向量,应输入一个Mat类型的vector,即vector<Mat> tvecs
  8. flags:为标定时所采用的算法,有如下参数:
参数 解释
CV_CALIB_USE_INTRINSIC_GUESS 使用该参数时,在cameraMatrix矩阵中应该有fx,fy,u0,v0的估计值。否则的话,将初始化(u0,v0)图像的中心点,使用最小二乘估算出fx,fy
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_RATIONAL_MODEL 计算k4,k5,k6三个畸变参数。如果没有设置,则只计算其它5个畸变参数
  1. criteria:最优迭代终止条件设定。

在使用该函数进行标定运算之前,需要对棋盘上每一个内角点的空间坐标系的位置坐标进行初始化,标定的结果是生成相机的内参矩阵cameraMatrix、相机的5个畸变系数distCoeffs,另外每张图像都会生成属于自己的平移向量和旋转向量。

2.2.6 标定评价 --- projectPoints

函数原型:

cpp 复制代码
CV_EXPORTS_W void projectPoints( InputArray objectPoints,
                                 InputArray rvec, InputArray tvec,
                                 InputArray cameraMatrix, InputArray distCoeffs,
                                 OutputArray imagePoints,
                                 OutputArray jacobian = noArray(),
                                 double aspectRatio = 0 );
  1. objectPoints:为相机坐标系中的三维点坐标。
  2. rvec:为旋转向量,每一张图像都有自己的旋转向量。
  3. tvec:为平移向量,每一张图像都有自己的平移向量。
  4. cameraMatrix:为求得的相机的内参数矩阵。
  5. distCoeffs:为相机的畸变矩阵。
  6. imagePoints:为每一个内角点对应的图像上的坐标点。
  7. jacobian:为雅可比行列式。
  8. aspectRatio:跟相机传感器的感光单元有关的可选参数,如果设置为非0,则函数默认感光单元的dx/dy是固定的,会依此对雅可比矩阵进行调整。
2.2.7 查看标定结果(两种方法)
  1. 方法一:使用initUndistortRectifyMap和remap两个函数配合实现。
2.2.7.1 initUndistortRectifyMap 与 remap
函数原型:
cpp 复制代码
CV_EXPORTS_W
void initUndistortRectifyMap(InputArray cameraMatrix, InputArray distCoeffs,
                             InputArray R, InputArray newCameraMatrix,
                             Size size, int m1type, OutputArray map1, OutputArray map2);

参数解释:

  1. cameraMatrix:为之前求得的相机的内参矩阵。
  2. distCoeffs:为之前求得的相机畸变矩阵系数。
  3. R:可选的输入,是第一个和第二相机坐标之间的旋转矩阵。
  4. newCameraMatrix:输入的校正后的3x3摄像机矩阵。
  5. size:摄像机采集的无失真的图像尺寸。
  6. m1type:定义map1的数据类型,可以是CV_32FC1或者CV_16SC2。
  7. map1:输出的X坐标重映射参数。
  8. map2:输出的Y坐标重映射参数。

函数原型:

cpp 复制代码
CV_EXPORTS_W void remap( InputArray src, OutputArray dst,
                         InputArray map1, InputArray map2,
                         int interpolation, int borderMode = BORDER_CONSTANT,
                         const Scalar& borderValue = Scalar());

参数解释:

  1. src:输入参数,代表畸变的 原始图像。

  2. dst:校正后的输出图像,跟输入图像具有相同的类型和大小。

  3. map1、map2:X和Y坐标的映射。

  4. interpolation:定义图像的插值方式。

  5. borderMode:定义边界的填充方式。

  6. 方法二:使用undistort函数实现。

2.2.7.2 undistort

函数原型:

cpp 复制代码
CV_EXPORTS_W void undistort( InputArray src, OutputArray dst,
                             InputArray cameraMatrix,
                             InputArray distCoeffs,
                             InputArray newCameraMatrix = noArray() );

参数解释:

  1. src:输入参数,代表畸变的原始图像。
  2. dst:输出参数,代表校正后的图像,跟输入图像具有相同类型和大小。
  3. cameraMatrix:之前求得的相机内参矩阵。
  4. distCoeffs:之前求得的相机的畸变矩阵系数。
  5. newCameraMatrix:默认跟cameraMatrix保持一致。

根据网上大佬测试,方法一相比方法二执行效率更高一些,推荐使用。

03、Qt+OpenCV程序

关于QT中使用OpenCV,这里不说了,详情可以查看以往的blog。

.pro文件

cpp 复制代码
#-------------------------------------------------
#
# Project created by QtCreator 2023-07-11T14:44:57
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = CalibrateDemo
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

CONFIG += c++11

SOURCES += \
        main.cpp \
        mainwindow.cpp

HEADERS += \
        mainwindow.h

FORMS += \
        mainwindow.ui

INCLUDEPATH += \
            C:\opencv\install\install\include \

LIBS += \
        C:\opencv\install\lib\libopencv_*.a \

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

mainwindow.h文件

cpp 复制代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <QMainWindow>
#include <iostream>
#include <fstream>
#include <io.h>
#include <QFileDialog>
#include <QDebug>
#include <vector>
#include <QLabel>
#include <QVBoxLayout>
#include <QThread>

using namespace std;
using namespace cv;

#define CALIBRATERESULTFILE "CalibrateResult.txt"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_LoadImage_clicked();

    void on_pushButton_SaveResult_clicked();

    void on_pushButton_StartCalibrate_clicked();

    void on_pushButton_AppraiseCalibrate_clicked();

public:
    // QT图像 to openCV图像  和  openCV图像 to QT图像
    QImage MatToQImage(Mat const& src);
    Mat QImageToMat(QImage const& src);

    void showCameraMatrix(Mat const& data);  // 显示内参矩阵
    void showDistCoeffs(Mat const& data);   // 显示畸变系数

private:
    Ui::MainWindow *ui;

    // 保存不同图片标定板上角点的三维坐标
    vector<vector<Point3f>> object_points;
    // 缓存每幅图像上检测到的角点
    vector<Point2f> image_points_buf;
    // 保存检测到的所有角点
    vector<vector<Point2f>> image_points_seq;
    // 相机内参数矩阵
    cv::Mat cameraMatrix;
    // 相机的畸变系数
    cv::Mat distCoeffs;
    // 每幅图像的平移向量
    vector<cv::Mat> tvecsMat;
    // 每幅图像的旋转向量
    vector<cv::Mat> rvecsMat;
    // 加载标定图片的文件夹
    QString m_strCalibrateFolder;
    // 保存标定结果的文件夹
    QString m_strSaveResultFolder;
    // 写入
    std::ofstream fout;
    // 图像数量
    int image_count = 0;
    // 每幅图像中角点的数量
    vector<int> point_counts;
};

#endif // MAINWINDOW_H

mainwindow.cpp文件

cpp 复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 渲染设置为硬件加速
    ui->label_showMat->setAttribute(Qt::WA_OpaquePaintEvent,true);
    ui->label_showMat->setAttribute(Qt::WA_NoSystemBackground,true);
    ui->label_showMat->setAutoFillBackground(false);

    cameraMatrix = cv::Mat(3,3,CV_32FC1, Scalar::all(0));
    distCoeffs = cv::Mat(1,5,CV_32FC1, Scalar::all(0));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_LoadImage_clicked()
{
    QString folderPath = QFileDialog::getExistingDirectory(this,QStringLiteral("选择标定图片文件夹"),tr(""),QFileDialog::ShowDirsOnly);
    if(!folderPath.isEmpty()) {
        //文件夹不为空
        m_strCalibrateFolder = folderPath;
    } else {
        qDebug()<< "未选择任何文件夹";
        return;
    }

    // 将加载的路径显示在界面
    ui->lineEdit_CalibrateImagePath->setText(folderPath);
    // 设置文字左对齐
    ui->lineEdit_SaveResultPath->setAlignment(Qt::AlignLeft);
}

void MainWindow::on_pushButton_SaveResult_clicked()
{
   QString folderPath = QFileDialog::getExistingDirectory(this,QStringLiteral("选择保存结果文件夹"),tr(""),QFileDialog::ShowDirsOnly);
   if(folderPath.isEmpty()) {
       qDebug()<< "未选择任何文件夹";
       return;
   }

   m_strSaveResultFolder = folderPath;
   // 设置路径到界面
   ui->lineEdit_SaveResultPath->setText(folderPath);
   // 左对齐
   ui->lineEdit_SaveResultPath->setAlignment(Qt::AlignLeft);
}

void MainWindow::on_pushButton_StartCalibrate_clicked()
{
    // 保存标定结果的txt
    QString strResult = m_strSaveResultFolder + QString("/%1").arg(CALIBRATERESULTFILE);
    fout.open(strResult.toStdString().c_str());

    // 1、加载标定图片
    vector<QString> imageNames;
    QDir dir(m_strCalibrateFolder);
    QStringList fileNames = dir.entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name);

    foreach(const QString& fileName, fileNames) {
        QString filePath = dir.filePath(fileName);
        imageNames.push_back(filePath);  // 将完整的路径添加到图片路径容器
    }

    // 2、分别对每张图片进行角点提取
    Size image_size;      // 图像尺寸
    Size board_size = Size(9,6);  // 标定板上每行、列的角点数
    int count = -1;  // 用于存储角点个数

    for(int i = 0; i < imageNames.size(); i++) {
        image_count++;
        // 输出观察
        qDebug()<< "image_count = " << image_count;
        // 输出校验
        qDebug()<< "Check count = " << count;

        // 读取图片
        Mat imageInput = imread(imageNames[i].toStdString().c_str());

        if(image_count == 1) {
            // 读入第一张图片时获取图像宽高信息
            image_size.width = imageInput.cols;
            image_size.height = imageInput.rows;
        }

        // 提取角点
        if(0 == findChessboardCorners(imageInput, board_size, image_points_buf))
        {// 未发现角点信息/找不到角点
            qDebug()<< "未发现角点信息";
            return;
        }
        else {
         // 3、对每一张标定图像进行亚像素化处理
            Mat view_gray;
            // 将imageInput转为灰度图像

            cvtColor(imageInput, view_gray, COLOR_RGB2GRAY);
            // 亚像素精准化(对粗提取的角点进行精准化)
            find4QuadCornerSubpix(view_gray,image_points_buf,Size(5,5));
            image_points_seq.push_back(image_points_buf);  // 尾插,保存亚像素角点

         // 4、在棋盘格显示,并在界面刷新图片(显示找到的内角点绘制图片)
            // 在图像上显示角点位置
            drawChessboardCorners(imageInput, board_size, image_points_buf, true);
#if 0
            imshow("Camera Calibration", imageInput);  // 显示图片
            imwrite("Calibration" + to_string(image_count) + ".png", imageInput); // 写入图片
            waitKey(100);  // 暂停0.1s
#else
            QImage tmpImage = MatToQImage(imageInput);
            ui->label_showMat->setPixmap(QPixmap::fromImage(tmpImage.rgbSwapped()));
            ui->label_showMat->show();
            QThread::msleep(100);  // 延时0.1s
            QCoreApplication::processEvents();
#endif

            qDebug()<< "角点提取完成";
        }
    }

    //destroyAllWindows();

    // 5、相机标定
    Size square_size = Size(5,5);

    // 初始化标定板上角点的三维坐标
    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.height;
                realPoint.y = j * square_size.width;
                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);
    }

    cv::calibrateCamera(object_points, image_points_seq,image_size,cameraMatrix,distCoeffs,rvecsMat,tvecsMat,0);
    qDebug()<< "标定完成!";

    // 6/7对应下面1/2
}

void MainWindow::on_pushButton_AppraiseCalibrate_clicked()
{
    // 1、对标定结果进行评价
    qDebug() << "开始评价标定结果.....";

    double total_err = 0.0;  // 所有图像的平均误差的总和
    double err = 0.0;  // 每幅图像的平均误差
    vector<Point2f> image_points2;  // 保存重新计算得到的投影点
    qDebug()<< "每幅图像的标定误差: ";
    fout << "每幅图像的标定误差: \n";
    for(int 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 < tempPointSet.size(); j++) {
            // 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];
        qDebug()<< "第" << i + 1 << "幅图像的平均误差: " << err << "像素";
        fout << "第" << i + 1 << "幅图像的平均误差: " << err << "像素" << endl;
    }

    qDebug()<< "总体平均误差: " << total_err / image_count << "像素";
    fout << "总体平均误差:" << total_err / image_count << "像素" << endl << endl;
    qDebug() << "评价完成!";

    // 2、查看标定结果并保存
    qDebug()<< "开始保存定标结果..................";
    Mat rotation_matrix = Mat(3, 3, CV_32FC1, Scalar::all(0)); /* 保存每幅图像的旋转矩阵 */
    fout << "相机内参数矩阵:" << endl;
    showCameraMatrix(cameraMatrix);
    fout << cameraMatrix << endl << endl;
    fout << "畸变系数:\n";
    showDistCoeffs(distCoeffs);
    fout << distCoeffs << endl;
    for (int i = 0; i < image_count; i++)
    {
        fout << "第" << i + 1 << "幅图像的旋转向量:" << endl;
        fout << rvecsMat[i] << endl;
        /* 将旋转向量转换为相对应的旋转矩阵 */
        Rodrigues(rvecsMat[i], rotation_matrix);
        fout << "第" << i + 1 << "幅图像的旋转矩阵:" << endl;
        fout << rotation_matrix << endl;
        fout << "第" << i + 1 << "幅图像的平移向量:" << endl;
        fout << tvecsMat[i] << endl << endl;
    }
    qDebug()<< "完成保存!";
    fout << endl;
}

QImage MainWindow::MatToQImage(Mat const& src)
{
    Mat temp;  //make the same cv::Mat
    cvtColor(src,temp,COLOR_BGR2RGB); //cvtColor makes a copt, that what i need
    QImage dest((uchar*)temp.data,temp.cols,temp.rows,temp.step,QImage::Format_RGB888);
    dest.bits();  //enforce deep copy, see documentation
    return dest;
}

Mat MainWindow::QImageToMat(QImage const& src)
{
    Mat tmp(src.height(),src.width(),CV_8UC4,(uchar*)src.bits(),src.bytesPerLine());
    Mat result;
    cvtColor(tmp,result,COLOR_RGBA2BGR);
    return result;
}

void MainWindow::showCameraMatrix(const Mat &data)
{
   std::ostringstream ss;
   ss << data;
   std::string strMatrix = ss.str();

   QVBoxLayout* layout = new QVBoxLayout(ui->groupBox_CameraInParam);
   QLabel* label = new QLabel();
   label->setText(QString::fromStdString(strMatrix));
   label->setAlignment(Qt::AlignCenter);
   layout->addWidget(label);
}

void MainWindow::showDistCoeffs(const Mat &data)
{
    std::ostringstream ss;
    ss << data;
    std::string strMatrix = ss.str();

    QVBoxLayout* layout = new QVBoxLayout(ui->groupBox_DistortionParam);
    QLabel* label = new QLabel();
    label->setText(QString::fromStdString(strMatrix));
    label->setAlignment(Qt::AlignCenter);
    label->setWordWrap(true);
    layout->addWidget(label);
}

04、运行截图

使用比较简单,输入图像路径和输出标定结果路径,然后标定、评价,显示相机内参、相机畸变,这里没有涉及相机外参。

相关推荐
锦亦之22337 小时前
QT+OSG+OSG-earth如何在窗口显示一个地球
开发语言·qt
高效办公能手9 小时前
图片翻译器,分享四款直接翻译图片的软件!
数码相机
柳鲲鹏10 小时前
编译成功!QT/6.7.2/Creator编译Windows64 MySQL驱动(MinGW版)
开发语言·qt·mysql
三玖诶10 小时前
如何在 Qt 的 QListWidget 中逐行添加和显示数据
开发语言·qt
阳光开朗_大男孩儿16 小时前
DBUS属性原理
linux·服务器·前端·数据库·qt
Alphapeople17 小时前
Qt Modbus
开发语言·qt
竹林海中敲代码17 小时前
Qt Creator 集成开发环境 常见问题
qt·qt工具常见问题
点PY17 小时前
基于Sparse Optical Flow 的Homography estimation
人工智能·opencv·计算机视觉
越甲八千17 小时前
opencv滤波算法总结
opencv
越甲八千17 小时前
opencv对比度增强方法算法汇总
人工智能·opencv·算法