天天看點

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

上一節的程式實作了立體視覺實作過程的所有步驟,現在對整體架構進行分析學習

書中架構結構介紹

書: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。

函數

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

Bouguet算法(Bouguet's algorithm)

Bouguet算法就是标定算法,其輸入參數為兩相機之間的R/T矩陣。立體矯正在Bouguet算法的實作原理是:在最大化公共視場區域的同時,最小化重投影畸變。

由于目前我的數學基礎還不紮實,以及目前這部分是為了用;這兩種算法的原理在後續繼續深入研究時将進行補充。

函數

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

本文調用函數彙總

stereoCalibrate

本函數實作立體标定,輸出R、T、E、F矩陣

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

stereoRectifyUncalibrated

本函數實作Hartley's algorithm進行立體校正标定

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

initUndistortRectifyMap

該函數的目的為擷取校正圖像,需求流程如下:

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

上述圖像為立體矯正的過程。立體校正的流程為:原始圖像a經過去畸變得到b,b再經過校正獲得行對準圖像c,c再經過裁剪獲得最終圖像d.

校正計算的過程則為c->a的過程。

在獲得了立體标定項後,通過調用initUndistortRectifyMao能夠預先計算從相機角度的左右查找圖。對于任何圖像-圖像的比對函數,前向比對(由原圖像計算到目标圖像)會由于浮點的影響使資訊丢失,進而使得目标圖像更像一塊奶酪。

在立體校正的過程中,我們将調用這個函數兩次(分别對應于左右相機)

函數原型

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

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

函數上一節講過,雖然用的比較笨,後續将進行繼續修煉。

資料流

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

該部分目的是在梳理重建的過程中回歸到作業中來。

作業

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

需了解及完成步驟

1,如何利用立體校正的方法擷取前向平行圖像?

2,如何用SGBM的方法擷取視差圖?

分步解決

早上鍵盤alt被預設鎖定了,半天沒打開,後面通過連按兩次alt解除了鎖定。廢話不多說,直接進入正文

控制台視窗顯示圖像:

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

首先輸出序列标定圖像得到相應的内外參,然後running stereo calibration獲得内外參後,獲得重投影誤差avg err,最後是校正與視差圖擷取。

調用函數第一項:stereoCalibrate

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

這部分調用的函數使用方法為

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

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

上面程式的核心在于

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,[]);

繼續向上走,可以整理得到這一段程式

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

由程式可知,map11\2和map21\2可通過initUndistortRectifyMap獲得

于是本步驟目的為,如何擷取map矩陣。

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

獲得map矩陣的需求為M,D,R,P一共八個矩陣,為了更好地了解下作業怎麼做,我們輸出一下相關調用參數:

相機1

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

相機2

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

看看我們作業中有什麼内容

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

作業中給出了旋轉向量,而上述程式需要旋轉矩陣,是以第一步為

向量到矩陣的轉換

羅德裡格斯變換

直接調用函數即可進行輸出

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立體校正的原則為最大化視場的重疊區域以及最小化重投影步驟

為了最小化重投影畸變,旋轉矩陣分為兩半(左旋轉矩陣和右旋轉矩陣)

【雙目視覺探索路6】整體代碼結構分析與作業完成(未完待續)書中架構結構介紹StereoCalib開放接口StereoCalib内部分析

下一步,分析矩陣構成,輸出Q矩陣

明日繼續補充

繼續閱讀