久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

基于雙目視覺(jué)的三維重建實(shí)戰(zhàn)C++

 山峰云繞 2022-07-20 發(fā)布于貴州



就在一年前,,在我開始寫這篇文章之前,我觀看了特斯拉人工智能總監(jiān) Andrej Karapathy 的一次演講,,他向世界展示了特斯拉汽車如何使用連接到汽車上的攝像頭感知深度,、在 3D 中重建其周圍環(huán)境并實(shí)時(shí)做出決策,一切(除了用于安全的前置雷達(dá))都是通過(guò)視覺(jué)計(jì)算的,。那個(gè)演講讓我大吃一驚,!

當(dāng)然,我知道可以通過(guò)攝像頭對(duì)環(huán)境進(jìn)行三維重建,,但我的想法是,,當(dāng)我們擁有激光雷達(dá)、雷達(dá)等如此高精度的傳感器時(shí),為什么會(huì)有人冒險(xiǎn)使用普通攝像頭,。用更少的計(jì)算量為我們提供準(zhǔn)確的三維環(huán)境呈現(xiàn),?我開始研究(試圖理解)與深度感知和視覺(jué)三維重建這一主題相關(guān)的論文,并得出結(jié)論,,我們?nèi)祟悘膩?lái)沒(méi)有從我們的頭腦中發(fā)出光線來(lái)感知我們周圍的深度和環(huán)境,,我們聰明,只用我們的兩只眼睛就能感知周圍環(huán)境,,從開車或騎自行車從辦公室到辦公室,,或者在世界上最危險(xiǎn)的賽道上以 230 英里/小時(shí)的速度駕駛一級(jí)方程式賽車,我們從不需要激光來(lái)做出決定以微秒為單位,。一旦我們解決了視覺(jué)問(wèn)題,,這些昂貴的傳感器將變得毫無(wú)意義。

在視覺(jué)深度感知領(lǐng)域正在進(jìn)行大量研究,,特別是隨著機(jī)器學(xué)習(xí)和深度學(xué)習(xí)的進(jìn)步,,我們現(xiàn)在能夠僅從視覺(jué)以高精度計(jì)算深度。所以在我們開始學(xué)習(xí)概念和實(shí)現(xiàn)這些技術(shù)之前,,讓我們先看看這項(xiàng)技術(shù)目前處于什么階段,,以及它的應(yīng)用是什么。

機(jī)器人視覺(jué):

使用 ZED 相機(jī)進(jìn)行環(huán)境感知

為自動(dòng)駕駛創(chuàng)建高清地圖:

深度學(xué)習(xí)的深度感知

SfM(基于運(yùn)動(dòng)的結(jié)構(gòu))和 SLAM(同時(shí)定位和映射)是我將在本教程中介紹的概念的主要技術(shù)之一,。

LSD-SLAM 的演示

現(xiàn)在我們已經(jīng)有了足夠的學(xué)習(xí)靈感,我將開始教程,。因此,,首先我將教你了解幕后發(fā)生的事情所需的基本概念,然后使用 C++ 中的 OpenCV 庫(kù)應(yīng)用它們,。您可能會(huì)問(wèn)的問(wèn)題是,,為什么我在 C++ 中實(shí)現(xiàn)這些概念,而在 python 中實(shí)現(xiàn)這些概念會(huì)容易得多,,這背后是有原因的,。第一個(gè)原因是 python 的速度不夠快,無(wú)法實(shí)時(shí)實(shí)現(xiàn)這些概念,,第二個(gè)原因是,,與 python 不同,使用 C++ 會(huì)要求我們理解這些概念,,否則無(wú)法實(shí)現(xiàn),。

在本教程中,我們將編寫兩個(gè)程序,,一個(gè)是獲取場(chǎng)景的深度圖,,另一個(gè)是獲取場(chǎng)景的點(diǎn)云,,均使用立體視覺(jué)。

在我們直接進(jìn)入編碼部分之前,,了解相機(jī)幾何的概念對(duì)我們來(lái)說(shuō)很重要,,我現(xiàn)在將教你。

1,、相機(jī)模型

自攝影開始以來(lái),,用于生成圖像的過(guò)程并沒(méi)有改變,。來(lái)自觀察場(chǎng)景的光線由相機(jī)通過(guò)正面光圈(鏡頭)捕獲,,該光圈將光線射到位于相機(jī)鏡頭后部的圖像平面上。該過(guò)程如下圖所示:

