diff --git a/SuperBuild/CMakeLists.txt b/SuperBuild/CMakeLists.txt
index 97636956..1584845f 100644
--- a/SuperBuild/CMakeLists.txt
+++ b/SuperBuild/CMakeLists.txt
@@ -103,7 +103,9 @@ set(custom_libs OpenGV
Catkin
Ecto
LAStools
- PDAL)
+ PDAL
+ MvsTexturing
+)
foreach(lib ${custom_libs})
SETUP_EXTERNAL_PROJECT_CUSTOM(${lib})
diff --git a/SuperBuild/cmake/External-MvsTexturing.cmake b/SuperBuild/cmake/External-MvsTexturing.cmake
new file mode 100644
index 00000000..601948f2
--- /dev/null
+++ b/SuperBuild/cmake/External-MvsTexturing.cmake
@@ -0,0 +1,29 @@
+set(_proj_name mvstexuring)
+set(_SB_BINARY_DIR "${SB_BINARY_DIR}/${_proj_name}")
+
+ExternalProject_Add(${_proj_name}
+ DEPENDS
+ PREFIX ${_SB_BINARY_DIR}
+ TMP_DIR ${_SB_BINARY_DIR}/tmp
+ STAMP_DIR ${_SB_BINARY_DIR}/stamp
+ #--Download step--------------
+ DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}
+ URL https://github.com/nmoehrle/mvs-texturing/archive/dab68acaa693275c183c254a958130ee6d29c3e4.zip
+ URL_MD5 0b0466f5d1046699594ce7fc77bdad02
+ #--Update/Patch step----------
+ UPDATE_COMMAND ""
+ #--Configure step-------------
+ SOURCE_DIR ${SB_SOURCE_DIR}/${_proj_name}
+ CMAKE_ARGS
+ -DRESEARCH=OFF
+ -DCMAKE_BUILD_TYPE:STRING=Release
+ -DCMAKE_INSTALL_PREFIX:PATH=${SB_INSTALL_DIR}
+ #--Build step-----------------
+ BINARY_DIR ${_SB_BINARY_DIR}
+ #--Install step---------------
+ INSTALL_DIR ${SB_INSTALL_DIR}
+ #--Output logging-------------
+ LOG_DOWNLOAD OFF
+ LOG_CONFIGURE OFF
+ LOG_BUILD OFF
+)
diff --git a/ccd_defs_check.py b/ccd_defs_check.py
old mode 100755
new mode 100644
diff --git a/configure.sh b/configure.sh
index d46fa7fa..2223e0ab 100644
--- a/configure.sh
+++ b/configure.sh
@@ -26,8 +26,16 @@ sudo apt-get install build-essential \
git \
python-pip \
libgdal-dev \
+ gdal-bin \
libgeotiff-dev \
pkg-config -y
+
+# If we have ubuntu version 14.04, the cmake version in apt-get is too low for mvs-texturing.
+if [[ `lsb_release -rs` == "14.04" ]];
+then
+ bash upgradecmake.sh
+fi
+
if [ $? -ne 0 ]
then
echo -e "\e[1;31mERROR: \e[39mWhen Installing Required Requisites\e[0m"
diff --git a/hooks/pre-commit b/hooks/pre-commit
old mode 100755
new mode 100644
diff --git a/modules/odm_georef/CMakeLists.txt.user b/modules/odm_georef/CMakeLists.txt.user
deleted file mode 100644
index 48239e25..00000000
--- a/modules/odm_georef/CMakeLists.txt.user
+++ /dev/null
@@ -1,203 +0,0 @@
-
-
-
-
-
- ProjectExplorer.Project.ActiveTarget
- 0
-
-
- ProjectExplorer.Project.EditorSettings
-
- true
- false
-
- Cpp
-
- CppGlobal
-
-
-
- QmlJS
-
- QmlJSGlobal
-
-
- 2
- System
- false
- 4
- true
- 1
- true
- 0
- true
- 0
- 8
- true
- 1
- true
- true
- true
- false
-
-
-
- ProjectExplorer.Project.PluginSettings
-
-
-
- ProjectExplorer.Project.Target.0
-
- Desktop
-
- CMakeProjectManager.DefaultCMakeTarget
- 0
- 0
- 0
-
- /home/spotscale/odm/OpenDroneMap-texturing_orthophoto_spotscale_additions/odm_georef-build
- ProjectExplorer.ToolChain.Gcc:/usr/bin/g++.x86-linux-generic-elf-32bit./usr/bin/gdb
- ProjectExplorer.ToolChain.Gcc:/usr/bin/g++.x86-linux-generic-elf-32bit./usr/bin/gdb
-
-
-
-
- false
- Make
-
- CMakeProjectManager.MakeStep
-
- 1
- Build
-
- ProjectExplorer.BuildSteps.Build
-
-
-
- clean
-
- true
- Make
-
- CMakeProjectManager.MakeStep
-
- 1
- Clean
-
- ProjectExplorer.BuildSteps.Clean
-
- 2
- false
-
- all
-
- CMakeProjectManager.CMakeBuildConfiguration
-
- 1
-
-
- 0
- Deploy
-
- ProjectExplorer.BuildSteps.Deploy
-
- 1
- No deployment
-
- ProjectExplorer.DefaultDeployConfiguration
-
- 1
-
- true
- true
-
-
- false
- false
- false
- false
- false
- false
- false
- false
- true
- true
- 0.01
- 0.01
- 10
- 10
- true
- true
- 25
- 25
-
-
- true
- true
- valgrind
- valgrind
-
- 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
-
-
- 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
-
- 2
- odm_georef
- -verbose -bundleFile ../../../shared_folder/seneca/reconstruction-with-image-size-1200/pmvs/bundle.rd.out -inputFile ../../../shared_folder/seneca/reconstruction-with-image-size-1200-results/odm_texturing/odm_textured_model.obj -imagesListPath ../../../shared_folder/seneca/reconstruction-with-image-size-1200/pmvs/list.rd.txt -gcpFile ../../../shared_folder/seneca_georef_input/gcp_list.txt -imagesPath ../../../shared_folder/seneca/ -bundleResizedTo 1200
- false
-
-
- odm_georef
-
- CMakeProjectManager.CMakeRunConfiguration.
- 3768
- true
- false
- false
-
- 1
-
-
-
- ProjectExplorer.Project.TargetCount
- 1
-
-
- ProjectExplorer.Project.Updater.EnvironmentId
- {785a73be-b55f-490c-9d46-e1451c235840}
-
-
- ProjectExplorer.Project.Updater.FileVersion
- 10
-
-
diff --git a/modules/odm_georef/src/Georef.cpp b/modules/odm_georef/src/Georef.cpp
index 10fac52c..6ff34a68 100644
--- a/modules/odm_georef/src/Georef.cpp
+++ b/modules/odm_georef/src/Georef.cpp
@@ -6,9 +6,6 @@
#include
#include
-// Modified PCL
-#include "modifiedPclFunctions.hpp"
-
// This
#include "Georef.hpp"
@@ -753,7 +750,7 @@ void Georef::performGeoreferencingWithGCP()
log_ << "Reading mesh file " << inputObjFilename_ <<"\n";
log_ << '\n';
pcl::TextureMesh mesh;
- if (pcl::io::loadOBJFile(inputObjFilename_, mesh) == -1)
+ if (loadObjFile(inputObjFilename_, mesh) == -1)
{
throw GeorefException("Error when reading model from:\n" + inputObjFilename_ + "\n");
}
@@ -1077,7 +1074,7 @@ void Georef::createGeoreferencedModelFromExifData()
log_ << '\n';
log_ << "Reading mesh file...\n";
pcl::TextureMesh mesh;
- pcl::io::loadOBJFile(inputObjFilename_, mesh);
+ loadObjFile(inputObjFilename_, mesh);
log_ << ".. mesh file read.\n";
// Contains the vertices of the mesh.
@@ -1249,3 +1246,364 @@ void Georef::printGeorefSystem()
}
+bool Georef::loadObjFile(std::string inputFile, pcl::TextureMesh &mesh)
+{
+ int data_type;
+ unsigned int data_idx;
+ int file_version;
+ int offset = 0;
+ Eigen::Vector4f origin;
+ Eigen::Quaternionf orientation;
+
+ if (!readHeader(inputFile, mesh.cloud, origin, orientation, file_version, data_type, data_idx, offset))
+ {
+ throw GeorefException("Problem reading header in modelfile!\n");
+ }
+
+ std::ifstream fs;
+
+ fs.open (inputFile.c_str (), std::ios::binary);
+ if (!fs.is_open () || fs.fail ())
+ {
+ //PCL_ERROR ("[pcl::OBJReader::readHeader] Could not open file '%s'! Error : %s\n", file_name.c_str (), strerror(errno));
+ fs.close ();
+ log_<<"Could not read mesh from file ";
+ log_ << inputFile.c_str();
+ log_ <<"\n";
+
+ throw GeorefException("Problem reading mesh from file!\n");
+ }
+
+ // Seek at the given offset
+ fs.seekg (data_idx, std::ios::beg);
+
+ // Get normal_x field indices
+ int normal_x_field = -1;
+ for (std::size_t i = 0; i < mesh.cloud.fields.size (); ++i)
+ {
+ if (mesh.cloud.fields[i].name == "normal_x")
+ {
+ normal_x_field = i;
+ break;
+ }
+ }
+
+ std::size_t v_idx = 0;
+ std::size_t vn_idx = 0;
+ std::size_t vt_idx = 0;
+ std::size_t f_idx = 0;
+ std::string line;
+ std::vector st;
+ std::vector coordinates;
+ std::vector allTexCoords;
+
+ std::map f2vt;
+
+ try
+ {
+ while (!fs.eof ())
+ {
+ getline (fs, line);
+ // Ignore empty lines
+ if (line == "")
+ continue;
+
+ // Tokenize the line
+ std::stringstream sstream (line);
+ sstream.imbue (std::locale::classic ());
+ line = sstream.str ();
+ boost::trim (line);
+ boost::split (st, line, boost::is_any_of ("\t\r "), boost::token_compress_on);
+
+ // Ignore comments
+ if (st[0] == "#")
+ continue;
+ // Vertex
+ if (st[0] == "v")
+ {
+ try
+ {
+ for (int i = 1, f = 0; i < 4; ++i, ++f)
+ {
+ float value = boost::lexical_cast (st[i]);
+ memcpy (&mesh.cloud.data[v_idx * mesh.cloud.point_step + mesh.cloud.fields[f].offset], &value, sizeof (float));
+ }
+
+ ++v_idx;
+ }
+ catch (const boost::bad_lexical_cast &e)
+ {
+ log_<<"Unable to convert %s to vertex coordinates!\n";
+ throw GeorefException("Unable to convert %s to vertex coordinates!");
+ }
+ continue;
+ }
+ // Vertex normal
+ if (st[0] == "vn")
+ {
+ try
+ {
+ for (int i = 1, f = normal_x_field; i < 4; ++i, ++f)
+ {
+ float value = boost::lexical_cast (st[i]);
+ memcpy (&mesh.cloud.data[vn_idx * mesh.cloud.point_step + mesh.cloud.fields[f].offset],
+ &value,
+ sizeof (float));
+ }
+ ++vn_idx;
+ }
+ catch (const boost::bad_lexical_cast &e)
+ {
+ log_<<"Unable to convert %s to vertex normal!\n";
+ throw GeorefException("Unable to convert %s to vertex normal!");
+ }
+ continue;
+ }
+ // Texture coordinates
+ if (st[0] == "vt")
+ {
+ try
+ {
+ Eigen::Vector3f c (0, 0, 0);
+ for (std::size_t i = 1; i < st.size (); ++i)
+ c[i-1] = boost::lexical_cast (st[i]);
+
+ if (c[2] == 0)
+ coordinates.push_back (Eigen::Vector2f (c[0], c[1]));
+ else
+ coordinates.push_back (Eigen::Vector2f (c[0]/c[2], c[1]/c[2]));
+ ++vt_idx;
+
+ }
+ catch (const boost::bad_lexical_cast &e)
+ {
+ log_<<"Unable to convert %s to vertex texture coordinates!\n";
+ throw GeorefException("Unable to convert %s to vertex texture coordinates!");
+ }
+ continue;
+ }
+ // Material
+ if (st[0] == "usemtl")
+ {
+ mesh.tex_polygons.push_back (std::vector ());
+ mesh.tex_materials.push_back (pcl::TexMaterial ());
+ for (std::size_t i = 0; i < companions_.size (); ++i)
+ {
+ std::vector::const_iterator mat_it = companions_[i].getMaterial (st[1]);
+ if (mat_it != companions_[i].materials_.end ())
+ {
+ mesh.tex_materials.back () = *mat_it;
+ break;
+ }
+ }
+ // We didn't find the appropriate material so we create it here with name only.
+ if (mesh.tex_materials.back ().tex_name == "")
+ mesh.tex_materials.back ().tex_name = st[1];
+ mesh.tex_coordinates.push_back (coordinates);
+ coordinates.clear ();
+ continue;
+ }
+ // Face
+ if (st[0] == "f")
+ {
+ //We only care for vertices indices
+ pcl::Vertices face_v; face_v.vertices.resize (st.size () - 1);
+ for (std::size_t i = 1; i < st.size (); ++i)
+ {
+ int v;
+ sscanf (st[i].c_str (), "%d", &v);
+ v = (v < 0) ? v_idx + v : v - 1;
+ face_v.vertices[i-1] = v;
+
+ int v2, vt, vn;
+ sscanf (st[i].c_str (), "%d/%d/%d", &v2, &vt, &vn);
+ f2vt[3*(f_idx) + i-1] = vt-1;
+ }
+ mesh.tex_polygons.back ().push_back (face_v);
+ ++f_idx;
+ continue;
+ }
+ }
+ }
+ catch (const char *exception)
+ {
+ fs.close ();
+ log_<<"Unable to read file!\n";
+ throw GeorefException("Unable to read file!");
+ }
+
+ if (vt_idx != v_idx)
+ {
+ std::vector texcoordinates = std::vector(0);
+
+ for (size_t faceIndex = 0; faceIndex < f_idx; ++faceIndex)
+ {
+ for(size_t i = 0; i < 3; ++i)
+ {
+ Eigen::Vector2f vt = mesh.tex_coordinates[0][f2vt[3*faceIndex+i]];
+ texcoordinates.push_back(vt);
+ }
+ }
+
+ mesh.tex_coordinates.clear();
+ mesh.tex_coordinates.push_back(texcoordinates);
+ }
+
+ fs.close();
+ return (0);
+}
+
+bool Georef::readHeader (const std::string &file_name, pcl::PCLPointCloud2 &cloud,
+ Eigen::Vector4f &origin, Eigen::Quaternionf &orientation,
+ int &file_version, int &data_type, unsigned int &data_idx,
+ const int offset)
+{
+ origin = Eigen::Vector4f::Zero ();
+ orientation = Eigen::Quaternionf::Identity ();
+ file_version = 0;
+ cloud.width = cloud.height = cloud.point_step = cloud.row_step = 0;
+ cloud.data.clear ();
+ data_type = 0;
+ data_idx = offset;
+
+ std::ifstream fs;
+ std::string line;
+
+ if (file_name == "" || !boost::filesystem::exists (file_name))
+ {
+ return false;
+ }
+
+ // Open file in binary mode to avoid problem of
+ // std::getline() corrupting the result of ifstream::tellg()
+ fs.open (file_name.c_str (), std::ios::binary);
+ if (!fs.is_open () || fs.fail ())
+ {
+ fs.close ();
+ return false;
+ }
+
+ // Seek at the given offset
+ fs.seekg (offset, std::ios::beg);
+
+ // Read the header and fill it in with wonderful values
+ bool vertex_normal_found = false;
+ bool vertex_texture_found = false;
+ // Material library, skip for now!
+ // bool material_found = false;
+ std::vector material_files;
+ std::size_t nr_point = 0;
+ std::vector st;
+
+ try
+ {
+ while (!fs.eof ())
+ {
+ getline (fs, line);
+ // Ignore empty lines
+ if (line == "")
+ continue;
+
+ // Tokenize the line
+ std::stringstream sstream (line);
+ sstream.imbue (std::locale::classic ());
+ line = sstream.str ();
+ boost::trim (line);
+ boost::split (st, line, boost::is_any_of ("\t\r "), boost::token_compress_on);
+ // Ignore comments
+ if (st.at (0) == "#")
+ continue;
+
+ // Vertex
+ if (st.at (0) == "v")
+ {
+ ++nr_point;
+ continue;
+ }
+
+ // Vertex texture
+ if ((st.at (0) == "vt") && !vertex_texture_found)
+ {
+ vertex_texture_found = true;
+ continue;
+ }
+
+ // Vertex normal
+ if ((st.at (0) == "vn") && !vertex_normal_found)
+ {
+ vertex_normal_found = true;
+ continue;
+ }
+
+ // Material library, skip for now!
+ if (st.at (0) == "mtllib")
+ {
+ material_files.push_back (st.at (1));
+ continue;
+ }
+ }
+ }
+ catch (const char *exception)
+ {
+ fs.close ();
+ return false;
+ }
+
+ if (!nr_point)
+ {
+ fs.close ();
+ return false;
+ }
+
+ int field_offset = 0;
+ for (int i = 0; i < 3; ++i, field_offset += 4)
+ {
+ cloud.fields.push_back (pcl::PCLPointField ());
+ cloud.fields[i].offset = field_offset;
+ cloud.fields[i].datatype = pcl::PCLPointField::FLOAT32;
+ cloud.fields[i].count = 1;
+ }
+
+ cloud.fields[0].name = "x";
+ cloud.fields[1].name = "y";
+ cloud.fields[2].name = "z";
+
+ if (vertex_normal_found)
+ {
+ std::string normals_names[3] = { "normal_x", "normal_y", "normal_z" };
+ for (int i = 0; i < 3; ++i, field_offset += 4)
+ {
+ cloud.fields.push_back (pcl::PCLPointField ());
+ pcl::PCLPointField& last = cloud.fields.back ();
+ last.name = normals_names[i];
+ last.offset = field_offset;
+ last.datatype = pcl::PCLPointField::FLOAT32;
+ last.count = 1;
+ }
+ }
+
+ if (material_files.size () > 0)
+ {
+ for (std::size_t i = 0; i < material_files.size (); ++i)
+ {
+ pcl::MTLReader companion;
+
+ if (companion.read (file_name, material_files[i]))
+ {
+ log_<<"Problem reading material file.";
+ }
+
+ companions_.push_back (companion);
+ }
+ }
+
+ cloud.point_step = field_offset;
+ cloud.width = nr_point;
+ cloud.height = 1;
+ cloud.row_step = cloud.point_step * cloud.width;
+ cloud.is_dense = true;
+ cloud.data.resize (cloud.point_step * nr_point);
+ fs.close ();
+ return true;
+}
+
diff --git a/modules/odm_georef/src/Georef.hpp b/modules/odm_georef/src/Georef.hpp
index ad89506c..2cdb884c 100644
--- a/modules/odm_georef/src/Georef.hpp
+++ b/modules/odm_georef/src/Georef.hpp
@@ -8,6 +8,8 @@
// PCL
#include
#include
+// Modified PCL
+#include "modifiedPclFunctions.hpp"
// Logger
#include "Logger.hpp"
@@ -204,6 +206,24 @@ private:
**/
void printGeorefSystem();
+ /*!
+ * \brief Loads a model from an .obj file (replacement for the pcl obj loader).
+ *
+ * \param inputFile Path to the .obj file.
+ * \param mesh The model.
+ * \return True if model was loaded successfully.
+ */
+ bool loadObjFile(std::string inputFile, pcl::TextureMesh &mesh);
+
+ /*!
+ * \brief Function is compied straight from the function in the pcl::io module.
+ */
+ bool readHeader (const std::string &file_name, pcl::PCLPointCloud2 &cloud,
+ Eigen::Vector4f &origin, Eigen::Quaternionf &orientation,
+ int &file_version, int &data_type, unsigned int &data_idx,
+ const int offset);
+
+
Logger log_; /**< Logging object. */
std::string logFile_; /**< The path to the output log file. */
@@ -230,6 +250,10 @@ private:
std::vector imageList_; /**< A vector containing the names of the corresponding cameras. **/
GeorefSystem georefSystem_; /**< Contains the georeference system. **/
+
+ bool multiMaterial_; /**< True if the mesh has multiple materials. **/
+
+ std::vector companions_; /**< Materials (used by loadOBJFile). **/
};
/*!
diff --git a/modules/odm_georef/src/modifiedPclFunctions.hpp b/modules/odm_georef/src/modifiedPclFunctions.hpp
index 5739df14..0e6a77dd 100644
--- a/modules/odm_georef/src/modifiedPclFunctions.hpp
+++ b/modules/odm_georef/src/modifiedPclFunctions.hpp
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
int saveOBJFile(const std::string &file_name, const pcl::TextureMesh &tex_mesh, unsigned precision);
diff --git a/modules/odm_orthophoto/CMakeLists.txt.user b/modules/odm_orthophoto/CMakeLists.txt.user
deleted file mode 100644
index 05ffbfd9..00000000
--- a/modules/odm_orthophoto/CMakeLists.txt.user
+++ /dev/null
@@ -1,203 +0,0 @@
-
-
-
-
-
- ProjectExplorer.Project.ActiveTarget
- 0
-
-
- ProjectExplorer.Project.EditorSettings
-
- true
- false
-
- Cpp
-
- CppGlobal
-
-
-
- QmlJS
-
- QmlJSGlobal
-
-
- 2
- System
- false
- 4
- true
- 1
- true
- 0
- true
- 0
- 8
- true
- 1
- true
- true
- true
- false
-
-
-
- ProjectExplorer.Project.PluginSettings
-
-
-
- ProjectExplorer.Project.Target.0
-
- Desktop
-
- CMakeProjectManager.DefaultCMakeTarget
- 0
- 0
- 0
-
- /home/spotscale/odm/OpenDroneMap-texturing_orthophoto_spotscale_additions/odm_orthophoto-build
- ProjectExplorer.ToolChain.Gcc:/usr/bin/g++.x86-linux-generic-elf-32bit./usr/bin/gdb
- ProjectExplorer.ToolChain.Gcc:/usr/bin/g++.x86-linux-generic-elf-32bit./usr/bin/gdb
-
-
-
-
- false
- Make
-
- CMakeProjectManager.MakeStep
-
- 1
- Build
-
- ProjectExplorer.BuildSteps.Build
-
-
-
- clean
-
- true
- Make
-
- CMakeProjectManager.MakeStep
-
- 1
- Clean
-
- ProjectExplorer.BuildSteps.Clean
-
- 2
- false
-
- all
-
- CMakeProjectManager.CMakeBuildConfiguration
-
- 1
-
-
- 0
- Deploy
-
- ProjectExplorer.BuildSteps.Deploy
-
- 1
- No deployment
-
- ProjectExplorer.DefaultDeployConfiguration
-
- 1
-
- true
- true
-
-
- false
- false
- false
- false
- false
- false
- false
- false
- true
- true
- 0.01
- 0.01
- 10
- 10
- true
- true
- 25
- 25
-
-
- true
- true
- valgrind
- valgrind
-
- 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
-
-
- 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
-
- 2
- odm_orthophoto
-
- false
-
-
- odm_orthophoto
-
- CMakeProjectManager.CMakeRunConfiguration.
- 3768
- true
- false
- false
-
- 1
-
-
-
- ProjectExplorer.Project.TargetCount
- 1
-
-
- ProjectExplorer.Project.Updater.EnvironmentId
- {785a73be-b55f-490c-9d46-e1451c235840}
-
-
- ProjectExplorer.Project.Updater.FileVersion
- 10
-
-
diff --git a/modules/odm_orthophoto/src/OdmOrthoPhoto.cpp b/modules/odm_orthophoto/src/OdmOrthoPhoto.cpp
index ce5056f4..ff771283 100644
--- a/modules/odm_orthophoto/src/OdmOrthoPhoto.cpp
+++ b/modules/odm_orthophoto/src/OdmOrthoPhoto.cpp
@@ -266,7 +266,7 @@ void OdmOrthoPhoto::printHelp()
log_ << "Call the program with flag \"-help\", or without parameters to print this message, or check any generated log file.\n";
log_ << "Call the program with flag \"-verbose\", to print log messages in the standard output stream as well as in the log file.\n\n";
- log_ << "Parameters are specified as: \"- \", (without <>), and the following parameters are configureable:n";
+ log_ << "Parameters are specified as: \"- \", (without <>), and the following parameters are configureable:\n";
log_ << "\"-inputFile \" (mandatory)\n";
log_ << "\"Input obj file that must contain a textured mesh.\n\n";
@@ -321,18 +321,20 @@ void OdmOrthoPhoto::createOrthoPhoto()
log_ << "Reading mesh file...\n";
// The textureds mesh.
pcl::TextureMesh mesh;
- pcl::io::loadOBJFile(inputFile_, mesh);
+ loadObjFile(inputFile_, mesh);
log_ << ".. mesh file read.\n\n";
// Does the model have more than one material?
multiMaterial_ = 1 < mesh.tex_materials.size();
+ bool splitModel = false;
+
if(multiMaterial_)
{
// Need to check relationship between texture coordinates and faces.
if(!isModelOk(mesh))
{
- throw OdmOrthoPhotoException("Could not generate ortho photo: The given mesh has multiple textures, but the number of texture coordinates is NOT equal to 3 times the number of faces.");
+ splitModel = true;
}
}
@@ -386,6 +388,58 @@ void OdmOrthoPhoto::createOrthoPhoto()
pcl::PointCloud::Ptr meshCloud (new pcl::PointCloud);
pcl::fromPCLPointCloud2 (mesh.cloud, *meshCloud);
+ // Split model and make copies of vertices and texture coordinates for all faces
+ //if (splitModel)
+ if (splitModel)
+ {
+ pcl::PointCloud::Ptr meshCloudSplit (new pcl::PointCloud);
+ std::vector textureCoordinates = std::vector(0);
+
+ size_t vertexIndexCount = 0;
+ for(size_t t = 0; t < mesh.tex_polygons.size(); ++t)
+ {
+
+ for(size_t faceIndex = 0; faceIndex < mesh.tex_polygons[t].size(); ++faceIndex)
+ {
+ pcl::Vertices polygon = mesh.tex_polygons[t][faceIndex];
+
+ // The index to the vertices of the polygon.
+ size_t v1i = polygon.vertices[0];
+ size_t v2i = polygon.vertices[1];
+ size_t v3i = polygon.vertices[2];
+
+ // The polygon's points.
+ pcl::PointXYZ v1 = meshCloud->points[v1i];
+ pcl::PointXYZ v2 = meshCloud->points[v2i];
+ pcl::PointXYZ v3 = meshCloud->points[v3i];
+
+ Eigen::Vector2f vt1 = mesh.tex_coordinates[0][3*faceIndex];
+ Eigen::Vector2f vt2 = mesh.tex_coordinates[0][3*faceIndex + 1];
+ Eigen::Vector2f vt3 = mesh.tex_coordinates[0][3*faceIndex + 2];
+
+ meshCloudSplit->points.push_back(v1);
+ textureCoordinates.push_back(vt1);
+ mesh.tex_polygons[t][faceIndex].vertices[0] = vertexIndexCount;
+ ++vertexIndexCount;
+
+ meshCloudSplit->points.push_back(v2);
+ textureCoordinates.push_back(vt2);
+ mesh.tex_polygons[t][faceIndex].vertices[1] = vertexIndexCount;
+ ++vertexIndexCount;
+
+ meshCloudSplit->points.push_back(v3);
+ textureCoordinates.push_back(vt3);
+ mesh.tex_polygons[t][faceIndex].vertices[2] = vertexIndexCount;
+ ++vertexIndexCount;
+ }
+ }
+
+ mesh.tex_coordinates.clear();
+ mesh.tex_coordinates.push_back(textureCoordinates);
+
+ meshCloud = meshCloudSplit;
+ }
+
// Creates a transformation which aligns the area for the ortho photo.
Eigen::Transform transform = getROITransform(xMin, -yMax);
@@ -401,6 +455,7 @@ void OdmOrthoPhoto::createOrthoPhoto()
{
uvs.insert(uvs.end(), mesh.tex_coordinates[t].begin(), mesh.tex_coordinates[t].end());
}
+ //cv::namedWindow("dsfs");
// The current material texture
cv::Mat texture;
@@ -416,7 +471,7 @@ void OdmOrthoPhoto::createOrthoPhoto()
// The material of the current submesh.
pcl::TexMaterial material = mesh.tex_materials[t];
texture = cv::imread(material.tex_file);
-
+
// Check for missing files.
if(texture.empty())
{
@@ -659,6 +714,7 @@ void OdmOrthoPhoto::drawTexturedTriangle(const cv::Mat &texture, const pcl::Vert
v1u = uvs[3*faceIndex][0]; v1v = uvs[3*faceIndex][1];
v2u = uvs[3*faceIndex+1][0]; v2v = uvs[3*faceIndex+1][1];
v3u = uvs[3*faceIndex+2][0]; v3v = uvs[3*faceIndex+2][1];
+
}
else
{
@@ -994,3 +1050,365 @@ bool OdmOrthoPhoto::isModelOk(const pcl::TextureMesh &mesh)
return 3*nFaces == nTextureCoordinates;
}
+
+
+bool OdmOrthoPhoto::loadObjFile(std::string inputFile, pcl::TextureMesh &mesh)
+{
+ int data_type;
+ unsigned int data_idx;
+ int file_version;
+ int offset = 0;
+ Eigen::Vector4f origin;
+ Eigen::Quaternionf orientation;
+
+ if (!readHeader(inputFile, mesh.cloud, origin, orientation, file_version, data_type, data_idx, offset))
+ {
+ throw OdmOrthoPhotoException("Problem reading header in modelfile!\n");
+ }
+
+ std::ifstream fs;
+
+ fs.open (inputFile.c_str (), std::ios::binary);
+ if (!fs.is_open () || fs.fail ())
+ {
+ //PCL_ERROR ("[pcl::OBJReader::readHeader] Could not open file '%s'! Error : %s\n", file_name.c_str (), strerror(errno));
+ fs.close ();
+ log_<<"Could not read mesh from file ";
+ log_ << inputFile.c_str();
+ log_ <<"\n";
+
+ throw OdmOrthoPhotoException("Problem reading mesh from file!\n");
+ }
+
+ // Seek at the given offset
+ fs.seekg (data_idx, std::ios::beg);
+
+ // Get normal_x field indices
+ int normal_x_field = -1;
+ for (std::size_t i = 0; i < mesh.cloud.fields.size (); ++i)
+ {
+ if (mesh.cloud.fields[i].name == "normal_x")
+ {
+ normal_x_field = i;
+ break;
+ }
+ }
+
+ std::size_t v_idx = 0;
+ std::size_t vn_idx = 0;
+ std::size_t vt_idx = 0;
+ std::size_t f_idx = 0;
+ std::string line;
+ std::vector st;
+ std::vector coordinates;
+ std::vector allTexCoords;
+
+ std::map f2vt;
+
+ try
+ {
+ while (!fs.eof ())
+ {
+ getline (fs, line);
+ // Ignore empty lines
+ if (line == "")
+ continue;
+
+ // Tokenize the line
+ std::stringstream sstream (line);
+ sstream.imbue (std::locale::classic ());
+ line = sstream.str ();
+ boost::trim (line);
+ boost::split (st, line, boost::is_any_of ("\t\r "), boost::token_compress_on);
+
+ // Ignore comments
+ if (st[0] == "#")
+ continue;
+ // Vertex
+ if (st[0] == "v")
+ {
+ try
+ {
+ for (int i = 1, f = 0; i < 4; ++i, ++f)
+ {
+ float value = boost::lexical_cast (st[i]);
+ memcpy (&mesh.cloud.data[v_idx * mesh.cloud.point_step + mesh.cloud.fields[f].offset], &value, sizeof (float));
+ }
+
+ ++v_idx;
+ }
+ catch (const boost::bad_lexical_cast &e)
+ {
+ log_<<"Unable to convert %s to vertex coordinates!\n";
+ throw OdmOrthoPhotoException("Unable to convert %s to vertex coordinates!");
+ }
+ continue;
+ }
+ // Vertex normal
+ if (st[0] == "vn")
+ {
+ try
+ {
+ for (int i = 1, f = normal_x_field; i < 4; ++i, ++f)
+ {
+ float value = boost::lexical_cast (st[i]);
+ memcpy (&mesh.cloud.data[vn_idx * mesh.cloud.point_step + mesh.cloud.fields[f].offset],
+ &value,
+ sizeof (float));
+ }
+ ++vn_idx;
+ }
+ catch (const boost::bad_lexical_cast &e)
+ {
+ log_<<"Unable to convert %s to vertex normal!\n";
+ throw OdmOrthoPhotoException("Unable to convert %s to vertex normal!");
+ }
+ continue;
+ }
+ // Texture coordinates
+ if (st[0] == "vt")
+ {
+ try
+ {
+ Eigen::Vector3f c (0, 0, 0);
+ for (std::size_t i = 1; i < st.size (); ++i)
+ c[i-1] = boost::lexical_cast (st[i]);
+
+ if (c[2] == 0)
+ coordinates.push_back (Eigen::Vector2f (c[0], c[1]));
+ else
+ coordinates.push_back (Eigen::Vector2f (c[0]/c[2], c[1]/c[2]));
+ ++vt_idx;
+
+ }
+ catch (const boost::bad_lexical_cast &e)
+ {
+ log_<<"Unable to convert %s to vertex texture coordinates!\n";
+ throw OdmOrthoPhotoException("Unable to convert %s to vertex texture coordinates!");
+ }
+ continue;
+ }
+ // Material
+ if (st[0] == "usemtl")
+ {
+ mesh.tex_polygons.push_back (std::vector ());
+ mesh.tex_materials.push_back (pcl::TexMaterial ());
+ for (std::size_t i = 0; i < companions_.size (); ++i)
+ {
+ std::vector::const_iterator mat_it = companions_[i].getMaterial (st[1]);
+ if (mat_it != companions_[i].materials_.end ())
+ {
+ mesh.tex_materials.back () = *mat_it;
+ break;
+ }
+ }
+ // We didn't find the appropriate material so we create it here with name only.
+ if (mesh.tex_materials.back ().tex_name == "")
+ mesh.tex_materials.back ().tex_name = st[1];
+ mesh.tex_coordinates.push_back (coordinates);
+ coordinates.clear ();
+ continue;
+ }
+ // Face
+ if (st[0] == "f")
+ {
+ //We only care for vertices indices
+ pcl::Vertices face_v; face_v.vertices.resize (st.size () - 1);
+ for (std::size_t i = 1; i < st.size (); ++i)
+ {
+ int v;
+ sscanf (st[i].c_str (), "%d", &v);
+ v = (v < 0) ? v_idx + v : v - 1;
+ face_v.vertices[i-1] = v;
+
+ int v2, vt, vn;
+ sscanf (st[i].c_str (), "%d/%d/%d", &v2, &vt, &vn);
+ f2vt[3*(f_idx) + i-1] = vt-1;
+ }
+ mesh.tex_polygons.back ().push_back (face_v);
+ ++f_idx;
+ continue;
+ }
+ }
+ }
+ catch (const char *exception)
+ {
+ fs.close ();
+ log_<<"Unable to read file!\n";
+ throw OdmOrthoPhotoException("Unable to read file!");
+ }
+
+ if (vt_idx != v_idx)
+ {
+ std::vector texcoordinates = std::vector(0);
+
+ for (size_t faceIndex = 0; faceIndex < f_idx; ++faceIndex)
+ {
+ for(size_t i = 0; i < 3; ++i)
+ {
+ Eigen::Vector2f vt = mesh.tex_coordinates[0][f2vt[3*faceIndex+i]];
+ texcoordinates.push_back(vt);
+ }
+ }
+
+ mesh.tex_coordinates.clear();
+ mesh.tex_coordinates.push_back(texcoordinates);
+ }
+
+ fs.close();
+ return (0);
+}
+
+bool OdmOrthoPhoto::readHeader (const std::string &file_name, pcl::PCLPointCloud2 &cloud,
+ Eigen::Vector4f &origin, Eigen::Quaternionf &orientation,
+ int &file_version, int &data_type, unsigned int &data_idx,
+ const int offset)
+{
+ origin = Eigen::Vector4f::Zero ();
+ orientation = Eigen::Quaternionf::Identity ();
+ file_version = 0;
+ cloud.width = cloud.height = cloud.point_step = cloud.row_step = 0;
+ cloud.data.clear ();
+ data_type = 0;
+ data_idx = offset;
+
+ std::ifstream fs;
+ std::string line;
+
+ if (file_name == "" || !boost::filesystem::exists (file_name))
+ {
+ return false;
+ }
+
+ // Open file in binary mode to avoid problem of
+ // std::getline() corrupting the result of ifstream::tellg()
+ fs.open (file_name.c_str (), std::ios::binary);
+ if (!fs.is_open () || fs.fail ())
+ {
+ fs.close ();
+ return false;
+ }
+
+ // Seek at the given offset
+ fs.seekg (offset, std::ios::beg);
+
+ // Read the header and fill it in with wonderful values
+ bool vertex_normal_found = false;
+ bool vertex_texture_found = false;
+ // Material library, skip for now!
+ // bool material_found = false;
+ std::vector material_files;
+ std::size_t nr_point = 0;
+ std::vector st;
+
+ try
+ {
+ while (!fs.eof ())
+ {
+ getline (fs, line);
+ // Ignore empty lines
+ if (line == "")
+ continue;
+
+ // Tokenize the line
+ std::stringstream sstream (line);
+ sstream.imbue (std::locale::classic ());
+ line = sstream.str ();
+ boost::trim (line);
+ boost::split (st, line, boost::is_any_of ("\t\r "), boost::token_compress_on);
+ // Ignore comments
+ if (st.at (0) == "#")
+ continue;
+
+ // Vertex
+ if (st.at (0) == "v")
+ {
+ ++nr_point;
+ continue;
+ }
+
+ // Vertex texture
+ if ((st.at (0) == "vt") && !vertex_texture_found)
+ {
+ vertex_texture_found = true;
+ continue;
+ }
+
+ // Vertex normal
+ if ((st.at (0) == "vn") && !vertex_normal_found)
+ {
+ vertex_normal_found = true;
+ continue;
+ }
+
+ // Material library, skip for now!
+ if (st.at (0) == "mtllib")
+ {
+ material_files.push_back (st.at (1));
+ continue;
+ }
+ }
+ }
+ catch (const char *exception)
+ {
+ fs.close ();
+ return false;
+ }
+
+ if (!nr_point)
+ {
+ fs.close ();
+ return false;
+ }
+
+ int field_offset = 0;
+ for (int i = 0; i < 3; ++i, field_offset += 4)
+ {
+ cloud.fields.push_back (pcl::PCLPointField ());
+ cloud.fields[i].offset = field_offset;
+ cloud.fields[i].datatype = pcl::PCLPointField::FLOAT32;
+ cloud.fields[i].count = 1;
+ }
+
+ cloud.fields[0].name = "x";
+ cloud.fields[1].name = "y";
+ cloud.fields[2].name = "z";
+
+ if (vertex_normal_found)
+ {
+ std::string normals_names[3] = { "normal_x", "normal_y", "normal_z" };
+ for (int i = 0; i < 3; ++i, field_offset += 4)
+ {
+ cloud.fields.push_back (pcl::PCLPointField ());
+ pcl::PCLPointField& last = cloud.fields.back ();
+ last.name = normals_names[i];
+ last.offset = field_offset;
+ last.datatype = pcl::PCLPointField::FLOAT32;
+ last.count = 1;
+ }
+ }
+
+ if (material_files.size () > 0)
+ {
+ for (std::size_t i = 0; i < material_files.size (); ++i)
+ {
+ pcl::MTLReader companion;
+
+ if (companion.read (file_name, material_files[i]))
+ {
+ log_<<"Problem reading material file.";
+ }
+
+ companions_.push_back (companion);
+ }
+ }
+
+ cloud.point_step = field_offset;
+ cloud.width = nr_point;
+ cloud.height = 1;
+ cloud.row_step = cloud.point_step * cloud.width;
+ cloud.is_dense = true;
+ cloud.data.resize (cloud.point_step * nr_point);
+ fs.close ();
+ return true;
+}
diff --git a/modules/odm_orthophoto/src/OdmOrthoPhoto.hpp b/modules/odm_orthophoto/src/OdmOrthoPhoto.hpp
index de2793d0..c02a7e53 100644
--- a/modules/odm_orthophoto/src/OdmOrthoPhoto.hpp
+++ b/modules/odm_orthophoto/src/OdmOrthoPhoto.hpp
@@ -172,6 +172,23 @@ private:
*/
bool isModelOk(const pcl::TextureMesh &mesh);
+ /*!
+ * \brief Loads a model from an .obj file (replacement for the pcl obj loader).
+ *
+ * \param inputFile Path to the .obj file.
+ * \param mesh The model.
+ * \return True if model was loaded successfully.
+ */
+ bool loadObjFile(std::string inputFile, pcl::TextureMesh &mesh);
+
+ /*!
+ * \brief Function is compied straight from the function in the pcl::io module.
+ */
+ bool readHeader (const std::string &file_name, pcl::PCLPointCloud2 &cloud,
+ Eigen::Vector4f &origin, Eigen::Quaternionf &orientation,
+ int &file_version, int &data_type, unsigned int &data_idx,
+ const int offset);
+
Logger log_; /**< Logging object. */
std::string inputFile_; /**< Path to the textured mesh as an obj-file. */
@@ -198,6 +215,8 @@ private:
cv::Mat depth_; /**< The depth of the ortho photo as an OpenCV matrix, CV_32F. */
bool multiMaterial_; /**< True if the mesh has multiple materials. **/
+
+ std::vector companions_; /**< Materials (used by loadOBJFile). **/
};
/*!
diff --git a/opendm/config.py b/opendm/config.py
index b8ec0295..9c9b663c 100644
--- a/opendm/config.py
+++ b/opendm/config.py
@@ -3,7 +3,7 @@ import context
# parse arguments
processopts = ['resize', 'opensfm', 'cmvs', 'pmvs',
- 'odm_meshing', 'odm_texturing', 'odm_georeferencing',
+ 'odm_meshing', 'mvs_texturing', 'odm_georeferencing',
'odm_orthophoto']
@@ -196,6 +196,45 @@ parser.add_argument('--odm_meshing-solverDivide',
'times slightly but helps reduce memory usage. '
'Default: %(default)s'))
+parser.add_argument('--mvs_texturing-dataTerm',
+ metavar='',
+ default='gmi',
+ help=('Data term: [area, gmi]. Default: %(default)s'))
+
+parser.add_argument('--mvs_texturing-outlierRemovalType',
+ metavar='',
+ default='none',
+ help=('Type of photometric outlier removal method: '
+ '[none, gauss_damping, gauss_clamping]. Default: '
+ '%(default)s'))
+
+parser.add_argument('--mvs_texturing-skipGeometricVisibilityTest',
+ metavar='',
+ default="false",
+ help=('Skip geometric visibility test. Default: %(default)s'))
+
+parser.add_argument('--mvs_texturing-skipGlobalSeamLeveling',
+ metavar='',
+ default="false",
+ help=('Skip geometric visibility test. Default: %(default)s'))
+
+parser.add_argument('--mvs_texturing-skipLocalSeamLeveling',
+ metavar='',
+ default="false",
+ help=('Skip local seam blending. Default: %(default)s'))
+
+parser.add_argument('--mvs_texturing-skipHoleFilling',
+ metavar='',
+ default="false",
+ help=('Skip filling of holes in the mesh. Default: %(default)s'))
+
+parser.add_argument('--mvs_texturing-keepUnseenFaces',
+ metavar='',
+ default="false",
+ help=('Keep faces in the mesh that are not seen in any camera. '
+ 'Default: %(default)s'))
+
+# Old odm_texturing arguments
parser.add_argument('--odm_texturing-textureResolution',
metavar='',
default=4096,
@@ -209,6 +248,7 @@ parser.add_argument('--odm_texturing-textureWithSize',
type=int,
help=('The resolution to rescale the images performing '
'the texturing. Default: %(default)s'))
+# End of old odm_texturing arguments
parser.add_argument('--odm_georeferencing-gcpFile',
metavar='',
diff --git a/opendm/context.py b/opendm/context.py
index a682d1f4..8850db27 100644
--- a/opendm/context.py
+++ b/opendm/context.py
@@ -23,6 +23,9 @@ cmvs_path = os.path.join(superbuild_path, "install/bin/cmvs")
cmvs_opts_path = os.path.join(superbuild_path, "install/bin/genOption")
pmvs2_path = os.path.join(superbuild_path, "install/bin/pmvs2")
+# define mvstex path
+mvstex_path = os.path.join(superbuild_path, "install/bin/texrecon")
+
# define txt2las path
txt2las_path = os.path.join(superbuild_path, 'src/las-tools/bin')
pdal_path = os.path.join(superbuild_path, 'build/pdal/bin')
diff --git a/opendm/tasks.py b/opendm/tasks.py
index aa13b664..0d413d14 100644
--- a/opendm/tasks.py
+++ b/opendm/tasks.py
@@ -13,7 +13,8 @@ tasks_dict = {'1': 'resize',
'3': 'cmvs',
'4': 'pmvs',
'5': 'odm_meshing',
- '6': 'odm_texturing',
+# '6': 'odm_texturing',
+ '6': 'mvs_texturing',
'7': 'odm_georeferencing',
'8': 'odm_orthophoto',
'9': 'zip_results'}
@@ -68,7 +69,7 @@ class ODMTaskManager(object):
command = None
inputs = {}
- elif task_name == 'odm_texturing':
+ elif task_name == 'mvs_texturing':
# setup this task
command = None
inputs = {}
diff --git a/opendm/types.py b/opendm/types.py
index 2df3c9b2..ef6fcf27 100644
--- a/opendm/types.py
+++ b/opendm/types.py
@@ -346,11 +346,12 @@ class ODM_Tree(object):
self.odm_mesh = io.join_paths(self.odm_meshing, 'odm_mesh.ply')
self.odm_meshing_log = io.join_paths(self.odm_meshing, 'odm_meshing_log.txt')
- # odm_texturing
+ # texturing
self.odm_textured_model_obj = io.join_paths(
self.odm_texturing, 'odm_textured_model.obj')
self.odm_textured_model_mtl = io.join_paths(
self.odm_texturing, 'odm_textured_model.mtl')
+# Log is only used by old odm_texturing
self.odm_texuring_log = io.join_paths(
self.odm_texturing, 'odm_texturing_log.txt')
diff --git a/scripts/mvstex.py b/scripts/mvstex.py
new file mode 100644
index 00000000..94e81ded
--- /dev/null
+++ b/scripts/mvstex.py
@@ -0,0 +1,111 @@
+import ecto
+
+from opendm import log
+from opendm import io
+from opendm import system
+from opendm import context
+
+import pmvs2nvmcams
+
+class ODMMvsTexCell(ecto.Cell):
+ def declare_params(self, params):
+ params.declare("data_term", 'Data term: [area, gmi] default: gmi', "gmi")
+ params.declare("outlier_rem_type", 'Type of photometric outlier removal method: [none, gauss_damping, gauss_clamping]. default: none', "none")
+ params.declare("skip_vis_test", 'Skip geometric visibility test based on ray intersection.', "false")
+ params.declare("skip_glob_seam_leveling", 'Skip global seam leveling.', "false")
+ params.declare("skip_loc_seam_leveling", 'Skip local seam leveling (Poisson editing).', "false")
+ params.declare("skip_hole_fill", 'Skip hole filling.', "false")
+ params.declare("keep_unseen_faces", 'Keep unseen faces.', "false")
+
+ def declare_io(self, params, inputs, outputs):
+ inputs.declare("tree", "Struct with paths", [])
+ inputs.declare("args", "The application arguments.", {})
+ inputs.declare("reconstruction", "Clusters output. list of ODMReconstructions", [])
+ outputs.declare("reconstruction", "Clusters output. list of ODMReconstructions", [])
+
+
+
+ def process(self, inputs, outputs):
+
+ # Benchmarking
+ start_time = system.now_raw()
+
+ log.ODM_INFO('Running MVS Texturing Cell')
+
+ # get inputs
+ args = self.inputs.args
+ tree = self.inputs.tree
+
+ # define paths and create working directories
+ system.mkdir_p(tree.odm_texturing)
+
+ # check if we rerun cell or not
+ rerun_cell = (args['rerun'] is not None and
+ args['rerun'] == 'mvs_texturing') or \
+ (args['rerun_all']) or \
+ (args['rerun_from'] is not None and
+ 'mvs_texturing' in args['rerun_from'])
+
+ if not io.file_exists(tree.odm_textured_model_obj) or rerun_cell:
+ log.ODM_DEBUG('Writing MVS Textured file in: %s'
+ % tree.odm_textured_model_obj)
+
+
+ # Format arguments to fit Mvs-Texturing app
+ skipGeometricVisibilityTest = ""
+ skipGlobalSeamLeveling = ""
+ skipLocalSeamLeveling = ""
+ skipHoleFilling = ""
+ keepUnseenFaces = ""
+
+ if (self.params.skip_vis_test.lower() == "true"):
+ skipGeometricVisibilityTest = "--skip_geometric_visibility_test"
+ if (self.params.skip_glob_seam_leveling.lower() == "true"):
+ skipGlobalSeamLeveling = "--skip_global_seam_leveling"
+ if (self.params.skip_loc_seam_leveling.lower() == "true"):
+ skipLocalSeamLeveling = "--skip_local_seam_leveling"
+ if (self.params.skip_hole_fill.lower() == "true"):
+ skipHoleFilling = "--skip_hole_filling"
+ if (self.params.keep_unseen_faces.lower() == "true"):
+ keepUnseenFaces = "--keep_unseen_faces"
+
+ # mvstex definitions
+ kwargs = {
+ 'bin': context.mvstex_path,
+ 'out_dir': io.join_paths(tree.odm_texturing, "odm_textured_model"),
+ 'pmvs_folder': tree.pmvs_rec_path,
+ 'nvm_file': io.join_paths(tree.pmvs_rec_path, "nvmCams.nvm"),
+ 'model': tree.odm_mesh,
+ 'dataTerm': self.params.data_term,
+ 'outlierRemovalType': self.params.outlier_rem_type,
+ 'skipGeometricVisibilityTest': skipGeometricVisibilityTest,
+ 'skipGlobalSeamLeveling': skipGlobalSeamLeveling,
+ 'skipLocalSeamLeveling': skipLocalSeamLeveling,
+ 'skipHoleFilling': skipHoleFilling,
+ 'keepUnseenFaces': keepUnseenFaces
+ }
+
+ log.ODM_DEBUG('Generating .nvm file from pmvs output: %s'
+ % '{nvm_file}'.format(**kwargs))
+
+ # Create .nvm camera file.
+ pmvs2nvmcams.run('{pmvs_folder}'.format(**kwargs),
+ '{nvm_file}'.format(**kwargs))
+
+ # run texturing binary
+ system.run('{bin} {nvm_file} {model} {out_dir} '
+ '-d {dataTerm} -o {outlierRemovalType} '
+ '{skipGeometricVisibilityTest} '
+ '{skipGlobalSeamLeveling} '
+ '{skipLocalSeamLeveling} '
+ '{skipHoleFilling} '
+ '{keepUnseenFaces}'.format(**kwargs))
+ else:
+ log.ODM_WARNING('Found a valid ODM Texture file in: %s'
+ % tree.odm_textured_model_obj)
+
+ if args['time']:
+ system.benchmark(start_time, tree.benchmarking, 'Texturing')
+
+ log.ODM_INFO('Running OMD Texturing Cell - Finished')
+ return ecto.OK if args['end_with'] != 'odm_texturing' else ecto.QUIT
diff --git a/scripts/odm_app.py b/scripts/odm_app.py
index 8cfd172e..48636d54 100644
--- a/scripts/odm_app.py
+++ b/scripts/odm_app.py
@@ -13,7 +13,8 @@ from opensfm import ODMOpenSfMCell
from pmvs import ODMPmvsCell
from cmvs import ODMCmvsCell
from odm_meshing import ODMeshingCell
-from odm_texturing import ODMTexturingCell
+#from odm_texturing import ODMTexturingCell
+from mvstex import ODMMvsTexCell
from odm_georeferencing import ODMGeoreferencingCell
from odm_orthophoto import ODMOrthoPhotoCell
@@ -57,9 +58,17 @@ class ODMApp(ecto.BlackBox):
oct_tree=p.args['odm_meshing_octreeDepth'],
samples=p.args['odm_meshing_samplesPerNode'],
solver=p.args['odm_meshing_solverDivide']),
- 'texturing': ODMTexturingCell(resize=p.args['resize_to'],
- resolution=p.args['odm_texturing_textureResolution'],
- size=p.args['odm_texturing_textureWithSize']),
+ 'texturing': ODMMvsTexCell(data_term=p.args['mvs_texturing_dataTerm'],
+ outlier_rem_type=p.args['mvs_texturing_outlierRemovalType'],
+ skip_vis_test=p.args['mvs_texturing_skipGeometricVisibilityTest'],
+ skip_glob_seam_leveling=p.args['mvs_texturing_skipGlobalSeamLeveling'],
+ skip_loc_seam_leveling=p.args['mvs_texturing_skipLocalSeamLeveling'],
+ skip_hole_fill=p.args['mvs_texturing_skipHoleFilling'],
+ keep_unseen_faces=p.args['mvs_texturing_keepUnseenFaces']),
+# Old odm_texturing
+# 'texturing': ODMTexturingCell(resize=p.args['resize_to'],
+# resolution=p.args['odm_texturing_textureResolution'],
+# size=p.args['odm_texturing_textureWithSize']),
'georeferencing': ODMGeoreferencingCell(img_size=p.args['resize_to'],
gcp_file=p.args['odm_georeferencing_gcpFile'],
use_gcp=p.args['odm_georeferencing_useGcp']),
diff --git a/scripts/pmvs2nvmcams.py b/scripts/pmvs2nvmcams.py
new file mode 100644
index 00000000..f9754579
--- /dev/null
+++ b/scripts/pmvs2nvmcams.py
@@ -0,0 +1,144 @@
+import os
+import numpy as np
+
+from opendm import log
+
+# Go from QR-factorizatoin to corresponding RQ-factorization.
+def rq(A):
+ Q,R = np.linalg.qr(np.flipud(A).T)
+ R = np.flipud(R.T)
+ Q = Q.T
+ return R[:,::-1],Q[::-1,:]
+
+# Create a unit quaternion from rotation matrix.
+def rot2quat(R):
+
+ # Float epsilon (use square root to be well with the stable region).
+ eps = np.sqrt(np.finfo(float).eps)
+
+ # If the determinant is not 1, it's not a rotation matrix
+ if np.abs(np.linalg.det(R) - 1.0) > eps:
+ log.ODM_ERROR('Matrix passed to rot2quat was not a rotation matrix, det != 1.0')
+
+ tr = np.trace(R)
+
+ quat = np.zeros((1,4))
+
+ # Is trace big enough be computationally stable?
+ if tr > eps:
+ S = 0.5 / np.sqrt(tr + 1.0)
+ quat[0,0] = 0.25 / S
+ quat[0,1] = (R[2,1] - R[1,2]) * S
+ quat[0,2] = (R[0,2] - R[2,0]) * S
+ quat[0,3] = (R[1,0] - R[0,1]) * S
+ else: # It's not, use the largest diagonal.
+ if R[0,0] > R[1,1] and R[0,0] > R[2,2]:
+ S = np.sqrt(1.0 + R[0,0] - R[1,1] - R[2,2]) * 2.0
+ quat[0,0] = (R[2,1] - R[1,2]) / S
+ quat[0,1] = 0.25 * S
+ quat[0,2] = (R[0,1] + R[1,0]) / S
+ quat[0,3] = (R[0,2] + R[2,0]) / S
+ elif R[1,1] > R[2,2]:
+ S = np.sqrt(1.0 - R[0,0] + R[1,1] - R[2,2]) * 2.0
+ quat[0,0] = (R[0,2] - R[2,0]) / S
+ quat[0,1] = (R[0,1] + R[1,0]) / S
+ quat[0,2] = 0.25 * S
+ quat[0,3] = (R[1,2] + R[2,1]) / S
+ else:
+ S = np.sqrt(1.0 - R[0,0] - R[1,1] + R[2,2]) * 2.0
+ quat[0,0] = (R[1,0] - R[0,1]) / S
+ quat[0,1] = (R[0,2] + R[2,0]) / S
+ quat[0,2] = (R[1,2] + R[2,1]) / S
+ quat[0,3] = 0.25 * S
+
+ return quat
+
+# Decompose a projection matrix into parts
+# (Intrinsic projection, Rotation, Camera position)
+def decomposeProjection(projectionMatrix):
+
+ # Check input:
+ if projectionMatrix.shape != (3,4):
+ log.ODM_ERROR('Unable to decompose projection matrix, shape != (3,4)')
+
+ RQ = rq(projectionMatrix[:,:3])
+
+ # Fix sign, since we know K is upper triangular and has a positive diagonal.
+ signMat = np.diag(np.diag(np.sign(RQ[0])))
+ K = signMat*RQ[0]
+ R = signMat*RQ[1]
+
+ # Calculate camera position from translation vector.
+ t = np.linalg.inv(-1.0*projectionMatrix[:,:3])*projectionMatrix[:,3]
+
+ return K, R, t
+
+# Parses pvms contour file.
+def parseContourFile(filePath):
+
+ with open(filePath, 'r') as contourFile:
+ if (contourFile.readline().strip() != "CONTOUR"):
+ return np.array([])
+ else:
+ pMatData = np.loadtxt(contourFile, float, '#', None, None, 0)
+ if pMatData.shape == (3,4):
+ return pMatData
+ return np.array([])
+
+
+
+# Creates a .nvm camera file in the pmvs folder.
+def run(pmvsFolder, outputFile):
+
+ projectionFolder = pmvsFolder + "/txt"
+ imageFolder = pmvsFolder + "/visualize"
+
+ pMatrices = []
+ imageFileNames = []
+
+ # for all files in the visualize folder:
+ for imageFileName in os.listdir(imageFolder):
+ fileNameNoExt = os.path.splitext(imageFileName)[0]
+
+ # look for corresponding projection matrix txt file
+ projectionFilePath = os.path.join(projectionFolder, fileNameNoExt)
+ projectionFilePath += ".txt"
+ if os.path.isfile(projectionFilePath):
+ pMatData = parseContourFile(projectionFilePath)
+ if pMatData.size == 0:
+ log.ODM_WARNING('Unable to parse contour file, skipping: %s'
+ % projectionFilePath)
+ else:
+ pMatrices.append(np.matrix(pMatData))
+ imageFileNames.append(imageFileName)
+
+
+ # Decompose projection matrices
+ focals = []
+ rotations = []
+ translations = []
+ for projection in pMatrices:
+ KRt = decomposeProjection(projection)
+ focals.append(KRt[0][0,0])
+ rotations.append(rot2quat(KRt[1]))
+ translations.append(KRt[2])
+
+ # Create .nvm file
+ with open (outputFile, 'w') as nvmFile:
+ nvmFile.write("NVM_V3\n\n")
+ nvmFile.write('%d' % len(rotations) + "\n")
+
+ for idx, imageFileName in enumerate(imageFileNames):
+ nvmFile.write(os.path.join("visualize", imageFileName))
+ nvmFile.write(" " + '%f' % focals[idx])
+ nvmFile.write(" " + '%f' % rotations[idx][0,0] +
+ " " + '%f' % rotations[idx][0,1] +
+ " " + '%f' % rotations[idx][0,2] +
+ " " + '%f' % rotations[idx][0,3])
+ nvmFile.write(" " + '%f' % translations[idx][0] +
+ " " + '%f' % translations[idx][1] +
+ " " + '%f' % translations[idx][2])
+ nvmFile.write(" 0 0\n")
+ nvmFile.write("0\n\n")
+ nvmFile.write("0\n\n")
+ nvmFile.write("0")
diff --git a/upgradecmake.sh b/upgradecmake.sh
new file mode 100644
index 00000000..57ab5ae9
--- /dev/null
+++ b/upgradecmake.sh
@@ -0,0 +1,7 @@
+
+# Install cmake 3.2 since cmake > 3.1 is is required by mvs-texturing.
+# Installation as proposed by:
+# http://askubuntu.com/questions/610291/how-to-install-cmake-3-2-on-ubuntu-14-04
+sudo add-apt-repository ppa:george-edison55/cmake-3.x -y
+sudo apt-get update -y
+sudo apt-get install cmake -y