[QT/OpenCV] QT implementiert die Kamerakalibrierung von Zhang Zhengyou

01. Kamerakalibrierung

Machine Vision nutzt Kamerabilder, um die Messung, Positionierung, Rekonstruktion und andere Prozesse dreidimensionaler Szenen zu realisieren. Es ist auch ein Prozess, bei dem zweidimensionale Bilder verwendet werden, um dreidimensionale Schlussfolgerungen zu ziehen. Die Welt, in der wir leben, ist dreidimensional, und Bilder oder Fotos sind zweidimensional. Wir können uns die Kamera als Funktion vorstellen, die Eingabe ist eine dreidimensionale Szene und die Ausgabe ist ein zweidimensionales Bild. Normalerweise ist der Prozess von drei Dimensionen zu zwei Dimensionen irreversibel.

Wenn wir ein geeignetes mathematisches Modell finden können, um den oben genannten dreidimensionalen zu zweidimensionalen Prozess anzunähern, und dann die Umkehrfunktion dieses mathematischen Modells finden, können wir den umgekehrten Prozess von zweidimensional zu dreidimensional realisieren.
Das heißt: Verwenden Sie ein einfaches mathematisches Modell, um den komplexen Bildgebungsprozess auszudrücken und den umgekehrten Bildgebungsprozess zu finden.

Warum Kamerakalibrierung verwenden?

Wie wir oben wissen, erfolgt die Umwandlung von dreidimensional in zweidimensional durch das Prinzip der Bildgebung. Bei diesem Vorgang unterscheidet sich das abgebildete Bild aufgrund der Werksparameter oder Verzerrungsparameter der Kamera stark vom Originalbild. Die Kalibrierung muss ermittelt werden Diese Parameter werden verwendet, und dann erfolgt die eigentliche Verarbeitung. Korrigieren Sie das Bild während der Arbeit mithilfe dieser Parameter.

An der Kalibrierung beteiligte Parameter:

Interne Parametermatrix Externe Parametermatrix Verzerrungsparameter
f/dx,f/dy,u0,v0 Kamerahaltung, Translation, Rotation k1,k2,p1,p2,k3

Unter den Verzerrungsparametern repräsentieren k1, k2 und k3 die radialen Verzerrungsparameter und p1 und p2 repräsentieren die tangentialen Verzerrungsparameter.
Die externe Parametermatrix hängt mit vielen Faktoren wie der Kameraplatzierung zusammen und wird daher in unspezifischen Anwendungen ignoriert.

Fazit: Bei der Kamerakalibrierung handelt es sich um den Prozess der Bestimmung der internen und externen Parameter sowie der Verzerrungsparameter der Kamera.

Gängige Methoden zur Kamerakalibrierung:

Kalibrierungsmethode Vorteil Mangel Gängige Methoden
Methode zur Selbstkalibrierung der Kamera Hohe Flexibilität und online kalibrierbar Geringe Genauigkeit und geringe Robustheit Hierarchische schrittweise Kalibrierung, basierend auf der Kruppa-Gleichung
Methode zur Kalibrierung einer Active-Vision-Kamera Keine Notwendigkeit, Objekte zu kalibrieren, einfacher Algorithmus, hohe Robustheit Hohe Kosten und teure Ausrüstung Aktive Systeme steuern Kameras, um bestimmte Bewegungen auszuführen
Kalibrierungsmethode Kann mit jedem Kameramodell verwendet werden, hohe Genauigkeit Erfordert Kalibrierungsmaterialien und komplexe Algorithmen Zweistufige Tsai-Methode, Zhang Zhengyou-Kalibrierungsmethode (alle Methoden in diesem Artikel)

Robustheit : bezieht sich auf die Eigenschaften eines Steuerungssystems, das eine bestimmte Leistung unter bestimmten (Struktur-, Größen-)Parameterstörungen aufrechterhält.

Es gibt auch eine weitere offizielle Erklärung für die oben genannten drei Typen: lineare Kalibrierungsmethode, nichtlineare Optimierungskalibrierungsmethode und zweistufige Methode.