在上圖中,,do是鏡頭到被觀察物體的距離,,di是鏡頭到像平面的距離。f將因此成為鏡頭的焦距,。這些描述的量之間存在所謂的“薄透鏡方程”之間的關(guān)系,,如下所示:

現(xiàn)在讓我們看看現(xiàn)實(shí)世界中的 3 維對(duì)象如何投影到 2 維平面(照片)的過(guò)程。我們理解這一點(diǎn)的最好方法是看看相機(jī)是如何工作的,。

相機(jī)可以被視為將 3-D 世界映射到 2-D 圖像的功能,。讓我們以最簡(jiǎn)單的相機(jī)模型為例,即針孔相機(jī)模型,,這是人類歷史上較古老的攝影機(jī)制,。下面是針孔攝像頭的工作圖:

從這張圖我們可以得出:

這里很自然地,由物體形成的圖像的大小hi將與物體到相機(jī)的距離do成反比,。此外,,位于 (X, Y, Z) 位置的 3-D 場(chǎng)景點(diǎn)將投影到 (x,y) 處的圖像平面上,其中 (x,y) = (fX/Z, fY/Z),。其中 Z 坐標(biāo)是指點(diǎn)的深度,,這在上一張圖像中完成。整個(gè)相機(jī)配置和符號(hào)可以使用齊次坐標(biāo) 用一個(gè)簡(jiǎn)單的矩陣來(lái)描述,。

當(dāng)相機(jī)生成世界的投影圖像時(shí),,投影幾何被用作現(xiàn)實(shí)世界中物體幾何、旋轉(zhuǎn)和變換的代數(shù)表示,。

齊次坐標(biāo)是射影幾何中使用的坐標(biāo)系統(tǒng),。即使我們可以在歐幾里得空間中表示現(xiàn)實(shí)世界中對(duì)象(或 3-D 空間中的任何點(diǎn))的位置,但必須執(zhí)行的任何變換或旋轉(zhuǎn)都必須在齊次坐標(biāo)空間中執(zhí)行,,然后再返回,。讓我們看看使用齊次坐標(biāo)的優(yōu)點(diǎn):

  • 涉及齊次坐標(biāo)的公式通常比笛卡爾世界中的更簡(jiǎn)單。
  • 無(wú)窮遠(yuǎn)處的點(diǎn)可以用有限坐標(biāo)來(lái)表示,。
  • 單個(gè)矩陣可以代表相機(jī)和世界之間可能發(fā)生的所有可能的保護(hù)性轉(zhuǎn)換,。

在齊次坐標(biāo)空間中,,2-D點(diǎn)用3個(gè)向量表示,3-D點(diǎn)用4個(gè)向量表示:

在上述方程中,,第一個(gè)帶有f符號(hào)的矩陣稱為內(nèi)參矩陣(或俗稱內(nèi)參矩陣),。這里的內(nèi)在矩陣現(xiàn)在只包含焦距(f),我們將在本教程之前研究這個(gè)矩陣的更多參數(shù),。

具有 r 和 t 符號(hào)的第二個(gè)矩陣稱為外部參數(shù)矩陣(或通常稱為外部矩陣),。該矩陣中的元素表示相機(jī)的旋轉(zhuǎn)和平移參數(shù)(即相機(jī)在現(xiàn)實(shí)世界中的放置位置和方式)。

因此,,這些內(nèi)在和外在矩陣一起可以為我們提供圖像中的 (x,y) 點(diǎn)和現(xiàn)實(shí)世界中的 (X, Y, Z) 點(diǎn)之間的關(guān)系,。這就是根據(jù)給定相機(jī)的內(nèi)在和外在參數(shù)將 3-D 場(chǎng)景點(diǎn)投影到 2-D 平面上的方式。

現(xiàn)在我們已經(jīng)獲得了關(guān)于射影幾何和相機(jī)模型的足夠知識(shí),,是時(shí)候介紹計(jì)算機(jī)視覺(jué)幾何中最重要的元素之一——基本矩陣,。

2、基礎(chǔ)矩陣

現(xiàn)在我們知道了如何將 3-D 世界中的點(diǎn)投影到相機(jī)的圖像平面上,。我們將研究顯示同一場(chǎng)景的兩個(gè)圖像之間存在的投影關(guān)系,。當(dāng)這兩個(gè)相機(jī)被剛性基線分開時(shí),我們使用術(shù)語(yǔ)立體視覺(jué),。考慮兩個(gè)針孔相機(jī)觀察一個(gè)給定的場(chǎng)景點(diǎn)共享相同的基線,,如下圖所示:

