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

相机标定

      • 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 guigreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsTARGET = 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.0CONFIG += c++11SOURCES += \main.cpp \mainwindow.cppHEADERS += \mainwindow.hFORMS += \mainwindow.uiINCLUDEPATH += \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_OBJECTpublic: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()
{// 保存标定结果的txtQString 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 0imshow("Camera Calibration", imageInput);  // 显示图片imwrite("Calibration" + to_string(image_count) + ".png", imageInput); // 写入图片waitKey(100);  // 暂停0.1s
#elseQImage tmpImage = MatToQImage(imageInput);ui->label_showMat->setPixmap(QPixmap::fromImage(tmpImage.rgbSwapped()));ui->label_showMat->show();QThread::msleep(100);  // 延时0.1sQCoreApplication::processEvents();
#endifqDebug()<< "角点提取完成";}}//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::MatcvtColor(src,temp,COLOR_BGR2RGB); //cvtColor makes a copt, that what i needQImage dest((uchar*)temp.data,temp.cols,temp.rows,temp.step,QImage::Format_RGB888);dest.bits();  //enforce deep copy, see documentationreturn 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.mzph.cn/news/1116.shtml

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

相关文章

生成式AI:大语言模型ChatGPT交互的机制

推荐&#xff1a;将NSDT场景编辑器加入你的3D工具链 3D工具集&#xff1a;NSDT简石数字孪生 与 ChatGPT 有效交互的快速工程 随着生成式人工智能的普及&#xff0c;特别是 ChatGPT&#xff0c;提示已成为人工智能世界中越来越重要的技能。制作提示&#xff0c;与大型语言模型&…

【C语言督学营 第十八天】考研408排序大题初探(将排序思想融入题目)

文章目录 题目一分析代码实战 题目二分析代码实战 补充(快排与归并)数据结构大题注意点&#xff01;&#xff01;&#xff01;(评分标准) 题目一 分析 (1&#xff09;算法的基本设计思想 由题意知&#xff0c;将最小的nl2个元素放在Ai中&#xff0c;其余的元素放在A2中&#x…

Linux信号

文章目录 一.信号基础二.信号的产生1.使用键盘组合键发送信号&#xff08;只能给当前正在运行的进程发&#xff09;信号捕捉2.使用kill指令&#xff08;可以向任意进程发送信号&#xff09;3.使用raise&#xff08;&#xff09;让进程自己给自己发送信号4.硬件异常产生信号a.除…

Java中List的使用方法简单介绍

Java中List的使用方法简单介绍 java中的List就是一种集合对象&#xff0c;将所有的对象集中到一起存储。List里面可以放任意的java对象&#xff0c;也可以直接放值。 使用方法很简单&#xff0c;类似于数组。 使用List之前必须在程序头引入java.util.* import java.util.*; pub…

分享四款导航页 个人主页html源码

一、开源免费&#xff0c;可以展示很多社交账号&#xff0c;也可以更换社交账号图标指向你的网站&#xff0c;上传后即可使用 https://wwwf.lanzout.com/ik7R912s031g 二、开源免费&#xff0c;不过部署稍微麻烦点 https://wwwf.lanzout.com/iCq2u12s02wb 三、适合做成导航页面…

golang网络编程学习-1rpc

网络编程主要的内容是&#xff1a; 1.TCP网络编程 2.http服务 3.rpc服务 4.websocket服务 一、rpc RPC 框架----- 远程过程调用协议RPC&#xff08;Remote Procedure Call Protocol)-----允许像调用本地服务一样调用远程服务。 RPC是指远程过程调用&#xff0c;也就是说两台服…

MySQL结构以及数据管理(增删改查)

目录 1.数据库的简介 2.数据库分类 2.1关系型数据库 2.2 非关系型数据库 3.mysql的数据类型 3.1 常用的数据库类型 4.mysql的数据库结构 4.1 查看库信息 4.2 查看表信息 5.SQL 语句 5.1 SQL语言分类&#xff1a; 1.数据库的简介 数据库&#xff08;database&#…

Spark高级特性

spark shuffle 中 map 和 reduce 是一个相对的概念&#xff0c;map是产生一批数据&#xff0c;reduce是接收一批数据&#xff0c;前一个任务是map&#xff0c;后一个任务是reduce。 hashShuffle&#xff1a;hash分组&#xff0c;一个task里面按hash值的不同&#xff0c;分到不…

微服务优雅上下线的实践方法

导语 本文介绍了微服务优雅上下线的实践方法及原理&#xff0c;包括适用于 Spring 应用的优雅上下线逻辑和服务预热&#xff0c;以及使用 Docker 实现无损下线的 Demo。同时&#xff0c;本文还总结了优雅上下线的价值和挑战。 作者简介 颜松柏 腾讯云微服务架构师 拥有超过…

Flask_实现token鉴权

目录 1、安装依赖 2、实现代码 3、测试 源码等资料获取方法 1、安装依赖 pip install flask pip install pycryptodome 2、实现代码 import random import string import time import base64from functools import wrapsfrom flask import Flask, jsonify, session, req…

RabbitMQ如何保证消息的可靠性6000字详解

RabbitMQ通过生产者、消费者以及MQ Broker达到了解耦的特点&#xff0c;实现了异步通讯等一些优点&#xff0c;但是在消息的传递中引入了MQ Broker必然会带来一些其他问题&#xff0c;比如如何保证消息在传输过程中可靠性&#xff08;即不让数据丢失&#xff0c;发送一次消息就…

学习babylon.js --- [2] 项目工程搭建

本文讲述如何搭建babylonjs的项目工程。 一 准备 首先创建一个目录叫MyProject&#xff0c;然后在这个目录里再创建三个目录&#xff1a;dist&#xff0c;public和src&#xff0c;如下&#xff0c; 接着在src目录里添加一个文件叫app.ts&#xff0c;本文使用typescript&#…

docker数据卷权限管理--理论和验证

一、Docker容器中用户权限管理 Linux系统的权限管理是由uid和gid负责&#xff0c;Linux系统会检查创建进程的uid和gid&#xff0c;以确定它是否有足够的权限修改文件&#xff0c;而非是通过用户名和用户组来确认。 同样&#xff0c;在docker容器中主机上运行的所有容器共享同一…

【kubernetes系列】Kubernetes之配置dashboard安装使用

Kubernetes之配置dashboard 概述 Dashboard 是基于网页的 Kubernetes 用户界面。 你可以使用 Dashboard 将容器应用部署到 Kubernetes 集群中&#xff0c;也可以对容器应用排错&#xff0c;还能管理集群资源。 你可以使用 Dashboard 获取运行在集群中的应用的概览信息&#x…

【单例模式】—— 每天一点小知识

&#x1f4a7; 单例模式 \color{#FF1493}{单例模式} 单例模式&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 《数据结构与算法》专栏的文章图文并茂&#x1f995;生动形…

LiveGBS流媒体平台GB/T28181功能-作为上级平台对接海康大华华为宇视等下级平台监控摄像机NVR硬件执法仪等GB28181设备

LiveGBS作为上级平台对接海康大华华为宇视等下级平台监控摄像机NVR硬件执法仪等GB28181设备 1、背景说明2、部署国标平台2.1、安装使用说明2.2、服务器网络环境2.3、信令服务配置 3、监控摄像头设备接入3.1、海康GB28181接入示例3.2、大华GB28181接入示例3.3、华为IPC GB28181接…

SpringBoot整合ZooKeeper完整教程

目录 ZooKeeper简单介绍 一、安装zookeeper 二、springboot整合zookeeper ZooKeeper简单介绍 zookeeper是为分布式应用程序提供的高性能协调服务。zookeeper将命名、配置管理、同步和组服务等常用服务公开在一个简单的接口中&#xff0c;因此用户无需从头开始编写这些服务。可…

Android GridPager实战,从RecyclerView to ViewPager

这个简单的的案例展示了如何从RecyclerView to ViewPager&#xff0c;以网上的公开图片为样例。 安卓开发中从RecyclerView 到 ViewPager demo运行结果demo项目工程目录结构关键代码 MainActivity关键代码GridFragment关键代码ImageFragment关键代码ImagePagerFragment关键布局…

CSS---CSS面试题

目录 1.盒模型 2.offsetHeight /clientheight/scrollHeight 3.left与offsetLeft 4.对BFC规范的理解 5.解决元素浮动导致的父元素高度塌陷的问题 6.CSS样式的先级 7.隐藏页面元素 8.display: none 与 visibility: hidden 的区别 9.页面引入样式时&#xff0c;使用link与import有…

C++学习——类和对象(一)

C语言和C语言最大的区别在于在C当中引入了面向对象的编程思想&#xff0c;想要完全了解c当中的类和对象&#xff0c;就要从头开始一点一点的积累并学习。 一&#xff1a;什么是面向对象编程 我们之前学习的C语言属于面向过程的编程方法。举一个简单的例子来说&#xff1a;面向过…