Ehrlich gesagt bin ich sehr verwirrt über die Theorie des Sehens. Wenn Sie sie nicht verstehen, schauen Sie einfach nach und lesen Sie den Code.

02. OpenCV-Funktion und Zhang Zhengyous Kalibrierungsmethode

2.1. Schritte zur Kamerakalibrierung

Unter Verwendung von OpenCV für die Zhang Zhengyou-Kalibrierung lautet die Zusammenfassung wie folgt:

  1. Bereiten Sie Kalibrierungsbilder vor
  2. Extrahieren Sie Eckinformationen aus jedem Bild
  3. Extrahieren Sie weitere Subpixel-Eckeninformationen für jedes Bild
  4. Innenecken auf dem Schachbrett zeichnen (angezeigt, optional)
  5. Kamerakalibrierung
  6. Kalibrierergebnisse auswerten
  7. Kalibrierungsergebnisse anzeigen/Kalibrierungsergebnisse verwenden, um Kamerabilder zu korrigieren.

Versuchen Sie, ein Schachbrettraster für Kalibrierungsbilder zu verwenden. Wenn Sie OpenCV verwenden, können Sie die enthaltenen Bilder direkt verwenden. Der Pfad befindet sich im Installationsverzeichnis wie folgt:

Fügen Sie hier eine Bildbeschreibung ein
Wenn nicht, können Sie selbst eines zeichnen. Der Zeichenvorgang ist wie folgt:

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

Machen Sie dann Fotos, vorzugsweise 10 bis 20 Fotos (verwenden Sie zum Fotografieren eine kalibrierte Kamera).

2.2. Kamerakalibrierungsbezogene Funktionen
2.2.1 Eckpunkte extrahieren – findChessboardCorners

Funktionsprototyp:

CV_EXPORTS_W bool findChessboardCorners( InputArray image, Size patternSize, OutputArray corners,
                                         int flags = CALIB_CB_ADAPTIVE_THRESH + CALIB_CB_NORMALIZE_IMAGE );

Parametererklärung:

  1. image: Das aufgenommene Schachbrettmattenbild muss ein 8-Bit-Graustufen- oder Farbbild sein.
  2. patternSize: Die Anzahl der Reihen und Spalten der inneren Eckpunkte auf jedem Schachbrett. Im Allgemeinen sollte die Anzahl der Reihen und Reihen nicht gleich sein, damit das nachfolgend zu bestimmende Programm die Richtung der Kalibrierungstafel identifizieren kann.
  3. corners: Wird zum Speichern der erkannten inneren Eckpunkt-Bildkoordinaten verwendet, die im Allgemeinen durch einen Vektor dargestellt werden, dessen Elemente Point2f sind, wie zum Beispiel: vector<Point2f> image_points_buf.
2.2.2 Subpixel-Eckpunktextraktion 1 – find4QuadCornerSubpix

Funktionsprototyp:

CV_EXPORTS_W bool find4QuadCornerSubpix( InputArray img, InputOutputArray corners, Size region_size );

Parametererklärung:

  1. img: Die Eingabe-Mat-Matrix ist vorzugsweise ein 8-Bit-Graustufenbild, das eine höhere Erkennungseffizienz aufweist.
  2. corners: Der anfängliche Eckpunkt-Koordinatenvektor wird auch als Ausgabe der Subpixel-Koordinatenposition verwendet, sodass Gleitkommadaten im Allgemeinen durch einen Vektor dargestellt werden, dessen Elemente Point2f/Point2d sind, wie zum Beispiel: vector<Point2f> imagePointBuf.
  3. region_size: Die Größe des Ecksuchfensters.
2.2.3 Subpixel-Eckpunktextraktion 2 – CornerSubPix

Funktionsprototyp:

CV_EXPORTS_W void cornerSubPix( InputArray image, InputOutputArray corners,
                                Size winSize, Size zeroZone,
                                TermCriteria criteria );