從上圖中,世界點(diǎn)X的圖像位于圖像平面上的位置x,,現(xiàn)在這個(gè)x可以位于 3-D 空間中這條線上的任何位置,。這意味著如果我們想在另一幅圖像中找到相同的點(diǎn)x,我們需要沿著這條線在第二幅圖像上的投影進(jìn)行搜索,。

從x繪制的這條假想線稱為x極線,。這條核線帶來(lái)了一個(gè)基本的約束,即給定點(diǎn)的匹配在另一個(gè)視圖中必須位于這條線上,。這意味著如果你想從第二張圖像中的第一張圖像中找到x ,,你必須沿著第二張圖像上x的核線尋找它。這些極線可以表征兩個(gè)視圖之間的幾何形狀,。這里要注意的重要一點(diǎn)是,,所有核線總是通過(guò)一個(gè)點(diǎn)。該點(diǎn)對(duì)應(yīng)于一個(gè)攝像機(jī)中心到另一臺(tái)攝像機(jī)中心的投影,,該點(diǎn)稱為極點(diǎn),。

我們可以將基礎(chǔ)矩陣F視為將一個(gè)視圖中的二維圖像點(diǎn)映射到另一個(gè)圖像視圖中的核線的矩陣。圖像對(duì)之間的基本矩陣可以通過(guò)求解一組方程來(lái)估計(jì),,這些方程涉及兩幅圖像之間一定數(shù)量的已知匹配點(diǎn),。這種匹配的最小數(shù)量是七,最佳數(shù)量是八,。然后對(duì)于一個(gè)圖像中的一個(gè)點(diǎn),,基本矩陣給出了應(yīng)該在另一個(gè)視圖中找到其對(duì)應(yīng)點(diǎn)的線的方程,。

如果一個(gè)點(diǎn)(x,y)的一個(gè)點(diǎn)的對(duì)應(yīng)點(diǎn)是(x',y'),并且兩個(gè)圖像平面之間的基本矩陣是F,,那么我們?cè)邶R次坐標(biāo)中一定有下面的方程:

這個(gè)方程表達(dá)了兩個(gè)對(duì)應(yīng)點(diǎn)之間的關(guān)系,,稱為對(duì)極約束

3,、使用 RANSAC 匹配圖像點(diǎn)

當(dāng)兩個(gè)攝像機(jī)觀察同一個(gè)場(chǎng)景時(shí),,它們看到的是相同的物體,但在不同的視點(diǎn)下,。C++ 和 Python 中都有像 OpenCV 這樣的庫(kù),,它們?yōu)槲覀兲峁┝颂卣鳈z測(cè)器,它們可以在圖像中找到帶有描述符的某些點(diǎn),,他們認(rèn)為這些點(diǎn)對(duì)圖像來(lái)說(shuō)是唯一的,,如果給定同一場(chǎng)景的另一個(gè)圖像,,就可以找到這些點(diǎn),。然而,實(shí)際上并不能保證通過(guò)比較檢測(cè)到的特征點(diǎn)的描述符(如 SIFT,、ORB 等)在兩幅圖像之間獲得的匹配集是準(zhǔn)確和真實(shí)的,。這就是為什么引入了基于RANSAC(隨機(jī)采樣共識(shí))策略的基本矩陣估計(jì)方法。

RANSAC 背后的想法是從給定的一組數(shù)據(jù)點(diǎn)中隨機(jī)選擇一些數(shù)據(jù)點(diǎn),,并僅使用這些數(shù)據(jù)點(diǎn)進(jìn)行估計(jì),。所選點(diǎn)的數(shù)量應(yīng)該是估計(jì)數(shù)學(xué)實(shí)體所需的最小點(diǎn)數(shù),在我們的基本矩陣的例子中是八個(gè)匹配,。一旦從這八個(gè)隨機(jī)匹配中估計(jì)出基本矩陣,,匹配集中的所有其他匹配都將針對(duì)我們討論的極線約束進(jìn)行測(cè)試。這些匹配形成計(jì)算的基本矩陣的支持集,。

支持集越大,,計(jì)算出的矩陣是正確的概率就越高。如果隨機(jī)選擇的匹配之一是不正確的匹配,,那么計(jì)算的基本矩陣也將是不正確的,,并且其支持集預(yù)計(jì)會(huì)很小。這個(gè)過(guò)程重復(fù)多次,,最后,,具有最大支持集的矩陣將被保留為最可能的矩陣。

4,、從立體圖像計(jì)算深度圖

