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

news2024/11/26 9:32:54

相机标定

      • 01、相机标定
      • 02、OpenCV函数及其张正友标定法
        • 2.1、相机标定步骤
        • 2.2、相机标定相关函数
          • 2.2.1 提取角点--- findChessboardCorners
          • 2.2.2 亚像素角点提取1--- find4QuadCornerSubpix
          • 2.2.3 亚像素角点提取2--- cornerSubPix
          • 2.2.4 绘制内角点 --- drawChessboardCorners
          • 2.2.5 相机标定 --- calibrateCamera
          • 2.2.6 标定评价 --- projectPoints
          • 2.2.7 查看标定结果(两种方法)
            • 2.2.7.1 initUndistortRectifyMap 与 remap
            • 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可以直接用包括的图片,路径在安装目录下面,如下:

在这里插入图片描述
如果没有,可以自己绘制一个,绘制程序如下:

// 生成棋盘格(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

函数原型:

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

函数原型:

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

函数原型:

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

函数原型:

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

函数原型:

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

函数原型:

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
函数原型:
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坐标重映射参数。

函数原型:

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:定义边界的填充方式。
  1. 方法二:使用undistort函数实现。
2.2.7.2 undistort

函数原型:

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文件

#-------------------------------------------------
#
# 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文件

#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文件

#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、运行截图

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/750236.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

操作系统复习(非抢占式的优先数调度算法)

今天在写题目的时候遇到了一个问题&#xff0c;在非抢占式的优先数调度算法中&#xff0c;存在一种情况。优先级相同&#xff0c;并且同时到达&#xff0c;这种情况下&#xff0c;短作业优先。例如&#xff1a; 这种情况下&#xff0c;调度顺序为&#xff1a;P1、P2、P4、P3。

终极实时测试工具:NCrunch 4.17 for vs19-22 Crack

适用于 .NET 的终极实时测试工具 在编码时以内联方式查看实时测试结果和指标。 Visual Studio 的自动并发测试 NCrunch 是一个全自动测试扩展&#xff0c;旨在使编码和测试变得轻而易举。 忘记停下来运行测试&#xff0c;让 NCrunch 为您完成工作。 以您认为的速度编码和测试…

记录一个AFR去嵌S参数异常的案例。

最近在使用AFR去嵌一个S参数的时候&#xff0c;遇到了如下问题&#xff1a; 首先介绍一下这个S参数&#xff0c;一端是MCIO连接器&#xff0c;另一端是CEM连接器&#xff0c;所以测试的时候一端接MCIO测试治具&#xff0c;一端接CEM测试治具&#xff0c;再通过线缆将测试治具连…

c++--继承

1.什么是继承 C有面向对象三大特性&#xff0c;封装&#xff0c;继承&#xff0c;多态&#xff0c;封装。而继承就是代码可以复用的重要手段。他可以让程序员在原有类的基础上进行扩展&#xff0c;增加功能&#xff0c;产生新的类&#xff0c;称为子类或派生类&#xff0c;继承…

【监控系统】Prometheus监控组件Node-Exporter配置实战

这一节&#xff0c;我们来配置一下Node-Exporter&#xff0c;那么我们先来了解一下什么是Prometheus的Exporter&#xff1f; 任何向Prometheus提供监控样本数据的程序都可以被称为一个Exporter&#xff0c;它是一种用于将不同数据源的指标提供给Prometheus进行收集和监控的工具…

会议口译服务,如何做好长交传翻译?

如何做好长交传翻译工作&#xff1f;我们知道&#xff0c;长交传是会议口译中常见的一种翻译方式&#xff0c;难度比较大&#xff0c;需要不间断的听取长度为3至5分钟的段落然后进行口译。那么&#xff0c;在进行长交传翻译练习中&#xff0c;如何提升交传口译的能力&#xff0…

Linux系统使用(超详细)

目录 Linux操作系统简介 Linux和windows区别 Linux常见命令 Linux目录结构 Linux命令提示符 常用命令 ls cd pwd touch cat echo mkdir rm cp mv vim vim的基本使用 grep netstat Linux面试题 Linux操作系统简介 Linux操作系统是和windows操作系统是并列…

JavaScript--改变 HTML 的值