Parametererklärung:

  1. image: Die Eingabe-Mat-Matrix ist vorzugsweise ein 8-Bit-Graustufenbild, das eine höhere Erkennungseffizienz aufweist.
  2. corners: Der anfängliche Eckpunkt-Koordinatenvektor wird auch als Ausgabe der Subpixel-Koordinatenposition verwendet, daher müssen es Gleitkommadaten sein. Er wird im Allgemeinen durch einen Vektor dargestellt, dessen Elemente Point2f/Point2d sind, wie zum Beispiel: vector<Point2f> imagePointBuf.
  3. winSize: Die Größe beträgt die Hälfte des Suchfensters.
  4. zeroZone: Halbe Größe der Totzone, bei der es sich um einen Bereich handelt, in dem die Summationsoperation nicht an der zentralen Position des Suchbereichs durchgeführt wird. Es wird verwendet, um bestimmte mögliche Singularitäten in der Autokorrelationsmatrix zu vermeiden. Wenn der Wert (-1, -1) ist, bedeutet dies, dass es keine Totzone gibt.
  5. criteria: Definieren Sie die Beendigungsbedingung des iterativen Prozesses zum Finden von Eckpunkten. Dies kann eine Kombination aus der Anzahl der Iterationen und der Eckpunktgenauigkeit sein.

Ich habe online gelesen, dass die Experten sagten, dass die Abweichung der von den beiden Funktionen gemessenen Ergebnisse im Wesentlichen innerhalb von 0,5 Pixeln kontrolliert wird.

2.2.4 Innenecken zeichnen – drawChessboardCorners

Funktionsprototyp:

CV_EXPORTS_W void drawChessboardCorners( InputOutputArray image, Size patternSize,
                                         InputArray corners, bool patternWasFound );

Parametererklärung:

  1. image: 8-Bit-Graustufen- oder Farbbild.
  2. patternSize: Die Anzahl der Reihen und Spalten der inneren Eckpunkte auf jedem kalibrierten Schachbrett.
  3. corners: Der anfängliche Eckpunkt-Koordinatenvektor wird auch als Ausgabe der Subpixel-Koordinatenposition verwendet, daher müssen es sich um Gleitkommadaten handeln, die im Allgemeinen durch einen Vektor dargestellt werden, dessen Elemente Pointf2f/Point2d sind: vector<Point2f> iamgePointsBuf.
  4. patternWasFound: Flag-Bit, das verwendet wird, um anzuzeigen, ob die definierten inneren Eckpunkte des Schachbretts vollständig erkannt wurden. True bedeutet, dass sie vollständig erkannt wurden. Die Funktion verwendet gerade Linien, um alle inneren Eckpunkte der Reihe nach zu verbinden. Insgesamt gilt: false bedeutet, dass es nicht erkannte Punkte gibt. Interner Eckpunkt, zu diesem Zeitpunkt markiert die Funktion den erkannten internen Eckpunkt mit einem (roten) Kreis.
2.2.5 Kamerakalibrierung – kalibrieren Sie die Kamera

Funktionsprototyp:

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) );