人類進(jìn)化成有兩只眼睛的物種的原因是我們可以感知深度,。當(dāng)我們?cè)跈C(jī)器中以類似的方式組織相機(jī)時(shí),它被稱為立體視覺(jué),。立體視覺(jué)系統(tǒng)通常由兩個(gè)并排的攝像機(jī)組成,,觀察同一場(chǎng)景,,下圖顯示了具有理想配置的立體設(shè)備的設(shè)置,完美對(duì)齊,。

在如上圖所示的相機(jī)理想配置下,,相機(jī)僅通過(guò)水平平移分開,因此所有核線都是水平的,。這意味著對(duì)應(yīng)的點(diǎn)具有相同的y坐標(biāo),,搜索減少到一維線。當(dāng)相機(jī)被這樣一個(gè)純水平平移分開時(shí),,第二個(gè)相機(jī)的投影方程將變?yōu)椋?/span>

通過(guò)查看下圖,,該等式會(huì)更有意義,這是數(shù)碼相機(jī)的一般情況:

其中點(diǎn)(uo, vo)是通過(guò)鏡頭主點(diǎn)的線穿過(guò)像平面的像素位置,。這里我們得到一個(gè)關(guān)系:

這里,,術(shù)語(yǔ)(x-x')稱為視差,Z當(dāng)然是深度,。為了從立體對(duì)計(jì)算深度圖,,必須計(jì)算每個(gè)像素的視差。

但在現(xiàn)實(shí)世界中,,獲得這樣一個(gè)理想的配置是非常困難的,。即使我們準(zhǔn)確地放置相機(jī),它們也不可避免地會(huì)包含一些額外的過(guò)渡和旋轉(zhuǎn)組件,。

幸運(yùn)的是,,可以通過(guò)使用穩(wěn)健的匹配算法來(lái)校正這些圖像以生成所需的水平線,該算法利用基本矩陣來(lái)執(zhí)行校正,。

現(xiàn)在讓我們從獲得以下立體圖像的基本矩陣開始:

立體圖像對(duì)

可以通過(guò)單擊此處從 GitHub 存儲(chǔ)庫(kù)下載上述圖像,。在開始編寫本教程中的代碼之前,請(qǐng)確保你的計(jì)算機(jī)上已構(gòu)建 opencv 和 opencv-contrib 庫(kù),。如果它們未構(gòu)建,,我建議你訪問(wèn)此鏈接以安裝它們( 僅針對(duì)Ubuntu )。

5,、編寫實(shí)現(xiàn)代碼

#include <opencv2/opencv.hpp>#include 'opencv2/xfeatures2d.hpp'using namespace std;using namespace cv;int main(){cv::Mat img1, img2;img1 = cv::imread('imR.png',cv::IMREAD_GRAYSCALE);img2 = cv::imread('imL.png',cv::IMREAD_GRAYSCALE);

我們做的第一件事是包含來(lái)自 opencv 和 opencv-contrib 的所需庫(kù),,我要求你在開始本節(jié)之前構(gòu)建它們。在main()函數(shù)中,,我們初始化了cv:Mat數(shù)據(jù)類型的兩個(gè)變量,,它是 opencv 庫(kù)的成員函數(shù),Mat數(shù)據(jù)類型可以通過(guò)動(dòng)態(tài)分配內(nèi)存來(lái)保存任意大小的向量,,尤其是圖像,。然后使用cv::imread()我們將圖像導(dǎo)入mat數(shù)據(jù)類型的img1img2中。cv::IMREAD_GRAYSCALE參數(shù)將圖像導(dǎo)入為灰度,。

// Define keypoints vectorstd::vector<cv::KeyPoint> keypoints1, keypoints2;// Define feature detectorcv::Ptr<cv::Feature2D> ptrFeature2D = cv::xfeatures2d::SIFT::create(74);// Keypoint detectionptrFeature2D->detect(img1,keypoints1);ptrFeature2D->detect(img2,keypoints2);// Extract the descriptorcv::Mat descriptors1;cv::Mat descriptors2;ptrFeature2D->compute(img1,keypoints1,descriptors1);ptrFeature2D->compute(img2,keypoints2,descriptors2);

在這里,,我們使用 opencv 的 SIFT 特征檢測(cè)器來(lái)從圖像中提取所需的特征點(diǎn),。如果你想了解有關(guān)這些特征檢測(cè)器如何工作的更多信息,請(qǐng)?jiān)L問(wèn)此鏈接,。我們上面獲得的描述符描述了提取的每個(gè)點(diǎn),,這個(gè)描述用于在另一個(gè)圖像中找到它:

