/* * SemiGlobalMatcher.cpp * * Copyright (c) 2014-2015 SEACAVE * * Author(s): * * cDc * * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * * Additional Terms: * * You are required to preserve legal notices and author attributions in * that material or in the Appropriate Legal Notices displayed by works * containing it. */ #include "Common.h" #include "SemiGlobalMatcher.h" #include "Scene.h" using namespace MVS; using namespace STEREO; // D E F I N E S /////////////////////////////////////////////////// // uncomment to enable OpenCV filter demo //#define _USE_FILTER_DEMO // S T R U C T S /////////////////////////////////////////////////// #ifdef _USE_FILTER_DEMO #include "opencv2/ximgproc/disparity_filter.hpp" const String keys = "{help h usage ? | | print this message }" "{GT |None | optional ground-truth disparity (MPI-Sintel or Middlebury format) }" "{dst_path |None | optional path to save the resulting filtered disparity map }" "{dst_raw_path |None | optional path to save raw disparity map before filtering }" "{algorithm |bm | stereo matching method (bm or sgbm) }" "{filter |wls_conf| used post-filtering (wls_conf or wls_no_conf) }" "{no-display | | don't display results }" "{no-downscale | | force stereo matching on full-sized views to improve quality }" "{dst_conf_path |None | optional path to save the confidence map used in filtering }" "{vis_mult |1.0 | coefficient used to scale disparity map visualizations }" "{max_disparity |160 | parameter of stereo matching }" "{window_size |-1 | parameter of stereo matching }" "{wls_lambda |8000.0 | parameter of post-filtering }" "{wls_sigma |1.5 | parameter of post-filtering }" ; cv::Rect computeROI(cv::Size2i src_sz, cv::Ptr matcher_instance) { int min_disparity = matcher_instance->getMinDisparity(); int num_disparities = matcher_instance->getNumDisparities(); int block_size = matcher_instance->getBlockSize(); int bs2 = block_size/2; int minD = min_disparity, maxD = min_disparity + num_disparities - 1; int xmin = maxD + bs2; int xmax = src_sz.width + minD - bs2; int ymin = bs2; int ymax = src_sz.height - bs2; return cv::Rect(xmin, ymin, xmax - xmin, ymax - ymin); } int disparityFiltering(cv::Mat left, cv::Mat right, int argc, const LPCSTR* argv) { cv::CommandLineParser parser(argc,argv,keys); parser.about("Disparity Filtering Demo"); if(parser.has("help")) { parser.printMessage(); return 0; } String GT_path = parser.get("GT"); String dst_path = parser.get("dst_path"); String dst_raw_path = parser.get("dst_raw_path"); String dst_conf_path = parser.get("dst_conf_path"); String algo = parser.get("algorithm"); String filter = parser.get("filter"); bool no_display = parser.has("no-display"); bool no_downscale = parser.has("no-downscale"); int max_disp = parser.get("max_disparity"); double lambda = parser.get("wls_lambda"); double sigma = parser.get("wls_sigma"); double vis_mult = parser.get("vis_mult"); int wsize; if(parser.get("window_size")>=0) //user provided window_size value wsize = parser.get("window_size"); else { if(algo=="sgbm") wsize = 3; //default window size for SGBM else if(!no_downscale && algo=="bm" && filter=="wls_conf") wsize = 7; //default window size for BM on downscaled views (downscaling is performed only for wls_conf) else wsize = 15; //default window size for BM on full-sized views } if(!parser.check()) { parser.printErrors(); return -1; } cv::Mat GT_disp; bool noGT(cv::ximgproc::readGT(GT_path,GT_disp)!=0); cv::Mat left_for_matcher, right_for_matcher; cv::Mat left_disp,right_disp; cv::Mat filtered_disp; cv::Mat conf_map(left.rows,left.cols,CV_8U); conf_map = cv::Scalar(255); cv::Rect ROI; cv::Ptr wls_filter; double matching_time, filtering_time; if(max_disp<=0 || max_disp%16!=0) { std::cout<<"Incorrect max_disparity value: it should be positive and divisible by 16"; return -1; } if(wsize<=0 || wsize%2!=1) { std::cout<<"Incorrect window_size value: it should be positive and odd"; return -1; } if(filter=="wls_conf") // filtering with confidence (significantly better quality than wls_no_conf) { if(!no_downscale) { // downscale the views to speed-up the matching stage, as we will need to compute both left // and right disparity maps for confidence map computation //! [downscale] max_disp/=2; if(max_disp%16!=0) max_disp += 16-(max_disp%16); resize(left ,left_for_matcher ,cv::Size(),0.5,0.5, cv::INTER_LINEAR_EXACT); resize(right,right_for_matcher,cv::Size(),0.5,0.5, cv::INTER_LINEAR_EXACT); //! [downscale] } else { left_for_matcher = left.clone(); right_for_matcher = right.clone(); } if(algo=="bm") { //! [matching] cv::Ptr left_matcher = cv::StereoBM::create(max_disp,wsize); wls_filter = cv::ximgproc::createDisparityWLSFilter(left_matcher); cv::Ptr right_matcher = cv::ximgproc::createRightMatcher(left_matcher); cvtColor(left_for_matcher, left_for_matcher, cv::COLOR_BGR2GRAY); cvtColor(right_for_matcher, right_for_matcher, cv::COLOR_BGR2GRAY); matching_time = (double)cv::getTickCount(); left_matcher-> compute(left_for_matcher, right_for_matcher,left_disp); right_matcher->compute(right_for_matcher,left_for_matcher, right_disp); matching_time = ((double)cv::getTickCount() - matching_time)/cv::getTickFrequency(); //! [matching] } else if(algo=="sgbm") { cv::Ptr left_matcher = cv::StereoSGBM::create(-max_disp/2+1,max_disp,wsize); left_matcher->setP1(24*wsize*wsize); left_matcher->setP2(96*wsize*wsize); left_matcher->setPreFilterCap(63); left_matcher->setMode(cv::StereoSGBM::MODE_SGBM_3WAY); wls_filter = cv::ximgproc::createDisparityWLSFilter(left_matcher); cv::Ptr right_matcher = cv::ximgproc::createRightMatcher(left_matcher); matching_time = (double)cv::getTickCount(); left_matcher-> compute(left_for_matcher, right_for_matcher,left_disp); right_matcher->compute(right_for_matcher,left_for_matcher, right_disp); matching_time = ((double)cv::getTickCount() - matching_time)/cv::getTickFrequency(); } else { std::cout<<"Unsupported algorithm"; return -1; } //! [filtering] wls_filter->setLambda(lambda); wls_filter->setSigmaColor(sigma); filtering_time = (double)cv::getTickCount(); wls_filter->filter(left_disp,left,filtered_disp,right_disp); filtering_time = ((double)cv::getTickCount() - filtering_time)/cv::getTickFrequency(); //! [filtering] conf_map = wls_filter->getConfidenceMap(); // Get the ROI that was used in the last filter call: ROI = wls_filter->getROI(); if(!no_downscale) { // upscale raw disparity and ROI back for a proper comparison: resize(left_disp,left_disp,cv::Size(),2.0,2.0,cv::INTER_LINEAR_EXACT); left_disp = left_disp*2.0; ROI = cv::Rect(ROI.x*2,ROI.y*2,ROI.width*2,ROI.height*2); } } else if(filter=="wls_no_conf") { /* There is no convenience function for the case of filtering with no confidence, so we will need to set the ROI and matcher parameters manually */ left_for_matcher = left.clone(); right_for_matcher = right.clone(); if(algo=="bm") { cv::Ptr matcher = cv::StereoBM::create(max_disp,wsize); matcher->setTextureThreshold(0); matcher->setUniquenessRatio(0); cvtColor(left_for_matcher, left_for_matcher, cv::COLOR_BGR2GRAY); cvtColor(right_for_matcher, right_for_matcher, cv::COLOR_BGR2GRAY); ROI = computeROI(left_for_matcher.size(),matcher); wls_filter = cv::ximgproc::createDisparityWLSFilterGeneric(false); wls_filter->setDepthDiscontinuityRadius((int)ceil(0.33*wsize)); matching_time = (double)cv::getTickCount(); matcher->compute(left_for_matcher,right_for_matcher,left_disp); matching_time = ((double)cv::getTickCount() - matching_time)/cv::getTickFrequency(); } else if(algo=="sgbm") { cv::Ptr matcher = cv::StereoSGBM::create(0,max_disp,wsize); matcher->setUniquenessRatio(0); matcher->setDisp12MaxDiff(1000000); matcher->setSpeckleWindowSize(0); matcher->setP1(24*wsize*wsize); matcher->setP2(96*wsize*wsize); matcher->setMode(cv::StereoSGBM::MODE_SGBM_3WAY); ROI = computeROI(left_for_matcher.size(),matcher); wls_filter = cv::ximgproc::createDisparityWLSFilterGeneric(false); wls_filter->setDepthDiscontinuityRadius((int)ceil(0.5*wsize)); matching_time = (double)cv::getTickCount(); matcher->compute(left_for_matcher,right_for_matcher,left_disp); matching_time = ((double)cv::getTickCount() - matching_time)/cv::getTickFrequency(); } else { std::cout<<"Unsupported algorithm"; return -1; } wls_filter->setLambda(lambda); wls_filter->setSigmaColor(sigma); filtering_time = (double)cv::getTickCount(); wls_filter->filter(left_disp,left,filtered_disp,cv::Mat(),ROI); filtering_time = ((double)cv::getTickCount() - filtering_time)/cv::getTickFrequency(); } else { std::cout<<"Unsupported filter"; return -1; } //collect and print all the stats: std::cout.precision(2); std::cout<<"Matching time: "< ftzero ? ftzero * 2 : x - OFS + ftzero); uint8_t val0 = tab[0 + OFS]; #ifdef _USE_SSE volatile bool useSIMD = cv::checkHardwareSupport(CV_CPU_SSE2); #endif ASSERT(src.type() == CV_8U); const cv::Size size(src.size()); dst.create(size, src.type()); int y; for (y = 0; y < size.height - 1; y += 2) { const uint8_t* srow1 = src.ptr(y); const uint8_t* srow0 = y > 0 ? srow1 - src.step : size.height > 1 ? srow1 + src.step : srow1; const uint8_t* srow2 = y < size.height - 1 ? srow1 + src.step : size.height > 1 ? srow1 - src.step : srow1; const uint8_t* srow3 = y < size.height - 2 ? srow1 + src.step * 2 : srow1; uint8_t* dptr0 = dst.ptr(y); uint8_t* dptr1 = dptr0 + dst.step; dptr0[0] = dptr0[size.width - 1] = dptr1[0] = dptr1[size.width - 1] = val0; int x = 1; #ifdef _USE_SSE if (useSIMD) { __m128i z = _mm_setzero_si128(), ftz = _mm_set1_epi16((short)ftzero); __m128i ftz2 = _mm_set1_epi8(cv::saturate_cast(ftzero * 2)); for (; x <= size.width - 9; x += 8) { __m128i c0 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow0 + x - 1)), z); __m128i c1 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow1 + x - 1)), z); __m128i d0 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow0 + x + 1)), z); __m128i d1 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow1 + x + 1)), z); d0 = _mm_sub_epi16(d0, c0); d1 = _mm_sub_epi16(d1, c1); __m128i c2 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow2 + x - 1)), z); __m128i c3 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow3 + x - 1)), z); __m128i d2 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow2 + x + 1)), z); __m128i d3 = _mm_unpacklo_epi8(_mm_loadl_epi64((__m128i*)(srow3 + x + 1)), z); d2 = _mm_sub_epi16(d2, c2); d3 = _mm_sub_epi16(d3, c3); __m128i v0 = _mm_add_epi16(d0, _mm_add_epi16(d2, _mm_add_epi16(d1, d1))); __m128i v1 = _mm_add_epi16(d1, _mm_add_epi16(d3, _mm_add_epi16(d2, d2))); v0 = _mm_packus_epi16(_mm_add_epi16(v0, ftz), _mm_add_epi16(v1, ftz)); v0 = _mm_min_epu8(v0, ftz2); _mm_storel_epi64((__m128i*)(dptr0 + x), v0); _mm_storel_epi64((__m128i*)(dptr1 + x), _mm_unpackhi_epi64(v0, v0)); } } #endif for (; x < size.width - 1; x++) { int d0 = srow0[x + 1] - srow0[x - 1], d1 = srow1[x + 1] - srow1[x - 1], d2 = srow2[x + 1] - srow2[x - 1], d3 = srow3[x + 1] - srow3[x - 1]; int v0 = tab[d0 + d1 * 2 + d2 + OFS]; int v1 = tab[d1 + d2 * 2 + d3 + OFS]; dptr0[x] = (uint8_t)v0; dptr1[x] = (uint8_t)v1; } } for (; y < size.height; y++) { uint8_t* dptr = dst.ptr(y); for (int x = 0; x < size.width; x++) dptr[x] = val0; } } /*----------------------------------------------------------------*/ // S T R U C T S /////////////////////////////////////////////////// enum EVENT_TYPE { EVT_JOB = 0, EVT_CLOSE, }; class EVTClose : public Event { public: EVTClose() : Event(EVT_CLOSE) {} }; class EVTPixelProcess : public Event { public: typedef std::function FncPixel; const cv::Size size; volatile Thread::safe_t& idxPixel; const FncPixel fncPixel; bool Run(void*) override { const int numPixels(size.area()); int idx; while ((idx=(int)Thread::safeInc(idxPixel)) < numPixels) fncPixel(idx, idx/size.width, idx%size.width); return true; } EVTPixelProcess(cv::Size s, volatile Thread::safe_t& idx, FncPixel f) : Event(EVT_JOB), size(s), idxPixel(idx), fncPixel(f) {} }; class EVTPixelAccumInc : public Event { public: typedef std::function FncPixel; const int numPixels; volatile Thread::safe_t& idxPixel; const FncPixel fncPixel; bool Run(void*) override { int idx; while ((idx=(int)Thread::safeInc(idxPixel)) < numPixels) fncPixel(idx); return true; } EVTPixelAccumInc(int s, volatile Thread::safe_t& idx, FncPixel f) : Event(EVT_JOB), numPixels(s), idxPixel(idx), fncPixel(f) {} }; class EVTPixelAccumDec : public Event { public: typedef std::function FncPixel; volatile Thread::safe_t& idxPixel; const FncPixel fncPixel; bool Run(void*) override { int idx; while ((idx=(int)Thread::safeDec(idxPixel)) >= 0) fncPixel(idx); return true; } EVTPixelAccumDec(volatile Thread::safe_t& idx, FncPixel f) : Event(EVT_JOB), idxPixel(idx), fncPixel(f) {} }; /*----------------------------------------------------------------*/ // S T R U C T S /////////////////////////////////////////////////// // - P1 and P2s are algorithm constants very similar to those from the original SGM algorithm; // they are set to defaults according to the patch size // - alpha & beta form the final P2 as P2*(1+alpha*e^(-DI^2/(2*beta^2))) // where DI is the difference in image intensity I(x)-I(x_prev) in [0,255] range // - subpixelSteps represents how much sub-pixel accuracy is searched/stored; // if 1 no sub-pixel precision, if for example 4 a 0.25 sub-pixel accuracy is stored; // the stored value is quantized and represented as integer: val=(float)valStored/subpixelSteps SemiGlobalMatcher::SemiGlobalMatcher(SgmSubpixelMode _subpixelMode, Disparity _subpixelSteps, AccumCost _P1, AccumCost P2, float P2alpha, float P2beta) : subpixelMode(_subpixelMode), subpixelSteps(_subpixelSteps), P1(_P1), P2s(GenerateP2s(P2, P2alpha, P2beta)) { } SemiGlobalMatcher::~SemiGlobalMatcher() { } CLISTDEF0IDX(SemiGlobalMatcher::AccumCost,int) SemiGlobalMatcher::GenerateP2s(AccumCost P2, float P2alpha, float P2beta) { CLISTDEF0IDX(AccumCost,int) P2s(256); FOREACH(i, P2s) P2s[i] = (AccumCost)ROUND2INT(P2*(1.f+P2alpha*EXP(-SQUARE((float)i)/(2.f*SQUARE(P2beta))))); return P2s; } // Compute SGM stereo for this image and each of the neighbor views: // - minResolution is the resolution of the top of the pyramid for tSGM; // can be 0 to force the standard SGM algorithm void SemiGlobalMatcher::Match(const Scene& scene, IIndex idxImage, IIndex numNeighbors, unsigned minResolution) { const Image& leftImage = scene.images[idxImage]; const float fMinScore(MAXF(leftImage.neighbors.front().score*OPTDENSE::fViewMinScoreRatio, OPTDENSE::fViewMinScore)); FOREACH(idxNeighbor, leftImage.neighbors) { const ViewScore& neighbor = leftImage.neighbors[idxNeighbor]; // exclude neighbors that over the limit or too small score ASSERT(scene.images[neighbor.ID].IsValid()); if ((numNeighbors && idxNeighbor >= numNeighbors) || (neighbor.score < fMinScore)) break; // check if the disparity-map was already estimated for the same image pairs const Image& rightImage = scene.images[neighbor.ID]; const String pairName(MAKE_PATH(String::FormatString("%04u_%04u", leftImage.ID, rightImage.ID))); if (File::isPresent((pairName+".dimap").c_str()) || File::isPresent(MAKE_PATH(String::FormatString("%04u_%04u.dimap", rightImage.ID, leftImage.ID)))) continue; TD_TIMER_STARTD(); IndexArr points; Matrix3x3 H; Matrix4x4 Q; ViewData leftData, rightData; MaskMap leftMaskMap, rightMaskMap; { // fetch pairs of corresponding image points //TODO: use precomputed points from SelectViews() Point3fArr leftPoints, rightPoints; FOREACH(idxPoint, scene.pointcloud.points) { const PointCloud::ViewArr& views = scene.pointcloud.pointViews[idxPoint]; if (views.FindFirst(idxImage) != PointCloud::ViewArr::NO_INDEX) { points.push_back((uint32_t)idxPoint); if (views.FindFirst(neighbor.ID) != PointCloud::ViewArr::NO_INDEX) { const Point3 X(scene.pointcloud.points[idxPoint]); leftPoints.emplace_back(leftImage.camera.TransformPointW2I3(X)); rightPoints.emplace_back(rightImage.camera.TransformPointW2I3(X)); } } } // stereo-rectify image pair if (!Image::StereoRectifyImages(leftImage, rightImage, leftPoints, rightPoints, leftData.imageColor, rightData.imageColor, leftMaskMap, rightMaskMap, H, Q)) continue; ASSERT(leftData.imageColor.size() == rightData.imageColor.size()); } #ifdef _USE_FILTER_DEMO // run openCV implementation const LPCSTR argv[] = {"disparityFiltering", "-algorithm=sgbm", "-max_disparity=160", "-no-downscale"}; disparityFiltering(leftData.imageColor, rightData.imageColor, (int)SizeOfArray(argv), argv); #endif // color to gray conversion #if SGM_SIMILARITY == SGM_SIMILARITY_CENSUS leftData.imageColor.toGray(leftData.imageGray, cv::COLOR_BGR2GRAY, false, true); rightData.imageColor.toGray(rightData.imageGray, cv::COLOR_BGR2GRAY, false, true); #else leftData.imageColor.toGray(leftData.imageGray, cv::COLOR_BGR2GRAY, true, true); rightData.imageColor.toGray(rightData.imageGray, cv::COLOR_BGR2GRAY, true, true); #endif // compute scale used for the disparity estimation REAL scale(1); if (minResolution) { unsigned resolutionLevel(8); Image8U::computeMaxResolution(leftData.imageGray.width(), leftData.imageGray.height(), resolutionLevel, minResolution); scale = REAL(1)/MAXF(2,POWI(2,resolutionLevel)); } const bool tSGM(!ISEQUAL(scale, REAL(1))); DisparityMap leftDisparityMap, rightDisparityMap; AccumCostMap costMap; do { #if 0 // export the intermediate disparity-maps if (!leftDisparityMap.empty()) { const REAL _scale(scale*0.5); Matrix3x3 _H(H); Matrix4x4 _Q(Q); Image::ScaleStereoRectification(_H, _Q, _scale); ExportDisparityDataRawFull(String::FormatString("%s_%d.dimap", pairName.c_str(), LOG2I(ROUND2INT(REAL(1)/_scale))), leftDisparityMap, costMap, Image8U::computeResize(leftImage.GetSize(), _scale), H, _Q, 1); } #endif // initialize const ViewData leftDataLevel(leftData.GetImage(scale)); const ViewData rightDataLevel(rightData.GetImage(scale)); const cv::Size size(leftDataLevel.imageGray.size()); const cv::Size sizeValid(size.width-2*halfWindowSizeX, size.height-2*halfWindowSizeY); const bool bFirstLevel(leftDisparityMap.empty()); if (bFirstLevel) { // initialize the disparity-map with a rough estimate based on the sparse point-cloud //TODO: remove DepthData::ViewData dependency Image leftImageLevel(leftImage.GetImage(scene.platforms, scale*0.5, false)); DepthData::ViewData image; image.pImageData = &leftImageLevel; // used only for avgDepth image.image.create(leftImageLevel.GetSize()); image.camera = leftImageLevel.camera; DepthMap depthMap; Depth dMin, dMax; TriangulatePoints2DepthMap(image, scene.pointcloud, points, depthMap, dMin, dMax, true); points.Release(); Matrix3x3 H2(H); Matrix4x4 Q2(Q); Image::ScaleStereoRectification(H2, Q2, scale*0.5); const cv::Size sizeHalf(Image8U::computeResize(size, 0.5)); const cv::Size sizeValidHalf(sizeHalf.width-2*halfWindowSizeX, sizeHalf.height-2*halfWindowSizeY); leftDisparityMap.create(sizeValidHalf); Depth2DisparityMap(depthMap, H2.inv(), Q2.inv(), 1, leftDisparityMap); // resize masks cv::resize(leftMaskMap, leftMaskMap, size, 0, 0, cv::INTER_NEAREST); cv::resize(rightMaskMap, rightMaskMap, size, 0, 0, cv::INTER_NEAREST); const cv::Rect ROI(halfWindowSizeX,halfWindowSizeY, sizeValid.width,sizeValid.height); leftMaskMap(ROI).copyTo(leftMaskMap); rightMaskMap(ROI).copyTo(rightMaskMap); } else { // upscale masks UpscaleMask(leftMaskMap, sizeValid); UpscaleMask(rightMaskMap, sizeValid); } // estimate right-left disparity-map Index numCosts; if (tSGM) { // upscale the disparity-map from the previous level FlipDirection(leftDisparityMap, rightDisparityMap); numCosts = Disparity2RangeMap(rightDisparityMap, rightMaskMap, bFirstLevel?11:5, bFirstLevel?33:7); } else { // extract global min and max disparities Range range{std::numeric_limits::max(), std::numeric_limits::min()}; ASSERT(leftDisparityMap.isContinuous()); const Disparity* pd = leftDisparityMap.ptr(); const Disparity* const pde = pd+leftDisparityMap.area(); do { const Disparity d(*pd); if (range.minDisp > d) range.minDisp = d; if (range.maxDisp < d) range.maxDisp = d; } while (++pd < pde); // set disparity search range to the global min/max range const Disparity numDisp(range.numDisp()+16); const Disparity disp(range.minDisp+range.maxDisp); range.minDisp = disp-numDisp; range.maxDisp = disp+numDisp; maxNumDisp = range.numDisp(); numCosts = 0; imagePixels.resize(sizeValid.area()); for (PixelData& pixel: imagePixels) { pixel.range = range; pixel.idx = numCosts; numCosts += maxNumDisp; } } imageCosts.resize(numCosts); imageAccumCosts.resize(numCosts); Match(rightDataLevel, leftDataLevel, rightDisparityMap, costMap); // estimate left-right disparity-map if (tSGM) { numCosts = Disparity2RangeMap(leftDisparityMap, leftMaskMap, bFirstLevel?11:5, bFirstLevel?33:7); imageCosts.resize(numCosts); imageAccumCosts.resize(numCosts); } else { for (PixelData& pixel: imagePixels) { const Disparity maxDisp(-pixel.range.minDisp); pixel.range.minDisp = -pixel.range.maxDisp; pixel.range.maxDisp = maxDisp; } } Match(leftDataLevel, rightDataLevel, leftDisparityMap, costMap); // check disparity-map cross-consistency #if 0 if (ISEQUAL(scale, REAL(1))) { cv::Ptr filter = cv::ximgproc::createDisparityWLSFilterGeneric(true); const cv::Rect rcValid(halfWindowSizeX,halfWindowSizeY, sizeValid.width,sizeValid.height); Image32F leftDisparityMap32F, rightDisparityMap32F, filtered32F; leftDisparityMap.convertTo(leftDisparityMap32F, CV_32F, 16); rightDisparityMap.convertTo(rightDisparityMap32F, CV_32F, 16); filter->filter(leftDisparityMap32F, leftData.imageColor(rcValid), filtered32F, rightDisparityMap32F); filtered32F.convertTo(leftDisparityMap, CV_16S, 1.0/16, 0.5); } else #endif if (bFirstLevel) { // perform a rigorous filtering of the estimated disparity maps in order to // estimate the common region or interest and set the validity masks ConsistencyCrossCheck(leftDisparityMap, rightDisparityMap); ConsistencyCrossCheck(rightDisparityMap, leftDisparityMap); cv::filterSpeckles(leftDisparityMap, NO_DISP, OPTDENSE::nSpeckleSize, 5); cv::filterSpeckles(rightDisparityMap, NO_DISP, OPTDENSE::nSpeckleSize, 5); ExtractMask(leftDisparityMap, leftMaskMap); ExtractMask(rightDisparityMap, rightMaskMap); } else { // simply run a left-right consistency check ConsistencyCrossCheck(leftDisparityMap, rightDisparityMap); } } while ((scale*=2) < REAL(1)+ZEROTOLERANCE()); #if 0 // remove speckles if (OPTDENSE::nSpeckleSize > 0) cv::filterSpeckles(leftDisparityMap, NO_DISP, OPTDENSE::nSpeckleSize, 5); #endif // sub-pixel disparity-map estimation RefineDisparityMap(leftDisparityMap); #if 1 // export disparity-map for the left image DEBUG_EXTRA("Disparity-map for images %3u and %3u: %dx%d (%s)", leftImage.ID, rightImage.ID, leftImage.width, leftImage.height, TD_TIMER_GET_FMT().c_str()); ExportPointCloud(pairName+".ply", leftImage, leftDisparityMap, Q, subpixelSteps); ExportDisparityMap(pairName+".png", leftDisparityMap); ExportDisparityDataRawFull(pairName+".dimap", leftDisparityMap, costMap, leftImage.GetSize(), H, Q, subpixelSteps); #else // convert disparity-map to final depth-map for the left image DepthMap depthMap(leftImage.image.size()); ConfidenceMap confMap; Disparity2DepthMap(leftDisparityMap, costMap, H, Q, subpixelSteps, depthMap, confMap); DEBUG_EXTRA("Depth-map for images %3u and %3u: %dx%d (%s)", leftImage.ID, rightImage.ID, depthMap.width(), depthMap.height(), TD_TIMER_GET_FMT().c_str()); ExportDepthMap(pairName+".png", depthMap); MVS::ExportPointCloud(pairName+".ply", leftImage, depthMap, NormalMap()); ExportDepthDataRaw(pairName+".dmap", leftImage.name, IIndexArr{leftImage.ID,rightImage.ID}, leftImage.GetSize(), leftImage.camera.K, leftImage.camera.R, leftImage.camera.C, 0, FLT_MAX, depthMap, NormalMap(), confMap); #endif } } void SemiGlobalMatcher::Fuse(const Scene& scene, IIndex idxImage, IIndex numNeighbors, unsigned minViews, DepthMap& depthMap, ConfidenceMap& confMap) { TD_TIMER_STARTD(); const Image& leftImage = scene.images[idxImage]; const float fMinScore(MAXF(leftImage.neighbors.front().score*OPTDENSE::fViewMinScoreRatio, OPTDENSE::fViewMinScore)); struct PairData { DepthMap depthMap; DepthRangeMap depthRangeMap; ConfidenceMap confMap; PairData(const cv::Size& size) : depthMap(size) {} }; // load and put in same space all disparity-maps containing this view CLISTDEFIDX(PairData,IIndex) pairs; pairs.reserve(numNeighbors); FOREACH(idxNeighbor, leftImage.neighbors) { const ViewScore& neighbor = leftImage.neighbors[idxNeighbor]; // exclude neighbors that over the limit or too small score ASSERT(scene.images[neighbor.ID].IsValid()); if ((numNeighbors && idxNeighbor >= numNeighbors) || (neighbor.score < fMinScore)) break; // check if the disparity-map was estimated for this images pair const Image& rightImage = scene.images[neighbor.ID]; Disparity subpixelSteps; cv::Size imageSize; Matrix3x3 H; Matrix4x4 Q; DisparityMap disparityMap; AccumCostMap costMap; if (!ImportDisparityDataRawFull(MAKE_PATH(String::FormatString("%04u_%04u.dimap", leftImage.ID, rightImage.ID)), disparityMap, costMap, imageSize, H, Q, subpixelSteps)) { if (!ImportDisparityDataRawFull(MAKE_PATH(String::FormatString("%04u_%04u.dimap", rightImage.ID, leftImage.ID)), disparityMap, costMap, imageSize, H, Q, subpixelSteps)) { DEBUG("warning: no disparity-data file found for image pair (%u,%u)", leftImage.ID, rightImage.ID); continue; } // adapt Q to project the 3D point from right into the left-image RMatrix poseR; CMatrix poseC; ComputeRelativePose(rightImage.camera.R, rightImage.camera.C, leftImage.camera.R, leftImage.camera.C, poseR, poseC); Matrix4x4 P(Matrix4x4::IDENTITY); AssembleProjectionMatrix(leftImage.camera.K, poseR, poseC, reinterpret_cast(P)); Matrix4x4 invK(Matrix4x4::IDENTITY); cv::Mat(rightImage.camera.GetInvK()).copyTo(cv::Mat(4,4,cv::DataType::type,invK.val)(cv::Rect(0,0,3,3))); Q = P*invK*Q; } // convert disparity-map to final depth-map for the left image PairData& pair = pairs.emplace_back(leftImage.image.size()); if (!ProjectDisparity2DepthMap(disparityMap, costMap, Q, subpixelSteps, pair.depthMap, pair.depthRangeMap, pair.confMap)) { pairs.RemoveLast(); continue; } #if TD_VERBOSE != TD_VERBOSE_OFF // save pair depth-map if (VERBOSITY_LEVEL > 3) { const String pairName(MAKE_PATH(String::FormatString("depth%04u_%04u", leftImage.ID, rightImage.ID))); ExportDepthMap(pairName+".png", pair.depthMap); MVS::ExportPointCloud(pairName+".ply", leftImage, pair.depthMap, NormalMap()); } #endif } // fuse available depth-maps such that for each pixel set its depth as the average of the largest cluster of agreeing depths; // pixel depths values agree if their trust regions overlap depthMap.create(leftImage.image.size()); confMap.create(depthMap.size()); for (int r=0; r range.y) cluster.range.y = range.y; ++numClusters; } if (numClusters == 0) clusters.emplace_back(Cluster{IIndexArr{p}, range}); } if (clusters.empty()) { depthMap(r,c) = Depth(0); confMap(r,c) = float(0); continue; } const Cluster& cluster = clusters.GetMax([](const Cluster& i, const Cluster& j) { return i.views.size() < j.views.size(); }); if (cluster.views.size() < minViews) { depthMap(r,c) = Depth(0); confMap(r,c) = float(0); continue; } Depth& depth = depthMap(r,c); depth = 0; float& conf = confMap(r,c); conf = 0; unsigned numDepths(0); for (IIndex p: cluster.views) { const PairData& pair = pairs[p]; depth += pair.depthMap(r,c); conf += pair.confMap(r,c); ++numDepths; } depth /= numDepths; conf /= numDepths; } } DEBUG_EXTRA("Depth-map for image %3u fused: %dx%d (%s)", leftImage.ID, depthMap.width(), depthMap.height(), TD_TIMER_GET_FMT().c_str()); #if TD_VERBOSE != TD_VERBOSE_OFF // save depth-map if (VERBOSITY_LEVEL > 2) { const String fileName(MAKE_PATH(String::FormatString("depth%04u", leftImage.ID))); ExportDepthMap(fileName+".png", depthMap); MVS::ExportPointCloud(fileName+".ply", leftImage, depthMap, NormalMap()); } #endif } // Compute SGM stereo on the images void SemiGlobalMatcher::Match(const ViewData& leftImage, const ViewData& rightImage, DisparityMap& disparityMap, AccumCostMap& costMap) { const cv::Size size(leftImage.imageGray.size()); const cv::Size sizeValid(size.width-2*halfWindowSizeX, size.height-2*halfWindowSizeY); ASSERT(leftImage.imageColor.size() == size); #if 0 && !defined(_RELEASE) // display search info (average disparity and range) DisplayState(sizeValid); #endif // compute costs { ASSERT(!imageCosts.empty()); const float eps(1e-3f); // used suppress the effect of noise in untextured regions auto pixel = [&](int idx, int r, int c) { // ignore pixel if not valid const PixelData& pixel = imagePixels[idx]; if (!pixel.range.isValid()) return; #if SGM_SIMILARITY == SGM_SIMILARITY_CENSUS struct Compute { static inline int HammingDistance(uint64_t c1, uint64_t c2) { return PopCnt(c1 ^ c2); } }; // compute pixel cost Cost* costs = imageCosts.data()+pixel.idx; const Census lc(leftImage.imageCensus(r,c)); for (int d=pixel.range.minDisp; d(windowSizeX,windowSizeY)))); return float(SQUARE(x) + SQUARE(y)) * sigmaSpatial; } }; const ImageRef u(c+halfWindowSizeX,r+halfWindowSizeY); // initialize pixel patch weights WeightedPatch w; w.normSq0 = 0; w.sumWeights = 0; int n = 0; const Pixel8U& colCenter = leftImage.imageColor(u); for (int i=-halfWindowSizeY; i<=halfWindowSizeY; ++i) { for (int j=-halfWindowSizeX; j<=halfWindowSizeX; ++j) { const ImageRef x(u.x+j,u.y+i); WeightedPatch::Pixel& pw = w.weights[n++]; w.normSq0 += (pw.tempWeight = leftImage.imageGray(x)) * (pw.weight = EXP(Compute::WeightColor(leftImage.imageColor, colCenter, x)+Compute::WeightSpatial(j,i))); w.sumWeights += pw.weight; } } ASSERT(n == numTexels); const float tm(w.normSq0/w.sumWeights); w.normSq0 = 0; n = 0; do { WeightedPatch::Pixel& pw = w.weights[n]; const float t(pw.tempWeight - tm); w.normSq0 += (pw.tempWeight = pw.weight * t) * t; } while (++n < numTexels); // compute pixel cost Cost* costs = imageCosts.data()+pixel.idx; for (int d=pixel.range.minDisp; d v) m = v; } }; ASSERT(Ls.R.isValid()); #if SGM_SIMILARITY == SGM_SIMILARITY_CENSUS const AccumCost P2(P2s[DI]); #else const AccumCost P2(P2s[ABS(ROUND2INT(255.f*DI))]); #endif const Disparity minDisp(MAXF(Lp.R.minDisp, Ls.R.minDisp)); const Disparity maxDisp(MINF(Lp.R.maxDisp, Ls.R.maxDisp)); if (minDisp >= maxDisp) { // the disparity ranges for the two pixels do not intersect; // fill all accumulated costs with L(d)=C(d)+P2 const Disparity numDisp(Ls.R.numDisp()); for (int idxDisp=0; idxDisp1 AccumCost minLp(std::numeric_limits::max()); for (const AccumCost *L=Lp.L+(minDisp-Lp.R.minDisp), *endL=L+(maxDisp-minDisp); L::max(); for (Disparity dp=minDisp; dp= 0); }; volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumInc(sizeValid.width, idxPixel, pixels)); WaitThreadWorkers(threads.size()); } { // height-left auto pixels = [&](int y) { ImageRef u(sizeValid.width-1,y); ACCUM_PIXELS(--u.x >= 0); }; volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumInc(sizeValid.height, idxPixel, pixels)); WaitThreadWorkers(threads.size()); } if (numDirs == 4) { { // width-right-down auto pixels = [&](int x) { ImageRef u(x,0); ACCUM_PIXELS(++u.x < sizeValid.width && ++u.y < sizeValid.height); }; volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumInc(sizeValid.width, idxPixel, pixels)); } { // height-right-down auto pixels = [&](int y) { ImageRef u(0,y); ACCUM_PIXELS(++u.x < sizeValid.width && ++u.y < sizeValid.height); }; volatile Thread::safe_t idxPixel(0); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumInc(sizeValid.height, idxPixel, pixels)); } WaitThreadWorkers(threads.size()*2); { // width-left-down auto pixels = [&](int x) { ImageRef u(x,0); ACCUM_PIXELS(--u.x >= 0 && ++u.y < sizeValid.height); }; volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumInc(sizeValid.width-1, idxPixel, pixels)); } { // height-left-down auto pixels = [&](int y) { ImageRef u(sizeValid.width-1,y); ACCUM_PIXELS(--u.x >= 0 && ++u.y < sizeValid.height); }; volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumInc(sizeValid.height, idxPixel, pixels)); } WaitThreadWorkers(threads.size()*2); { // width-right-up auto pixels = [&](int x) { ImageRef u(x,sizeValid.height-1); ACCUM_PIXELS(++u.x < sizeValid.width && --u.y >= 0); }; volatile Thread::safe_t idxPixel(0); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumInc(sizeValid.width, idxPixel, pixels)); } { // height-right-up auto pixels = [&](int y) { ImageRef u(0,y); ACCUM_PIXELS(++u.x < sizeValid.width && --u.y >= 0); }; volatile Thread::safe_t idxPixel(sizeValid.height); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumDec(idxPixel, pixels)); } WaitThreadWorkers(threads.size()*2); { // width-left-up auto pixels = [&](int x) { ImageRef u(x,sizeValid.height-1); ACCUM_PIXELS(--u.x >= 0 && --u.y >= 0); }; volatile Thread::safe_t idxPixel(sizeValid.width); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumDec(idxPixel, pixels)); } { // height-left-up auto pixels = [&](int y) { ImageRef u(sizeValid.width-1,y); ACCUM_PIXELS(--u.x >= 0 && --u.y >= 0); }; volatile Thread::safe_t idxPixel(sizeValid.height-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumDec(idxPixel, pixels)); } WaitThreadWorkers(threads.size()*2); } #undef ACCUM_PIXELS } else { const ImageRef dirs[] = {{-1,0}, {0,-1}, {-1,-1}, {1,-1}}; struct AccumLines { const Disparity maxNumDisp; LineData* linesBuffer; LineData* lines[numDirs][2]; AccumLines(Disparity _maxNumDisp) : maxNumDisp(_maxNumDisp), linesBuffer(NULL) {} ~AccumLines() { delete[] linesBuffer; } void Init(int w) { const int linewidth(w+2); const int buffersize(2*linewidth*numDirs); if (linesBuffer == NULL) { linesBuffer = new LineData[buffersize]; for (int i=0; i=0; ) { for (int c=sizeValid.width; --c>=0; ) { ACCUM_PIXELS(-dir.x, -dir.y, sizeValid.width-1-c); } lines.NextLine(); } #undef ACCUM_PIXELS } } // select best disparity and cost { disparityMap.create(sizeValid); costMap.create(sizeValid); auto pixel = [&](int idx) { const PixelData& pixel = imagePixels[idx]; if (pixel.range.isValid()) { const AccumCost* accums = imageAccumCosts.cdata()+pixel.idx; const AccumCost* bestAccum = accums; for (const AccumCost *accum=accums+1, *accumEnd=accums+pixel.range.numDisp(); accum *accum) bestAccum = accum; } disparityMap(idx) = pixel.range.minDisp+(Disparity)(bestAccum-accums); costMap(idx) = *bestAccum; } else { disparityMap(idx) = pixel.range.minDisp; costMap(idx) = NO_ACCUMCOST; } }; ASSERT(threads.IsEmpty()); if (!threads.empty()) { volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumInc(sizeValid.area(), idxPixel, pixel)); WaitThreadWorkers(threads.size()); } else for (int r=0; r(r*2+halfWindowSizeY, halfWindowSizeX)); for (int c=0, c2=0; c()*2); const auto minmax(disps.GetMinMax()); numDisp = (minmax.second-minmax.first)*2; if (numDisp < minNumDisp) { numDisp = minNumDisp; range.minDisp = disp-numDisp/2; range.maxDisp = disp+(numDisp+1)/2; } else { const Disparity maxNumDisp(bInvalid ? 64 : 32); if (numDisp > maxNumDisp) { range.minDisp = disp-(maxNumDisp*(disp-minmax.first*2)+1)/numDisp; range.maxDisp = disp+(maxNumDisp*(minmax.second*2+1-disp)+1)/numDisp; numDisp = range.numDisp(); } else { range.minDisp = disp-numDisp/2; range.maxDisp = disp+(numDisp+1)/2; } } } ASSERT(range.numDisp() == numDisp); if (maxNumDisp < numDisp) maxNumDisp = numDisp; } c2e += 2; do { ASSERT(c2 < size2x.width); PixelData& pixel = imagePixels[offset+c2]; pixel.range = range; pixel.idx = numCosts; numCosts += numDisp; } while (++c2 < c2e); } ASSERT(c2e < size2x.width); do { const PixelData& pixel = imagePixels[offset+c2e-1]; PixelData& _pixel = imagePixels[offset+c2e]; _pixel.range = pixel.range; _pixel.idx = numCosts; numCosts += pixel.range.numDisp(); } while (++c2e < size2x.width); const int _offsete((r+1 == disparityMap.rows ? size2x.height : r*2+halfWindowSizeY+2)*size2x.width); for (int _offset=offset+size2x.width; _offset<_offsete; _offset+=size2x.width) { for (int c2=0; c2= 0); ASSERT(!l2r.empty() && !r2l.empty()); ASSERT(l2r.height() == r2l.height()); auto pixel = [&](int, int r, int c) { Disparity& ld = l2r(r,c); if (ld == NO_DISP) return; // compute the corresponding disparity pixel according to the disparity value const ImageRef v(c+ld,r); // check image bounds if (v.x < 0 || v.x >= r2l.width()) { ld = NO_DISP; return; } // check right disparity is valid const Disparity rd = r2l(v); if (r2l(v) == NO_DISP) { ld = NO_DISP; return; } // check disparity consistency: // since the left and right disparities are opposite in sign, // we determine their similarity by *summing* them, rather // than differencing them as you might expect if (ABS(ld + rd) > thCross) ld = NO_DISP; }; ASSERT(threads.IsEmpty()); if (!threads.empty()) { volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelProcess(l2r.size(), idxPixel, pixel)); WaitThreadWorkers(threads.size()); } else for (int r=0; r 0); ASSERT(!disparityMap.empty() && disparityMap.size() == costMap.size()); auto pixel = [&](int, int r, int c) { Disparity& d = disparityMap(r,c); if (d == NO_DISP) return; if (costMap(r,c) > th) d = NO_DISP; }; ASSERT(threads.IsEmpty()); if (!threads.empty()) { volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelProcess(disparityMap.size(), idxPixel, pixel)); WaitThreadWorkers(threads.size()); } else for (int r=0; r= thValid) \ break // left-right direction { auto pixel = [&](int r) { int numValid(0); for (int c=0; c=0; ) { MASK_PIXEL(); } }; ASSERT(threads.IsEmpty()); if (!threads.empty()) { volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumInc(disparityMap.height(), idxPixel, pixel)); WaitThreadWorkers(threads.size()); } else for (int r=0; r=0; ) { MASK_PIXEL(); } }; ASSERT(threads.IsEmpty()); if (!threads.empty()) { volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumInc(disparityMap.width(), idxPixel, pixel)); WaitThreadWorkers(threads.size()); } else for (int c=0; c (int)std::numeric_limits::min()); ASSERT((int)d*subpixelSteps < (int)std::numeric_limits::max()); d *= subpixelSteps; }; ASSERT(threads.IsEmpty()); if (!threads.empty()) { volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelProcess(disparityMap.size(), idxPixel, pixel)); WaitThreadWorkers(threads.size()); } else for (int r=0; r(primary) / static_cast(other)); } // compute the offset to be added to the integer disparity to get the final result static real subpixelMode(AccumCost prev, AccumCost center, AccumCost next, SgmSubpixelMode subpixelMode) { ASSERT(prev != NO_ACCUMCOST && center != NO_ACCUMCOST && next != NO_ACCUMCOST); // use a lower quality two value interpolation if only two values are available if (prev == center) return center == next ? real(0) : semisubpixel(center, next); if (center == next) return prev == center ? real(0) : -semisubpixel(center, prev); // pick which direction to interpolate in const AccumCost ld(prev-center); const AccumCost rd(next-center); real x, mult; if (ld < rd) { x = static_cast(ld) / static_cast(rd); mult = real(1); } else { x = static_cast(rd) / static_cast(ld); mult = real(-1); } // use the selected subpixelMode function real value(0); switch (subpixelMode) { case SUBPIXEL_LINEAR: value = linear(x); break; case SUBPIXEL_POLY4: value = poly4(x); break; case SUBPIXEL_PARABOLA: value = parabola(x); break; case SUBPIXEL_SINE: value = sine(x); break; case SUBPIXEL_COSINE: value = cosine(x); break; case SUBPIXEL_LC_BLEND: value = lcBlend(x); break; }; // complete computation return (value - real(0.5))*mult; } }; // estimate sub-pixel disparity based on the cost values auto pixel = [&](int idx) { const PixelData& pixel = imagePixels[idx]; if (pixel.range.numDisp() < 2) return; Disparity& d = disparityMap(idx); if (d == NO_DISP) return; const AccumCost* accums = imageAccumCosts.cdata()+pixel.idx; const int idxDisp(d-pixel.range.minDisp); real disparity((real)d); if (d == pixel.range.minDisp) disparity += Fit::semisubpixel(accums[idxDisp], accums[idxDisp+1]); else if (d+1 == pixel.range.maxDisp) disparity -= Fit::semisubpixel(accums[idxDisp], accums[idxDisp-1]); else disparity += Fit::subpixelMode(accums[idxDisp-1], accums[idxDisp], accums[idxDisp+1], subpixelMode); ASSERT(ROUND2INT(disparity*subpixelSteps) > (int)std::numeric_limits::min()); ASSERT(ROUND2INT(disparity*subpixelSteps) < (int)std::numeric_limits::max()); d = (Disparity)ROUND2INT(disparity*subpixelSteps); }; ASSERT(threads.IsEmpty()); if (!threads.empty()) { volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelAccumInc(disparityMap.size().area(), idxPixel, pixel)); WaitThreadWorkers(threads.size()); } else for (int r=0; r 0; }) || !Image::Depth2Disparity(invQ, u, depth, disparity)) disparityMap(r,c) = NO_DISP; else disparityMap(r,c) = (Disparity)ROUND2INT(disparity*subpixelSteps); }; ASSERT(threads.IsEmpty()); if (!threads.empty()) { volatile Thread::safe_t idxPixel(-1); FOREACH(i, threads) threads.AddEvent(new EVTPixelProcess(disparityMap.size(), idxPixel, pixel)); WaitThreadWorkers(threads.size()); } else for (int r=0; r overlapBorder || absDist.y > overlapBorder) continue; const int idx((dist.x<0?1:0)+(dist.y<0?2:0)); DepthData& depthData = depthDatas[idx]; const float deistSq(normSq(dist)); float& ndeistSq = depthData.distMap(nx); Depth& ndepth = depthData.depthMap(nx); if (ndepth > 0 && ndeistSq <= deistSq) continue; ndeistSq = deistSq; ndepth = depth; depthData.depthRangeMap(nx) = depthRange; depthData.confMap(nx) = cost; } } } } typedef TAccumulator ValueAccumulator; depthRangeMap.create(depthMap.size()); confMap.create(depthMap.size()); const float thDist(SQUARE(0.75f)); const Depth thDepth(0.02f); unsigned numDepths(0); for (int r=0; r::max()); Depth depthCenter; for (int i=0; i<4; ++i) { const DepthData& depthData = depthDatas[i]; const Depth depth(depthData.depthMap(r,c)); if (depth <= 0) continue; const float dist(depthData.distMap(r,c)); if (distCenter > dist) { distCenter = dist; depthCenter = depth; } } if (distCenter > thDist) { depthMap(r,c) = Depth(0); continue; } ValueAccumulator acc(Vec4f::ZERO, 0.f); for (int i=0; i<4; ++i) { const DepthData& depthData = depthDatas[i]; const Depth depth(depthData.depthMap(r,c)); if (depth <= 0 || !IsDepthSimilar(depthCenter, depth, thDepth)) continue; const DepthRange& depthRange(depthData.depthRangeMap(r,c)); acc.Add(Vec4f((float)depth,depthRange.x,depthRange.y,depthData.confMap(r,c)), SQRT(depthData.distMap(r,c))); } ASSERT(!acc.IsEmpty()); const Vec4f value(acc.Normalized()); depthMap(r,c) = value(0); depthRangeMap(r,c) = DepthRange(value(1),value(2)); confMap(r,c) = value(3); ++numDepths; } } if (costMap.empty()) confMap.release(); return numDepths > 0; } EventThreadPool SemiGlobalMatcher::threads; Semaphore SemiGlobalMatcher::sem; // start worker threads void SemiGlobalMatcher::CreateThreads(unsigned nMaxThreads) { ASSERT(nMaxThreads > 0); ASSERT(threads.IsEmpty() && threads.empty()); if (nMaxThreads > 1) { threads.resize(nMaxThreads); threads.start(ThreadWorker); } } // destroy worker threads void SemiGlobalMatcher::DestroyThreads() { ASSERT(threads.IsEmpty()); if (!threads.empty()) { FOREACH(i, threads) threads.AddEvent(new EVTClose()); threads.Release(); } } void* SemiGlobalMatcher::ThreadWorker(void*) { while (true) { CAutoPtr evt(threads.GetEvent()); switch (evt->GetID()) { case EVT_JOB: evt->Run(); break; case EVT_CLOSE: return NULL; default: ASSERT("Should not happen!" == NULL); } sem.Signal(); } return NULL; } void SemiGlobalMatcher::WaitThreadWorkers(unsigned nJobs) { while (nJobs-- > 0) sem.Wait(); ASSERT(threads.IsEmpty()); } /*----------------------------------------------------------------*/ // S T R U C T S /////////////////////////////////////////////////// bool SemiGlobalMatcher::ExportDisparityDataRaw(const String& fileName, const DisparityMap& disparityMap, const AccumCostMap& costMap, const cv::Size& imageSize, const Matrix3x3& H, const Matrix4x4& Q, Disparity subpixelSteps) { ASSERT(!disparityMap.empty()); ASSERT(costMap.empty() || disparityMap.size() == costMap.size()); FILE *f = fopen(fileName, "wb"); if (f == NULL) return false; // write info fwrite(&imageSize.width, sizeof(int), 2, f); fwrite(H.val, sizeof(REAL), 9, f); fwrite(Q.val, sizeof(REAL), 16, f); fwrite(&subpixelSteps, sizeof(Disparity), 1, f); // write resolution fwrite(&disparityMap.cols, sizeof(int), 1, f); fwrite(&disparityMap.rows, sizeof(int), 1, f); // write disparity-map fwrite(disparityMap.getData(), sizeof(Disparity), disparityMap.area(), f); // write cost-map if (!costMap.empty()) fwrite(costMap.getData(), sizeof(AccumCost), costMap.area(), f); fclose(f); return true; } // ExportDisparityDataRaw // same as above, but exports also the empty border bool SemiGlobalMatcher::ExportDisparityDataRawFull(const String& fileName, const DisparityMap& disparityMap, const AccumCostMap& costMap, const cv::Size& imageSize, const Matrix3x3& H, const Matrix4x4& Q, Disparity subpixelSteps) { ASSERT(!disparityMap.empty()); ASSERT(costMap.empty() || disparityMap.size() == costMap.size()); const cv::Size size(disparityMap.width()+2*halfWindowSizeX,disparityMap.height()+2*halfWindowSizeY); const cv::Rect ROI(halfWindowSizeX,halfWindowSizeY, disparityMap.width(),disparityMap.height()); DisparityMap disparityMapFull(size, NO_DISP); disparityMap.copyTo(disparityMapFull(ROI)); if (costMap.empty()) return ExportDisparityDataRaw(fileName, disparityMapFull, costMap, imageSize, H, Q, subpixelSteps); AccumCostMap costMapFull(size, NO_ACCUMCOST); costMap.copyTo(costMapFull(ROI)); return ExportDisparityDataRaw(fileName, disparityMapFull, costMapFull, imageSize, H, Q, subpixelSteps); } // ExportDisparityDataRawFull bool SemiGlobalMatcher::ImportDisparityDataRaw(const String& fileName, DisparityMap& disparityMap, AccumCostMap& costMap, cv::Size& imageSize, Matrix3x3& H, Matrix4x4& Q, Disparity& subpixelSteps) { FILE *f = fopen(fileName, "rb"); if (f == NULL) return false; // read info fread(&imageSize.width, sizeof(int), 2, f); fread(H.val, sizeof(REAL), 9, f); fread(Q.val, sizeof(REAL), 16, f); fread(&subpixelSteps, sizeof(Disparity), 1, f); ASSERT(imageSize.width > 0 && imageSize.height > 0); // read resolution int w, h; fread(&w, sizeof(int), 1, f); fread(&h, sizeof(int), 1, f); ASSERT(w > 0 && h > 0); // read disparity-map disparityMap.create(h,w); fread(disparityMap.getData(), sizeof(Disparity), w*h, f); // read cost-map if (fgetc(f) != EOF) { fseek(f, -1, SEEK_CUR); costMap.create(h,w); fread(costMap.getData(), sizeof(AccumCost), w*h, f); } fclose(f); return true; } // ImportDisparityDataRaw // same as above, but imports also the empty border bool SemiGlobalMatcher::ImportDisparityDataRawFull(const String& fileName, DisparityMap& disparityMap, AccumCostMap& costMap, cv::Size& imageSize, Matrix3x3& H, Matrix4x4& Q, Disparity& subpixelSteps) { if (!ImportDisparityDataRaw(fileName, disparityMap, costMap, imageSize, H, Q, subpixelSteps)) return false; const cv::Size sizeValid(disparityMap.width()-2*halfWindowSizeX,disparityMap.height()-2*halfWindowSizeY); const cv::Rect ROI(halfWindowSizeX,halfWindowSizeY, sizeValid.width,sizeValid.height); disparityMap = disparityMap(ROI).clone(); if (!costMap.empty()) costMap = costMap(ROI).clone(); return true; } // ImportDisparityDataRawFull // export disparity-map as an image (red - maximum disparity, blue - minimum disparity) Image8U3 SemiGlobalMatcher::DisparityMap2Image(const DisparityMap& disparityMap, Disparity minDisparity, Disparity maxDisparity) { ASSERT(!disparityMap.empty()); // find min and max values if (minDisparity == NO_DISP || maxDisparity == NO_DISP) { CLISTDEF0(Disparity) disparities(0, disparityMap.area()); for (int i=disparityMap.area(); i-- > 0; ) { const Disparity disparity = disparityMap[i]; if (disparity != NO_DISP) disparities.emplace_back(disparity); } if (!disparities.empty()) { const std::pair th(ComputeX84Threshold(disparities.data(), disparities.size(), 5.2f)); minDisparity = (Disparity)ROUND2INT(th.first-th.second); maxDisparity = (Disparity)ROUND2INT(th.first+th.second); } DEBUG_ULTIMATE("\tdisparity range: [%d, %d]", minDisparity, maxDisparity); } const float sclDepth(1.f/(float)(maxDisparity - minDisparity)); // create color image Image8U3 img(cv::Size(disparityMap.width()+2*halfWindowSizeX,disparityMap.height()+2*halfWindowSizeY), Pixel8U::BLACK); for (int r=0; r(u)) continue; const Point3f X(imageData.camera.TransformPointI2W(Point3(u,depth))); vertex.x = X.x; vertex.y = X.y; vertex.z = X.z; const Pixel8U C(imageData.image.empty() ? Pixel8U::WHITE : imageData.image.sample(u)); vertex.r = C.r; vertex.g = C.g; vertex.b = C.b; ply.put_element(&vertex); } } if (ply.get_current_element_count() == 0) return false; // write to file return ply.header_complete(); } // ExportPointCloud // imports a DIMAP file and converts it to point-cloud bool SemiGlobalMatcher::ImportPointCloud(const String& fileName, const ImageArr& images, PointCloud& pointcloud) { // load disparity-map Disparity subpixelSteps; cv::Size imageSize; Matrix3x3 H; Matrix4x4 Q; DisparityMap disparityMap; AccumCostMap costMap; if (!ImportDisparityDataRawFull(fileName, disparityMap, costMap, imageSize, H, Q, subpixelSteps)) return false; // parse image index from the file name const String name(Util::getFileName(fileName)); IIndex idxImage(NO_ID), idxImagePair(NO_ID); if (sscanf(name, "%u_%u", &idxImage, &idxImagePair) != 2 || idxImage == NO_ID || idxImagePair == NO_ID) return false; const Image& imageData = images[idxImage]; ASSERT(imageData.image.size() == imageSize); // import the array of 3D points for (int r=0; r(u)) continue; pointcloud.points.emplace_back(Cast(imageData.camera.TransformPointI2W(Point3(u,depth)))); pointcloud.colors.emplace_back(imageData.image.empty() ? Pixel8U::WHITE : imageData.image.sample(u)); } } return true; } // ImportPointCloud /*----------------------------------------------------------------*/ bool MVS::STEREO::ExportCamerasEngin(const Scene& scene, const String& fileName) { ASSERT(!scene.IsEmpty()); File f(fileName, File::WRITE, File::CREATE | File::TRUNCATE); if (!f.isOpen()) return false; // write header f.print("n_cameras %u\n", scene.images.size()); f.print("n_points %u\n", 0); // write cameras for (const Image& image: scene.images) { if (!image.IsValid()) continue; const Point3 t(image.camera.GetT()); f.print("%u %u %u %s " "%g %g %g %g " "%g %g %g %g %g %g %g %g %g " "%g %g %g " "%g %g " "%u", image.ID, image.width, image.height, image.name.c_str(), image.camera.K(0,0), image.camera.K(1,1), image.camera.K(0,2), image.camera.K(1,2), image.camera.R(0,0), image.camera.R(0,1), image.camera.R(0,2), image.camera.R(1,0), image.camera.R(1,1), image.camera.R(1,2), image.camera.R(2,0), image.camera.R(2,1), image.camera.R(2,2), t.x, t.y, t.z, 0, 0, image.neighbors.size() ); for (const auto& neighbor: image.neighbors) f.print(" %u", neighbor.ID); f.print("\n"); } return true; } // ExportCamerasEngin /*----------------------------------------------------------------*/