You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1428 lines
49 KiB

/*
* SceneRefine.cpp
*
* Copyright (c) 2014-2015 SEACAVE
*
* Author(s):
*
* cDc <cdc.seacave@gmail.com>
*
*
* 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 <http://www.gnu.org/licenses/>.
*
*
* 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<type>& var = *ImagePool<type>()
#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<type> 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<Real> Grad;
typedef CLISTDEF0IDX(Grad,VIndex) GradArr;
typedef TImage<cuint32_t> FaceMap;
typedef TImage<Point3f> BaryMap;
// store necessary data about a view
struct View {
typedef TPoint2<float> Grad;
typedef TImage<Grad> ImageGrad;
Image32F image; // image pixels
ImageGrad imageGrad; // image pixel gradients
TImage<Real> imageMean; // image pixels mean
TImage<Real> 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<RasterMesh> {
typedef TRasterMesh<RasterMesh> 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 <typename TP, typename TX, typename T, typename TJ>
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<Real>& imageMean, TImage<Real>& imageVar);
static float ComputeLocalZNCC(
const Image32F& imageA, const TImage<Real>& imageMeanA, const TImage<Real>& imageVarA,
const Image32F& imageB, const TImage<Real>& imageMeanB, const TImage<Real>& imageVarB,
const BitMatrix& mask, TImage<Real>& imageZNCC, TImage<Real>& 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<Real>& 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<typename TYPE>
static TYPE* TypePool(TYPE* = NULL);
template<typename TYPE>
static inline TImage<TYPE>* ImagePool(TImage<TYPE>* pImage = NULL) { return TypePool< TImage<TYPE> >(pImage); }
static inline BitMatrix* BitMatrixPool(BitMatrix* pMask = NULL) { return TypePool<BitMatrix>(pMask); }
static void* ThreadWorkerTmp(void*);
void ThreadWorker();
void WaitThreadWorkers(size_t nJobs);
void ThSelectNeighbors(uint32_t idxImage, std::unordered_set<uint64_t>& 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<SEACAVE::Thread> 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<typename TYPE>
TYPE* MeshRefine::TypePool(TYPE* pObj)
{
typedef CAutoPtr<TYPE> TypePtr;
static CriticalSection cs;
static cList<TypePtr,TYPE*> objects;
static cList<TYPE*,TYPE*,0> 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<uint64_t>& mapPairs;
unsigned nMaxViews;
bool Run(void* pArgs) {
((MeshRefine*)pArgs)->ThSelectNeighbors(idxImage, mapPairs, nMaxViews);
return true;
}
EVTSelectNeighbors(uint32_t _idxImage, std::unordered_set<uint64_t>& _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<SEACAVE::Thread> 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<uint64_t> 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<float,5> 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<Mesh::AreaArr> 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<faceMap.rows; ++j) {
for (int i=0; i<faceMap.cols; ++i) {
const FIndex idxFace(faceMap(j,i));
ASSERT((idxFace == NO_ID && views[idxImage].depthMap(j,i) == 0) || (idxFace != NO_ID && views[idxImage].depthMap(j,i) > 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<vertices.GetSize()) {
const VIndex idxNext(MINF(idx+idxStep, vertices.GetSize()));
events.AddEvent(new EVTSmoothVertices1(idx, idxNext));
idx = idxNext;
}
WaitThreadWorkers(threads.GetSize());
}
// loop through all vertices and compute the smoothing gradient
smoothGrad2.Resize(vertices.GetSize());
{
ASSERT(events.IsEmpty());
VIndex idx(0);
while (idx<vertices.GetSize()) {
const VIndex idxNext(MINF(idx+idxStep, vertices.GetSize()));
events.AddEvent(new EVTSmoothVertices2(idx, idxNext));
idx = idxNext;
}
WaitThreadWorkers(threads.GetSize());
}
// set the final gradient as the combination of photometric and smoothness gradients
if (ratioRigidityElasticity >= 1.f) {
FOREACH(v, vertices)
((Point3d*)gradients)[v] = photoGradNorm[v] > 0 ?
Cast<double>(photoGrad[v]/photoGradNorm[v] + smoothGrad2[v]*weightRegularity) :
Cast<double>(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<double>(photoGrad[v]/photoGradNorm[v] + smoothGrad2[v]*elasticity - smoothGrad1[v]*rigidity) :
Cast<double>(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 <typename TP, typename TX, typename T, typename TJ>
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<int,3>(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<float> Sampler;
const Sampler sampler;
mask.create(imageA.size());
mask.memset(0);
for (int j=0; j<depthMapA.rows; ++j) {
for (int i=0; i<depthMapA.cols; ++i) {
const Depth& depthA = depthMapA(j,i);
if (depthA <= 0)
continue;
const Point3 X(cameraA.TransformPointI2W(Point3(i,j,depthA)));
const Point3f ptC(cameraB.TransformPointW2C(X));
const Point2f pt(cameraB.TransformPointC2I(ptC));
if (!IsDepthSimilar(depthMapB, pt, ptC.z))
continue;
imageA(j,i) = imageB.sample<Sampler,Sampler::Type>(sampler, pt);
mask.set(j,i);
}
}
}
// compute local variance for each image pixel
void MeshRefine::ComputeLocalVariance(const Image32F& image, const BitMatrix& mask, TImage<Real>& imageMean, TImage<Real>& 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<RowsEnd; ++r) {
for (int c=HalfSize; c<ColsEnd; ++c) {
if (!mask(r,c))
continue;
imageMean(r,c) = (Real)((
imageSum(r+HalfSize+1, c+HalfSize+1) -
imageSum(r+HalfSize+1, c-HalfSize ) -
imageSum(r-HalfSize, c+HalfSize+1) +
imageSum(r-HalfSize, c-HalfSize ) ) * (1.0/(double)n));
}
}
DST_Image(imageSum);
for (int r=HalfSize; r<RowsEnd; ++r) {
for (int c=HalfSize; c<ColsEnd; ++c) {
if (!mask(r,c))
continue;
const Real sumSq = (Real)((
imageSumSq(r+HalfSize+1, c+HalfSize+1) -
imageSumSq(r+HalfSize+1, c-HalfSize ) -
imageSumSq(r-HalfSize, c+HalfSize+1) +
imageSumSq(r-HalfSize, c-HalfSize ) ) * (1.0/(double)n));
imageVar(r,c) = MAXF(sumSq-SQUARE(imageMean(r,c)), Real(0.0001));
}
}
DST_Image(imageSumSq);
}
// compute local ZNCC and its gradient for each image pixel
float MeshRefine::ComputeLocalZNCC(
const Image32F& imageA, const TImage<Real>& imageMeanA, const TImage<Real>& imageVarA,
const Image32F& imageB, const TImage<Real>& imageMeanB, const TImage<Real>& imageVarB,
const BitMatrix& mask, TImage<Real>& imageZNCC, TImage<Real>& 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<RowsEnd; ++r) {
for (int c=HalfSize; c<ColsEnd; ++c) {
if (!mask(r, c))
continue;
const Real cv = (Real)((
imageABSum(r+HalfSize+1, c+HalfSize+1) -
imageABSum(r+HalfSize+1, c-HalfSize ) -
imageABSum(r-HalfSize, c+HalfSize+1) +
imageABSum(r-HalfSize, c-HalfSize ) ) * (1.0/(double)n));
const Real invSqrtVAVB(Real(1)/SQRT(imageVarA(r,c)*imageVarB(r,c)));
imageZNCC(r,c) = (cv - imageMeanA(r,c)*imageMeanB(r,c)) * invSqrtVAVB;
imageInvSqrtVAVB(r,c) = invSqrtVAVB;
}
}
DST_Image(imageABSum);
imageDZNCC.memset(0);
for (int r=HalfSize; r<RowsEnd; ++r) {
for (int c=HalfSize; c<ColsEnd; ++c) {
if (!mask(r,c))
continue;
#if 1
const Real ZNCC(imageZNCC(r,c));
const Real invSqrtVAVB(imageInvSqrtVAVB(r,c));
const Real ZNCCinvVB(ZNCC/imageVarB(r,c));
const Real dZNCC((Real)imageA(r,c)*invSqrtVAVB - (Real)imageB(r,c)*ZNCCinvVB + imageMeanB(r,c)*ZNCCinvVB - imageMeanA(r,c)*invSqrtVAVB);
#else
Real sumA(0), sumB(0), sumC(0);
int n(0);
for (int i=-HalfSize; i<=HalfSize; ++i) {
const int rw(r+i);
for (int j=-HalfSize; j<=HalfSize; ++j) {
const int cw(c+j);
if (!mask(rw,cw))
continue;
const Real invSqrtVAVB(imageInvSqrtVAVB(rw,cw));
const Real ZNCCinvVB(imageZNCC(rw,cw)/imageVarB(rw,cw));
sumA += invSqrtVAVB;
sumB -= ZNCCinvVB;
sumC += imageMeanB(rw,cw)*ZNCCinvVB - imageMeanA(rw,cw)*invSqrtVAVB;
++n;
}
}
if (n == 0)
continue;
const Real dZNCC((sumA*imageA(r,c) + sumB*imageB(r,c) + sumC)/(Real)n);
const Real ZNCC(imageZNCC(r,c));
#endif
const Real minVAVB(MINF(imageVarA(r,c),imageVarB(r,c)));
const Real ReliabilityFactor(minVAVB/(minVAVB+Real(0.0015)));
imageDZNCC(r,c) = -ReliabilityFactor*dZNCC;
score += (float)(ReliabilityFactor*(Real(1)-ZNCC));
}
}
DST_Image(imageInvSqrtVAVB);
return score;
}
// compute the photometric gradient for all vertices seen by an image pair
void MeshRefine::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<Real>& 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<View::Grad::Type> Sampler;
const Sampler sampler;
TMatrix<Real,2,3> xJac;
Point2f xB;
photoGrad.Memset(0);
photoGradNorm.Memset(0);
for (int r=HalfSize; r<RowsEnd; ++r) {
for (int c=HalfSize; c<ColsEnd; ++c) {
if (!mask(r,c))
continue;
const FIndex idxFace(faceMapA(r,c));
ASSERT(idxFace != NO_ID);
const Grad N(normals[idxFace]);
const Point3 rayA(cameraA.RayPoint(Point2(c,r)));
const Grad dA(normalized(rayA));
const Real Nd(N.dot(dA));
#if 1
if (Nd > -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<Real,1,2> gB(viewB.imageGrad.sample<Sampler,View::Grad>(sampler, xB));
// compute gradient scale
const Real dZNCC(imageDZNCC(r,c));
const Real sg((gB*(xJac*(const TMatrix<Real,3,1>&)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<idxEnd; ++idxV) {
Grad& grad = smoothGrad1[idxV];
grad = Grad::ZERO;
#if 1
if (vertexBoundary[idxV])
continue;
#endif
const Mesh::VertexIdxArr& verts = vertexVertices[idxV];
if (verts.IsEmpty())
continue;
FOREACH(v, verts)
grad += Cast<Real>(vertices[verts[v]]);
grad = grad/(Real)verts.GetSize() - Cast<Real>(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<idxEnd; ++idxV) {
Grad& grad = smoothGrad2[idxV];
grad = Grad::ZERO;
#if 1
if (vertexBoundary[idxV])
continue;
#endif
const Mesh::VertexIdxArr& verts = vertexVertices[idxV];
if (verts.IsEmpty())
continue;
Real w(0);
FOREACH(v, verts) {
const VIndex idxVert(verts[v]);
grad += smoothGrad1[idxVert];
const VIndex numVert(vertexVertices[idxVert].GetSize());
if (numVert > 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<Event> 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<uint64_t>& 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<GradType> grad[2];
#if 0
cv::Sobel(img, grad[0], cv::DataType<GradType>::type, 1, 0, 3, 1.0/8.0);
cv::Sobel(img, grad[1], cv::DataType<GradType>::type, 0, 1, 3, 1.0/8.0);
#elif 1
const TMatrix<GradType,3,5> kernel(CreateDerivativeKernel3x5());
cv::filter2D(img, grad[0], cv::DataType<GradType>::type, kernel);
cv::filter2D(img, grad[1], cv::DataType<GradType>::type, kernel.t());
#else
const TMatrix<GradType,5,7> kernel(CreateDerivativeKernel5x7());
cv::filter2D(img, grad[0], cv::DataType<GradType>::type, kernel);
cv::filter2D(img, grad[1], cv::DataType<GradType>::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<Real> *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<Real>*)imageMeanA));
DST_Image(*((TImage<Real>*)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 <ceres/ceres.h>
#include <ceres/cost_function.h>
#include <ceres/dynamic_autodiff_cost_function.h>
#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<nScales; ++nScale) {
// init images
const Real scale(POWI(fScaleStep, nScales-nScale-1));
const Real step(POWI(2.f, nScales-nScale));
DEBUG_ULTIMATE("Refine mesh at: %.2f image scale", scale);
if (!refine.InitImages(scale, Real(0.12)*step+Real(0.2)))
return false;
// extract array of triangles incident to each vertex
refine.ListVertexFacesPre();
// automatic mesh subdivision
refine.SubdivideMesh(nMaxFaceArea, nScale == 0 ? fDecimateMesh : 1.f, nCloseHoles, nEnsureEdgeSize);
// extract array of triangle normals
refine.ListVertexFacesPost();
#if TD_VERBOSE != TD_VERBOSE_OFF
if (VERBOSITY_LEVEL > 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<double,Eigen::Dynamic,3,Eigen::RowMajor> gradients(refine.vertices.GetSize(),3);
Util::Progress progress(_T("Processed iterations"), iters);
GET_LOGCONSOLE().Pause();
for (int iter=0; iter<iters; ++iter) {
refine.iteration = (unsigned)iter;
refine.nAlternatePair = (iter+1 < iters ? nAlternatePair : 0);
refine.ratioRigidityElasticity = (iter <= iterStop ? fRatioRigidityElasticity : 1.f);
const bool bAdaptMesh(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<Vertex::Type>(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<Vertex::Type>(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
/*----------------------------------------------------------------*/