// Construction of the matchercv::BFMatcher matcher(cv::NORM_L2);// Match the two image descriptorsstd::vector<cv::DMatch> outputMatches;matcher.match(descriptors1,descriptors2, outputMatches);

BFMatcher獲取第一組中一個(gè)特征的描述符,并使用一些閾值距離計(jì)算與第二組中的所有其他特征匹配,,并返回最接近的一個(gè),。我們將 BFMatches 返回的所有匹配項(xiàng)存儲(chǔ)在vector<cv::DMatch>類型的輸出匹配變量中。

// Convert keypoints into Point2fstd::vector<cv::Point2f> points1, points2;for (std::vector<cv::DMatch>::const_iterator it=   outputMatches.begin(); it!= outputMatches.end(); ++it) {    // Get the position of left keypoints    points1.push_back(keypoints1[it->queryIdx].pt);    // Get the position of right keypoints    points2.push_back(keypoints2[it->trainIdx].pt);     }

獲取的關(guān)鍵點(diǎn)首先需要轉(zhuǎn)換為cv::Point2f類型,,以便與cv::findFundamentalMat一起使用,,我們將使用該函數(shù)使用我們抽象的這些特征點(diǎn)來(lái)計(jì)算基本矩陣。兩個(gè)結(jié)果向量Points1Points2包含兩個(gè)圖像中的對(duì)應(yīng)點(diǎn)坐標(biāo),。

std::vector<uchar> inliers(points1.size(),0);cv::Mat fundamental= cv::findFundamentalMat( points1,points2, // matching points inliers, // match status (inlier or outlier) cv::FM_RANSAC, // RANSAC method 1.0, // distance to epipolar line 0.98); // confidence probabilitycout<<fundamental; //include this for seeing fundamental matrix

最后,,我們調(diào)用了 cv::findFundamentalMat。

// Compute homographic rectificationcv::Mat h1, h2;cv::stereoRectifyUncalibrated(points1, points2, fundamental, img1.size(), h1, h2);// Rectify the images through warpingcv::Mat rectified1;cv::warpPerspective(img1, rectified1, h1, img1.size());cv::Mat rectified2;cv::warpPerspective(img2, rectified2, h2, img1.size());

正如我之前在教程中解釋的那樣,,在實(shí)際世界中,,獲得理想的相機(jī)配置而沒(méi)有任何錯(cuò)誤是非常困難的,因此 opencv 提供了一個(gè)校正功能,,該功能應(yīng)用單應(yīng)變換將每個(gè)相機(jī)的圖像平面投影到完美對(duì)齊的虛擬平面上. 這種變換是根據(jù)一組匹配點(diǎn)和基本矩陣計(jì)算得出的,。

// Compute disparitycv::Mat disparity;cv::Ptr<cv::StereoMatcher> pStereo = cv::StereoSGBM::create(0, 32,5);pStereo->compute(rectified1, rectified2, disparity);cv::imwrite('disparity.jpg', disparity);

最后,,我們計(jì)算了視差圖,。從下圖中,較暗的像素代表離相機(jī)較近的物體,,較亮的像素代表遠(yuǎn)離相機(jī)的物體,。你在輸出視差圖中看到的白色像素噪聲可以使用一些我不會(huì)在本教程中介紹的過(guò)濾器來(lái)去除。

現(xiàn)在我們已經(jīng)成功地從給定的立體對(duì)中獲得了深度圖?,F(xiàn)在讓我們嘗試使用 opencv 中名為 3D-Viz 的工具將獲得的 2-D 圖像點(diǎn)重新投影到 3-D 空間,,該工具將幫助我們渲染 3-D 點(diǎn)云。

但是這一次,,我們不是從給定的圖像點(diǎn)估計(jì)一個(gè)基本矩陣,,而是使用一個(gè)基本矩陣來(lái)投影這些點(diǎn)。

6,、本質(zhì)矩陣

本質(zhì)矩陣可以看作是基礎(chǔ)矩陣,,但用于校準(zhǔn)的相機(jī)。我們也可以將其稱為對(duì)基礎(chǔ)矩陣的專業(yè)化,,其中矩陣是使用校準(zhǔn)的相機(jī)計(jì)算的,,這意味著我們必須首先獲取有關(guān)我們?cè)谑澜缟系南鄼C(jī)的知識(shí)。

因此,,為了讓我們估計(jì)本質(zhì)矩陣,,我們首先需要相機(jī)的內(nèi)在矩陣(表示給定相機(jī)的光學(xué)中心和焦距的矩陣),。讓我們看一下下面的等式:

