上一節的程式實作了立體視覺實作過程的所有步驟,現在對整體架構進行分析學習
書中架構結構介紹
書:Learning OpenCV3
第一步:讀取左右圖像對
目的:尋找亞像素精度的circles
方法:調用stereoCalibrate()标定
需求:相機矩陣_M以及畸變矢量_D
輸出:_R,_T,_E,_F
第二步:評估精度
方式:圖像上的點距離另一張圖像上的極線到底有多遠。
調用函數 undistortPoints()以及computeCorrespondEpilines()
第三步:計算校正圖像
方式
Hartley-> cv::stereoRectifyUncalibrated()
如果使用這一步,代碼還需要計算基礎矩陣或者直接調用立體标定得到的基礎矩陣
然後調用cv::remap()計算校正圖像。
本執行個體中通過劃線,表明了校正圖像之間的聯系。
Bouguet->stereoRectify()
第四步:計算視差圖
這一部分通過StereoSGBM()尋找标定/非标定的校正立體圖像對的視差圖。
例子中給了水準放置以及垂直放置相機的校正執行個體。
但是垂直放置隻能通過非标定校正算法,除非你通過代碼對圖像進行了轉換
StereoCalib開放接口
接口參數表
static void StereoCalib(const char *imageList, int nx, int ny,
bool useUncalibrated)
在主程式中,使用了StereoCalib函數,使用形式為
StereoCalib(board_list, board_w, board_h, true);
其中board_list為包含了标定闆名稱的txt檔案;board_w,board_h為棋盤格的方格數目,最後一個參數為校正的形式(使用标定校正(Bouguet's algorithm)還是非标定校正(Hartely's algorithm))
StereoCalib内部分析
工作流程
程式的流程為雙目立體視覺成像的流程:
單目标定(擷取内外參)->雙目标定(相機之間的轉換矩陣)->校正(輸出行對準圖像)->立體比對(輸出深度圖)
立體校正的兩種方法
前面幾篇文章隻是泛泛的提了一下立體校正,本文由于是進一步深入的應用,将對涉及的兩種校正方法:Bouguet算法以及Hartely算法進行原理上的梳理。
資料來源:
Learning OpenCV3
Hartely算法(Hartley's algorithm)
本文研究的主程式對useUncalibrate指派為true,進入程式中則自動選擇非标定校正算法;這裡采用的非标定校正算法就是Hartley算法。
Hartley算法通過将極點比對到無窮遠的方法尋找單應性,可以減少兩立體圖像之間的視差。這種方法繞過了計算相機的内參,進而不用進行點的比對,是以算法複雜度較低。基礎矩陣可由stereoCalibrate()獲得。
優點:通過簡單的觀察場景中的點,可以實作線上立體标定(online stereo calibrate)
缺點:由于避開了圖像的内參調用;我們無法了解圖像的尺度因子,比如棋盤格到底是距離我們100cm還是100m。
函數
Bouguet算法(Bouguet's algorithm)
Bouguet算法就是标定算法,其輸入參數為兩相機之間的R/T矩陣。立體矯正在Bouguet算法的實作原理是:在最大化公共視場區域的同時,最小化重投影畸變。
由于目前我的數學基礎還不紮實,以及目前這部分是為了用;這兩種算法的原理在後續繼續深入研究時将進行補充。
函數
本文調用函數彙總
stereoCalibrate
本函數實作立體标定,輸出R、T、E、F矩陣
stereoRectifyUncalibrated
本函數實作Hartley's algorithm進行立體校正标定
initUndistortRectifyMap
該函數的目的為擷取校正圖像,需求流程如下:
上述圖像為立體矯正的過程。立體校正的流程為:原始圖像a經過去畸變得到b,b再經過校正獲得行對準圖像c,c再經過裁剪獲得最終圖像d.
校正計算的過程則為c->a的過程。
在獲得了立體标定項後,通過調用initUndistortRectifyMao能夠預先計算從相機角度的左右查找圖。對于任何圖像-圖像的比對函數,前向比對(由原圖像計算到目标圖像)會由于浮點的影響使資訊丢失,進而使得目标圖像更像一塊奶酪。
在立體校正的過程中,我們将調用這個函數兩次(分别對應于左右相機)
函數原型
remap
remap是一個基礎的重映射函數,相關内容可以參考 OpenCV中的重映射:remap()函數
原型為
void remap(InputArray src, OutputArray dst, InputArray map1, InputArray map2,
int interpolation, intborderMode = BORDER_CONSTANT,
const Scalar& borderValue = Scalar())
前兩個參為輸入輸出矩陣;map1與map2可為映射矩陣模式,也可為圖像色彩格式;第五個參數為插值方式;第六個參數為邊界模式;第七個參數為邊界顔色,預設Scalar()黑色。
具體例子不再拓展
StereoSGBM
函數上一節講過,雖然用的比較笨,後續将進行繼續修煉。
資料流
該部分目的是在梳理重建的過程中回歸到作業中來。
作業
需了解及完成步驟
1,如何利用立體校正的方法擷取前向平行圖像?
2,如何用SGBM的方法擷取視差圖?
分步解決
早上鍵盤alt被預設鎖定了,半天沒打開,後面通過連按兩次alt解除了鎖定。廢話不多說,直接進入正文
控制台視窗顯示圖像:
首先輸出序列标定圖像得到相應的内外參,然後running stereo calibration獲得内外參後,獲得重投影誤差avg err,最後是校正與視差圖擷取。
調用函數第一項:stereoCalibrate
這部分調用的函數使用方法為
stereoCalibrate(
objectPoints, points[0], points[1], M1, D1, M2, D2, imageSize, R, T, E, F,
TermCriteria(TermCriteria::COUNT | TermCriteria::EPS, 100, 1e-5),
CALIB_FIX_ASPECT_RATIO | CALIB_ZERO_TANGENT_DIST |
CALIB_SAME_FOCAL_LENGTH
);
也就是為下面的校正與比對提供了R,T,E,F四個矩陣
傳遞給第二項stereoRectify
回憶前文
Hartley-> cv::stereoRectifyUncalibrated()
如果使用這一步,代碼還需要計算基礎矩陣或者直接調用立體标定得到的基礎矩陣
然後調用cv::remap()計算校正圖像。
本執行個體中通過劃線,表明了校正圖像之間的聯系。
實作
三個點:
1,基礎矩陣
2,remap()計算校正圖像
3,圖像劃線
coding
vector<cv::Point2f> allpoints[2];
for (i = 0; i < nframes; i++) {
copy(points[0][i].begin(), points[0][i].end(),
back_inserter(allpoints[0]));
copy(points[1][i].begin(), points[1][i].end(),
back_inserter(allpoints[1]));
}
cv::Mat F = findFundamentalMat(allpoints[0], allpoints[1], cv::FM_8POINT);
cv::Mat H1, H2;
cv::stereoRectifyUncalibrated(allpoints[0], allpoints[1], F, imageSize,
H1, H2, 3);
R1 = M1.inv() * H1 * M1;
R2 = M2.inv() * H2 * M2;
// Precompute map for cvRemap()
//
cv::initUndistortRectifyMap(M1, D1, R1, P1, imageSize, CV_16SC2, map11,
map12);
cv::initUndistortRectifyMap(M2, D2, R2, P2, imageSize, CV_16SC2, map21,
map22);
// RECTIFY THE IMAGES AND FIND DISPARITY MAPS
//
cv::Mat pair;
if (!isVerticalStereo)
pair.create(imageSize.height, imageSize.width * 2, CV_8UC3);
else
pair.create(imageSize.height * 2, imageSize.width, CV_8UC3);
下一步:繼續拆分程式進行解讀
首先應當删除一些不需要的程式,然後進行分段test!梳理好思路,結合書上
12.27任務:
完成一對圖像的校正與DepthMap生成的過程
分析代碼段
// CALIBRATE THE STEREO CAMERAS
Mat M1 = Mat::eye(3, 3, CV_64F);
Mat M2 = Mat::eye(3, 3, CV_64F);
Mat D1, D2, R, T, E, F;
cout << "\nRunning stereo calibration ...\n";
stereoCalibrate(
objectPoints, points[0], points[1], M1, D1, M2, D2, imageSize, R, T, E, F,
TermCriteria(TermCriteria::COUNT | TermCriteria::EPS, 100, 1e-5),
CALIB_FIX_ASPECT_RATIO | CALIB_ZERO_TANGENT_DIST |
CALIB_SAME_FOCAL_LENGTH
);
//參數對應
//輸入:objectPoints, imagePoints 1,2 雙相機矩陣M1,D1 M2,D2 imageSize
//輸出 R T E F
int nframes = (int)objectPoints.size();
// COMPUTE AND DISPLAY RECTIFICATION
//
if (showUndistorted) {
cv::Mat R1, R2, P1, P2, map11, map12, map21, map22;
//Hartley's Uncalibrated
vector<cv::Point2f> allpoints[2];
for (i = 0; i < nframes; i++) {
copy(points[0][i].begin(), points[0][i].end(),
back_inserter(allpoints[0]));
copy(points[1][i].begin(), points[1][i].end(),
back_inserter(allpoints[1]));
}
cv::Mat F = findFundamentalMat(allpoints[0], allpoints[1], cv::FM_8POINT);
cv::Mat H1, H2;
cv::stereoRectifyUncalibrated(allpoints[0], allpoints[1], F, imageSize,
H1, H2, 3);
R1 = M1.inv() * H1 * M1;//inv()求逆
R2 = M2.inv() * H2 * M2;
// Precompute map for cvRemap()
//
cv::initUndistortRectifyMap(M1, D1, R1, P1, imageSize, CV_16SC2, map11,
map12);//相機内參矩陣,即便系數矩陣,一二相機之間的旋轉矩陣,輸入的矯正後的相機矩陣,無失真矩陣
cv::initUndistortRectifyMap(M2, D2, R2, P2, imageSize, CV_16SC2, map21,
map22);
// RECTIFY THE IMAGES AND FIND DISPARITY MAPS
//
cv::Mat pair;
if (!isVerticalStereo)
pair.create(imageSize.height, imageSize.width * 2, CV_8UC3);
else
pair.create(imageSize.height * 2, imageSize.width, CV_8UC3);
// Setup for finding stereo correspondences
//
StereoSGBM sgbm;
int SADWindowSize = 9;
//預處理sobel,獲得圖像梯度資訊,用于計算代價
sgbm.preFilterCap = 63;
//代價參數,得到SAD代價
int numberOfDisparities = 64;
sgbm.SADWindowSize = SADWindowSize > 0 ? SADWindowSize : 3;
sgbm.minDisparity = 0;
sgbm.numberOfDisparities = numberOfDisparities;
//動态規劃參數,預設四條路徑
IplImage *img1 = cvLoadImage("left01.jpg", 0);
int cn = img1->nChannels;
sgbm.P1 = 8 * cn*sgbm.SADWindowSize*sgbm.SADWindowSize;
sgbm.P2 = 32 * cn*sgbm.SADWindowSize*sgbm.SADWindowSize;
//後處理參數,唯一性檢測、亞像素插值、左右一緻性檢測、連通區域檢測
sgbm.uniquenessRatio = 10;
sgbm.speckleWindowSize = 100;
sgbm.speckleRange = 32;
sgbm.disp12MaxDiff = 1;
Mat disp, disp8;
for (i = 0; i < nframes; i++) {
Mat img1 = imread(imageNames[0][i].c_str(), 0);
Mat img2 = imread(imageNames[1][i].c_str(), 0);
Mat img1r, img2r, disp, vdisp;
if (img1.empty() || img2.empty())
continue;
remap(img1, img1r, map11, map12, cv::INTER_LINEAR);
remap(img2, img2r, map21, map22, cv::INTER_LINEAR);
if (!isVerticalStereo) {
sgbm(img1r, img2r, disp);
cv::normalize(disp, vdisp, 0, 256, cv::NORM_MINMAX, CV_8U);
imshow("disparity", vdisp);
}
if (!isVerticalStereo) {
cv::Mat part = pair.colRange(0, imageSize.width);
cvtColor(img1r, part, cv::COLOR_GRAY2BGR);
part = pair.colRange(imageSize.width, imageSize.width * 2);
cvtColor(img2r, part, cv::COLOR_GRAY2BGR);
for (j = 0; j < imageSize.height; j += 16)
cv::line(pair, cv::Point(0, j), cv::Point(imageSize.width * 2, j),
cv::Scalar(0, 255, 0));
}
else {
cv::Mat part = pair.rowRange(0, imageSize.height);
cv::cvtColor(img1r, part, cv::COLOR_GRAY2BGR);
part = pair.rowRange(imageSize.height, imageSize.height * 2);
cv::cvtColor(img2r, part, cv::COLOR_GRAY2BGR);
for (j = 0; j < imageSize.width; j += 16)
line(pair, cv::Point(j, 0), cv::Point(j, imageSize.height * 2),
cv::Scalar(0, 255, 0));
}
cv::imshow("rectified", pair);
if ((cv::waitKey() & 255) == 27)
break;
}
}
函數總結如下,依次調用了下面三個函數:stereoRectifyUncalibrated, initUndistortRectifyMap和StereoSGBM
上面程式的核心在于
Mat disp, disp8;
for (i = 0; i < nframes; i++) {
Mat img1 = imread(imageNames[0][i].c_str(), 0);
Mat img2 = imread(imageNames[1][i].c_str(), 0);
Mat img1r, img2r, disp, vdisp;
if (img1.empty() || img2.empty())
continue;
remap(img1, img1r, map11, map12, cv::INTER_LINEAR);
remap(img2, img2r, map21, map22, cv::INTER_LINEAR);
if (!isVerticalStereo) {
sgbm(img1r, img2r, disp);
cv::normalize(disp, vdisp, 0, 256, cv::NORM_MINMAX, CV_8U);
imshow("disparity", vdisp);
}
if (!isVerticalStereo) {
cv::Mat part = pair.colRange(0, imageSize.width);
cvtColor(img1r, part, cv::COLOR_GRAY2BGR);
part = pair.colRange(imageSize.width, imageSize.width * 2);
cvtColor(img2r, part, cv::COLOR_GRAY2BGR);
for (j = 0; j < imageSize.height; j += 16)
cv::line(pair, cv::Point(0, j), cv::Point(imageSize.width * 2, j),
cv::Scalar(0, 255, 0));
}
else {
cv::Mat part = pair.rowRange(0, imageSize.height);
cv::cvtColor(img1r, part, cv::COLOR_GRAY2BGR);
part = pair.rowRange(imageSize.height, imageSize.height * 2);
cv::cvtColor(img2r, part, cv::COLOR_GRAY2BGR);
for (j = 0; j < imageSize.width; j += 16)
line(pair, cv::Point(j, 0), cv::Point(j, imageSize.height * 2),
cv::Scalar(0, 255, 0));
}
cv::imshow("rectified", pair);
if ((cv::waitKey() & 255) == 27)
break;
}
最終能夠擷取視差圖的核心語句為:
Mat img1 = imread("left01.jpg");
Mat img2 = imread("right01.jpg");
Mat img1r, img2r, disp1, vdisp;
remap(img1, img1r, map11, map12, cv::INTER_LINEAR);
remap(img2, img2r, map21, map22, cv::INTER_LINEAR);
sgbm(img1r, img2r, disp1);
cv::normalize(disp1, vdisp, 0, 256, cv::NORM_MINMAX, CV_8U);
imshow("disparity", vdisp);
waitKey(100000);
其中normalize的作用為歸一化,相當于matlab中的imshow(disp,[]);
繼續向上走,可以整理得到這一段程式
由程式可知,map11\2和map21\2可通過initUndistortRectifyMap獲得
于是本步驟目的為,如何擷取map矩陣。
獲得map矩陣的需求為M,D,R,P一共八個矩陣,為了更好地了解下作業怎麼做,我們輸出一下相關調用參數:
相機1
相機2
看看我們作業中有什麼内容
作業中給出了旋轉向量,而上述程式需要旋轉矩陣,是以第一步為
向量到矩陣的轉換
羅德裡格斯變換
直接調用函數即可進行輸出
Mat Rvl = (Mat_<double>(1,3)<< 0.04345, -0.05236, -0.01810);
Mat RvTl;
cout << "Rvl=Rvr= " << endl << " " << Rvl << endl<<endl;
Rodrigues(Rvl, RvTl);
經過一段時間的摸索,能夠輸出視差圖,剩下部分: 1,視差圖轉化為距離圖 2,輸出相應的點雲資料
3,顯示極線校正結果
4,用opencv3表示sgbm的内容
視差圖轉化為距離圖
在實施的過程中發現需要調用重投影矩陣Q,而重投影矩陣Q似乎隻能由校正算法(Bouguet's algorithm)獲得
現在我們來學學Bouguet's algorithm
Bouguet's algorithm立體校正的原則為最大化視場的重疊區域以及最小化重投影步驟
為了最小化重投影畸變,旋轉矩陣分為兩半(左旋轉矩陣和右旋轉矩陣)
下一步,分析矩陣構成,輸出Q矩陣
明日繼續補充