Parametererklärung:

  1. objectPoints: Es handelt sich um einen dreidimensionalen Punkt im Weltkoordinatensystem. Wenn Sie ihn verwenden, sollten Sie den Vektor eines dreidimensionalen Koordinatenpunkts eingeben. Sie müssen die Weltkoordinaten jedes inneren Eckpunkts berechnen (initialisieren) vector<vector<Point3f>> object_points. basierend auf der Größe einer einzelnen schwarzen Matrix auf dem Schachbrett. .
  2. imagePoints: Der Bildkoordinatenpunkt, der jedem inneren Eckpunkt entspricht. Wie bei objectPoints vector<vector<Point2f>> image_points_seqsollte eine Variable im Formular eingegeben werden.
  3. imageSize: Dies ist die Pixelgröße des Bildes. Dieser Parameter muss bei der Berechnung der internen Parameter und der Verzerrungsmatrix der Kamera verwendet werden.
  4. cameraMatrix: ist die interne Parametermatrix der Kamera. Geben Sie einfach eins ein Mat cameraMatrix, zum Beispiel: Mat cameraMatrix=Mat(3,3,CV_32FC1,Scalar::all(0)).
  5. distCoeffs: ist die Verzerrungsmatrix. Geben Sie eins ein Mat distCoeffs = Mat(1,5CV_32FC1,Scalar::all(0)).
  6. rvercs: Für den Rotationsvektor sollte ein Vektor vom Typ Mat eingegeben werden, also vector<Mat> rvecs.
  7. tvecs: Für den Übersetzungsvektor sollte ein Vektor vom Typ Mat eingegeben werden, also vector<Mat> tvecs.
  8. Flags: Dies ist der bei der Kalibrierung verwendete Algorithmus mit den folgenden Parametern:
Parameter erklären
CV_CALIB_USE_INTRINSIC_GUESS Bei Verwendung dieses Parameters sollten in der cameraMatrix-Matrix geschätzte Werte von fx, fy, u0, v0 vorhanden sein. Andernfalls wird der Mittelpunkt des (u0, v0)-Bildes initialisiert und fx, fy werden mithilfe der Methode der kleinsten Quadrate geschätzt
CV_CALIB_FIX_PRINCIPAL_POINT Der optische Achsenpunkt wird während der Optimierung fixiert. Wenn der Parameter CV_CALIB_USE_INTRINSIC_GUESS gesetzt ist, bleibt der Punkt der optischen Achse in der Mitte oder einem bestimmten Eingabewert
CV_CALIB_FIX_ASPECT_RATIO Das Verhältnis von fx/fy ist fest und nur fy wird als variable Variable für die Optimierungsberechnung verwendet. Wenn CV_CALIB_USE_INTRINSIC_GUESS nicht festgelegt ist, werden fx und fy ignoriert. In den Berechnungen wird nur das fx/fy-Verhältnis verwendet
CV_CALIB_ZERO_TANGENT_DIST Setzen Sie die Tangentialverzerrungsparameter (p1, p2) auf Null
CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6 Die entsprechende radiale Verzerrung bleibt bei der Optimierung unverändert
CV_CALIB_RATIONAL_MODEL Berechnen Sie die drei Verzerrungsparameter k4, k5 und k6. Wenn nicht festgelegt, werden nur die anderen 5 Verzerrungsparameter berechnet.
  1. Kriterien: Optimale Einstellung der Iterations-Beendigungsbedingungen.

Bevor diese Funktion für den Kalibrierungsvorgang verwendet wird, müssen die Positionskoordinaten des räumlichen Koordinatensystems jedes inneren Eckpunkts auf dem Schachbrett initialisiert werden. Das Ergebnis der Kalibrierung besteht darin, die interne Parametermatrix cameraMatrix der Kamera und die fünf Verzerrungskoeffizienten der Kamera zu generieren distCoeffs, und jedes Bild generiert seinen eigenen Translationsvektor und Rotationsvektor.

2.2.6 Kalibrierungsbewertung – projectPoints

Funktionsprototyp:

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: sind die dreidimensionalen Punktkoordinaten im Kamerakoordinatensystem.
  2. rvec: ist der Rotationsvektor. Jedes Bild hat seinen eigenen Rotationsvektor.
  3. tvec: ist der Übersetzungsvektor, jedes Bild hat seinen eigenen Übersetzungsvektor.
  4. cameraMatrix: ist die interne Parametermatrix der erhaltenen Kamera.
  5. distCoeffs: ist die Verzerrungsmatrix der Kamera.
  6. imagePoints: Der Koordinatenpunkt auf dem Bild, der jedem inneren Eckpunkt entspricht.
  7. jacobian: ist die Jacobi-Determinante.
  8. AspectRatio: ein optionaler Parameter, der sich auf die lichtempfindliche Einheit des Kamerasensors bezieht. Wenn er auf nicht 0 eingestellt ist, verwendet die Funktion standardmäßig einen festen dx/dy der lichtempfindlichen Einheit und die Jacobi-Matrix wird entsprechend angepasst.