這里,從第一個(gè)矩陣中,,fxfy代表相機(jī)的焦距,,(uo, vo)是主點(diǎn)。這是內(nèi)在矩陣,,我們的目標(biāo)是估計(jì)它,。

這種尋找不同相機(jī)參數(shù)的過(guò)程稱為相機(jī)校準(zhǔn)。我們顯然可以使用相機(jī)制造商提供的規(guī)格,,但是對(duì)于我們將要做的 3-D 重建等任務(wù),,這些規(guī)格不夠準(zhǔn)確。因此,,我們將執(zhí)行我們自己的相機(jī)校準(zhǔn),。

這個(gè)想法是向相機(jī)顯示一組場(chǎng)景點(diǎn),這些點(diǎn)我們知道它們?cè)诂F(xiàn)實(shí)世界中的實(shí)際 3-D 位置,,然后觀察這些點(diǎn)在獲得的圖像平面上的投影位置,。有了足夠數(shù)量的 3-D 點(diǎn)和相關(guān)的 2-D 圖像點(diǎn),我們就可以從投影方程中抽象出精確的相機(jī)參數(shù),。

做到這一點(diǎn)的一種方法是從不同的視點(diǎn)拍攝一組世界的 3-D 點(diǎn)及其已知 3-D 位置的多張圖像,。我們將使用 opencv 的校準(zhǔn)方法,其中一種方法將棋盤圖像作為輸入,,并返回所有存在的角,。我們可以自由假設(shè)板位于 Z=0,X 和 Y 軸與網(wǎng)格很好地對(duì)齊,。我們將在下面的部分中了解 OpenCV 的這些校準(zhǔn)功能是如何工作的,。

6、三維場(chǎng)景重建

讓我們首先創(chuàng)建三個(gè)函數(shù),,我們將在 main 函數(shù)中使用它們,。這三個(gè)功能將

  • addChessBoardPoints() //返回給定棋盤圖像的角點(diǎn)
  • calibrate() // 從提取的點(diǎn)返回內(nèi)在矩陣
  • triangulate() //返回重建點(diǎn)的 3-D 坐標(biāo)
#include 'CameraCalibrator.h'#include <opencv2/opencv.hpp>#include 'opencv2/xfeatures2d.hpp'using namespace std;using namespace cv;std::vector<cv::Mat> rvecs, tvecs;// Open chessboard images and extract corner pointsint CameraCalibrator::addChessboardPoints(         const std::vector<std::string>& filelist,          cv::Size & boardSize) {// the points on the chessboardstd::vector<cv::Point2f> imageCorners;std::vector<cv::Point3f> objectCorners;// 3D Scene Points:// Initialize the chessboard corners // in the chessboard reference frame// The corners are at 3D location (X,Y,Z)= (i,j,0)for (int i=0; i<boardSize.height; i++) {  for (int j=0; j<boardSize.width; j++) {objectCorners.push_back(cv::Point3f(i, j, 0.0f));   } }// 2D Image points:cv::Mat image; // to contain chessboard imageint successes = 0;// for all viewpointsfor (int i=0; i<filelist.size(); i++) {// Open the image        image = cv::imread(filelist[i],0);// Get the chessboard corners        bool found = cv::findChessboardCorners(                        image, boardSize, imageCorners);// Get subpixel accuracy on the corners        cv::cornerSubPix(image, imageCorners,                   cv::Size(5,5),                   cv::Size(-1,-1),       cv::TermCriteria(cv::TermCriteria::MAX_ITER +                          cv::TermCriteria::EPS,              30,    // max number of iterations              0.1));     // min accuracy// If we have a good board, add it to our data      if (imageCorners.size() == boardSize.area()) {// Add image and scene points from one view            addPoints(imageCorners, objectCorners);            successes++;          }//Draw the corners        cv::drawChessboardCorners(image, boardSize, imageCorners, found);        cv::imshow('Corners on Chessboard', image);        cv::waitKey(100);    }return successes;}

在上面的代碼中,可以觀察到我們包含了一個(gè)頭文件“CameraCalibrator.h”,,它將包含該文件的所有函數(shù)聲明和變量初始化,。可以通過(guò)訪問(wèn)此鏈接在我的 Github 上下載本教程中的標(biāo)題以及所有其他文件,。

