雙目相機標定校正(Qt+OpenCV+VS)


雙目立體校正

計算機視覺課的第二次作業,使用給定的雙目相機加標定板(紙)進行雙目相機的標定+校正。

工具

qt5 + opencv4.4.0 + vs2019

程序設計

程序設計重心主要放在qt5的界面布局,槽與信號之間的傳遞等。

雙目立體標定的程序在opencv中有一個單獨的例子,可以直接拿來做參考。

(..\opencv\sources\samples\cpp\stereo_calib.cpp)

但是,想要運行成功,需要對程序進行一定的修改!

運行結果圖:

整體使用上下兩個group存放攝像機拍攝的畫面和校正的畫面,畫面使用label組件進行顯示。

一共設計六個按鈕控制相機的打開關閉,標定校正,拍攝以及查詢標定信息。

11

內參信息顯示:

忽略右上角顯示不全的logo(最新版本已經修改)

22

上框存放兩個相機公共的信息,下面分別存放左右兩個相機各自的信息。

注意點

相機的讀取

老師發的雙目相機需要分別讀取兩個攝像頭,具體如下:

  1. 定義兩個VideoCapture類

    cv::VideoCapture capture_l;
    cv::VideoCapture capture_r;
    
  2. 打開攝像頭

    capture_l.open(1);
    capture_r.open(0);
    

    非常奇怪的是,我這里的第1個攝像頭是右攝像頭,所以先讀取的右邊(1)后讀取的左邊(0)

  3. 測試攝像頭是否正確打開

    if (capture_l.isOpened() || capture_r.isOpened())
    
  4. 讀取當前幀中

    cv::Mat frame_l;
    cv::Mat frame_r;
    capture_l >> frame_l;
    capture_r >> frame_r;
    
  5. 關閉相機

    capture_l.release();
    capture_r.release();
    

對於opecv的例子,需要進行一定的修改,具體如下:

  1. 需要刪減的部分

    (1) 原程序119行,由於例子中使用的是灰度圖,所以添加了此句將灰度圖轉換為了RGB圖,但使用自己的雙目相機拍攝的為RGB圖,如果加上這一句會報錯。所以需要去掉。

    cvtColor(img, cimg, COLOR_GRAY2BGR);
    

    同理還有304行的

    cvtColor(rimg, cimg, COLOR_GRAY2BGR);
    
  2. 需要修改的部分

    (1) boardSize修改為自己標定板的內點,即下圖圈出的點,類型為Size(x,y)為x方向的角點個數和y方向的角點個數。

    image-20201026191958358

    (2) square修改為一個格子的邊長寬度

    ​ 但是這里經過測試發現了一個問題。程序里使用的是以cm做為單位,而網上對程序的評價則認為使用mm做為單位,即1還是10的問題。然而我經過測試,無論使用多少對程序的結果都沒有影響??

    image-20201026194630169

    (3) clone()

    ​ Mat cimg = img;實際上cimg是img的引用,對cimg進行修改也就等於對img進行了修改。

    ​ 所以drawChessboardCorners(cimg, boardSize, corners, found);這里對cimg畫上了圈和線,也就是將原本的img進行了修改。程序的本意並不是這樣, 僅僅只想對cimg進行畫角點的標注。那么就需要將這句話修改為:

    cv::Mat cimg = img.clone();
    

    ​ 使用clone()就只是得到了img的副本,而不是引用。

    (4) 刪除選項CALIB_SAME_FOCAL_LENGTH

    ​ 在使用像素點和相機內參計算畸變稀疏和RTEF時,opencv已經提供了封裝好的函數stereoCalibrate,這個函數需要傳遞一個CALIB_的選項,opencv的原例子里使用了CALIB_SAME_FOCAL_LENGTH即焦距相等,但會產生以下的問題:

    雖然標定成功,但是校正出現了問題。

    33

    經過漫長的測試和懷疑自我,最終將問題鎖定在CALIB_SAME_FOCAL_LENGTH這個選項上,將其去掉即可成功校正。

    44

    double rms = stereoCalibrate(objectPoints, imagePoints[0], imagePoints[1],
                        cameraMatrix[0], distCoeffs[0],
                        cameraMatrix[1], distCoeffs[1],
                        imageSize, R, T, E, F,
                        CALIB_FIX_ASPECT_RATIO +
                        CALIB_ZERO_TANGENT_DIST +
                        CALIB_USE_INTRINSIC_GUESS +
                        CALIB_SAME_FOCAL_LENGTH +
                        CALIB_RATIONAL_MODEL +
                        CALIB_FIX_K3 + CALIB_FIX_K4 + CALIB_FIX_K5,
                        TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 100, 1e-5) );
    

    !!!!注意!!!!

    非常重要的一點,上述函數是有返回值的,返回double型的重投影誤差,經過大量的數據測試,重投影誤差一旦大於1.5以上時,幾乎就無法正確校正,所以我在程序中設計了rms判斷,一旦大於1.5的閾值直接跳出,提示用戶重新拍攝。