2.2.7 Kalibrierungsergebnisse anzeigen (zwei Methoden)
  1. Methode 1: Verwenden Sie die beiden Funktionen initUndistortRectifyMap und remap, um es zu implementieren.
2.2.7.1 initUndistortRectifyMap und remap
函数原型:
CV_EXPORTS_W
void initUndistortRectifyMap(InputArray cameraMatrix, InputArray distCoeffs,
                             InputArray R, InputArray newCameraMatrix,
                             Size size, int m1type, OutputArray map1, OutputArray map2);

Parametererklärung:

  1. cameraMatrix: ist die zuvor erhaltene interne Parametermatrix der Kamera.
  2. distCoeffs: ist der zuvor erhaltene Koeffizient der Kameraverzerrungsmatrix.
  3. R: Optionale Eingabe, bei der es sich um die Rotationsmatrix zwischen den ersten und zweiten Kamerakoordinaten handelt.
  4. newCameraMatrix: Die eingabekorrigierte 3x3-Kameramatrix.
  5. Größe: Die Größe des von der Kamera aufgenommenen Bildes ohne Verzerrung.
  6. m1type: Definiert den Datentyp von Map1, der CV_32FC1 oder CV_16SC2 sein kann.
  7. map1: X-Koordinaten-Neuzuordnungsparameter ausgeben.
  8. map2: Y-Koordinaten-Neuzuordnungsparameter ausgeben.

Funktionsprototyp:

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

Parametererklärung:

  1. src: Eingabeparameter, der das verzerrte Originalbild darstellt.
  2. dst: Das korrigierte Ausgabebild hat denselben Typ und dieselbe Größe wie das Eingabebild.
  3. Karte1, Karte2: Zuordnung der X- und Y-Koordinaten.
  4. Interpolation: Definieren Sie die Interpolationsmethode des Bildes.
  5. borderMode: Definiert die Füllmethode des Rahmens.
  1. Methode 2: Verwenden Sie zur Implementierung die Undistort-Funktion.
2.2.7.2 entzerren

Funktionsprototyp:

CV_EXPORTS_W void undistort( InputArray src, OutputArray dst,
                             InputArray cameraMatrix,
                             InputArray distCoeffs,
                             InputArray newCameraMatrix = noArray() );

Parametererklärung:

  1. src: Eingabeparameter, der das verzerrte Originalbild darstellt.
  2. dst: Ausgabeparameter, der das korrigierte Bild darstellt und denselben Typ und dieselbe Größe wie das Eingabebild hat.
  3. cameraMatrix: Die zuvor erhaltene kamerainterne Parametermatrix.
  4. distCoeffs: Der zuvor erhaltene Verzerrungsmatrixkoeffizient der Kamera.
  5. newCameraMatrix: Die Standardeinstellung stimmt mit cameraMatrix überein.

Laut Tests von Online-Experten ist Methode eins effizienter als Methode zwei und wird empfohlen.

03. Qt+OpenCV-Programm

Bezüglich der Verwendung von OpenCV in QT werde ich hier nicht näher darauf eingehen. Weitere Informationen finden Sie in früheren Blogs.

.prodokumentieren

#-------------------------------------------------
#
# 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.hdokumentieren

#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.cppdokumentieren

#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. Screenshots ausführen

Fügen Sie hier eine Bildbeschreibung ein
Die Verwendung ist relativ einfach. Sie geben den Bildpfad ein und geben den Pfad des Kalibrierungsergebnisses aus, kalibrieren und bewerten die internen Parameter der Kamera und die Kameraverzerrung und zeigen sie an. Hier sind keine externen Parameter der Kamera beteiligt.

Ich denke du magst

Origin blog.csdn.net/m0_43458204/article/details/131701183
Empfohlen
Rangfolge