我們的函數(shù)利用了 opencv 的findChessBoardCorners()函數(shù),,該函數(shù)將圖像位置數(shù)組(數(shù)組必須包含每個(gè)棋盤圖像的位置)和棋盤尺寸(你應(yīng)該輸入棋盤中水平和垂直角的數(shù)量)作為輸入?yún)?shù)并返回給我們一個(gè)包含角點(diǎn)位置的向量。

double CameraCalibrator::calibrate(cv::Size &imageSize){ // undistorter must be reinitialized mustInitUndistort= true;// start calibration return calibrateCamera(objectPoints, // the 3D points imagePoints, // the image points imageSize, // image size cameraMatrix, // output camera matrix distCoeffs, // output distortion matrix rvecs, tvecs, // Rs, Ts flag); // set options}

在這個(gè)函數(shù)中,,我們使用了calibrateCamera()函數(shù),,它獲取我們上面獲得的 3-D 點(diǎn)和圖像點(diǎn),并返回給我們固有矩陣、旋轉(zhuǎn)向量(描述相機(jī)相對(duì)于場(chǎng)景點(diǎn)的旋轉(zhuǎn))和平移矩陣(描述相機(jī)相對(duì)于場(chǎng)景點(diǎn)的位置),。

cv::Vec3d CameraCalibrator::triangulate(const cv::Mat &p1, const cv::Mat &p2, const cv::Vec2d &u1, const cv::Vec2d &u2) {// system of equations assuming image=[u,v] and X=[x,y,z,1]  // from u(p3.X)= p1.X and v(p3.X)=p2.X  cv::Matx43d A(u1(0)*p1.at<double>(2, 0) - p1.at<double>(0, 0),  u1(0)*p1.at<double>(2, 1) - p1.at<double>(0, 1),  u1(0)*p1.at<double>(2, 2) - p1.at<double>(0, 2),  u1(1)*p1.at<double>(2, 0) - p1.at<double>(1, 0),  u1(1)*p1.at<double>(2, 1) - p1.at<double>(1, 1),  u1(1)*p1.at<double>(2, 2) - p1.at<double>(1, 2),  u2(0)*p2.at<double>(2, 0) - p2.at<double>(0, 0),  u2(0)*p2.at<double>(2, 1) - p2.at<double>(0, 1),  u2(0)*p2.at<double>(2, 2) - p2.at<double>(0, 2),  u2(1)*p2.at<double>(2, 0) - p2.at<double>(1, 0),  u2(1)*p2.at<double>(2, 1) - p2.at<double>(1, 1),  u2(1)*p2.at<double>(2, 2) - p2.at<double>(1, 2));cv::Matx41d B(p1.at<double>(0, 3) - u1(0)*p1.at<double>(2,3),                p1.at<double>(1, 3) - u1(1)*p1.at<double>(2,3),                p2.at<double>(0, 3) - u2(0)*p2.at<double>(2,3),                p2.at<double>(1, 3) - u2(1)*p2.at<double>(2,3));// X contains the 3D coordinate of the reconstructed point  cv::Vec3d X;  // solve AX=B  cv::solve(A, B, X, cv::DECOMP_SVD);  return X;}

上述函數(shù)采用可以使用先前函數(shù)的固有矩陣獲得的投影矩陣和歸一化圖像點(diǎn),,并返回上述點(diǎn)的 3-D 坐標(biāo)。

下面是從立體對(duì)進(jìn)行 3-D 重建的完整代碼,。此代碼需要至少 25 到 30 張棋盤圖像,,這些圖像來(lái)自你拍攝立體對(duì)圖像的同一臺(tái)相機(jī)。為了首先在你的 PC 中運(yùn)行此代碼,,請(qǐng)克隆我的 GitHub 存儲(chǔ)庫(kù),,將雙目對(duì)替換為自己的,并將棋盤圖像位置數(shù)組替換為自己的數(shù)組,,然后構(gòu)建和編譯,。我正在將示例棋盤圖像上傳到我的 GitHub 以供你參考,你必須拍攝大約 30 張這樣的圖像并在代碼中提及,。

int main(){cout<<'compiled'<<endl;const std::vector<std::string> files = {'boards/1.jpg'......}; cv::Size board_size(7,7);CameraCalibrator cal; cal.addChessboardPoints(files, board_size);cv::Mat img = cv::imread('boards/1.jpg');cv::Size img_size = img.size(); cal.calibrate(img_size); cout<<cameraMatrix<<endl;cv::Mat image1 = cv::imread('imR.png'); cv::Mat image2 = cv::imread('imL.png');// vector of keypoints and descriptors std::vector<cv::KeyPoint> keypoints1; std::vector<cv::KeyPoint> keypoints2; cv::Mat descriptors1, descriptors2;// Construction of the SIFT feature detector cv::Ptr<cv::Feature2D> ptrFeature2D = cv::xfeatures2d::SIFT::create(10000);// Detection of the SIFT features and associated descriptors ptrFeature2D->detectAndCompute(image1, cv::noArray(), keypoints1, descriptors1); ptrFeature2D->detectAndCompute(image2, cv::noArray(), keypoints2, descriptors2);// Match the two image descriptors // Construction of the matcher with crosscheck cv::BFMatcher matcher(cv::NORM_L2, true); std::vector<cv::DMatch> matches; matcher.match(descriptors1, descriptors2, matches);cv::Mat matchImage;cv::namedWindow('img1'); cv::drawMatches(image1, keypoints1, image2, keypoints2, matches, matchImage, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS); cv::imwrite('matches.jpg', matchImage);// Convert keypoints into Point2f std::vector<cv::Point2f> points1, points2;for (std::vector<cv::DMatch>::const_iterator it = matches.begin(); it != matches.end(); ++it) { // Get the position of left keypoints float x = keypoints1[it->queryIdx].pt.x; float y = keypoints1[it->queryIdx].pt.y; points1.push_back(cv::Point2f(x, y)); // Get the position of right keypoints x = keypoints2[it->trainIdx].pt.x; y = keypoints2[it->trainIdx].pt.y; points2.push_back(cv::Point2f(x, y)); }// Find the essential between image 1 and image 2 cv::Mat inliers; cv::Mat essential = cv::findEssentialMat(points1, points2, cameraMatrix, cv::RANSAC, 0.9, 1.0, inliers);cout<<essential<<endl;// recover relative camera pose from essential matrix cv::Mat rotation, translation; cv::recoverPose(essential, points1, points2, cameraMatrix, rotation, translation, inliers); cout<<rotation<<endl; cout<<translation<<endl;// compose projection matrix from R,T cv::Mat projection2(3, 4, CV_64F); // the 3x4 projection matrix rotation.copyTo(projection2(cv::Rect(0, 0, 3, 3))); translation.copyTo(projection2.colRange(3, 4)); // compose generic projection matrix cv::Mat projection1(3, 4, CV_64F, 0.); // the 3x4 projection matrix cv::Mat diag(cv::Mat::eye(3, 3, CV_64F)); diag.copyTo(projection1(cv::Rect(0, 0, 3, 3))); // to contain the inliers std::vector<cv::Vec2d> inlierPts1; std::vector<cv::Vec2d> inlierPts2; // create inliers input point vector for triangulation int j(0); for (int i = 0; i < inliers.rows; i++) { if (inliers.at<uchar>(i)) { inlierPts1.push_back(cv::Vec2d(points1[i].x, points1[i].y)); inlierPts2.push_back(cv::Vec2d(points2[i].x, points2[i].y)); } } // undistort and normalize the image points std::vector<cv::Vec2d> points1u; cv::undistortPoints(inlierPts1, points1u, cameraMatrix, distCoeffs); std::vector<cv::Vec2d> points2u; cv::undistortPoints(inlierPts2, points2u, cameraMatrix, distCoeffs);// Triangulation std::vector<cv::Vec3d> points3D; cal.triangulate(projection1, projection2, points1u, points2u, points3D);cout<<'3D points :'<<points3D.size()<<endl;viz::Viz3d window; //creating a Viz window//Displaying the Coordinate Origin (0,0,0) window.showWidget('coordinate', viz::WCoordinateSystem());window.setBackgroundColor(cv::viz::Color::black());//Displaying the 3D points in green window.showWidget('points', viz::WCloud(points3D, viz::Color::green())); window.spin();}

我知道 代碼顯示是一團(tuán)糟,,尤其是對(duì)于 C++,所以我建議你去我的GitHub并理解上面的代碼,。

對(duì)我來(lái)說(shuō),,給定對(duì)的輸出如下所示,可以通過(guò)調(diào)整特征檢測(cè)器及其類型來(lái)改進(jìn),。

任何有興趣深入學(xué)習(xí)這些概念的人,,我都會(huì)在下面推薦這本書,我認(rèn)為這本書是計(jì)算機(jī)視覺(jué)幾何的圣經(jīng),。這也是本教程的參考書,。

計(jì)算機(jī)視覺(jué)中的多視圖幾何第 2 版- Richard Hartley 和 Andrew Zisserman。


原文鏈接:
http://www./blog/3d-reconstruction-hands-on/

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多