要改变 HTML 元素的值&#xff0c;可以使用以下方法&#xff1a; 1.使用元素节点的 innerText 属性来改变元素的文本内容。 例如&#xff1a;element.innerText 新的文本内容; 2.使用元素节点的 innerHTML 属性来改变元素的 HTML 内容。 例如&#xff1a;element.innerHTML …

ModaHub魔搭社区:Zilliz Cloud 版本类型和价格计算器教程

目录 企业版 专有部署 价格计算器 在配置集群前,您需要先选择版本。Zilliz Cloud 各版本提供不同的服务体验、计费模型,且性能及可扩展性也都有所不同 。目前, Zilliz Cloud 共有 2 个版本可供选择,以满足多样的用户需求。 企业版专有部署2 个不同的版本为不同的用户需求…

python pytest脚本执行工具

pytest脚本执行工具 支持获取当前路径下所有.py脚本 添加多个脚本&#xff0c;一起执行 import tkinter as tk from tkinter import filedialog import subprocess import os from datetime import datetimedef select_script():script_path filedialog.askopenfilename(fil…

Redis 从入门到精通【进阶篇】之高可用哨兵机制(Redis Sentinel)详解

文章目录 0.前言1. 原理详解1.1. 哨兵机制的组建1.1. 哨兵是如何知道从库的信息 1.2. 主库下线的判定1.3. 哨兵集群选举1.4. 故障的转移 2. 总结3. Redis从入门到精通系列文章4. Redis哨兵模式面试题4. 1. 什么是Redis的哨兵模式&#xff1f;4. 2. 哨兵模式的优点是什么&#x…

云原生TDengine-v3.0部署手册

云原生TDengine-v3.0部署手册 一、管理namespace1.1 创建namespace1.2 namespaces列表 二、配置3份yaml文件2.1 tdengine3-storage-class.yaml2.2 taosd-service.yaml2.3 taosd-tdengine.yaml 三、服务部署3.1 部署StorageClass3.2 部署Service3.3 部署StatefulSet3.4 查看启动…

Linux--环境变量

指令分为两种&#xff1a; ①路径指令 比如我们都知道输入ls的作用是显示当前文件及目录&#xff0c;并且ls的路径是/usr/bin/ls。那么我输入ls与/usr/bin/ls的作用是等价的。之所以带路径&#xff0c;是因为不带路径找不到命令 ②系统指令 ls就是嵌入了环境变量后&#xff0c…

C语言程序设计——指针

一、字符指针 字符指针char*两种使用方法&#xff1a; //用法一&#xff1a;指向一个字符变量 char ch a; char* pc &ch; //用法二&#xff1a;指向一个字符串首地址 const char* p "abcde"; //注意p存储的是字符串的首地址&#xff0c;也就是字符a的地址。 …

基于node.js和Vue3的医院信息管理挂号系统

随着时代的发展,无线互联网技术的应用和普及给人们的生活带来了极大的改变,现在信息技术不仅可以提高我们的工作效率,还能有效的规避一些错误风险,节约人力成本。我国国民一方面对健康的要求越来越重视了&#xff0c;另一方面现代人的健康问题日益严重&#xff0c;所以医院信息…

【imarkdown】一个轻量级markdown图片链接转换器

imarkdown imarkdown是一个轻量级markdown图片链接转换器&#xff0c;你可以轻松地对图片链接进行本地到图片服务器、图片服务器到本地、图片服务器到图片服务器的转换。 因为语雀转markdown的时候图片存在防外链行为&#xff0c;如果想要把转出的markdown发表在其他平台&…

电子电路基础知识--电阻

视频教程 薄膜电阻 &#xff08;包括碳膜电阻、合成碳膜电阻、金属氧化膜电阻、玻璃釉膜电阻&#xff09;… 碳膜电阻 气态碳氢化合物在高温和真空中分解&#xff0c;碳沉积在蜜挂或者瓷管上&#xff0c;形成一层结晶碳膜。改变碳膜厚度和用刻槽的方法变更碳膜的长度&#…

驱动 day10 作业

要求&#xff1a;platform驱动实现 现象&#xff1a; test.c应用程序 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #in…

Edge浏览器和Google浏览器占用内存情况

最近发现适用edge浏览器看视频时&#xff0c;或者打开多个网页&#xff0c;电脑就会变得非常卡顿&#xff0c;打开任务管理器发现内存占用较多&#xff0c;网上也有人说edge浏览器确实占用内存较多。但是给出的解决方案是更新windows系统&#xff0c;这个方法显然不能接受&…