/* * SceneRefine.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 "Scene.h" using namespace MVS; // D E F I N E S /////////////////////////////////////////////////// // uncomment to ensure edge size and improve vertex valence // (should enable more stable flow) #define MESHOPT_ENSUREEDGESIZE 1 // 0 - at all resolution // uncomment to use constant z-buffer bias // (should be enough, as the numerical error does not depend on the depth) #define MESHOPT_DEPTHCONSTBIAS 0.05f // uncomment to enable memory pool // (should reduce the allocation times for frequent used images) #define MESHOPT_TYPEPOOL // uncomment to enable CERES optimization module // (similar performance with the custom minimizer) #ifdef _USE_CERES #define MESHOPT_CERES #endif #ifdef MESHOPT_TYPEPOOL #define DEC_BitMatrix(var) BitMatrix& var = *BitMatrixPool() #define DEC_Image(type, var) TImage& var = *ImagePool() #define DST_BitMatrix(var) BitMatrixPool(&(var)) #define DST_Image(var) ImagePool(&(var)) #else #define DEC_BitMatrix(var) BitMatrix var; #define DEC_Image(type, var) TImage var; #define DST_BitMatrix(var) #define DST_Image(var) #endif // S T R U C T S /////////////////////////////////////////////////// typedef float Real; typedef Mesh::Vertex Vertex; typedef Mesh::VIndex VIndex; typedef Mesh::Face Face; typedef Mesh::FIndex FIndex; class MeshRefine { public: typedef TPoint3 Grad; typedef CLISTDEF0IDX(Grad,VIndex) GradArr; typedef TImage FaceMap; typedef TImage BaryMap; // store necessary data about a view struct View { typedef TPoint2 Grad; typedef TImage ImageGrad; Image32F image; // image pixels ImageGrad imageGrad; // image pixel gradients TImage imageMean; // image pixels mean TImage imageVar; // image pixels variance FaceMap faceMap; // remember for each pixel what face projects there DepthMap depthMap; // depth-map BaryMap baryMap; // barycentric coordinates }; typedef CLISTDEF2(View) ViewsArr; // used to render a mesh for optimization struct RasterMesh : TRasterMesh { typedef TRasterMesh Base; FaceMap& faceMap; BaryMap& baryMap; FIndex idxFace; RasterMesh(const Mesh::VertexArr& _vertices, const Camera& _camera, DepthMap& _depthMap, FaceMap& _faceMap, BaryMap& _baryMap) : Base(_vertices, _camera, _depthMap), faceMap(_faceMap), baryMap(_baryMap) {} void Clear() { Base::Clear(); faceMap.memset((uint8_t)NO_ID); baryMap.memset(0); } void Raster(const ImageRef& pt, const Triangle& t, const Point3f& bary) { const Point3f pbary(PerspectiveCorrectBarycentricCoordinates(t, bary)); const Depth z(ComputeDepth(t, pbary)); ASSERT(z > Depth(0)); Depth& depth = depthMap(pt); if (depth == 0 || depth > z) { depth = z; faceMap(pt) = idxFace; baryMap(pt) = pbary; } } }; public: MeshRefine(Scene& _scene, unsigned _nReduceMemory, unsigned _nAlternatePair=true, Real _weightRegularity=1.5f, Real _ratioRigidityElasticity=0.8f, unsigned _nResolutionLevel=0, unsigned _nMinResolution=640, unsigned nMaxViews=8, unsigned nMaxThreads=1); ~MeshRefine(); bool IsValid() const { return !pairs.IsEmpty(); } bool InitImages(Real scale, Real sigma=0); void ListVertexFacesPre(); void ListVertexFacesPost(); void ListCameraFaces(); void ListFaceAreas(Mesh::AreaArr& maxAreas); void SubdivideMesh(uint32_t maxArea, float fDecimate=1.f, unsigned nCloseHoles=15, unsigned nEnsureEdgeSize=1); double ScoreMesh(double* gradients); // given a vertex position and a projection camera, compute the projected position and its derivative template static T ProjectVertex(const TP* P, const TX* X, T* x, TJ* jacobian=NULL); static bool IsDepthSimilar(const DepthMap& depthMap, const Point2f& pt, Depth z); static void ProjectMesh( const Mesh::VertexArr& vertices, const Mesh::FaceArr& faces, const Mesh::FaceIdxArr& cameraFaces, const Camera& camera, const Image8U::Size& size, DepthMap& depthMap, FaceMap& faceMap, BaryMap& baryMap); static void ImageMeshWarp( const DepthMap& depthMapA, const Camera& cameraA, const DepthMap& depthMapB, const Camera& cameraB, const Image32F& imageB, Image32F& imageA, BitMatrix& mask); static void ComputeLocalVariance( const Image32F& image, const BitMatrix& mask, TImage& imageMean, TImage& imageVar); static float ComputeLocalZNCC( const Image32F& imageA, const TImage& imageMeanA, const TImage& imageVarA, const Image32F& imageB, const TImage& imageMeanB, const TImage& imageVarB, const BitMatrix& mask, TImage& imageZNCC, TImage& imageDZNCC); static void ComputePhotometricGradient( const Mesh::FaceArr& faces, const Mesh::NormalArr& normals, const DepthMap& depthMapA, const FaceMap& faceMapA, const BaryMap& baryMapA, const Camera& cameraA, const Camera& cameraB, const View& viewB, const TImage& imageDZNCC, const BitMatrix& mask, GradArr& photoGrad, UnsignedArr& photoGradNorm, Real RegularizationScale); static float ComputeSmoothnessGradient1( const Mesh::VertexArr& vertices, const Mesh::VertexVerticesArr& vertexVertices, const BoolArr& vertexBoundary, GradArr& smoothGrad1, VIndex idxStart, VIndex idxEnd); static void ComputeSmoothnessGradient2( const GradArr& smoothGrad1, const Mesh::VertexVerticesArr& vertexVertices, const BoolArr& vertexBoundary, GradArr& smoothGrad2, VIndex idxStart, VIndex idxEnd); template static TYPE* TypePool(TYPE* = NULL); template static inline TImage* ImagePool(TImage* pImage = NULL) { return TypePool< TImage >(pImage); } static inline BitMatrix* BitMatrixPool(BitMatrix* pMask = NULL) { return TypePool(pMask); } static void* ThreadWorkerTmp(void*); void ThreadWorker(); void WaitThreadWorkers(size_t nJobs); void ThSelectNeighbors(uint32_t idxImage, std::unordered_set& mapPairs, unsigned nMaxViews); void ThInitImage(uint32_t idxImage, Real scale, Real sigma); void ThProjectMesh(uint32_t idxImage, const Mesh::FaceIdxArr& cameraFaces); void ThProcessPair(uint32_t idxImageA, uint32_t idxImageB); void ThSmoothVertices1(VIndex idxStart, VIndex idxEnd); void ThSmoothVertices2(VIndex idxStart, VIndex idxEnd); public: const Real weightRegularity; // a scalar regularity weight to balance between photo-consistency and regularization terms Real ratioRigidityElasticity; // a scalar ratio used to compute the regularity gradient as a combination of rigidity and elasticity const unsigned nResolutionLevel; // how many times to scale down the images before mesh optimization const unsigned nMinResolution; // how many times to scale down the images before mesh optimization const unsigned nReduceMemory; // recompute image mean and variance in order to reduce memory requirements unsigned nAlternatePair; // using an image pair alternatively as reference image (0 - both, 1 - alternate, 2 - only left, 3 - only right) unsigned iteration; // current refinement iteration Scene& scene; // the mesh vertices and faces // gradient related float scorePhoto; float scoreSmooth; GradArr photoGrad; FloatArr photoGradNorm; FloatArr vertexDepth; GradArr smoothGrad1; GradArr smoothGrad2; // valid after ListCameraFaces() Mesh::NormalArr& faceNormals; // normals corresponding to each face // valid the entire time, but changes Mesh::VertexArr& vertices; Mesh::FaceArr& faces; Mesh::VertexVerticesArr& vertexVertices; // for each vertex, the list of adjacent vertices Mesh::VertexFacesArr& vertexFaces; // for each vertex, the list of faces containing it BoolArr& vertexBoundary; // for each vertex, stores if it is at the boundary or not // constant the entire time ImageArr& images; ViewsArr views; // views' data PairIdxArr pairs; // image pairs used to refine the mesh // multi-threading static SEACAVE::EventQueue events; // internal events queue (processed by the working threads) static SEACAVE::cList threads; // worker threads static CriticalSection cs; // mutex static Semaphore sem; // signal job end enum { HalfSize = 3 }; // half window size used to compute ZNCC }; // call with empty parameter to get an unused image; // call with an image pointer retrieved earlier to signal that is not needed anymore template TYPE* MeshRefine::TypePool(TYPE* pObj) { typedef CAutoPtr TypePtr; static CriticalSection cs; static cList objects; static cList unused; Lock l(cs); if (pObj == NULL) { if (unused.IsEmpty()) return objects.AddConstruct(new TYPE); return unused.RemoveTail(); } else { ASSERT(objects.Find(pObj) != NO_IDX); ASSERT(unused.Find(pObj) == NO_IDX); unused.Insert(pObj); return NULL; } } enum EVENT_TYPE { EVT_JOB = 0, EVT_CLOSE, }; class EVTClose : public Event { public: EVTClose() : Event(EVT_CLOSE) {} }; class EVTSelectNeighbors : public Event { public: uint32_t idxImage; std::unordered_set& mapPairs; unsigned nMaxViews; bool Run(void* pArgs) { ((MeshRefine*)pArgs)->ThSelectNeighbors(idxImage, mapPairs, nMaxViews); return true; } EVTSelectNeighbors(uint32_t _idxImage, std::unordered_set& _mapPairs, unsigned _nMaxViews) : Event(EVT_JOB), idxImage(_idxImage), mapPairs(_mapPairs), nMaxViews(_nMaxViews) {} }; class EVTInitImage : public Event { public: uint32_t idxImage; Real scale, sigma; bool Run(void* pArgs) { ((MeshRefine*)pArgs)->ThInitImage(idxImage, scale, sigma); return true; } EVTInitImage(uint32_t _idxImage, Real _scale, Real _sigma) : Event(EVT_JOB), idxImage(_idxImage), scale(_scale), sigma(_sigma) {} }; class EVTProjectMesh : public Event { public: uint32_t idxImage; const Mesh::FaceIdxArr& cameraFaces; bool Run(void* pArgs) { ((MeshRefine*)pArgs)->ThProjectMesh(idxImage, cameraFaces); return true; } EVTProjectMesh(uint32_t _idxImage, const Mesh::FaceIdxArr& _cameraFaces) : Event(EVT_JOB), idxImage(_idxImage), cameraFaces(_cameraFaces) {} }; class EVTProcessPair : public Event { public: uint32_t idxImageA, idxImageB; bool Run(void* pArgs) { ((MeshRefine*)pArgs)->ThProcessPair(idxImageA, idxImageB); return true; } EVTProcessPair(uint32_t _idxImageA, uint32_t _idxImageB) : Event(EVT_JOB), idxImageA(_idxImageA), idxImageB(_idxImageB) {} }; class EVTSmoothVertices1 : public Event { public: VIndex idxStart, idxEnd; bool Run(void* pArgs) { ((MeshRefine*)pArgs)->ThSmoothVertices1(idxStart, idxEnd); return true; } EVTSmoothVertices1(VIndex _idxStart, VIndex _idxEnd) : Event(EVT_JOB), idxStart(_idxStart), idxEnd(_idxEnd) {} }; class EVTSmoothVertices2 : public Event { public: VIndex idxStart, idxEnd; bool Run(void* pArgs) { ((MeshRefine*)pArgs)->ThSmoothVertices2(idxStart, idxEnd); return true; } EVTSmoothVertices2(VIndex _idxStart, VIndex _idxEnd) : Event(EVT_JOB), idxStart(_idxStart), idxEnd(_idxEnd) {} }; SEACAVE::EventQueue MeshRefine::events; SEACAVE::cList MeshRefine::threads; CriticalSection MeshRefine::cs; Semaphore MeshRefine::sem; MeshRefine::MeshRefine(Scene& _scene, unsigned _nReduceMemory, unsigned _nAlternatePair, Real _weightRegularity, Real _ratioRigidityElasticity, unsigned _nResolutionLevel, unsigned _nMinResolution, unsigned nMaxViews, unsigned nMaxThreads) : weightRegularity(_weightRegularity), ratioRigidityElasticity(_ratioRigidityElasticity), nResolutionLevel(_nResolutionLevel), nMinResolution(_nMinResolution), nReduceMemory(_nReduceMemory), nAlternatePair(_nAlternatePair), scene(_scene), faceNormals(_scene.mesh.faceNormals), vertices(_scene.mesh.vertices), faces(_scene.mesh.faces), vertexVertices(_scene.mesh.vertexVertices), vertexFaces(_scene.mesh.vertexFaces), vertexBoundary(_scene.mesh.vertexBoundary), images(_scene.images) { // start worker threads ASSERT(nMaxThreads > 0); ASSERT(threads.IsEmpty()); threads.Resize(nMaxThreads); FOREACHPTR(pThread, threads) pThread->start(ThreadWorkerTmp, this); // keep only best neighbor views for each image std::unordered_set mapPairs; mapPairs.reserve(images.GetSize()*nMaxViews); ASSERT(events.IsEmpty()); FOREACH(idxImage, images) events.AddEvent(new EVTSelectNeighbors(idxImage, mapPairs, nMaxViews)); WaitThreadWorkers(images.GetSize()); pairs.Reserve(mapPairs.size()); for (uint64_t pair: mapPairs) pairs.AddConstruct(pair); } MeshRefine::~MeshRefine() { // wait for the working threads to close FOREACH(i, threads) events.AddEvent(new EVTClose()); FOREACHPTR(pThread, threads) pThread->join(); scene.mesh.ReleaseExtra(); } // load and initialize all images at the given scale // and compute the gradient for each input image // optional: blur them using the given sigma bool MeshRefine::InitImages(Real scale, Real sigma) { views.Resize(images.GetSize()); ASSERT(events.IsEmpty()); FOREACH(idxImage, images) events.AddEvent(new EVTInitImage(idxImage, scale, sigma)); WaitThreadWorkers(images.GetSize()); iteration = 0; return true; } // extract array of triangles incident to each vertex // and check each vertex if it is at the boundary or not void MeshRefine::ListVertexFacesPre() { scene.mesh.EmptyExtra(); scene.mesh.ListIncidenteFaces(); } void MeshRefine::ListVertexFacesPost() { scene.mesh.ListIncidenteVertices(); scene.mesh.ListBoundaryVertices(); } // extract array of faces viewed by each image void MeshRefine::ListCameraFaces() { // extract array of faces viewed by each camera typedef CLISTDEF2(Mesh::FaceIdxArr) CameraFacesArr; CameraFacesArr arrCameraFaces(images.GetSize()); { Mesh::Octree octree; Mesh::FacesInserter::CreateOctree(octree, scene.mesh); FOREACH(ID, images) { const Image& imageData = images[ID]; if (!imageData.IsValid()) continue; const TFrustum frustum(Matrix3x4f(imageData.camera.P), (float)imageData.width, (float)imageData.height); Mesh::FacesInserter inserter(arrCameraFaces[ID]); octree.Traverse(frustum, inserter); } } // project mesh to each camera plane ASSERT(events.IsEmpty()); FOREACH(idxImage, images) events.AddEvent(new EVTProjectMesh(idxImage, arrCameraFaces[idxImage])); WaitThreadWorkers(images.GetSize()); } // compute for each face the projection area as the maximum area in both images of a pair // (make sure ListCameraFaces() was called before) void MeshRefine::ListFaceAreas(Mesh::AreaArr& maxAreas) { ASSERT(maxAreas.IsEmpty()); // for each image, compute the projection area of visible faces typedef cList ImageAreaArr; ImageAreaArr viewAreas(images.GetSize()); FOREACH(idxImage, images) { const Image& imageData = images[idxImage]; if (!imageData.IsValid()) continue; Mesh::AreaArr& areas = viewAreas[idxImage]; areas.Resize(faces.GetSize()); areas.Memset(0); const FaceMap& faceMap = views[idxImage].faceMap; // compute area covered by all vertices (incident faces) viewed by this image for (int j=0; j 0)); if (idxFace == NO_ID) continue; ++areas[idxFace]; } } } // for each pair, mark the faces that have big projection areas in both images maxAreas.Resize(faces.GetSize()); maxAreas.Memset(0); FOREACHPTR(pPair, pairs) { const Mesh::AreaArr& areasA = viewAreas[pPair->i]; const Mesh::AreaArr& areasB = viewAreas[pPair->j]; ASSERT(areasA.GetSize() == areasB.GetSize()); FOREACH(f, areasA) { const uint16_t minArea(MINF(areasA[f], areasB[f])); uint16_t& maxArea = maxAreas[f]; if (maxArea < minArea) maxArea = minArea; } } } // decimate or subdivide mesh such that for each face there is no image pair in which // its projection area is bigger than the given number of pixels in both images void MeshRefine::SubdivideMesh(uint32_t maxArea, float fDecimate, unsigned nCloseHoles, unsigned nEnsureEdgeSize) { Mesh::AreaArr maxAreas; // first decimate if necessary const bool bNoDecimation(fDecimate >= 1.f); const bool bNoSimplification(maxArea == 0); if (!bNoDecimation) { if (fDecimate > 0.f) { // decimate to the desired resolution scene.mesh.Clean(fDecimate, 0.f, false, nCloseHoles, 0u, 0.f, false); scene.mesh.Clean(1.f, 0.f, false, nCloseHoles, 0u, 0.f, true); #ifdef MESHOPT_ENSUREEDGESIZE // make sure there are no edges too small or too long if (nEnsureEdgeSize > 0 && bNoSimplification) { scene.mesh.EnsureEdgeSize(); scene.mesh.Clean(1.f, 0.f, false, nCloseHoles, 0u, 0.f, true); } #endif // re-map vertex and camera faces ListVertexFacesPre(); } else { // extract array of faces viewed by each camera ListCameraFaces(); // estimate the faces' area that have big projection areas in both images of a pair ListFaceAreas(maxAreas); ASSERT(!maxAreas.IsEmpty()); const float fMaxArea((float)(maxArea > 0 ? maxArea : 64)); const float fMedianArea(6.f*(float)Mesh::AreaArr(maxAreas).GetMedian()); if (fMedianArea < fMaxArea) { maxAreas.Empty(); // decimate to the auto detected resolution scene.mesh.Clean(MAXF(0.1f, fMedianArea/fMaxArea), 0.f, false, nCloseHoles, 0u, 0.f, false); scene.mesh.Clean(1.f, 0.f, false, nCloseHoles, 0u, 0.f, true); #ifdef MESHOPT_ENSUREEDGESIZE // make sure there are no edges too small or too long if (nEnsureEdgeSize > 0 && bNoSimplification) { scene.mesh.EnsureEdgeSize(); scene.mesh.Clean(1.f, 0.f, false, nCloseHoles, 0u, 0.f, true); } #endif // re-map vertex and camera faces ListVertexFacesPre(); } } } if (bNoSimplification) return; if (maxAreas.IsEmpty()) { // extract array of faces viewed by each camera ListCameraFaces(); // estimate the faces' area that have big projection areas in both images of a pair ListFaceAreas(maxAreas); } // subdivide mesh faces if its projection area is bigger than the given number of pixels const size_t numVertsOld(vertices.GetSize()); const size_t numFacesOld(faces.GetSize()); scene.mesh.Subdivide(maxAreas, maxArea); #ifdef MESHOPT_ENSUREEDGESIZE // make sure there are no edges too small or too long #if MESHOPT_ENSUREEDGESIZE==1 if ((nEnsureEdgeSize == 1 && !bNoDecimation) || nEnsureEdgeSize > 1) #endif { scene.mesh.EnsureEdgeSize(); scene.mesh.Clean(1.f, 0.f, false, nCloseHoles, 0u, 0.f, true); } #endif // re-map vertex and camera faces ListVertexFacesPre(); DEBUG_EXTRA("Mesh subdivided: %u/%u -> %u/%u vertices/faces", numVertsOld, numFacesOld, vertices.GetSize(), faces.GetSize()); #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 3) scene.mesh.Save(MAKE_PATH("MeshSubdivided.ply")); #endif } // score mesh using photo-consistency // and compute vertices gradient using analytical method double MeshRefine::ScoreMesh(double* gradients) { // extract array of faces viewed by each camera ListCameraFaces(); // compute face normals scene.mesh.ComputeNormalFaces(); // for each pair of images, compute a photo-consistency score // between the reference image and the pixels of the second image // projected in the reference image through the mesh surface scorePhoto = 0; photoGrad.Resize(vertices.GetSize()); photoGrad.Memset(0); photoGradNorm.Resize(vertices.GetSize()); photoGradNorm.Memset(0); if (!vertexDepth.IsEmpty()) { ASSERT(vertexDepth.GetSize() == vertices.GetSize()); vertexDepth.MemsetValue(FLT_MAX); } ASSERT(events.IsEmpty()); FOREACHPTR(pPair, pairs) { ASSERT(pPair->i < pPair->j); switch (nAlternatePair) { case 1: events.AddEvent(iteration%2 ? new EVTProcessPair(pPair->j,pPair->i) : new EVTProcessPair(pPair->i,pPair->j)); break; case 2: events.AddEvent(new EVTProcessPair(pPair->i, pPair->j)); break; case 3: events.AddEvent(new EVTProcessPair(pPair->j, pPair->i)); break; default: for (int ip=0; ip<2; ++ip) events.AddEvent(ip ? new EVTProcessPair(pPair->j,pPair->i) : new EVTProcessPair(pPair->i,pPair->j)); } } WaitThreadWorkers(nAlternatePair ? pairs.GetSize() : pairs.GetSize()*2); // loop through all vertices and compute the smoothing score scoreSmooth = 0; const VIndex idxStep((vertices.GetSize()+(VIndex)threads.GetSize()-1)/(VIndex)threads.GetSize()); smoothGrad1.Resize(vertices.GetSize()); { ASSERT(events.IsEmpty()); VIndex idx(0); while (idx= 1.f) { FOREACH(v, vertices) ((Point3d*)gradients)[v] = photoGradNorm[v] > 0 ? Cast(photoGrad[v]/photoGradNorm[v] + smoothGrad2[v]*weightRegularity) : Cast(smoothGrad2[v]*weightRegularity); } else { // compute smoothing gradient as a combination of level 1 and 2 of the Laplacian operator; // (see page 105 of "Stereo and Silhouette Fusion for 3D Object Modeling from Uncalibrated Images Under Circular Motion" C. Hernandez, 2004) const Real rigidity((Real(1)-ratioRigidityElasticity)*weightRegularity); const Real elasticity(ratioRigidityElasticity*weightRegularity); FOREACH(v, vertices) ((Point3d*)gradients)[v] = photoGradNorm[v] > 0 ? Cast(photoGrad[v]/photoGradNorm[v] + smoothGrad2[v]*elasticity - smoothGrad1[v]*rigidity) : Cast(smoothGrad2[v]*elasticity - smoothGrad1[v]*rigidity); } return (nAlternatePair ? 0.2f : 0.1f)*scorePhoto + 0.01f*scoreSmooth; } // given a vertex position and a projection camera, compute the projected position and its derivative // returns the depth template T MeshRefine::ProjectVertex(const TP* P, const TX* X, T* x, TJ* jacobian) { const TX& x1(X[0]); const TX& x2(X[1]); const TX& x3(X[2]); const TP& p1_1(P[ 0]); const TP& p1_2(P[ 1]); const TP& p1_3(P[ 2]); const TP& p1_4(P[ 3]); const TP& p2_1(P[ 4]); const TP& p2_2(P[ 5]); const TP& p2_3(P[ 6]); const TP& p2_4(P[ 7]); const TP& p3_1(P[ 8]); const TP& p3_2(P[ 9]); const TP& p3_3(P[10]); const TP& p3_4(P[11]); const TP t5(p3_4+p3_1*x1+p3_2*x2+p3_3*x3); const TP t6(1.0/t5); const TP t10(p1_4+p1_1*x1+p1_2*x2+p1_3*x3); const TP t11(t10*t6); const TP t15(p2_4+p2_1*x1+p2_2*x2+p2_3*x3); const TP t16(t15*t6); x[0] = T(t11); x[1] = T(t16); if (jacobian) { jacobian[0] = TJ((p1_1-p3_1*t11)*t6); jacobian[1] = TJ((p1_2-p3_2*t11)*t6); jacobian[2] = TJ((p1_3-p3_3*t11)*t6); jacobian[3] = TJ((p2_1-p3_1*t16)*t6); jacobian[4] = TJ((p2_2-p3_2*t16)*t6); jacobian[5] = TJ((p2_3-p3_3*t16)*t6); } return T(t5); } // check if any of the depths surrounding the given coordinate is similar to the given value bool MeshRefine::IsDepthSimilar(const DepthMap& depthMap, const Point2f& pt, Depth z) { const ImageRef tl(FLOOR2INT(pt)); for (int x=0; x<2; ++x) { for (int y=0; y<2; ++y) { const ImageRef ir(tl.x+x, tl.y+y); if (!depthMap.isInsideWithBorder(ir)) continue; const Depth& depth = depthMap(ir); #ifndef MESHOPT_DEPTHCONSTBIAS if (depth <= 0 || ABS(depth-z) > z*0.01f /*!IsDepthSimilar(depth, z, 0.01f)*/) #else if (depth <= 0 || depth+MESHOPT_DEPTHCONSTBIAS < z) #endif continue; return true; } } return false; } // project mesh to the given camera plane void MeshRefine::ProjectMesh( const Mesh::VertexArr& vertices, const Mesh::FaceArr& faces, const Mesh::FaceIdxArr& cameraFaces, const Camera& camera, const Image8U::Size& size, DepthMap& depthMap, FaceMap& faceMap, BaryMap& baryMap) { // init view data depthMap.create(size); faceMap.create(size); baryMap.create(size); // project all triangles on this image and keep the closest ones RasterMesh rasterer(vertices, camera, depthMap, faceMap, baryMap); RasterMesh::Triangle triangle; RasterMesh::TriangleRasterizer triangleRasterizer(triangle, rasterer); rasterer.Clear(); for (auto idxFace : cameraFaces) { const Face& facet = faces[idxFace]; rasterer.idxFace = idxFace; rasterer.Project(facet, triangleRasterizer); } } // project image from view B to view A through the mesh; // the projected image is stored in imageA // (imageAB is assumed to be initialize to the right size) void MeshRefine::ImageMeshWarp( const DepthMap& depthMapA, const Camera& cameraA, const DepthMap& depthMapB, const Camera& cameraB, const Image32F& imageB, Image32F& imageA, BitMatrix& mask) { ASSERT(!imageA.empty()); typedef Sampler::Linear Sampler; const Sampler sampler; mask.create(imageA.size()); mask.memset(0); for (int j=0; j(sampler, pt); mask.set(j,i); } } } // compute local variance for each image pixel void MeshRefine::ComputeLocalVariance(const Image32F& image, const BitMatrix& mask, TImage& imageMean, TImage& imageVar) { ASSERT(image.size() == mask.size()); imageMean.create(image.size()); imageVar.create(image.size()); imageMean.memset(0); imageVar.memset(0); const int RowsEnd(image.rows-HalfSize); const int ColsEnd(image.cols-HalfSize); const int n(SQUARE(HalfSize*2+1)); DEC_Image(double, imageSum); DEC_Image(double, imageSumSq); #if CV_MAJOR_VERSION > 2 cv::integral(image, imageSum, imageSumSq, CV_64F, CV_64F); #else cv::integral(image, imageSum, imageSumSq, CV_64F); #endif for (int r=HalfSize; r& imageMeanA, const TImage& imageVarA, const Image32F& imageB, const TImage& imageMeanB, const TImage& imageVarB, const BitMatrix& mask, TImage& imageZNCC, TImage& imageDZNCC) { ASSERT(imageA.size() == mask.size() && imageB.size() == mask.size() && !mask.empty()); float score(0); imageZNCC.create(mask.size()); imageDZNCC.create(mask.size()); const int RowsEnd(mask.rows-HalfSize); const int ColsEnd(mask.cols-HalfSize); const int n(SQUARE(HalfSize*2+1)); imageZNCC.memset(0); DEC_Image(double, imageABSum); { DEC_Image(float, imageAB); cv::multiply(imageA, imageB, imageAB); cv::integral(imageAB, imageABSum, CV_64F); DST_Image(imageAB); } DEC_Image(Real, imageInvSqrtVAVB); imageInvSqrtVAVB.create(mask.size()); for (int r=HalfSize; r& imageDZNCC, const BitMatrix& mask, GradArr& photoGrad, UnsignedArr& photoGradNorm, Real RegularizationScale) { ASSERT(faces.GetSize() == normals.GetSize() && !faces.IsEmpty()); ASSERT(depthMapA.size() == mask.size() && faceMapA.size() == mask.size() && baryMapA.size() == mask.size() && imageDZNCC.size() == mask.size() && !mask.empty()); ASSERT(viewB.image.size() == viewB.imageGrad.size() && !viewB.image.empty()); const int RowsEnd(mask.rows-HalfSize); const int ColsEnd(mask.cols-HalfSize); typedef Sampler::Linear Sampler; const Sampler sampler; TMatrix xJac; Point2f xB; photoGrad.Memset(0); photoGradNorm.Memset(0); for (int r=HalfSize; r -0.1) continue; #endif const Depth depthA(depthMapA(r,c)); ASSERT(depthA > 0); const Point3 X(rayA*REAL(depthA)+cameraA.C); // project point in second image and // projection Jacobian matrix in the second image of the 3D point on the surface MAYBEUNUSED const float depthB(ProjectVertex(cameraB.P.val, X.ptr(), xB.ptr(), xJac.val)); ASSERT(depthB > 0); // compute gradient in image B const TMatrix gB(viewB.imageGrad.sample(sampler, xB)); // compute gradient scale const Real dZNCC(imageDZNCC(r,c)); const Real sg((gB*(xJac*(const TMatrix&)dA))(0)*dZNCC*RegularizationScale/Nd); // add gradient to the three vertices const Face& face(faces[idxFace]); const Point3f& b(baryMapA(r,c)); for (int v=0; v<3; ++v) { const Grad g(N*(sg*(Real)b[v])); const VIndex idxVert(face[v]); photoGrad[idxVert] += g; ++photoGradNorm[idxVert]; } } } } // computes the discrete analog of the Laplacian using // the umbrella-operator on the first triangle ring at each point float MeshRefine::ComputeSmoothnessGradient1( const Mesh::VertexArr& vertices, const Mesh::VertexVerticesArr& vertexVertices, const BoolArr& vertexBoundary, GradArr& smoothGrad1, VIndex idxStart, VIndex idxEnd) { ASSERT(!vertices.IsEmpty() && vertices.GetSize() == vertexVertices.GetSize() && vertices.GetSize() == smoothGrad1.GetSize()); float score(0); for (VIndex idxV=idxStart; idxV(vertices[verts[v]]); grad = grad/(Real)verts.GetSize() - Cast(vertices[idxV]); const float regularityScore((float)norm(grad)); ASSERT(ISFINITE(regularityScore)); score += regularityScore; } return score; } // same as above, but used to compute level 2; // normalized as in "Stereo and Silhouette Fusion for 3D Object Modeling from Uncalibrated Images Under Circular Motion" C. Hernandez, 2004 void MeshRefine::ComputeSmoothnessGradient2( const GradArr& smoothGrad1, const Mesh::VertexVerticesArr& vertexVertices, const BoolArr& vertexBoundary, GradArr& smoothGrad2, VIndex idxStart, VIndex idxEnd) { ASSERT(!smoothGrad1.IsEmpty() && smoothGrad1.GetSize() == vertexVertices.GetSize() && smoothGrad1.GetSize() == smoothGrad2.GetSize()); for (VIndex idxV=idxStart; idxV 0) w += Real(1)/(Real)numVert; } const Real numVert((Real)verts.GetSize()); const Real nrm(Real(1)/(Real(1)+w/numVert)); grad = grad*(nrm/numVert) - smoothGrad1[idxV]*nrm; } } void* MeshRefine::ThreadWorkerTmp(void* arg) { MeshRefine& refine = *((MeshRefine*)arg); refine.ThreadWorker(); return NULL; } void MeshRefine::ThreadWorker() { while (true) { CAutoPtr evt(events.GetEvent()); switch (evt->GetID()) { case EVT_JOB: evt->Run(this); break; case EVT_CLOSE: return; default: ASSERT("Should not happen!" == NULL); } sem.Signal(); } } void MeshRefine::WaitThreadWorkers(size_t nJobs) { while (nJobs-- > 0) sem.Wait(); ASSERT(events.IsEmpty()); } void MeshRefine::ThSelectNeighbors(uint32_t idxImage, std::unordered_set& mapPairs, unsigned nMaxViews) { // keep only best neighbor views const float fMinArea(0.1f); const float fMinScale(0.2f), fMaxScale(3.2f); const float fMinAngle(FD2R(2.5f)), fMaxAngle(FD2R(45.f)); Image& imageData = images[idxImage]; if (!imageData.IsValid()) return; if (imageData.neighbors.IsEmpty()) { IndexArr points; scene.SelectNeighborViews(idxImage, points); } ViewScoreArr neighbors(imageData.neighbors); Scene::FilterNeighborViews(neighbors, fMinArea, fMinScale, fMaxScale, fMinAngle, fMaxAngle, nMaxViews); Lock l(cs); for (const ViewScore& neighbor: neighbors) { ASSERT(images[neighbor.ID].IsValid()); mapPairs.insert(MakePairIdx((uint32_t)idxImage, neighbor.ID)); } } void MeshRefine::ThInitImage(uint32_t idxImage, Real scale, Real sigma) { Image& imageData = images[idxImage]; if (!imageData.IsValid()) return; // load and init image unsigned level(nResolutionLevel); const unsigned imageSize(imageData.RecomputeMaxResolution(level, nMinResolution)); if ((imageData.image.empty() || MAXF(imageData.width,imageData.height) != imageSize) && !imageData.ReloadImage(imageSize)) ABORT("can not load image"); View& view = views[idxImage]; Image32F& img = view.image; imageData.image.toGray(img, cv::COLOR_BGR2GRAY, true); imageData.image.release(); if (sigma > 0) cv::GaussianBlur(img, img, cv::Size(), sigma); if (scale < 1.0) { cv::resize(img, img, cv::Size(), scale, scale, cv::INTER_AREA); imageData.width = img.width(); imageData.height = img.height(); } imageData.UpdateCamera(scene.platforms); if (!nReduceMemory) { // compute image mean and variance ComputeLocalVariance(img, BitMatrix(img.size(), 0xFF), view.imageMean, view.imageVar); } // compute image gradient typedef View::Grad::Type GradType; TImage grad[2]; #if 0 cv::Sobel(img, grad[0], cv::DataType::type, 1, 0, 3, 1.0/8.0); cv::Sobel(img, grad[1], cv::DataType::type, 0, 1, 3, 1.0/8.0); #elif 1 const TMatrix kernel(CreateDerivativeKernel3x5()); cv::filter2D(img, grad[0], cv::DataType::type, kernel); cv::filter2D(img, grad[1], cv::DataType::type, kernel.t()); #else const TMatrix kernel(CreateDerivativeKernel5x7()); cv::filter2D(img, grad[0], cv::DataType::type, kernel); cv::filter2D(img, grad[1], cv::DataType::type, kernel.t()); #endif cv::merge(grad, 2, view.imageGrad); } void MeshRefine::ThProjectMesh(uint32_t idxImage, const Mesh::FaceIdxArr& cameraFaces) { const Image& imageData = images[idxImage]; if (!imageData.IsValid()) return; // project mesh to the given camera plane View& view = views[idxImage]; ProjectMesh(vertices, faces, cameraFaces, imageData.camera, view.image.size(), view.depthMap, view.faceMap, view.baryMap); } void MeshRefine::ThProcessPair(uint32_t idxImageA, uint32_t idxImageB) { // fetch view A data const Image& imageDataA = images[idxImageA]; ASSERT(imageDataA.IsValid()); const View& viewA = views[idxImageA]; const BaryMap& baryMapA = viewA.baryMap; const FaceMap& faceMapA = viewA.faceMap; const DepthMap& depthMapA = viewA.depthMap; const Image32F& imageA = viewA.image; const Camera& cameraA = imageDataA.camera; // fetch view B data const Image& imageDataB = images[idxImageB]; ASSERT(imageDataB.IsValid()); const View& viewB = views[idxImageB]; const DepthMap& depthMapB = viewB.depthMap; const Image32F& imageB = viewB.image; const Camera& cameraB = imageDataB.camera; // warp imageB to imageA using the mesh DEC_BitMatrix(mask); DEC_Image(float, imageAB); imageA.copyTo(imageAB); ImageMeshWarp(depthMapA, cameraA, depthMapB, cameraB, imageB, imageAB, mask); // compute ZNCC and its gradient const TImage *imageMeanA, *imageVarA; if (nReduceMemory) { DEC_Image(Real, _imageMeanA); DEC_Image(Real, _imageVarA); ComputeLocalVariance(viewA.image, mask, _imageMeanA, _imageVarA); imageMeanA = &_imageMeanA; imageVarA = &_imageVarA; } else { imageMeanA = &viewA.imageMean; imageVarA = &viewA.imageVar; } DEC_Image(Real, imageMeanAB); DEC_Image(Real, imageVarAB); ComputeLocalVariance(imageAB, mask, imageMeanAB, imageVarAB); DEC_Image(Real, imageZNCC); DEC_Image(Real, imageDZNCC); const float score(ComputeLocalZNCC(imageA, *imageMeanA, *imageVarA, imageAB, imageMeanAB, imageVarAB, mask, imageZNCC, imageDZNCC)); #ifdef MESHOPT_TYPEPOOL DST_Image(imageZNCC); DST_Image(imageVarAB); DST_Image(imageMeanAB); if (nReduceMemory) { DST_Image(*((TImage*)imageMeanA)); DST_Image(*((TImage*)imageVarA)); } DST_Image(imageAB); #endif // compute field gradient GradArr _photoGrad(photoGrad.GetSize()); UnsignedArr _photoGradNorm(photoGrad.GetSize()); const Real RegularizationScale((Real)((REAL)(imageDataA.avgDepth*imageDataB.avgDepth)/(cameraA.GetFocalLength()*cameraB.GetFocalLength()))); ComputePhotometricGradient(faces, faceNormals, depthMapA, faceMapA, baryMapA, cameraA, cameraB, viewB, imageDZNCC, mask, _photoGrad, _photoGradNorm, RegularizationScale); DST_Image(imageDZNCC); DST_BitMatrix(mask); Lock l(cs); if (vertexDepth.IsEmpty()) { FOREACH(i, photoGrad) { if (_photoGradNorm[i] > 0) { photoGrad[i] += _photoGrad[i]; photoGradNorm[i] += 1.f; } } } else { const float depth(MINF(imageDataA.avgDepth, imageDataB.avgDepth)); FOREACH(i, photoGrad) { if (_photoGradNorm[i] > 0) { photoGrad[i] += _photoGrad[i]; photoGradNorm[i] += 1.f; if (vertexDepth[i] > depth) vertexDepth[i] = depth; } } } scorePhoto += (float)RegularizationScale*score; } void MeshRefine::ThSmoothVertices1(VIndex idxStart, VIndex idxEnd) { const float score(ComputeSmoothnessGradient1(vertices, vertexVertices, vertexBoundary, smoothGrad1, idxStart, idxEnd)); Lock l(cs); scoreSmooth += score; } void MeshRefine::ThSmoothVertices2(VIndex idxStart, VIndex idxEnd) { ComputeSmoothnessGradient2(smoothGrad1, vertexVertices, vertexBoundary, smoothGrad2, idxStart, idxEnd); } /*----------------------------------------------------------------*/ // S T R U C T S /////////////////////////////////////////////////// #ifdef MESHOPT_CERES #pragma push_macro("LOG") #undef LOG #pragma push_macro("CHECK") #undef CHECK #pragma push_macro("ERROR") #undef ERROR #define GLOG_NO_ABBREVIATED_SEVERITIES #include #include #include #pragma pop_macro("ERROR") #pragma pop_macro("CHECK") #pragma pop_macro("LOG") namespace ceres { class MeshProblem : public FirstOrderFunction, public IterationCallback { public: MeshProblem(MeshRefine& _refine) : refine(_refine), params(refine.vertices.GetSize()*3) { // init params FOREACH(i, refine.vertices) *((Point3d*)params.Begin()+i) = refine.vertices[i]; } virtual ~MeshProblem() {} void ApplyParams() const { FOREACH(i, refine.vertices) refine.vertices[i] = *((Point3d*)params.Begin()+i); } void ApplyParams(const double* parameters) const { memcpy(params.Begin(), parameters, sizeof(double)*params.GetSize()); ApplyParams(); } bool Evaluate(const double* const parameters, double* cost, double* gradient) const { // update surface parameters ApplyParams(parameters); // evaluate residuals and gradients Point3dArr gradients; if (!gradient) { gradients.Resize(refine.vertices.GetSize()); gradient = (double*)gradients.Begin(); } *cost = refine.ScoreMesh(gradient); return true; } CallbackReturnType operator()(const IterationSummary& summary) { refine.iteration = summary.iteration; return ceres::SOLVER_CONTINUE; } int NumParameters() const { return (int)params.GetSize(); } const double* GetParameters() const { return params.Begin(); } double* GetParameters() { return params.Begin(); } protected: MeshRefine& refine; DoubleArr params; }; } // namespace ceres #endif // MESHOPT_CERES // optimize mesh using photo-consistency // fThPlanarVertex - threshold used to remove vertices on planar patches (percentage of the minimum depth, 0 - disable) bool Scene::RefineMesh(unsigned nResolutionLevel, unsigned nMinResolution, unsigned nMaxViews, float fDecimateMesh, unsigned nCloseHoles, unsigned nEnsureEdgeSize, unsigned nMaxFaceArea, unsigned nScales, float fScaleStep, unsigned nAlternatePair, float fRegularityWeight, float fRatioRigidityElasticity, float fGradientStep, float fThPlanarVertex, unsigned nReduceMemory) { if (pointcloud.IsEmpty() && !ImagesHaveNeighbors()) SampleMeshWithVisibility(); MeshRefine refine(*this, nReduceMemory, nAlternatePair, fRegularityWeight, fRatioRigidityElasticity, nResolutionLevel, nMinResolution, nMaxViews, nMaxThreads); if (!refine.IsValid()) return false; // run the mesh optimization on multiple scales (coarse to fine) for (unsigned nScale=0; nScale 2) mesh.Save(MAKE_PATH(String::FormatString("MeshRefine%u.ply", nScales-nScale-1))); #endif // minimize #ifdef MESHOPT_CERES if (fGradientStep == 0) { // DefineProblem refine.ratioRigidityElasticity = 1.f; ceres::MeshProblem* problemData(new ceres::MeshProblem(refine)); ceres::GradientProblem problem(problemData); // SetMinimizerOptions ceres::GradientProblemSolver::Options options; if (VERBOSITY_LEVEL > 1) { options.logging_type = ceres::LoggingType::PER_MINIMIZER_ITERATION; options.minimizer_progress_to_stdout = true; } else { options.logging_type = ceres::LoggingType::SILENT; options.minimizer_progress_to_stdout = false; } options.function_tolerance = 1e-3; options.gradient_tolerance = 1e-7; options.max_num_line_search_step_size_iterations = 10; options.callbacks.push_back(problemData); ceres::GradientProblemSolver::Summary summary; // SolveProblem ceres::Solve(options, problem, problemData->GetParameters(), &summary); DEBUG_ULTIMATE(summary.FullReport().c_str()); switch (summary.termination_type) { case ceres::TerminationType::NO_CONVERGENCE: DEBUG_EXTRA("CERES: maximum number of iterations reached!"); case ceres::TerminationType::CONVERGENCE: case ceres::TerminationType::USER_SUCCESS: break; default: VERBOSE("CERES surface refine error: %s!", summary.message.c_str()); return false; } ASSERT(summary.IsSolutionUsable()); problemData->ApplyParams(); } else #endif // MESHOPT_CERES { // loop a constant number of iterations and apply the gradient int iters(75); double gstep(0.4); if (fGradientStep > 1) { iters = FLOOR2INT(fGradientStep); gstep = (fGradientStep-(float)iters)*10; } iters = MAXF(iters/(int)(nScale+1),8); const int iterStop(iters*7/10); const int iterStart(fThPlanarVertex > 0 ? iters*4/10 : INT_MAX); Eigen::Matrix gradients(refine.vertices.GetSize(),3); Util::Progress progress(_T("Processed iterations"), iters); GET_LOGCONSOLE().Pause(); for (int iter=0; iter= iterStart && (iter-iterStart)%3 == 0 && iters-iter > 5); // evaluate residuals and gradients if (bAdaptMesh) refine.vertexDepth.Resize(refine.vertices.GetSize()); const double cost = refine.ScoreMesh(gradients.data()); double gv(0); VIndex numVertsRemoved(0); if (bAdaptMesh) { // apply gradients and // remove planar vertices (small gradient and almost on the center of their surrounding patch) ASSERT(refine.vertexDepth.GetSize() == refine.vertices.GetSize()); Mesh::VertexIdxArr vertexRemove; FOREACH(v, refine.vertices) { Vertex& vert = refine.vertices[v]; const Point3d grad(gradients.row(v)); vert -= Cast(grad*gstep); const double gn(norm(grad)); gv += gn; const float depth(refine.vertexDepth[v]); if (depth < FLT_MAX) { const float th(depth*fThPlanarVertex); if (!refine.vertexBoundary[v] && (float)gn < th && norm(refine.smoothGrad1[v]) < th) vertexRemove.Insert(v); } } if (!vertexRemove.IsEmpty()) { numVertsRemoved = vertexRemove.GetSize(); mesh.Decimate(vertexRemove); refine.ListVertexFacesPost(); } refine.vertexDepth.Empty(); } else { // apply gradients FOREACH(v, refine.vertices) { Vertex& vert = refine.vertices[v]; const Point3d grad(gradients.row(v)); vert -= Cast(grad*gstep); gv += norm(grad); } } DEBUG_EXTRA("\t%2d. f: %.5f (%.4e)\tg: %.5f (%.4e - %.4e)\ts: %.3f\tv: %5u", iter+1, cost, cost/refine.vertices.GetSize(), gradients.norm(), gradients.norm()/refine.vertices.GetSize(), gv/refine.vertices.GetSize(), gstep, numVertsRemoved); gstep *= 0.98; progress.display(iter); } GET_LOGCONSOLE().Play(); progress.close(); } #if TD_VERBOSE != TD_VERBOSE_OFF if (VERBOSITY_LEVEL > 2) mesh.Save(MAKE_PATH(String::FormatString("MeshRefined%u.ply", nScales-nScale-1))); #endif } return true; } // RefineMesh /*----------------------------------------------------------------*/