對於qt5,需要注意以下部分。

  1. qt5項目的正確創建

    文件\(\longrightarrow\)新建\(\longrightarrow\)項目\(\longrightarrow\)Qt Widgets Application\(\longrightarrow\)創建

    image-20201026200130626

    點擊next后,注意這里的選項,一定要選擇與Platform對應的。

    image-20201026200223428

  2. 槽函數的定義與使用

    在vs中使用qt,與qtCreator最大的區別是無法直接轉到槽函數,需要自己定義,具體步驟如下:

    點擊選項欄中的編輯信號/槽

    image-20201026200608462

    image-20201026200643478

    默認是選擇的Widget,需要在右邊的查看器里點擊添加槽函數的部件,然后點擊向外拖動(注意不要拖到到其他組件上去,拖到空白的地方),由於程序比較簡單,僅僅使用了點擊事件,所以點擊左邊的clicked(),然后點擊右邊的編輯。

    image-20201026200749418

    點擊左下角的加號,寫上自己定義的槽函數名稱,點擊OK。

    image-20201026200836759

    然后在右邊的框里選擇剛才定義的函數,點擊OK

    image-20201026200957578

    操作之后在右下角的槽編輯器里可以看到剛剛關聯的組件和槽函數

    image-20201026201123398

    然后返回我們的類.h文件

    在類內定義一個private slots專門存放槽函數,注意名稱要與剛才創建的對應上。

    image-20201026201202333

    然后進入cpp文件進行槽函數的具體實現即可

    image-20201026201249629

    這里不需要再connect,因為剛才在ui里的操作實際上已經將按鈕和槽connect起來了。

  3. 善用計時器QTimer

    在程序里我設計了2秒一次顯示校正后的圖片和角點圖片,如何實現這個功能呢?

    使用了Qt自帶的QTimer類。具體用法這里不多贅述,僅僅提供一個簡單的思路:

    定義一個指針型的QTimer變量

    QTimer* timer_camera;
    

    在構造函數中使用Connect將QTimer和需要間隔調用的函數聯系起來,比如我想每2秒調用一次readFrame函數:

    timer_camera = new QTimer(this);
    connect(timer_camera, SIGNAL(timeout()), this, SLOT(readFrame()));
    

    然后需要在一個特定的函數中將timer觸發(即開始)

    程序中我點擊打開相機后,每幀讀取相機的畫面輸出到label中,因此在打開相機按鈕的槽函數中設置timer的開啟。

    這里start()里的25是指間隔,1000為1s

    timer_camera->start(25);
    

    當不需要繼續調用時,需要關閉timer,我在關閉相機的槽函數在中進行關閉

    timer_camera->stop();
    
  4. 不同窗口之間傳遞值

    我將標定的值存放在Info類中,作為主窗口的私有成員變量,但是我想在新的窗口中顯示這些值,就需要將主窗口的變量傳遞到子窗口中去,具體操作如下:

    在主窗口的.h文件中定義sendInfo傳遞信號:

    signals:
        void sendInfo(Info info);
    

    在子窗口中定義槽函數receiveInfo接收信號:

    private slots:
    	void receiveInfo(Info ifo);
    

    在主函數的構造函數中將兩者連接:

    connect(this, SIGNAL(sendInfo(Info)), CAMERA_INFO, SLOT(receiveInfo(Info)));
    

    在主窗口的查看信息的槽函數中進行傳遞,並顯示子窗口

    void CameraCalibrate::checkInfo()
    {
        emit sendInfo(info);
        CAMERA_INFO->show();
    }
    

    在子窗口中實現receiveInfo函數

    void CameraInfo::receiveInfo(Info info_)
    {
    	info = info_;
    	/// todo 
    }
    

    這樣就實現了不同窗口之間的值傳遞

  5. 鎖定子窗口

    在子窗口展示時,我想要鎖定主窗口無法點擊切換,一句話實現:

    // 鎖定窗口
    this->setWindowModality(Qt::ApplicationModal);
    
  6. QString與其他類型的轉換

    該部分參考自:https://blog.csdn.net/qq_35223389/article/details/83112753

    Qt的label里顯示字符串是QString類型,如果是其他類型需要進行轉換,具體如下:

    (1) int 與 QString

    //int轉QString
    int a = 123456;
    QString b;
    b = QString::number(a,10,5);//QString::number(a,基底,精度)
    //方法2,利用arg()
    int a = 123456;
    QString b = QString("%1").arg(a);
    
    //QString轉int
    QString c = "123456";
    int d;
    d = c.toInt();
    

    (2) double 與 QString

    //double轉QString
    double a = 123.456;
    QString b;
    b =  QString::number(a,10,5);//同int
    
    //QString轉double
    QString c = "123.456";
    double d;
    d = c.toDouble();//類似int
    

    (3) string 與 QString

    //string轉QString
    string a = "123.456";
    QString b;
    b = QString::fromStdString(a);
    
    //QString轉string
    QString c = "123,456";
    string d;
    d = c.toStdString();
    
  7. 調用控件

    Q里調用設計的控件非常簡單,直接使用ui.進行調用

    例如設置關閉按鈕不可用:

    ui.close_btn->setEnabled(false);
    
  8. label顯示圖片

    最重要的放在最后說!

    首先是由於label的大小有限,需要將相機實時拍攝到的畫面進行resize到與label同大小

    然后定義一個QImage類,具體見下面的實現

    然后將其變為QPixmap類顯示在label上

    // 可選項
    cv::resize(frame, frame, cv::Size(xx, yy));
    
    // 必寫
    QImage image = QImage((const uchar*)frame.data, frame.cols, frame.rows, QImage::Format_RGB888).rgbSwapped();
    ui.label->setPixmap(QPixmap::fromImage(image));
    

問題

目前沒有解決的問題是,由於我是使用A4紙打印的棋盤格單人測量,所以只能將A4紙放在桌面上,然后變換相機,但是在實際測試中,大多數時間都是拍攝好的數據集由於重投影誤差過大( > 1.5,一般在13左右)無法使用,很奇怪。程序是正確的,opecncv的數據集跑的完全正確,但是自己拍攝的數據集,大部分時間都不可用,這個問題還需要進一步研究。

好消息是,終於不用再傻傻舉着相機一舉一上午了,解放了!

源碼將會在之后發布。雖然程序非常簡單,但是花了我四天時間:半天速學qt,半天寫完,三天debug。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM