三維重構(7):雙目立體視覺梳理(細節處理,算法理解)

在上一篇博客中,遺留下來以下問題:

  • 視差圖填充,視差圖格式,深度範圍限制(防止出現inf出現,導致視圖顯示不完整),視差圖轉色溫圖(爲了顯示好看)
  • 另外,在保存視差圖的時候發現:視差圖的精度會受格式的影響,有的博主說可以將視差圖同意保存xml類型的數據。
  • 還有SGBM算法是怎麼工作的?如何計算得到視差的,函數可以隨意調用,但是參數設置應該和原理有關,因此還是需要細緻學習一下
  • 重構出的點那麼醜,有一些點看起來不屬於重構物體或者誤差看起來很大,考慮刪除等等……
    本篇博主選擇自己感興趣或認爲有用的進行較爲細緻的梳理

視差圖的填充

真實場景的雙目立體匹配中提到了對視差圖進行填充,我主要也是參考此博客理解視差填充的原理。
其實視差填充不難理解:視差圖在計算出來之後,會出現或大或小的非匹配點,這些非匹配點(塊)會使重構結果不好看。於是我們就希望通過使用周邊“計算相對準確的視差”對非匹配點進行填充
引用真實場景的雙目立體匹配對於視差填充的描述:
① 以視差圖dispImg爲例。計算圖像的積分圖integral,並保存對應積分圖中每個積分值處所有累加的像素點個數n(空洞處的像素點不計入n中,因爲空洞處像素值爲0,對積分值沒有任何作用,反而會平滑圖像)。
  ② 採用多層次均值濾波。首先以一個較大的初始窗口去做均值濾波(積分圖實現均值濾波就不多做介紹了,可以參考我之前的一篇博客),將大區域的空洞賦值。然後下次濾波時,將窗口尺寸縮小爲原來的一半,利用原來的積分圖再次濾波,給較小的空洞賦值(覆蓋原來的值);依次類推,直至窗口大小變爲3x3,此時停止濾波,得到最終結果。
③ 多層次濾波考慮的是對於初始較大的空洞區域,需要參考更多的鄰域值,如果採用較小的濾波窗口,不能夠完全填充,而如果全部採用較大的窗口,則圖像會被嚴重平滑。因此根據空洞的大小,不斷調整濾波窗口。先用大窗口給所有空洞賦值,然後利用逐漸變成小窗口濾波覆蓋原來的值,這樣既能保證空洞能被填充上,也能保證圖像不會被過度平滑。
博主突然想偷懶:記錄一下函數接口好了,depth就是我們希望對之填充的視差,我在自己的代碼中輸入的是經過類型轉換的視差圖,除以16,且轉化爲CV_32F類型的數據,用於下列函數:

void two_Eyes::insertDepth32f(cv::Mat& depth)
{
	const int width = depth.cols;
	const int height = depth.rows;
	float* data = (float*)depth.data;
	//uchar* data = (uchar*)depth.data;

	cv::Mat integralMap = cv::Mat::zeros(height, width, CV_64F);
	cv::Mat ptsMap = cv::Mat::zeros(height, width, CV_32S);
	double* integral = (double*)integralMap.data;
	int* ptsIntegral = (int*)ptsMap.data;
	memset(integral, 0, sizeof(double) * width * height);
	memset(ptsIntegral, 0, sizeof(int) * width * height);
	for (int i = 0; i < height; ++i)
	{
		int id1 = i * width;
		for (int j = 0; j < width; ++j)
		{
			int id2 = id1 + j;
			if (data[id2] > 1e-3)
			{
				integral[id2] = data[id2];
				ptsIntegral[id2] = 1;
			}
		}
	}
	// 積分區間
	for (int i = 0; i < height; ++i)
	{
		int id1 = i * width;
		for (int j = 1; j < width; ++j)
		{
			int id2 = id1 + j;
			integral[id2] += integral[id2 - 1];
			ptsIntegral[id2] += ptsIntegral[id2 - 1];
		}
	}
	for (int i = 1; i < height; ++i)
	{
		int id1 = i * width;
		for (int j = 0; j < width; ++j)
		{
			int id2 = id1 + j;
			integral[id2] += integral[id2 - width];
			ptsIntegral[id2] += ptsIntegral[id2 - width];
		}
	}
	int wnd;
	double dWnd = 2;
	while (dWnd > 1)
	{
		wnd = int(dWnd);
		dWnd /= 2;
		for (int i = 0; i < height; ++i)
		{
			int id1 = i * width;
			for (int j = 0; j < width; ++j)
			{
				int id2 = id1 + j;
				int left = j - wnd - 1;
				int right = j + wnd;
				int top = i - wnd - 1;
				int bot = i + wnd;
				left = max(0, left);
				right = min(right, width - 1);
				top = max(0, top);
				bot = min(bot, height - 1);
				int dx = right - left;
				int dy = (bot - top) * width;
				int idLeftTop = top * width + left;
				int idRightTop = idLeftTop + dx;
				int idLeftBot = idLeftTop + dy;
				int idRightBot = idLeftBot + dx;
				int ptsCnt = ptsIntegral[idRightBot] + ptsIntegral[idLeftTop] - (ptsIntegral[idLeftBot] + ptsIntegral[idRightTop]);
				double sumGray = integral[idRightBot] + integral[idLeftTop] - (integral[idLeftBot] + integral[idRightTop]);
				if (ptsCnt <= 0)
				{
					continue;
				}
				data[id2] = float(sumGray / ptsCnt);
			}
		}
		int s = wnd / 2 * 2 + 1;
		if (s > 201)
		{
			s = 201;
		}
		cv::GaussianBlur(depth, depth, cv::Size(s, s), s, s);
	}
}

視差圖的彩色顯示

彩色顯示視差能夠將視差顯示得更加明顯
disp8是之前記錄下來的標準視差(除以16之後的,函數來自網絡,我將其封裝到了two_eyes類裏)
將彩色視差圖輸出到disp8_color中。該函數其實就是對灰度圖disp8進行一次規定模式的RGB轉化。

void two_Eyes::GenerateFalseMap( cv::Mat &disp8_color)
{
	// color map  
	if (disp8.channels() > 1)
		cvtColor(disp8, disp8, CV_RGB2GRAY);
	float max_val = 255.0f;
	float map[8][4] = { { 0,0,0,114 },{ 0,0,1,185 },{ 1,0,0,114 },{ 1,0,1,174 },
	{ 0,1,0,114 },{ 0,1,1,185 },{ 1,1,0,114 },{ 1,1,1,0 } };
	float sum = 0;
	for (int i = 0; i < 8; i++)
		sum += map[i][3];

	float weights[8]; // relative   weights  
	float cumsum[8];  // cumulative weights  
	cumsum[0] = 0;
	for (int i = 0; i < 7; i++) {
		weights[i] = sum / map[i][3];
		cumsum[i + 1] = cumsum[i] + map[i][3] / sum;
	}

	int height_ = disp8.rows;
	int width_ = disp8.cols;
	// for all pixels do  
	for (int v = 0; v < height_; v++) {
		for (int u = 0; u < width_; u++) {

			// get normalized value  
			float val = std::min(std::max(disp8.data[v*width_ + u] / max_val, 0.0f), 1.0f);
			// find bin  
			int i;
			for (i = 0; i < 7; i++)
				if (val < cumsum[i + 1])
					break;

			// compute red/green/blue values  
			float   w = 1.0 - (val - cumsum[i])*weights[i];
			uchar r = (uchar)((w*map[i][0] + (1.0 - w)*map[i + 1][0]) * 255.0);
			uchar g = (uchar)((w*map[i][1] + (1.0 - w)*map[i + 1][1]) * 255.0);
			uchar b = (uchar)((w*map[i][2] + (1.0 - w)*map[i + 1][2]) * 255.0);
			//rgb內存連續存放  
			disp8_color.data[v*width_ * 3 + 3 * u + 0] = b;
			disp8_color.data[v*width_ * 3 + 3 * u + 1] = g;
			disp8_color.data[v*width_ * 3 + 3 * u + 2] = r;
		}
	}
}

視差圖的保存

兩種觀點:視差圖可以保存爲xml類型的數據,另外一種觀點爲視差圖可以保存爲.png類型的圖片。

使用OpenCV對視差進行保存與顯示

	// write
	Mat src = (Mat_<double>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
	FileStorage fswrite("test.xml", FileStorage::WRITE);// 新建文件,覆蓋掉已有文件
	fswrite << "src1" << src;
	fswrite.release();
	cout << "Write Fineshed!" << endl;

	// read
	FileStorage fsread("test.xml", FileStorage::READ);
	Mat dst;
	fsread["src1"] >> dst; // 讀出src1節點裏的數據
	cout << dst << endl;
	fsread.release();
	cout << "Read Finishied" << endl;

SGBM算法的理解

參考SGBM算法介紹比較詳細的博客
一直待在草稿裏,今天發了吧。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章