kopia lustrzana https://github.com/OpenDroneMap/ODM
Merge pull request #13 from OpenDroneMap/texturing_orthophoto_spotscale_additions
add Spotscale orthophoto/textured mesh additions -- tested now on 12.04 and 14.04. Fingers crossed -- this is a big one... .pull/38/head
commit
b5c3843e7c
142
install.sh
142
install.sh
|
@ -38,8 +38,15 @@ echo " - script started - `date`"
|
|||
VLFEAT_PATH="$TOOLS_SRC_PATH/vlfeat"
|
||||
PARALLEL_PATH="$TOOLS_SRC_PATH/parallel"
|
||||
PSR_PATH="$TOOLS_SRC_PATH/PoissonRecon"
|
||||
GRACLUS_PATH="$TOOLS_SRC_PATH/graclus"
|
||||
|
||||
GRACLUS_PATH="$TOOLS_SRC_PATH/graclus"
|
||||
|
||||
PCL_PATH="$TOOLS_SRC_PATH/pcl"
|
||||
ODM_MESHING_PATH="$TOOLS_SRC_PATH/odm_meshing"
|
||||
ODM_TEXTURING_PATH="$TOOLS_SRC_PATH/odm_texturing"
|
||||
ODM_ORTHOPHOTO_PATH="$TOOLS_SRC_PATH/odm_orthophoto"
|
||||
ODM_EXTRACT_UTM_PATH="$TOOLS_SRC_PATH/odm_extract_utm"
|
||||
ODM_GEOREF_PATH="$TOOLS_SRC_PATH/odm_georef"
|
||||
|
||||
## executables
|
||||
EXTRACT_FOCAL="$TOOLS_BIN_PATH/extract_focal.pl"
|
||||
MATCHKEYS="$TOOLS_BIN_PATH/KeyMatch"
|
||||
|
@ -54,6 +61,12 @@ echo " - script started - `date`"
|
|||
PSR="$TOOLS_BIN_PATH/PoissonRecon"
|
||||
VLSIFT_TO_LOWESIFT="$TOOLS_BIN_PATH/convert_vlsift_to_lowesift.pl"
|
||||
|
||||
ODM_MESHING="$TOOLS_BIN_PATH/odm_meshing"
|
||||
ODM_TEXTURING="$TOOLS_BIN_PATH/odm_texturing"
|
||||
ODM_ORTHOPHOTO="$TOOLS_BIN_PATH/odm_orthophoto"
|
||||
ODM_EXTRACT_UTM="$TOOLS_BIN_PATH/odm_extract_utm"
|
||||
ODM_GEOREF="$TOOLS_BIN_PATH/odm_georef"
|
||||
|
||||
## get sys vars
|
||||
ARCH=`uname -m`
|
||||
CORES=`grep -c processor /proc/cpuinfo`
|
||||
|
@ -75,6 +88,13 @@ mkdir -p "$TOOLS_LIB_PATH"
|
|||
mkdir -p "$TOOLS_SRC_PATH"
|
||||
mkdir -p "$TOOLS_LOG_PATH"
|
||||
|
||||
## Copy meshing and texturing to src folder
|
||||
cp -rf "odm_meshing" "$TOOLS_SRC_PATH/"
|
||||
cp -rf "odm_texturing" "$TOOLS_SRC_PATH/"
|
||||
cp -rf "odm_orthophoto" "$TOOLS_SRC_PATH/"
|
||||
cp -rf "odm_extract_utm" "$TOOLS_SRC_PATH/"
|
||||
cp -rf "odm_georef" "$TOOLS_SRC_PATH/"
|
||||
|
||||
## output sys info
|
||||
echo "System info:" > "$TOOLS_LOG_PATH/sysinfo.txt"
|
||||
uname -a > "$TOOLS_LOG_PATH/sysinfo.txt"
|
||||
|
@ -88,14 +108,14 @@ sudo apt-get update --assume-yes > "$TOOLS_LOG_PATH/apt-get_get.log" 2>&1
|
|||
|
||||
echo " - installing"
|
||||
sudo apt-get install --assume-yes --install-recommends \
|
||||
build-essential cmake g++ gcc gFortran perl git \
|
||||
build-essential cmake g++ gcc gFortran perl git autoconf \
|
||||
curl wget \
|
||||
unzip \
|
||||
imagemagick jhead \
|
||||
libjpeg-dev libboost-dev libgsl0-dev libx11-dev libxext-dev liblapack-dev \
|
||||
imagemagick jhead proj-bin libproj-dev\
|
||||
libjpeg-dev libboost1.48-all-dev libgsl0-dev libx11-dev libxext-dev liblapack-dev \
|
||||
libeigen3-dev libflann-dev libvtk5-dev libqhull-dev libusb-1.0-0-dev\
|
||||
libzip-dev \
|
||||
libswitch-perl \
|
||||
libcv-dev libcvaux-dev \
|
||||
libcv-dev libcvaux-dev libopencv-dev \
|
||||
> "$TOOLS_LOG_PATH/apt-get_install.log" 2>&1
|
||||
|
||||
|
||||
|
@ -129,6 +149,7 @@ PoissonRecon.zip http://www.cs.jhu.edu/~misha/Code/PoissonRecon/Version2/Poisson
|
|||
vlfeat.tar.gz http://www.vlfeat.org/download/vlfeat-0.9.13-bin.tar.gz
|
||||
cmvs.tar.gz http://www.di.ens.fr/cmvs/cmvs-fix2.tar.gz
|
||||
graclus.tar.gz http://smathermather.github.io/BundlerTools/patched_files/src/graclus/graclus1.2.tar.gz
|
||||
pcl.tar.gz https://github.com/PointCloudLibrary/pcl/archive/pcl-1.7.2.tar.gz
|
||||
EOF
|
||||
|
||||
echo " < done - `date`"
|
||||
|
@ -149,13 +170,15 @@ done
|
|||
|
||||
wait
|
||||
|
||||
mv -f graclus1.2 "$GRACLUS_PATH"
|
||||
mv -f graclus1.2 "$GRACLUS_PATH"
|
||||
mv -f clapack-3.2.1-CMAKE "$CLAPACK_PATH"
|
||||
mv -f vlfeat-0.9.13 "$VLFEAT_PATH"
|
||||
mv -f bundler-v0.4-source "$BUNDLER_PATH"
|
||||
mv -f parallel-20141022 "$PARALLEL_PATH"
|
||||
mv -f PoissonRecon "$PSR_PATH"
|
||||
mv -f cmvs "$CMVS_PATH"
|
||||
mv -f pcl-pcl-1.7.2 "$PCL_PATH"
|
||||
|
||||
|
||||
echo " < done - `date`"
|
||||
|
||||
|
@ -180,7 +203,6 @@ echo
|
|||
sudo chown -R `id -u`:`id -g` *
|
||||
#sudo chmod -R 777 *
|
||||
|
||||
|
||||
echo " > graclus"
|
||||
cd "$GRACLUS_PATH"
|
||||
|
||||
|
@ -325,6 +347,108 @@ echo " > bundler"
|
|||
echo " < done - `date`"
|
||||
echo
|
||||
|
||||
echo " > pcl "
|
||||
#cd "$PCL_PATH"
|
||||
|
||||
#Install pcl dependencies using the default package manager.
|
||||
#sudo apt-get install libeigen3-dev libflann-dev libvtk5-dev libqhull-dev
|
||||
|
||||
#install the required boost version.
|
||||
#sudo apt-get install libboost1.48-all-dev
|
||||
|
||||
mkdir -p "pcl"
|
||||
mkdir -p "$TOOLS_LIB_PATH/pcl"
|
||||
mkdir -p "$PCL_PATH/pcl_tmp"
|
||||
mkdir -p "$PCL_PATH/pcl_build"
|
||||
|
||||
#mv -f "pcl-pcl-1.7.2" "$PCL_PATH/pcl_tmp"
|
||||
|
||||
cd "$PCL_PATH/pcl_build"
|
||||
|
||||
echo " - configuring pcl"
|
||||
|
||||
cmake .. -DCMAKE_INSTALL_PREFIX="$TOOLS_LIB_PATH/pcl" -DCMAKE_BUILD_TYPE=Release -DPCL_VERBOSITY_LEVEL=Error -DBUILD_features=OFF -DBUILD_filters=OFF -DBUILD_geometry=OFF -DBUILD_keypoints=OFF -DBUILD_outofcore=OFF -DBUILD_people=OFF -DBUILD_recognition=OFF -DBUILD_registration=OFF -DBUILD_sample_consensus=OFF -DBUILD_segmentation=OFF -DBUILD_features=OFF -DBUILD_surface_on_nurbs=OFF -DBUILD_tools=OFF -DBUILD_tracking=OFF -DBUILD_visualization=OFF -DWITH_QT=OFF -DBUILD_OPENNI=OFF -DBUILD_OPENNI2=OFF -DWITH_OPENNI=OFF -DWITH_OPENNI2=OFF -DWITH_FZAPI=OFF -DWITH_LIBUSB=OFF -DWITH_PCAP=OFF -DWITH_PXCAPI=OFF > "$TOOLS_LOG_PATH/pcl_1_build.log" 2>&1
|
||||
|
||||
echo " - building and installing pcl"
|
||||
make install > "$TOOLS_LOG_PATH/pcl_2_build.log" 2>&1
|
||||
|
||||
echo " < done - `date`"
|
||||
echo
|
||||
|
||||
echo " > meshing "
|
||||
cd "$ODM_MESHING_PATH"
|
||||
|
||||
echo " - configuring odm_meshing"
|
||||
cmake . -DPCL_DIR="$TOOLS_LIB_PATH/pcl" > "$TOOLS_LOG_PATH/odm_meshing_1_build.log" 2>&1
|
||||
|
||||
echo " - building odm_meshing"
|
||||
make > "$TOOLS_LOG_PATH/odm_meshing_2_build.log" 2>&1
|
||||
|
||||
# copy output program to the binaries folder.
|
||||
cp -f "odm_meshing" "$TOOLS_BIN_PATH/odm_meshing"
|
||||
|
||||
echo " < done - `date`"
|
||||
echo
|
||||
|
||||
echo " > texturing "
|
||||
cd "$ODM_TEXTURING_PATH"
|
||||
|
||||
echo " - configuring odm_texturing"
|
||||
cmake . -DPCL_DIR="$TOOLS_LIB_PATH/pcl" > "$TOOLS_LOG_PATH/odm_texturing_1_build.log" 2>&1
|
||||
|
||||
echo " - building odm_texturing"
|
||||
make > "$TOOLS_LOG_PATH/odm_texturing_2_build.log" 2>&1
|
||||
|
||||
# copy output program to the binaries folder.
|
||||
cp -f "odm_texturing" "$TOOLS_BIN_PATH/odm_texturing"
|
||||
|
||||
echo " < done - `date`"
|
||||
echo
|
||||
|
||||
echo " > extract_utm "
|
||||
cd "$ODM_EXTRACT_UTM_PATH"
|
||||
|
||||
echo " - configuring odm_extract_utm"
|
||||
cmake . > "$TOOLS_LOG_PATH/odm_extract_utm_1_build.log" 2>&1
|
||||
|
||||
echo " - building odm_extract_utm"
|
||||
make > "$TOOLS_LOG_PATH/odm_extract_utm_2_build.log" 2>&1
|
||||
|
||||
# copy output program to the binaries folder.
|
||||
cp -f "odm_extract_utm" "$TOOLS_BIN_PATH/odm_extract_utm"
|
||||
|
||||
echo " < done - `date`"
|
||||
echo
|
||||
|
||||
echo " > georef "
|
||||
cd "$ODM_GEOREF_PATH"
|
||||
|
||||
echo " - configuring odm_georef"
|
||||
cmake . -DPCL_DIR="$TOOLS_LIB_PATH/pcl" > "$TOOLS_LOG_PATH/odm_georef_1_build.log" 2>&1
|
||||
|
||||
echo " - building odm_georef"
|
||||
make > "$TOOLS_LOG_PATH/odm_georef_2_build.log" 2>&1
|
||||
|
||||
# copy output program to the binaries folder.
|
||||
cp -f "odm_georef" "$TOOLS_BIN_PATH/odm_georef"
|
||||
|
||||
echo " < done - `date`"
|
||||
echo
|
||||
|
||||
echo " > orthophoto "
|
||||
cd "$ODM_ORTHOPHOTO_PATH"
|
||||
|
||||
echo " - configuring odm_orthophoto"
|
||||
cmake . -DPCL_DIR="$TOOLS_LIB_PATH/pcl" > "$TOOLS_LOG_PATH/odm_orthophoto_1_build.log" 2>&1
|
||||
|
||||
echo " - building odm_orthophoto"
|
||||
make > "$TOOLS_LOG_PATH/odm_orthophoto_2_build.log" 2>&1
|
||||
|
||||
# copy output program to the binaries folder.
|
||||
cp -f "odm_orthophoto" "$TOOLS_BIN_PATH/odm_orthophoto"
|
||||
|
||||
echo " < done - `date`"
|
||||
echo
|
||||
|
||||
cd "$TOOLS_PATH"
|
||||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
project(odm_extract_utm)
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
set(PROJ4_INCLUDE_DIR "/usr/include/" CACHE "PROJ4_INCLUDE_DIR" "Path to the proj4 inlcude directory")
|
||||
set(PROJ4_LIBRARY "/usr/lib/libproj.so" CACHE "PROJ4_LIBRARY" "Path to the proj4 library directory")
|
||||
|
||||
# Add compiler options.
|
||||
add_definitions(-Wall -Wextra)
|
||||
|
||||
# Add source directory
|
||||
aux_source_directory("./src" SRC_LIST)
|
||||
|
||||
# Add exectuteable
|
||||
add_executable(${PROJECT_NAME} ${SRC_LIST})
|
||||
|
||||
# Link
|
||||
target_link_libraries(${PROJECT_NAME} ${PROJ4_LIBRARY})
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#include "Logger.hpp"
|
||||
|
||||
|
||||
Logger::Logger(bool isPrintingInCout) : isPrintingInCout_(isPrintingInCout)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Logger::~Logger()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Logger::printToFile(std::string filePath)
|
||||
{
|
||||
std::ofstream file(filePath.c_str(), std::ios::binary);
|
||||
file << logStream_.str();
|
||||
file.close();
|
||||
}
|
||||
|
||||
bool Logger::isPrintingInCout() const
|
||||
{
|
||||
return isPrintingInCout_;
|
||||
}
|
||||
|
||||
void Logger::setIsPrintingInCout(bool isPrintingInCout)
|
||||
{
|
||||
isPrintingInCout_ = isPrintingInCout;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
// STL
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
/*!
|
||||
* \brief The Logger class is used to store program messages in a log file.
|
||||
* \details By using the << operator while printInCout is set, the class writes both to
|
||||
* cout and to file, if the flag is not set, output is written to file only.
|
||||
*/
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* \brief Logger Contains functionality for printing and displaying log information.
|
||||
* \param printInCout Flag toggling if operator << also writes to cout.
|
||||
*/
|
||||
Logger(bool isPrintingInCout = true);
|
||||
|
||||
/*!
|
||||
* \brief Destructor.
|
||||
*/
|
||||
~Logger();
|
||||
|
||||
/*!
|
||||
* \brief print Prints the contents of the log to file.
|
||||
* \param filePath Path specifying where to write the log.
|
||||
*/
|
||||
void printToFile(std::string filePath);
|
||||
|
||||
/*!
|
||||
* \brief isPrintingInCout Check if console printing flag is set.
|
||||
* \return Console printing flag.
|
||||
*/
|
||||
bool isPrintingInCout() const;
|
||||
|
||||
/*!
|
||||
* \brief setIsPrintingInCout Set console printing flag.
|
||||
* \param isPrintingInCout Value, if true, messages added to the log are also printed in cout.
|
||||
*/
|
||||
void setIsPrintingInCout(bool isPrintingInCout);
|
||||
|
||||
/*!
|
||||
* Operator for printing messages to log and in the standard output stream if desired.
|
||||
*/
|
||||
template<class T>
|
||||
friend Logger& operator<< (Logger &log, T t)
|
||||
{
|
||||
// If console printing is enabled.
|
||||
if (log.isPrintingInCout_)
|
||||
{
|
||||
std::cout << t;
|
||||
std::cout.flush();
|
||||
}
|
||||
// Write to log.
|
||||
log.logStream_ << t;
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
private:
|
||||
bool isPrintingInCout_; /*!< If flag is set, log is printed in cout and written to the log. */
|
||||
|
||||
std::stringstream logStream_; /*!< Stream for storing the log. */
|
||||
};
|
|
@ -0,0 +1,383 @@
|
|||
// STL
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <sstream>
|
||||
#include <math.h>
|
||||
|
||||
// Proj4
|
||||
#include <proj_api.h>
|
||||
|
||||
// This
|
||||
#include "UtmExtractor.hpp"
|
||||
|
||||
UtmExtractor::UtmExtractor() : log_(false)
|
||||
{
|
||||
logFile_ = "odm_extracting_utm_log.txt";
|
||||
}
|
||||
|
||||
UtmExtractor::~UtmExtractor()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
int UtmExtractor::run(int argc, char **argv)
|
||||
{
|
||||
if (argc <= 1)
|
||||
{
|
||||
printHelp();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
parseArguments(argc, argv);
|
||||
extractUtm();
|
||||
}
|
||||
catch (const UtmExtractorException& e)
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
log_ << e.what() << "\n";
|
||||
log_.printToFile(logFile_);
|
||||
log_ << "For more detailed information, see log file." << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
log_ << "Error in OdmExtractUtm:\n";
|
||||
log_ << e.what() << "\n";
|
||||
log_.printToFile(logFile_);
|
||||
log_ << "For more detailed information, see log file." << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
log_ << "Unknown error in OdmExtractUtm:\n";
|
||||
log_.printToFile(logFile_);
|
||||
log_ << "For more detailed information, see log file." << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
log_.printToFile(logFile_);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void UtmExtractor::parseArguments(int argc, char **argv)
|
||||
{
|
||||
for(int argIndex = 1; argIndex < argc; ++argIndex)
|
||||
{
|
||||
// The argument to be parsed
|
||||
std::string argument = std::string(argv[argIndex]);
|
||||
if (argument == "-help")
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
else if (argument == "-verbose")
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
}
|
||||
else if (argument == "-imageListFile")
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw UtmExtractorException("Missing argument for '" + argument + "'.");
|
||||
}
|
||||
imageListFileName_ = std::string(argv[argIndex]);
|
||||
std::ifstream testFile(imageListFileName_.c_str(), std::ios_base::binary);
|
||||
if (!testFile.is_open())
|
||||
{
|
||||
throw UtmExtractorException("Argument '" + argument + "' has a bad value (file not accessible).");
|
||||
}
|
||||
log_ << "imageListFile was set to: " << imageListFileName_ << "\n";
|
||||
}
|
||||
else if (argument == "-imagesPath")
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw UtmExtractorException("Missing argument for '" + argument + "'.");
|
||||
}
|
||||
std::stringstream ss(argv[argIndex]);
|
||||
ss >> imagesPath_;
|
||||
if (ss.bad())
|
||||
{
|
||||
throw UtmExtractorException("Argument '" + argument + "' has a bad value. (wrong type)");
|
||||
}
|
||||
log_ << "imagesPath was set to: " << imagesPath_ << "\n";
|
||||
}
|
||||
else if (argument == "-outputCoordFile")
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw UtmExtractorException("Missing argument for '" + argument + "'.");
|
||||
}
|
||||
std::stringstream ss(argv[argIndex]);
|
||||
ss >> outputCoordFileName_;
|
||||
if (ss.bad())
|
||||
{
|
||||
throw UtmExtractorException("Argument '" + argument + "' has a bad value. (wrong type)");
|
||||
}
|
||||
log_ << "outputCoordFile was set to: " << outputCoordFileName_ << "\n";
|
||||
}
|
||||
else if (argument == "-logFile")
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw UtmExtractorException("Missing argument for '" + argument + "'.");
|
||||
}
|
||||
std::stringstream ss(argv[argIndex]);
|
||||
ss >> logFile_;
|
||||
if (ss.bad())
|
||||
{
|
||||
throw UtmExtractorException("Argument '" + argument + "' has a bad value. (wrong type)");
|
||||
}
|
||||
log_ << "logFile_ was set to: " << logFile_ << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
printHelp();
|
||||
throw UtmExtractorException("Unrecognised argument '" + argument + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void UtmExtractor::extractUtm()
|
||||
{
|
||||
// Open file listing all used camera images
|
||||
std::ifstream imageListStream(imageListFileName_.c_str());
|
||||
if (!imageListStream.good()) {
|
||||
throw UtmExtractorException("Failed to open " + imageListFileName_ + " for reading.");
|
||||
}
|
||||
|
||||
// Traverse images
|
||||
int utmZone = 99; // for auto-select
|
||||
char hemisphere;
|
||||
std::string imageFilename;
|
||||
std::vector<Coord> coords;
|
||||
while (getline(imageListStream, imageFilename)) {
|
||||
// Run jhead on image to extract EXIF data to temporary file
|
||||
std::string commandLine = "jhead -v " + imagesPath_ + "/" + imageFilename + " > extract_utm_output.txt";
|
||||
system(commandLine.c_str());
|
||||
|
||||
// Read temporary EXIF data file
|
||||
std::ifstream jheadDataStream;
|
||||
jheadDataStream.open("extract_utm_output.txt");
|
||||
if (!jheadDataStream.good()) {
|
||||
throw UtmExtractorException("Failed to open temporary jhead data file extract_utm_output.txt");
|
||||
}
|
||||
|
||||
// Delete temporary file
|
||||
remove("extract_utm_output.txt");
|
||||
|
||||
// Parse jhead output
|
||||
double lon, lat, alt;
|
||||
if (!parsePosition(jheadDataStream, lon, lat, alt)) {
|
||||
throw UtmExtractorException("Failed parsing GPS position.");
|
||||
jheadDataStream.close();
|
||||
}
|
||||
jheadDataStream.close();
|
||||
|
||||
// Convert to UTM
|
||||
double x, y, z;
|
||||
convert(lon, lat, alt, x, y, z, utmZone, hemisphere);
|
||||
coords.push_back(Coord(x, y, z));
|
||||
}
|
||||
imageListStream.close();
|
||||
|
||||
// Calculate average
|
||||
double dx = 0.0, dy = 0.0;
|
||||
double num = static_cast<double>(coords.size());
|
||||
for (std::vector<Coord>::iterator iter = coords.begin(); iter != coords.end(); ++iter) {
|
||||
dx += iter->x/num;
|
||||
dy += iter->y/num;
|
||||
}
|
||||
|
||||
dx = floor(dx);
|
||||
dy = floor(dy);
|
||||
|
||||
// Open output file
|
||||
std::ofstream outputCoordStream(outputCoordFileName_.c_str());
|
||||
if (!outputCoordStream.good()) {
|
||||
throw UtmExtractorException("Failed to openg " + outputCoordFileName_ + " for writing.");
|
||||
}
|
||||
outputCoordStream.precision(10);
|
||||
|
||||
// Write coordinate file
|
||||
outputCoordStream << "WGS84 UTM " << utmZone << hemisphere << std::endl;
|
||||
outputCoordStream << dx << " " << dy << std::endl;
|
||||
for (std::vector<Coord>::iterator iter = coords.begin(); iter != coords.end(); ++iter) {
|
||||
outputCoordStream << (iter->x - dx) << " " << (iter->y - dy) << " " << iter->z << std::endl;
|
||||
}
|
||||
|
||||
outputCoordStream.close();
|
||||
}
|
||||
|
||||
bool UtmExtractor::convert(const double &lon, const double &lat, const double &alt, double &x, double &y, double &z, int &utmZone, char &hemisphere)
|
||||
{
|
||||
x = y = z = 0.0;
|
||||
|
||||
// Create WGS84 longitude/latitude coordinate system
|
||||
projPJ pjLatLon = pj_init_plus("+proj=latlong +datum=WGS84");
|
||||
if (!pjLatLon) {
|
||||
throw UtmExtractorException("Couldn't create WGS84 coordinate system with PROJ.4.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate UTM zone if it's set to magic 99
|
||||
// NOTE: Special UTM cases in Norway/Svalbard not supported here
|
||||
if (utmZone == 99) {
|
||||
utmZone = ((static_cast<int>(floor((lon + 180.0)/6.0)) % 60) + 1);
|
||||
if (lat < 0)
|
||||
hemisphere = 'S';
|
||||
else
|
||||
hemisphere = 'N';
|
||||
}
|
||||
|
||||
std::ostringstream ostr;
|
||||
ostr << utmZone;
|
||||
if (hemisphere == 'S')
|
||||
ostr << " +south";
|
||||
|
||||
// Create UTM coordinate system
|
||||
projPJ pjUtm = pj_init_plus(("+proj=utm +datum=WGS84 +zone=" + ostr.str()).c_str());
|
||||
if (!pjUtm) {
|
||||
throw UtmExtractorException("Couldn't create UTM coordinate system with PROJ.4.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert to radians
|
||||
x = lon * DEG_TO_RAD;
|
||||
y = lat * DEG_TO_RAD;
|
||||
z = alt;
|
||||
|
||||
// Transform
|
||||
int res = pj_transform(pjLatLon, pjUtm, 1, 1, &x, &y, &z);
|
||||
if (res != 0) {
|
||||
throw UtmExtractorException("Failed to transform coordinates");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UtmExtractor::parsePosition(std::ifstream &jheadStream, double &lon, double &lat, double &alt)
|
||||
{
|
||||
lon = lat = alt = 0.0;
|
||||
|
||||
// Parse position
|
||||
std::string str;
|
||||
std::string latStr, lonStr, altStr;
|
||||
while (std::getline(jheadStream, str))
|
||||
{
|
||||
size_t index = str.find("GPS Latitude : ");
|
||||
if (index != std::string::npos)
|
||||
{
|
||||
latStr = str.substr(index + 15);
|
||||
size_t find = latStr.find_first_of("0123456789");
|
||||
if(std::string::npos == find)
|
||||
{
|
||||
throw UtmExtractorException("Image is missing GPS Latitude data");
|
||||
}
|
||||
|
||||
}
|
||||
index = str.find("GPS Longitude: ");
|
||||
if (index != std::string::npos)
|
||||
{
|
||||
lonStr = str.substr(index + 15);
|
||||
size_t find = lonStr.find_first_of("0123456789");
|
||||
if(std::string::npos == find)
|
||||
{
|
||||
throw UtmExtractorException("Image is missing GPS Latitude data");
|
||||
}
|
||||
}
|
||||
index = str.find("GPSAltitude");
|
||||
if (index != std::string::npos)
|
||||
{
|
||||
altStr = str.substr(index + 12);
|
||||
size_t find = altStr.find_first_of("0123456789");
|
||||
if(std::string::npos == find)
|
||||
{
|
||||
throw UtmExtractorException("Image is missing GPS Latitude data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lonStr.empty() || latStr.empty()) {
|
||||
throw UtmExtractorException("No valid GPS position found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse longitude
|
||||
std::string hemisphere;
|
||||
double degrees, minutes, seconds;
|
||||
std::istringstream istr(lonStr);
|
||||
char degChar = 'd', minChar = 'm', secChar = 's';
|
||||
istr >> hemisphere >> degrees >> degChar >> minutes >> minChar >> seconds >> secChar;
|
||||
lon = (hemisphere == "W" ? -1 : 1) * (degrees + minutes/60.0 + seconds/3600.0);
|
||||
|
||||
// Parse latitude
|
||||
istr.clear();
|
||||
istr.str(latStr);
|
||||
istr >> hemisphere >> degrees >> degChar >> minutes >> minChar >> seconds >> secChar;
|
||||
lat = (hemisphere == "S" ? -1 : 1) * (degrees + minutes/60.0 + seconds/3600.0);
|
||||
|
||||
if (!altStr.empty())
|
||||
{
|
||||
size_t index = altStr.find_last_of("=");
|
||||
if (index != std::string::npos)
|
||||
{
|
||||
altStr = altStr.substr(index + 1);
|
||||
istr.clear();
|
||||
istr.str(altStr);
|
||||
|
||||
char dummyChar;
|
||||
int nominator, denominator;
|
||||
istr >> nominator;
|
||||
istr >> dummyChar;
|
||||
istr >> denominator;
|
||||
|
||||
alt = static_cast<double>(nominator)/static_cast<double>(denominator);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UtmExtractor::printHelp()
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
|
||||
log_ << "Purpose:\n";
|
||||
log_ << "Create a coordinate file containing the GPS positions of all cameras to be used later in the ODM toolchain for automatic georeferecing.\n";
|
||||
|
||||
log_ << "Usage:\n";
|
||||
log_ << "The program requires paths to a image list file, a image folder path and an output textfile to store the results.\n";
|
||||
|
||||
log_ << "The following flags are available:\n";
|
||||
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.\n\n";
|
||||
|
||||
log_ << "Parameters are specified as: \"-<argument name> <argument>\", (without <>), and the following parameters are configurable:\n";
|
||||
log_ << "\"-imageListFile <path>\" (mandatory)\n";
|
||||
log_ << "Path to the list containing the image names used in the bundle.out file.\n";
|
||||
|
||||
log_ << "\"-imagesPath <path>\" (mandatory)\n";
|
||||
log_ << "Path folder containing all images in the imageListFile.\n";
|
||||
|
||||
log_ << "\"-outputCoordFile <path>\" (mandatory)\n";
|
||||
log_ << "Path output textfile.\n";
|
||||
|
||||
log_.setIsPrintingInCout(false);
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
#pragma once
|
||||
|
||||
// Logging
|
||||
#include "Logger.hpp"
|
||||
|
||||
/*!
|
||||
* \breif The Coord struct Class used in UtmExtractor to extract GPS positions from images and ODM output
|
||||
*/
|
||||
struct Coord
|
||||
{
|
||||
double x, y, z;
|
||||
Coord(double ix, double iy, double iz) : x(ix), y(iy), z(iz) {}
|
||||
};
|
||||
|
||||
class UtmExtractor
|
||||
{
|
||||
public:
|
||||
UtmExtractor();
|
||||
~UtmExtractor();
|
||||
|
||||
/*!
|
||||
* \brief run Runs the texturing functionality using the provided input arguments.
|
||||
* For a list of the accepted arguments, please see the main page documentation or
|
||||
* call the program with parameter "-help".
|
||||
* \param argc Application argument count.
|
||||
* \param argv Argument values.
|
||||
* \return 0 if successful.
|
||||
*/
|
||||
int run (int argc, char **argv);
|
||||
|
||||
private:
|
||||
|
||||
/*!
|
||||
* \brief parseArguments Parses command line arguments.
|
||||
* \param argc Application argument count.
|
||||
* \param argv Argument values.
|
||||
*/
|
||||
void parseArguments(int argc, char **argv);
|
||||
|
||||
/*!
|
||||
* \breif extractUtm Performs the extraction of coordinates inside the run function.
|
||||
*/
|
||||
void extractUtm();
|
||||
|
||||
/*!
|
||||
* /brief Static method that converts a WGS84 longitude/latitude coordinate in decimal degrees to UTM.
|
||||
*
|
||||
* \param lon The longitude in decimal degrees (negative if western hemisphere).
|
||||
* \param lat The latitude in decimal degrees (negative if southern hemisphere).
|
||||
* \param alt The altitude in meters.
|
||||
* \param x Output parameter, the easting UTM value in meters.
|
||||
* \param y Output parameter, the northing UTM value in meters.
|
||||
* \param utmZone Input or output parameter, the UTM zone. Set to 99 for automatic selection.
|
||||
* \param hemisphere Input or output parameter, 'N' for norther hemisphere, 'S' for southern. Automatically selected if utmZone is 99.
|
||||
*
|
||||
* \returns True if successful (otherwise output parameters are 0)
|
||||
*/
|
||||
static bool convert(const double &lon, const double &lat, const double &alt, double &x, double &y, double &z, int &utmZone, char &hemisphere);
|
||||
|
||||
/*!
|
||||
* \brief Static method that parses a GPS position from jhead data.
|
||||
*
|
||||
* \param jheadDataStream Jhead data stream with EXIF information.
|
||||
* \param lon Output parameter, the longitude in decimal degrees.
|
||||
* \param lat Output parameter, the latitude in decimal degrees.
|
||||
* \param alt Output parameter, the altitude in meters.
|
||||
*
|
||||
* \returns True if successful (otherwise output parameters are 0)
|
||||
*/
|
||||
static bool parsePosition(std::ifstream &jheadStream, double &lon, double &lat, double &alt);
|
||||
|
||||
/*!
|
||||
* \brief printHelp Prints help, explaining usage. Can be shown by calling the program with arguments: "-help".
|
||||
*/
|
||||
void printHelp();
|
||||
|
||||
std::string imageListFileName_; /**< Path to the image list. */
|
||||
std::string outputCoordFileName_; /**< Path to the file to store the output textfile. */
|
||||
std::string imagesPath_; /**< Path to the folder with all images in the image list. */
|
||||
|
||||
Logger log_; /**< Logging object. */
|
||||
std::string logFile_; /**< Path to store the log file. */
|
||||
|
||||
};
|
||||
|
||||
class UtmExtractorException : public std::exception
|
||||
{
|
||||
public:
|
||||
UtmExtractorException() : message("Error in OdmExtractUtm") {}
|
||||
UtmExtractorException(std::string msgInit) : message("Error in OdmExtractUtm:\n" + msgInit) {}
|
||||
~UtmExtractorException() throw() {}
|
||||
virtual const char* what() const throw() {return message.c_str(); }
|
||||
|
||||
private:
|
||||
std::string message; /**< The error message. */
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
|
||||
#include "UtmExtractor.hpp"
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
UtmExtractor utmExtractor;
|
||||
utmExtractor.run(argc, argv);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
project(odm_georef)
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
# Set pcl dir to the input spedified with option -DPCL_DIR="path"
|
||||
set(PCL_DIR "PCL_DIR-NOTFOUND" CACHE "PCL_DIR" "Path to the pcl installation directory")
|
||||
set(PROJ4_INCLUDE_DIR "/usr/include/" CACHE "PROJ4_INCLUDE_DIR" "Path to the proj4 inlcude directory")
|
||||
set(PROJ4_LIBRARY "/usr/lib/libproj.so" CACHE "PROJ4_LIBRARY" "Path to the proj4 library directory")
|
||||
|
||||
# Add compiler options.
|
||||
add_definitions(-Wall -Wextra -Wconversion -pedantic)
|
||||
#add_definitions(-pedantic -pedantic-errors -Wall -Wextra -Werror -Wfatal-errors -Wabi -Wctor-dtor-privacy -Wnon-virtual-dtor -Wreorder -Weffc++ -Wstrict-null-sentinel -Wnon-template-friend -Wold-style-cast -Woverloaded-virtual -Wpmf-conversions -Wsign-promo -Waddress -Warray-bounds -Wattributes -Wbuiltin-macro-redefined -Wc++0x-compat -Wcast-align -Wcast-qual -Wchar-subscripts -Wclobbered -Wcomment -Wconversion -Wcoverage-mismatch -Wdeprecated -Wdeprecated-declarations -Wdisabled-optimization -Wdiv-by-zero -Wempty-body -Wenum-compare -Wendif-labels -Wfatal-errors -Wfloat-equal -Wformat -Wformat=2 -Wformat-contains-nul -Wformat-extra-args -Wformat-nonliteral -Wformat-security -Wformat-y2k -Wignored-qualifiers -Winit-self -Wint-to-pointer-cast -Winvalid-offsetof -Winvalid-pch -Wlogical-op -Wmain -Wvariadic-macros -Wmissing-braces -Wmissing-field-initializers -Wmissing-include-dirs -Wmissing-noreturn -Wvla -Wmultichar -Wfatal-errors -Wnonnull -Woverflow -Woverlength-strings -Wpacked -Wpacked-bitfield-compat -Wparentheses -Wpointer-arith -Wredundant-decls -Wsequence-point -Wshadow -Wsign-compare -Wsign-conversion -Wstack-protector -Wstrict-overflow=5 -Wswitch -Wswitch-enum -Wsync-nand -Wvolatile-register-var -Wtrigraphs -Wtype-limits -Wuninitialized -Wunknown-pragmas -Wwrite-strings -Wpragmas -Wunreachable-code -Wunused -Wunused-function -Wunused-label -Wunused-parameter -Wunused-value -Wunused-variable -Wno-return-type)
|
||||
|
||||
# Find pcl at the location specified by PCL_DIR
|
||||
find_package(PCL 1.7 HINTS "${PCL_DIR}/share/pcl-1.7")
|
||||
|
||||
# Add the PCL and Eigen include dirs.
|
||||
# Necessary since the PCL_INCLUDE_DIR variable set bu find_package is broken.)
|
||||
include_directories(${PCL_ROOT}/include/pcl-${PCL_VERSION_MAJOR}.${PCL_VERSION_MINOR})
|
||||
include_directories(${EIGEN_ROOT})
|
||||
|
||||
# Add source directory
|
||||
aux_source_directory("./src" SRC_LIST)
|
||||
|
||||
# Add exectuteable
|
||||
add_executable(${PROJECT_NAME} ${SRC_LIST})
|
||||
|
||||
# Link
|
||||
target_link_libraries(${PROJECT_NAME} ${PCL_COMMON_LIBRARIES} ${PCL_IO_LIBRARIES} ${PCL_SURFACE_LIBRARIES} ${PROJ4_LIBRARY})
|
|
@ -0,0 +1,149 @@
|
|||
// This
|
||||
#include "FindTransform.hpp"
|
||||
|
||||
Vec3::Vec3(double x, double y, double z) :x_(x), y_(y), z_(z)
|
||||
{
|
||||
|
||||
}
|
||||
Vec3::Vec3(const Vec3 &o) : x_(o.x_), y_(o.y_), z_(o.z_)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Vec3 Vec3::cross(Vec3 o) const
|
||||
{
|
||||
Vec3 res;
|
||||
res.x_ = y_*o.z_ - z_*o.y_;
|
||||
res.y_ = z_*o.x_ - x_*o.z_;
|
||||
res.z_ = x_*o.y_ - y_*o.x_;
|
||||
return res;
|
||||
}
|
||||
|
||||
double Vec3::dot(Vec3 o) const
|
||||
{
|
||||
return x_*o.x_ + y_*o.y_ + z_*o.z_;
|
||||
}
|
||||
|
||||
double Vec3::length() const
|
||||
{
|
||||
return sqrt(x_*x_ + y_*y_ + z_*z_);
|
||||
}
|
||||
|
||||
Vec3 Vec3::norm() const
|
||||
{
|
||||
Vec3 res;
|
||||
double l = length();
|
||||
res.x_ = x_ / l;
|
||||
res.y_ = y_ / l;
|
||||
res.z_ = z_ / l;
|
||||
return res;
|
||||
}
|
||||
|
||||
Vec3 Vec3::operator*(double d) const
|
||||
{
|
||||
return Vec3(x_*d, y_*d, z_*d);
|
||||
}
|
||||
|
||||
Vec3 Vec3::operator+(Vec3 o) const
|
||||
{
|
||||
return Vec3(x_ + o.x_, y_ + o.y_,z_ + o.z_);
|
||||
}
|
||||
|
||||
Vec3 Vec3::operator-(Vec3 o) const
|
||||
{
|
||||
return Vec3(x_ - o.x_, y_ - o.y_,z_ - o.z_);
|
||||
}
|
||||
|
||||
OnMat3::OnMat3(Vec3 r1, Vec3 r2, Vec3 r3) : r1_(r1), r2_(r2), r3_(r3)
|
||||
{
|
||||
c1_.x_ = r1_.x_; c2_.x_ = r1_.y_; c3_.x_ = r1_.z_;
|
||||
c1_.y_ = r2_.x_; c2_.y_ = r2_.y_; c3_.y_ = r2_.z_;
|
||||
c1_.z_ = r3_.x_; c2_.z_ = r3_.y_; c3_.z_ = r3_.z_;
|
||||
}
|
||||
OnMat3::OnMat3(const OnMat3 &o) : r1_(o.r1_), r2_(o.r2_), r3_(o.r3_)
|
||||
{
|
||||
c1_.x_ = r1_.x_; c2_.x_ = r1_.y_; c3_.x_ = r1_.z_;
|
||||
c1_.y_ = r2_.x_; c2_.y_ = r2_.y_; c3_.y_ = r2_.z_;
|
||||
c1_.z_ = r3_.x_; c2_.z_ = r3_.y_; c3_.z_ = r3_.z_;
|
||||
}
|
||||
|
||||
double OnMat3::det() const
|
||||
{
|
||||
return r1_.x_*r2_.y_*r3_.z_ + r1_.y_*r2_.z_*r3_.x_ + r1_.z_*r2_.x_*r3_.y_ - r1_.z_*r2_.y_*r3_.x_ - r1_.y_*r2_.x_*r3_.z_ - r1_.x_*r2_.z_*r3_.y_;
|
||||
}
|
||||
|
||||
OnMat3 OnMat3::transpose() const
|
||||
{
|
||||
return OnMat3(Vec3(r1_.x_, r2_.x_, r3_.x_), Vec3(r1_.y_, r2_.y_, r3_.y_), Vec3(r1_.z_, r2_.z_, r3_.z_));
|
||||
}
|
||||
|
||||
OnMat3 OnMat3::operator*(OnMat3 o) const
|
||||
{
|
||||
return OnMat3( Vec3(r1_.dot(o.c1_), r1_.dot(o.c2_), r1_.dot(o.c3_)),
|
||||
Vec3(r2_.dot(o.c1_), r2_.dot(o.c2_), r2_.dot(o.c3_)),
|
||||
Vec3(r3_.dot(o.c1_), r3_.dot(o.c2_), r3_.dot(o.c3_)));
|
||||
}
|
||||
|
||||
Vec3 OnMat3::operator*(Vec3 o)
|
||||
{
|
||||
return Vec3(r1_.dot(o), r2_.dot(o), r3_.dot(o));
|
||||
}
|
||||
|
||||
Mat4::Mat4()
|
||||
{
|
||||
r1c1_ = 1.0; r1c2_ = 0.0; r1c3_ = 0.0; r1c4_ = 0.0;
|
||||
r2c1_ = 0.0; r2c2_ = 1.0; r2c3_ = 0.0; r2c4_ = 0.0;
|
||||
r3c1_ = 0.0; r3c2_ = 0.0; r3c3_ = 1.0; r3c4_ = 0.0;
|
||||
r4c1_ = 0.0; r4c2_ = 0.0; r4c3_ = 0.0; r4c4_ = 1.0;
|
||||
}
|
||||
|
||||
Mat4::Mat4(OnMat3 rotation, Vec3 translation, double scaling)
|
||||
{
|
||||
r1c1_ = scaling * rotation.r1_.x_; r1c2_ = scaling * rotation.r1_.y_; r1c3_ = scaling * rotation.r1_.z_; r1c4_ = translation.x_;
|
||||
r2c1_ = scaling * rotation.r2_.x_; r2c2_ = scaling * rotation.r2_.y_; r2c3_ = scaling * rotation.r2_.z_; r2c4_ = translation.y_;
|
||||
r3c1_ = scaling * rotation.r3_.x_; r3c2_ = scaling * rotation.r3_.y_; r3c3_ = scaling * rotation.r3_.z_; r3c4_ = translation.z_;
|
||||
r4c1_ = 0.0; r4c2_ = 0.0; r4c3_ = 0.0; r4c4_ = 1.0;
|
||||
}
|
||||
|
||||
Vec3 Mat4::operator*(Vec3 o)
|
||||
{
|
||||
return Vec3(
|
||||
r1c1_ * o.x_ + r1c2_* o.y_ + r1c3_* o.z_ + r1c4_,
|
||||
r2c1_ * o.x_ + r2c2_* o.y_ + r2c3_* o.z_ + r2c4_,
|
||||
r3c1_ * o.x_ + r3c2_* o.y_ + r3c3_* o.z_ + r3c4_
|
||||
);
|
||||
}
|
||||
|
||||
void FindTransform::findTransform(Vec3 fromA, Vec3 fromB, Vec3 fromC, Vec3 toA, Vec3 toB, Vec3 toC)
|
||||
{
|
||||
Vec3 a1 = toA;
|
||||
Vec3 b1 = toB;
|
||||
Vec3 c1 = toC;
|
||||
Vec3 a2 = fromA;
|
||||
Vec3 b2 = fromB;
|
||||
Vec3 c2 = fromC;
|
||||
|
||||
Vec3 y1 = (a1 - c1).cross(b1 - c1).norm();
|
||||
Vec3 z1 = (a1 - c1).norm();
|
||||
Vec3 x1 = y1.cross(z1);
|
||||
|
||||
Vec3 y2 = (a2 - c2).cross(b2 - c2).norm();
|
||||
Vec3 z2 = (a2 - c2).norm();
|
||||
Vec3 x2 = y2.cross(z2);
|
||||
OnMat3 mat1 = OnMat3(x1, y1, z1).transpose();
|
||||
OnMat3 mat2 = OnMat3(x2, y2, z2).transpose();
|
||||
|
||||
OnMat3 rotation = mat1 * mat2.transpose();
|
||||
Vec3 translation = c1 - c2;
|
||||
|
||||
double scale = (a1 - c1).length() / (a2 - c2).length();
|
||||
|
||||
translation = rotation * c2 * (-scale) + c1;
|
||||
Mat4 transformation(rotation, translation, scale);
|
||||
transform_ = transformation;
|
||||
}
|
||||
|
||||
double FindTransform::error(Vec3 fromA, Vec3 toA)
|
||||
{
|
||||
return (transform_*fromA - toA).length();
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
// C++
|
||||
#include <math.h>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
/*!
|
||||
* \brief Handles basic 3d vector math.
|
||||
**/
|
||||
struct Vec3
|
||||
{
|
||||
Vec3(double x = 0.0, double y = 0.0, double z = 0.0);
|
||||
Vec3(const Vec3 &o);
|
||||
|
||||
double x_,y_,z_; /**< The x, y and z values of the vector. **/
|
||||
|
||||
/*!
|
||||
* \brief cross The cross product between two vectors.
|
||||
**/
|
||||
Vec3 cross(Vec3 o) const;
|
||||
|
||||
/*!
|
||||
* \brief dot The scalar product between two vectors.
|
||||
**/
|
||||
double dot(Vec3 o) const;
|
||||
|
||||
/*!
|
||||
* \brief length The length of the vector.
|
||||
**/
|
||||
double length() const;
|
||||
|
||||
/*!
|
||||
* \brief norm Returns a normalized version of this vector.
|
||||
**/
|
||||
Vec3 norm() const;
|
||||
|
||||
/*!
|
||||
* \brief Scales this vector.
|
||||
**/
|
||||
Vec3 operator*(double d) const;
|
||||
|
||||
/*!
|
||||
* \brief Addition between two vectors.
|
||||
**/
|
||||
Vec3 operator+(Vec3 o) const;
|
||||
|
||||
/*!
|
||||
* \brief Subtraction between two vectors.
|
||||
**/
|
||||
Vec3 operator-(Vec3 o) const;
|
||||
|
||||
friend std::ostream & operator<<(std::ostream &os, Vec3 v)
|
||||
{
|
||||
return os << "[" << std::setprecision(8) << v.x_ << ", " << std::setprecision(4) << v.y_ << ", " << v.z_ << "]";
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Describes a 3d orthonormal matrix.
|
||||
**/
|
||||
class OnMat3
|
||||
{
|
||||
public:
|
||||
OnMat3(Vec3 r1, Vec3 r2, Vec3 r3);
|
||||
OnMat3(const OnMat3 &o);
|
||||
|
||||
Vec3 r1_; /**< The first row of the matrix. **/
|
||||
Vec3 r2_; /**< The second row of the matrix. **/
|
||||
Vec3 r3_; /**< The third row of the matrix. **/
|
||||
Vec3 c1_; /**< The first column of the matrix. **/
|
||||
Vec3 c2_; /**< The second column of the matrix. **/
|
||||
Vec3 c3_; /**< The third column of the matrix. **/
|
||||
|
||||
/*!
|
||||
* \brief The determinant of the matrix.
|
||||
**/
|
||||
double det() const;
|
||||
|
||||
/*!
|
||||
* \brief The transpose of the OnMat3 (equal to inverse).
|
||||
**/
|
||||
OnMat3 transpose() const;
|
||||
|
||||
/*!
|
||||
* \brief Matrix multiplication between two ON matrices.
|
||||
**/
|
||||
OnMat3 operator*(OnMat3 o) const;
|
||||
|
||||
/*!
|
||||
* \brief Right side multiplication with a 3d vector.
|
||||
**/
|
||||
Vec3 operator*(Vec3 o);
|
||||
|
||||
friend std::ostream & operator<<(std::ostream &os, OnMat3 m)
|
||||
{
|
||||
return os << "[" << std::endl << m.r1_ << std::endl << m.r2_ << std::endl << m.r3_ << std::endl << "]" << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Describes an affine transformation.
|
||||
**/
|
||||
class Mat4
|
||||
{
|
||||
public:
|
||||
Mat4();
|
||||
Mat4(OnMat3 rotation, Vec3 translation, double scaling);
|
||||
|
||||
/*!
|
||||
* \brief Right side multiplication with a 3d vector.
|
||||
**/
|
||||
Vec3 operator*(Vec3 o);
|
||||
|
||||
double r1c1_; /**< Matrix element 0 0 **/
|
||||
double r1c2_; /**< Matrix element 0 1 **/
|
||||
double r1c3_; /**< Matrix element 0 2 **/
|
||||
double r1c4_; /**< Matrix element 0 3 **/
|
||||
double r2c1_; /**< Matrix element 1 0 **/
|
||||
double r2c2_; /**< Matrix element 1 1 **/
|
||||
double r2c3_; /**< Matrix element 1 2 **/
|
||||
double r2c4_; /**< Matrix element 1 3 **/
|
||||
double r3c1_; /**< Matrix element 2 0 **/
|
||||
double r3c2_; /**< Matrix element 2 1 **/
|
||||
double r3c3_; /**< Matrix element 2 2 **/
|
||||
double r3c4_; /**< Matrix element 2 3 **/
|
||||
double r4c1_; /**< Matrix element 3 0 **/
|
||||
double r4c2_; /**< Matrix element 3 1 **/
|
||||
double r4c3_; /**< Matrix element 3 2 **/
|
||||
double r4c4_; /**< Matrix element 3 3 **/
|
||||
|
||||
friend std::ostream & operator<<(std::ostream &os, Mat4 m)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss.precision(8);
|
||||
ss.setf(std::ios::fixed, std::ios::floatfield);
|
||||
|
||||
ss << "[ " << m.r1c1_ << ",\t" << m.r1c2_ << ",\t" << m.r1c3_ << ",\t" << m.r1c4_ << " ]" << std::endl <<
|
||||
"[ " << m.r2c1_ << ",\t" << m.r2c2_ << ",\t" << m.r2c3_ << ",\t" << m.r2c4_ << " ]" << std::endl <<
|
||||
"[ " << m.r3c1_ << ",\t" << m.r3c2_ << ",\t" << m.r3c3_ << ",\t" << m.r3c4_ << " ]" << std::endl <<
|
||||
"[ " << m.r4c1_ << ",\t" << m.r4c2_ << ",\t" << m.r4c3_ << ",\t" << m.r4c4_ << " ]";
|
||||
|
||||
return os << ss.str();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class FindTransform
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* \brief findTransform Generates an affine transform from the three 'from' vector to the three 'to' vectors.
|
||||
* The transform is such that transform * fromA = toA,
|
||||
* transform * fromB = toB,
|
||||
* transform * fromC = toC,
|
||||
**/
|
||||
void findTransform(Vec3 fromA, Vec3 fromB, Vec3 fromC, Vec3 toA, Vec3 toB, Vec3 toC);
|
||||
|
||||
/*!
|
||||
* \brief error Returns the distance beteween the 'from' and 'to' vectors, after the transform has been applied.
|
||||
**/
|
||||
double error(Vec3 fromA, Vec3 toA);
|
||||
|
||||
Mat4 transform_; /**< The affine transform. **/
|
||||
};
|
|
@ -0,0 +1,530 @@
|
|||
// PCL
|
||||
#include <pcl/io/obj_io.h>
|
||||
#include <pcl/common/transforms.h>
|
||||
|
||||
// Modified PCL
|
||||
#include "modifiedPclFunctions.hpp"
|
||||
|
||||
// This
|
||||
#include "Georef.hpp"
|
||||
|
||||
std::ostream& operator<<(std::ostream &os, const GeorefSystem &geo)
|
||||
{
|
||||
return os << geo.system_ << " " << static_cast<int>(geo.falseEasting_) << " " << static_cast<int>(geo.falseNorthing_);
|
||||
}
|
||||
|
||||
GeorefCamera::GeorefCamera()
|
||||
:focalLength_(0.0), k1_(0.0), k2_(0.0), transform_(NULL), position_(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
GeorefCamera::GeorefCamera(const GeorefCamera &other)
|
||||
: focalLength_(other.focalLength_), k1_(other.k1_), k2_(other.k2_),
|
||||
easting_(other.easting_), northing_(other.northing_), altitude_(other.altitude_),
|
||||
transform_(NULL), position_(NULL)
|
||||
{
|
||||
if(NULL != other.transform_)
|
||||
{
|
||||
transform_ = new Eigen::Affine3f(*other.transform_);
|
||||
}
|
||||
if(NULL != other.position_)
|
||||
{
|
||||
position_ = new Eigen::Vector3f(*other.position_);
|
||||
}
|
||||
}
|
||||
|
||||
GeorefCamera::~GeorefCamera()
|
||||
{
|
||||
if(NULL != transform_)
|
||||
{
|
||||
delete transform_;
|
||||
transform_ = NULL;
|
||||
}
|
||||
if(NULL != position_)
|
||||
{
|
||||
delete position_;
|
||||
position_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void GeorefCamera::extractCamera(std::ifstream &bundleStream)
|
||||
{
|
||||
// Extract intrinsic parameters.
|
||||
bundleStream >> focalLength_ >> k1_ >> k2_;
|
||||
|
||||
Eigen::Vector3f t;
|
||||
Eigen::Matrix3f rot;
|
||||
Eigen::Affine3f transform;
|
||||
|
||||
bundleStream >> transform(0,0); // Read rotation (0,0) from bundle file
|
||||
bundleStream >> transform(0,1); // Read rotation (0,1) from bundle file
|
||||
bundleStream >> transform(0,2); // Read rotation (0,2) from bundle file
|
||||
|
||||
bundleStream >> transform(1,0); // Read rotation (1,0) from bundle file
|
||||
bundleStream >> transform(1,1); // Read rotation (1,1) from bundle file
|
||||
bundleStream >> transform(1,2); // Read rotation (1,2) from bundle file
|
||||
|
||||
bundleStream >> transform(2,0); // Read rotation (2,0) from bundle file
|
||||
bundleStream >> transform(2,1); // Read rotation (2,1) from bundle file
|
||||
bundleStream >> transform(2,2); // Read rotation (2,2) from bundle file
|
||||
|
||||
bundleStream >> t(0); // Read translation (0,3) from bundle file
|
||||
bundleStream >> t(1); // Read translation (1,3) from bundle file
|
||||
bundleStream >> t(2); // Read translation (2,3) from bundle file
|
||||
|
||||
rot = transform.matrix().topLeftCorner<3,3>();
|
||||
|
||||
// Calculate translation according to -R't and store in vector.
|
||||
t = -rot.transpose()*t;
|
||||
|
||||
transform(0,3) = t(0);
|
||||
transform(1,3) = t(1);
|
||||
transform(2,3) = t(2);
|
||||
|
||||
// Set transform and position.
|
||||
if(NULL != transform_)
|
||||
{
|
||||
delete transform_;
|
||||
transform_ = NULL;
|
||||
}
|
||||
|
||||
transform_ = new Eigen::Affine3f(transform);
|
||||
|
||||
if(NULL != position_)
|
||||
{
|
||||
delete position_;
|
||||
position_ = NULL;
|
||||
}
|
||||
position_ = new Eigen::Vector3f(t);
|
||||
}
|
||||
|
||||
void GeorefCamera::extractCameraGeoref(std::istringstream &coordStream)
|
||||
{
|
||||
coordStream >> easting_ >> northing_ >> altitude_;
|
||||
}
|
||||
|
||||
Vec3 GeorefCamera::getPos()
|
||||
{
|
||||
return Vec3((*position_)(0),(*position_)(1),(*position_)(2));
|
||||
}
|
||||
|
||||
Vec3 GeorefCamera::getReferencedPos()
|
||||
{
|
||||
return Vec3(easting_,northing_,altitude_);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream &os, const GeorefCamera &cam)
|
||||
{
|
||||
os << "Focal, k1, k2 : " << cam.focalLength_ << ", " << cam.k1_ << ", " << cam.k2_ << "\n";
|
||||
if(NULL != cam.transform_)
|
||||
{
|
||||
os << "Transform :\n" << cam.transform_->matrix() << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
os << "Transform :\nNULL\n";
|
||||
}
|
||||
if(NULL != cam.position_)
|
||||
{
|
||||
os << "Position :\n" << cam.position_->matrix() << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
os << "Position :\nNULL\n";
|
||||
}
|
||||
os << "east, north, alt : " << cam.easting_ << ", " << cam.northing_ << ", " << cam.altitude_ << '\n';
|
||||
return os;
|
||||
}
|
||||
|
||||
Georef::Georef()
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
|
||||
bundleFilename_ = "";
|
||||
coordFilename_ = "";
|
||||
inputObjFilename_ = "";
|
||||
outputObjFilename_ = "";
|
||||
}
|
||||
|
||||
Georef::~Georef()
|
||||
{
|
||||
}
|
||||
|
||||
int Georef::run(int argc, char *argv[])
|
||||
{
|
||||
try
|
||||
{
|
||||
parseArguments(argc, argv);
|
||||
makeGeoreferencedModel();
|
||||
}
|
||||
catch (const GeorefException& e)
|
||||
{
|
||||
log_ << e.what() << "\n";
|
||||
log_.print(logFile_);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_ << "Error in Georef:\n";
|
||||
log_ << e.what() << "\n";
|
||||
log_.print(logFile_);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
log_ << "Unknown error, terminating:\n";
|
||||
log_.print(logFile_);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
log_.print(logFile_);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void Georef::parseArguments(int argc, char *argv[])
|
||||
{
|
||||
bool outputSpecified = false;
|
||||
|
||||
logFile_ = std::string(argv[0]) + "_log.txt";
|
||||
log_ << logFile_ << "\n";
|
||||
|
||||
// If no arguments were passed, print help.
|
||||
if (argc == 1)
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
|
||||
log_ << "Arguments given\n";
|
||||
for(int argIndex = 1; argIndex < argc; ++argIndex)
|
||||
{
|
||||
log_ << argv[argIndex] << '\n';
|
||||
}
|
||||
|
||||
log_ << '\n';
|
||||
for(int argIndex = 1; argIndex < argc; ++argIndex)
|
||||
{
|
||||
// The argument to be parsed.
|
||||
std::string argument = std::string(argv[argIndex]);
|
||||
|
||||
if(argument == "-help")
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
else if(argument == "-verbose")
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
}
|
||||
else if(argument == "-bundleFile" && argIndex < argc)
|
||||
{
|
||||
argIndex++;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw GeorefException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
bundleFilename_ = std::string(argv[argIndex]);
|
||||
log_ << "Reading cameras from: " << bundleFilename_ << "\n";
|
||||
}
|
||||
else if(argument == "-coordFile" && argIndex < argc)
|
||||
{
|
||||
argIndex++;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw GeorefException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
coordFilename_ = std::string(argv[argIndex]);
|
||||
log_ << "Reading cameras georeferenced positions from: " << coordFilename_ << "\n";
|
||||
}
|
||||
else if(argument == "-inputFile" && argIndex < argc)
|
||||
{
|
||||
argIndex++;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw GeorefException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
inputObjFilename_ = std::string(argv[argIndex]);
|
||||
log_ << "Reading textured mesh from: " << inputObjFilename_ << "\n";
|
||||
}
|
||||
else if(argument == "-outputFile" && argIndex < argc)
|
||||
{
|
||||
argIndex++;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw GeorefException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
outputObjFilename_ = std::string(argv[argIndex]);
|
||||
log_ << "Writing output to: " << outputObjFilename_ << "\n";
|
||||
outputSpecified = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
printHelp();
|
||||
throw GeorefException("Unrecognised argument '" + argument + "'");
|
||||
}
|
||||
}
|
||||
|
||||
if(!outputSpecified)
|
||||
{
|
||||
makeDefaultOutput();
|
||||
}
|
||||
}
|
||||
|
||||
void Georef::printHelp()
|
||||
{
|
||||
bool printInCoutPop = log_.isPrintingInCout();
|
||||
log_.setIsPrintingInCout(true);
|
||||
|
||||
log_ << "Georef.exe\n\n";
|
||||
|
||||
log_ << "Purpose:" << "\n";
|
||||
log_ << "Create an orthograpical photo from an oriented textured mesh." << "\n";
|
||||
|
||||
log_ << "Usage:" << "\n";
|
||||
log_ << "The program requires a path to a camera bundle file, a camera georeference coords file, and an input OBJ mesh file. All other input parameters are optional." << "\n\n";
|
||||
|
||||
log_ << "The following flags are available\n";
|
||||
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: \"-<argument name> <argument>\", (without <>), and the following parameters are configureable: " << "\n";
|
||||
log_ << "\"-bundleFile <path>\" (mandatory)" << "\n";
|
||||
log_ << "\"Input cameras bundle file.\n\n";
|
||||
|
||||
log_ << "\"-coordFile <path>\" (mandatory)" << "\n";
|
||||
log_ << "\"Input cameras geroreferenced coords file.\n\n";
|
||||
|
||||
log_ << "\"-inputFile <path>\" (mandatory)" << "\n";
|
||||
log_ << "\"Input obj file that must contain a textured mesh.\n\n";
|
||||
|
||||
log_ << "\"-outputFile <path>\" (optional, default <inputFile>_geo)" << "\n";
|
||||
log_ << "\"Output obj file that will contain the georeferenced texture mesh.\n\n";
|
||||
|
||||
log_.setIsPrintingInCout(printInCoutPop);
|
||||
}
|
||||
|
||||
void Georef::makeDefaultOutput()
|
||||
{
|
||||
if(inputObjFilename_.empty())
|
||||
{
|
||||
throw GeorefException("Tried to generate default ouptut file without having an input file.");
|
||||
}
|
||||
|
||||
std::string tmp = inputObjFilename_;
|
||||
size_t findPos = tmp.find_last_of(".");
|
||||
|
||||
if(std::string::npos == findPos)
|
||||
{
|
||||
throw GeorefException("Tried to generate default ouptut file, could not find .obj in the input file:\n\'"+inputObjFilename_+"\'");
|
||||
}
|
||||
|
||||
tmp = tmp.substr(0, findPos);
|
||||
|
||||
outputObjFilename_ = tmp + "_geo.obj";
|
||||
log_ << "Writing output to: " << outputObjFilename_ << "\n";
|
||||
}
|
||||
|
||||
void Georef::makeGeoreferencedModel()
|
||||
{
|
||||
// Read translations from bundle file
|
||||
std::ifstream bundleStream(bundleFilename_.c_str());
|
||||
if (!bundleStream.good())
|
||||
{
|
||||
throw GeorefException("Failed opening " + bundleFilename_ + " for reading." + '\n');
|
||||
}
|
||||
|
||||
// Read Cameras.
|
||||
std::string bundleLine;
|
||||
std::getline(bundleStream, bundleLine); // Read past bundle version comment
|
||||
int numCameras, numPoints;
|
||||
bundleStream >> numCameras >> numPoints;
|
||||
for (int i=0; i<numCameras; ++i)
|
||||
{
|
||||
cameras_.push_back(GeorefCamera());
|
||||
cameras_.back().extractCamera(bundleStream);
|
||||
}
|
||||
|
||||
// Read coords from coord file generated by extract_utm tool
|
||||
std::ifstream coordStream(coordFilename_.c_str());
|
||||
if (!coordStream.good())
|
||||
{
|
||||
throw GeorefException("Failed opening " + coordFilename_ + " for reading." + '\n');
|
||||
}
|
||||
|
||||
std::string coordString;
|
||||
std::getline(coordStream, georefSystem_.system_); // System
|
||||
{
|
||||
std::getline(coordStream, coordString); // Flase easting & northing.
|
||||
std::stringstream ss(coordString);
|
||||
|
||||
ss >> georefSystem_.falseEasting_ >> georefSystem_.falseNorthing_;
|
||||
}
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Geographical reference system\n";
|
||||
log_ << georefSystem_ << '\n';
|
||||
|
||||
// The number of cameras in the coords file.
|
||||
size_t nGeorefCameras = 0;
|
||||
|
||||
// Read the georefernced position for all cameras.
|
||||
while (std::getline(coordStream, coordString))
|
||||
{
|
||||
if(nGeorefCameras >= cameras_.size())
|
||||
{
|
||||
throw GeorefException("Error to many cameras in \'" + coordFilename_ + "\' coord file.\n");
|
||||
}
|
||||
|
||||
std::istringstream istr(coordString);
|
||||
cameras_[nGeorefCameras].extractCameraGeoref(istr);
|
||||
|
||||
++nGeorefCameras;
|
||||
}
|
||||
coordStream.close();
|
||||
|
||||
if(nGeorefCameras < cameras_.size())
|
||||
{
|
||||
throw GeorefException("Not enough cameras in \'" + coordFilename_ + "\' coord file.\n");
|
||||
}
|
||||
|
||||
// The optimal camera triplet.
|
||||
size_t cam0, cam1, cam2;
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Choosing optimal camera triplet...\n";
|
||||
chooseBestCameraTriplet(cam0, cam1, cam2);
|
||||
log_ << "... optimal camera triplet chosen:\n";
|
||||
log_ << cam0 << ", " << cam1 << ", " << cam2 << '\n';
|
||||
log_ << '\n';
|
||||
FindTransform transFinal;
|
||||
transFinal.findTransform(cameras_[cam0].getPos(), cameras_[cam1].getPos(), cameras_[cam2].getPos(),
|
||||
cameras_[cam0].getReferencedPos(), cameras_[cam1].getReferencedPos(), cameras_[cam2].getReferencedPos());
|
||||
log_ << "Final transform:\n";
|
||||
log_ << transFinal.transform_ << '\n';
|
||||
|
||||
// The tranform used to move the chosen area into the ortho photo.
|
||||
Eigen::Transform<float, 3, Eigen::Affine> transform;
|
||||
|
||||
transform(0, 0) = static_cast<float>(transFinal.transform_.r1c1_);
|
||||
transform(1, 0) = static_cast<float>(transFinal.transform_.r2c1_);
|
||||
transform(2, 0) = static_cast<float>(transFinal.transform_.r3c1_);
|
||||
transform(3, 0) = static_cast<float>(transFinal.transform_.r4c1_);
|
||||
|
||||
transform(0, 1) = static_cast<float>(transFinal.transform_.r1c2_);
|
||||
transform(1, 1) = static_cast<float>(transFinal.transform_.r2c2_);
|
||||
transform(2, 1) = static_cast<float>(transFinal.transform_.r3c2_);
|
||||
transform(3, 1) = static_cast<float>(transFinal.transform_.r4c2_);
|
||||
|
||||
transform(0, 2) = static_cast<float>(transFinal.transform_.r1c3_);
|
||||
transform(1, 2) = static_cast<float>(transFinal.transform_.r2c3_);
|
||||
transform(2, 2) = static_cast<float>(transFinal.transform_.r3c3_);
|
||||
transform(3, 2) = static_cast<float>(transFinal.transform_.r4c3_);
|
||||
|
||||
transform(0, 3) = static_cast<float>(transFinal.transform_.r1c4_);
|
||||
transform(1, 3) = static_cast<float>(transFinal.transform_.r2c4_);
|
||||
transform(2, 3) = static_cast<float>(transFinal.transform_.r3c4_);
|
||||
transform(3, 3) = static_cast<float>(transFinal.transform_.r4c4_);
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Reading mesh file...\n";
|
||||
// The textureds mesh.e
|
||||
pcl::TextureMesh mesh;
|
||||
pcl::io::loadOBJFile(inputObjFilename_, mesh);
|
||||
log_ << ".. mesh file read.\n";
|
||||
|
||||
// Contains the vertices of the mesh.
|
||||
pcl::PointCloud<pcl::PointXYZ>::Ptr meshCloud (new pcl::PointCloud<pcl::PointXYZ>);
|
||||
pcl::fromPCLPointCloud2 (mesh.cloud, *meshCloud);
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Applying transform to mesh...\n";
|
||||
// Move the mesh into position.
|
||||
pcl::transformPointCloud(*meshCloud, *meshCloud, transform);
|
||||
log_ << ".. mesh transformed.\n";
|
||||
|
||||
// Update the mesh.
|
||||
pcl::toPCLPointCloud2 (*meshCloud, mesh.cloud);
|
||||
|
||||
|
||||
// Iterate over each part of the mesh (one per material), to make texture file paths relative the .mtl file.
|
||||
for(size_t t = 0; t < mesh.tex_materials.size(); ++t)
|
||||
{
|
||||
// The material of the current submesh.
|
||||
pcl::TexMaterial& material = mesh.tex_materials[t];
|
||||
|
||||
size_t find = material.tex_file.find_last_of("/\\");
|
||||
if(std::string::npos != find)
|
||||
{
|
||||
material.tex_file = material.tex_file.substr(find + 1);
|
||||
}
|
||||
}
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Saving mesh file to \'" << outputObjFilename_ << "\'...\n";
|
||||
saveOBJFile(outputObjFilename_, mesh, 8);
|
||||
log_ << ".. mesh file saved.\n";
|
||||
|
||||
printGeorefSystem();
|
||||
}
|
||||
|
||||
void Georef::chooseBestCameraTriplet(size_t &cam0, size_t &cam1, size_t &cam2)
|
||||
{
|
||||
double minTotError = std::numeric_limits<double>::infinity();
|
||||
|
||||
for(size_t t = 0; t < cameras_.size(); ++t)
|
||||
{
|
||||
for(size_t s = t; s < cameras_.size(); ++s)
|
||||
{
|
||||
for(size_t p = s; p < cameras_.size(); ++p)
|
||||
{
|
||||
FindTransform trans;
|
||||
trans.findTransform(cameras_[t].getPos(), cameras_[s].getPos(), cameras_[p].getPos(),
|
||||
cameras_[t].getReferencedPos(), cameras_[s].getReferencedPos(), cameras_[p].getReferencedPos());
|
||||
|
||||
// The total error for the curren camera triplet.
|
||||
double totError = 0.0;
|
||||
|
||||
for(size_t r = 0; r < cameras_.size(); ++r)
|
||||
{
|
||||
totError += trans.error(cameras_[r].getPos(), cameras_[r].getReferencedPos());
|
||||
}
|
||||
|
||||
if(minTotError > totError)
|
||||
{
|
||||
minTotError = totError;
|
||||
cam0 = t;
|
||||
cam1 = s;
|
||||
cam2 = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log_ << "Mean georeference error " << minTotError / static_cast<double>(cameras_.size()) << '\n';
|
||||
}
|
||||
|
||||
void Georef::printGeorefSystem()
|
||||
{
|
||||
if(outputObjFilename_.empty())
|
||||
{
|
||||
throw GeorefException("Output file path empty!.");
|
||||
}
|
||||
|
||||
std::string tmp = outputObjFilename_;
|
||||
size_t findPos = tmp.find_last_of(".");
|
||||
|
||||
if(std::string::npos == findPos)
|
||||
{
|
||||
throw GeorefException("Tried to generate default ouptut file, could not find .obj in the output file:\n\'"+outputObjFilename_+"\'");
|
||||
}
|
||||
|
||||
tmp = tmp.substr(0, findPos);
|
||||
|
||||
tmp = tmp + "_georef_system.txt";
|
||||
log_ << '\n';
|
||||
log_ << "Saving georeference system file to \'" << tmp << "\'...\n";
|
||||
std::ofstream geoStream(tmp.c_str());
|
||||
geoStream << georefSystem_ << std::endl;
|
||||
geoStream.close();
|
||||
log_ << "... georeference system saved.\n";
|
||||
}
|
||||
|
|
@ -0,0 +1,530 @@
|
|||
// PCL
|
||||
#include <pcl/io/obj_io.h>
|
||||
#include <pcl/common/transforms.h>
|
||||
|
||||
// Modified PCL
|
||||
#include "modifiedPclFunctions.hpp"
|
||||
|
||||
// This
|
||||
#include "Georef.hpp"
|
||||
|
||||
std::ostream& operator<<(std::ostream &os, const GeorefSystem &geo)
|
||||
{
|
||||
return os << geo.system_ << " " << static_cast<int>(geo.falseEasting_) << " " << static_cast<int>(geo.falseNorthing_);
|
||||
}
|
||||
|
||||
GeorefCamera::GeorefCamera()
|
||||
:focalLength_(0.0), k1_(0.0), k2_(0.0), transform_(NULL), position_(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
GeorefCamera::GeorefCamera(const GeorefCamera &other)
|
||||
: focalLength_(other.focalLength_), k1_(other.k1_), k2_(other.k2_),
|
||||
easting_(other.easting_), northing_(other.northing_), altitude_(other.altitude_),
|
||||
transform_(NULL), position_(NULL)
|
||||
{
|
||||
if(NULL != other.transform_)
|
||||
{
|
||||
transform_ = new Eigen::Affine3f(*other.transform_);
|
||||
}
|
||||
if(NULL != other.position_)
|
||||
{
|
||||
position_ = new Eigen::Vector3f(*other.position_);
|
||||
}
|
||||
}
|
||||
|
||||
GeorefCamera::~GeorefCamera()
|
||||
{
|
||||
if(NULL != transform_)
|
||||
{
|
||||
delete transform_;
|
||||
transform_ = NULL;
|
||||
}
|
||||
if(NULL != position_)
|
||||
{
|
||||
delete position_;
|
||||
position_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void GeorefCamera::extractCamera(std::ifstream &bundleStream)
|
||||
{
|
||||
// Extract intrinsic parameters.
|
||||
bundleStream >> focalLength_ >> k1_ >> k2_;
|
||||
|
||||
Eigen::Vector3f t;
|
||||
Eigen::Matrix3f rot;
|
||||
Eigen::Affine3f transform;
|
||||
|
||||
bundleStream >> transform(0,0); // Read rotation (0,0) from bundle file
|
||||
bundleStream >> transform(0,1); // Read rotation (0,1) from bundle file
|
||||
bundleStream >> transform(0,2); // Read rotation (0,2) from bundle file
|
||||
|
||||
bundleStream >> transform(1,0); // Read rotation (1,0) from bundle file
|
||||
bundleStream >> transform(1,1); // Read rotation (1,1) from bundle file
|
||||
bundleStream >> transform(1,2); // Read rotation (1,2) from bundle file
|
||||
|
||||
bundleStream >> transform(2,0); // Read rotation (2,0) from bundle file
|
||||
bundleStream >> transform(2,1); // Read rotation (2,1) from bundle file
|
||||
bundleStream >> transform(2,2); // Read rotation (2,2) from bundle file
|
||||
|
||||
bundleStream >> t(0); // Read translation (0,3) from bundle file
|
||||
bundleStream >> t(1); // Read translation (1,3) from bundle file
|
||||
bundleStream >> t(2); // Read translation (2,3) from bundle file
|
||||
|
||||
rot = transform.matrix().topLeftCorner<3,3>();
|
||||
|
||||
// Calculate translation according to -R't and store in vector.
|
||||
t = -rot.transpose()*t;
|
||||
|
||||
transform(0,3) = t(0);
|
||||
transform(1,3) = t(1);
|
||||
transform(2,3) = t(2);
|
||||
|
||||
// Set transform and position.
|
||||
if(NULL != transform_)
|
||||
{
|
||||
delete transform_;
|
||||
transform_ = NULL;
|
||||
}
|
||||
|
||||
transform_ = new Eigen::Affine3f(transform);
|
||||
|
||||
if(NULL != position_)
|
||||
{
|
||||
delete position_;
|
||||
position_ = NULL;
|
||||
}
|
||||
position_ = new Eigen::Vector3f(t);
|
||||
}
|
||||
|
||||
void GeorefCamera::extractCameraGeoref(std::istringstream &coordStream)
|
||||
{
|
||||
coordStream >> easting_ >> northing_ >> altitude_;
|
||||
}
|
||||
|
||||
Vec3 GeorefCamera::getPos()
|
||||
{
|
||||
return Vec3((*position_)(0),(*position_)(1),(*position_)(2));
|
||||
}
|
||||
|
||||
Vec3 GeorefCamera::getReferencedPos()
|
||||
{
|
||||
return Vec3(easting_,northing_,altitude_);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream &os, const GeorefCamera &cam)
|
||||
{
|
||||
os << "Focal, k1, k2 : " << cam.focalLength_ << ", " << cam.k1_ << ", " << cam.k2_ << "\n";
|
||||
if(NULL != cam.transform_)
|
||||
{
|
||||
os << "Transform :\n" << cam.transform_->matrix() << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
os << "Transform :\nNULL\n";
|
||||
}
|
||||
if(NULL != cam.position_)
|
||||
{
|
||||
os << "Position :\n" << cam.position_->matrix() << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
os << "Position :\nNULL\n";
|
||||
}
|
||||
os << "east, north, alt : " << cam.easting_ << ", " << cam.northing_ << ", " << cam.altitude_ << '\n';
|
||||
return os;
|
||||
}
|
||||
|
||||
Georef::Georef()
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
|
||||
bundleFilename_ = "";
|
||||
coordFilename_ = "";
|
||||
inputObjFilename_ = "";
|
||||
outputObjFilename_ = "";
|
||||
}
|
||||
|
||||
Georef::~Georef()
|
||||
{
|
||||
}
|
||||
|
||||
int Georef::run(int argc, char *argv[])
|
||||
{
|
||||
try
|
||||
{
|
||||
parseArguments(argc, argv);
|
||||
makeGeoreferencedModel();
|
||||
}
|
||||
catch (const GeorefException& e)
|
||||
{
|
||||
log_ << e.what() << "\n";
|
||||
log_.print(logFile_);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_ << "Error in Georef:\n";
|
||||
log_ << e.what() << "\n";
|
||||
log_.print(logFile_);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
log_ << "Unknown error, terminating:\n";
|
||||
log_.print(logFile_);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
log_.print(logFile_);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void Georef::parseArguments(int argc, char *argv[])
|
||||
{
|
||||
bool outputSpecified = false;
|
||||
|
||||
logFile_ = std::string(argv[0]) + "_log.txt";
|
||||
log_ << logFile_ << "\n";
|
||||
|
||||
// If no arguments were passed, print help.
|
||||
if (argc == 1)
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
|
||||
log_ << "Arguments given\n";
|
||||
for(int argIndex = 1; argIndex < argc; ++argIndex)
|
||||
{
|
||||
log_ << argv[argIndex] << '\n';
|
||||
}
|
||||
|
||||
log_ << '\n';
|
||||
for(int argIndex = 1; argIndex < argc; ++argIndex)
|
||||
{
|
||||
// The argument to be parsed.
|
||||
std::string argument = std::string(argv[argIndex]);
|
||||
|
||||
if(argument == "-help")
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
else if(argument == "-verbose")
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
}
|
||||
else if(argument == "-bundleFile" && argIndex < argc)
|
||||
{
|
||||
argIndex++;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw GeorefException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
bundleFilename_ = std::string(argv[argIndex]);
|
||||
log_ << "Reading cameras from: " << bundleFilename_ << "\n";
|
||||
}
|
||||
else if(argument == "-coordFile" && argIndex < argc)
|
||||
{
|
||||
argIndex++;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw GeorefException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
coordFilename_ = std::string(argv[argIndex]);
|
||||
log_ << "Reading cameras georeferenced positions from: " << coordFilename_ << "\n";
|
||||
}
|
||||
else if(argument == "-inputFile" && argIndex < argc)
|
||||
{
|
||||
argIndex++;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw GeorefException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
inputObjFilename_ = std::string(argv[argIndex]);
|
||||
log_ << "Reading textured mesh from: " << inputObjFilename_ << "\n";
|
||||
}
|
||||
else if(argument == "-outputFile" && argIndex < argc)
|
||||
{
|
||||
argIndex++;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw GeorefException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
outputObjFilename_ = std::string(argv[argIndex]);
|
||||
log_ << "Writing output to: " << outputObjFilename_ << "\n";
|
||||
outputSpecified = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
printHelp();
|
||||
throw GeorefException("Unrecognised argument '" + argument + "'");
|
||||
}
|
||||
}
|
||||
|
||||
if(!outputSpecified)
|
||||
{
|
||||
makeDefaultOutput();
|
||||
}
|
||||
}
|
||||
|
||||
void Georef::printHelp()
|
||||
{
|
||||
bool printInCoutPop = log_.isPrintingInCout();
|
||||
log_.setIsPrintingInCout(true);
|
||||
|
||||
log_ << "Georef.exe\n\n";
|
||||
|
||||
log_ << "Purpose:" << "\n";
|
||||
log_ << "Create an orthograpical photo from an oriented textured mesh." << "\n";
|
||||
|
||||
log_ << "Usage:" << "\n";
|
||||
log_ << "The program requires a path to a camera bundle file, a camera georeference coords file, and an input OBJ mesh file. All other input parameters are optional." << "\n\n";
|
||||
|
||||
log_ << "The following flags are available\n";
|
||||
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: \"-<argument name> <argument>\", (without <>), and the following parameters are configureable: " << "\n";
|
||||
log_ << "\"-bundleFile <path>\" (mandatory)" << "\n";
|
||||
log_ << "\"Input cameras bundle file.\n\n";
|
||||
|
||||
log_ << "\"-coordFile <path>\" (mandatory)" << "\n";
|
||||
log_ << "\"Input cameras geroreferenced coords file.\n\n";
|
||||
|
||||
log_ << "\"-inputFile <path>\" (mandatory)" << "\n";
|
||||
log_ << "\"Input obj file that must contain a textured mesh.\n\n";
|
||||
|
||||
log_ << "\"-outputFile <path>\" (optional, default <inputFile>_geo)" << "\n";
|
||||
log_ << "\"Output obj file that will contain the georeferenced texture mesh.\n\n";
|
||||
|
||||
log_.setIsPrintingInCout(printInCoutPop);
|
||||
}
|
||||
|
||||
void Georef::makeDefaultOutput()
|
||||
{
|
||||
if(inputObjFilename_.empty())
|
||||
{
|
||||
throw GeorefException("Tried to generate default ouptut file without having an input file.");
|
||||
}
|
||||
|
||||
std::string tmp = inputObjFilename_;
|
||||
size_t findPos = tmp.find_last_of(".");
|
||||
|
||||
if(std::string::npos == findPos)
|
||||
{
|
||||
throw GeorefException("Tried to generate default ouptut file, could not find .obj in the input file:\n\'"+inputObjFilename_+"\'");
|
||||
}
|
||||
|
||||
tmp = tmp.substr(0, findPos);
|
||||
|
||||
outputObjFilename_ = tmp + "_geo.obj";
|
||||
log_ << "Writing output to: " << outputObjFilename_ << "\n";
|
||||
}
|
||||
|
||||
void Georef::makeGeoreferencedModel()
|
||||
{
|
||||
// Read translations from bundle file
|
||||
std::ifstream bundleStream(bundleFilename_.c_str());
|
||||
if (!bundleStream.good())
|
||||
{
|
||||
throw GeorefException("Failed opening " + bundleFilename_ + " for reading." + '\n');
|
||||
}
|
||||
|
||||
// Read Cameras.
|
||||
std::string bundleLine;
|
||||
std::getline(bundleStream, bundleLine); // Read past bundle version comment
|
||||
int numCameras, numPoints;
|
||||
bundleStream >> numCameras >> numPoints;
|
||||
for (int i=0; i<numCameras; ++i)
|
||||
{
|
||||
cameras_.push_back(GeorefCamera());
|
||||
cameras_.back().extractCamera(bundleStream);
|
||||
}
|
||||
|
||||
// Read coords from coord file generated by extract_utm tool
|
||||
std::ifstream coordStream(coordFilename_.c_str());
|
||||
if (!coordStream.good())
|
||||
{
|
||||
throw GeorefException("Failed opening " + coordFilename_ + " for reading." + '\n');
|
||||
}
|
||||
|
||||
std::string coordString;
|
||||
std::getline(coordStream, georefSystem_.system_); // System
|
||||
{
|
||||
std::getline(coordStream, coordString); // Flase easting & northing.
|
||||
std::stringstream ss(coordString);
|
||||
|
||||
ss >> georefSystem_.falseEasting_ >> georefSystem_.falseNorthing_;
|
||||
}
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Geographical reference system\n";
|
||||
log_ << georefSystem_ << '\n';
|
||||
|
||||
// The number of cameras in the coords file.
|
||||
size_t nGeorefCameras = 0;
|
||||
|
||||
// Read the georefernced position for all cameras.
|
||||
while (std::getline(coordStream, coordString))
|
||||
{
|
||||
if(nGeorefCameras >= cameras_.size())
|
||||
{
|
||||
throw GeorefException("Error to many cameras in \'" + coordFilename_ + "\' coord file.\n");
|
||||
}
|
||||
|
||||
std::istringstream istr(coordString);
|
||||
cameras_[nGeorefCameras].extractCameraGeoref(istr);
|
||||
|
||||
++nGeorefCameras;
|
||||
}
|
||||
coordStream.close();
|
||||
|
||||
if(nGeorefCameras < cameras_.size())
|
||||
{
|
||||
throw GeorefException("Not enough cameras in \'" + coordFilename_ + "\' coord file.\n");
|
||||
}
|
||||
|
||||
// The optimal camera triplet.
|
||||
size_t cam0, cam1, cam2;
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Choosing optimal camera triplet...\n";
|
||||
chooseBestCameraTriplet(cam0, cam1, cam2);
|
||||
log_ << "... optimal camera triplet chosen:\n";
|
||||
log_ << cam0 << ", " << cam1 << ", " << cam2 << '\n';
|
||||
log_ << '\n';
|
||||
FindTransform transFinal;
|
||||
transFinal.findTransform(cameras_[cam0].getPos(), cameras_[cam1].getPos(), cameras_[cam2].getPos(),
|
||||
cameras_[cam0].getReferencedPos(), cameras_[cam1].getReferencedPos(), cameras_[cam2].getReferencedPos());
|
||||
log_ << "Final transform:\n";
|
||||
log_ << transFinal.transform_ << '\n';
|
||||
|
||||
// The tranform used to move the chosen area into the ortho photo.
|
||||
Eigen::Transform<float, 3, Eigen::Affine> transform;
|
||||
|
||||
transform(0, 0) = static_cast<float>(transFinal.transform_.r1c1_);
|
||||
transform(1, 0) = static_cast<float>(transFinal.transform_.r2c1_);
|
||||
transform(2, 0) = static_cast<float>(transFinal.transform_.r3c1_);
|
||||
transform(3, 0) = static_cast<float>(transFinal.transform_.r4c1_);
|
||||
|
||||
transform(0, 1) = static_cast<float>(transFinal.transform_.r1c2_);
|
||||
transform(1, 1) = static_cast<float>(transFinal.transform_.r2c2_);
|
||||
transform(2, 1) = static_cast<float>(transFinal.transform_.r3c2_);
|
||||
transform(3, 1) = static_cast<float>(transFinal.transform_.r4c2_);
|
||||
|
||||
transform(0, 2) = static_cast<float>(transFinal.transform_.r1c3_);
|
||||
transform(1, 2) = static_cast<float>(transFinal.transform_.r2c3_);
|
||||
transform(2, 2) = static_cast<float>(transFinal.transform_.r3c3_);
|
||||
transform(3, 2) = static_cast<float>(transFinal.transform_.r4c3_);
|
||||
|
||||
transform(0, 3) = static_cast<float>(transFinal.transform_.r1c4_);
|
||||
transform(1, 3) = static_cast<float>(transFinal.transform_.r2c4_);
|
||||
transform(2, 3) = static_cast<float>(transFinal.transform_.r3c4_);
|
||||
transform(3, 3) = static_cast<float>(transFinal.transform_.r4c4_);
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Reading mesh file...\n";
|
||||
// The textureds mesh.e
|
||||
pcl::TextureMesh mesh;
|
||||
pcl::io::loadOBJFile(inputObjFilename_, mesh);
|
||||
log_ << ".. mesh file read.\n";
|
||||
|
||||
// Contains the vertices of the mesh.
|
||||
pcl::PointCloud<pcl::PointXYZ>::Ptr meshCloud (new pcl::PointCloud<pcl::PointXYZ>);
|
||||
pcl::fromPCLPointCloud2 (mesh.cloud, *meshCloud);
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Applying transform to mesh...\n";
|
||||
// Move the mesh into position.
|
||||
pcl::transformPointCloud(*meshCloud, *meshCloud, transform);
|
||||
log_ << ".. mesh transformed.\n";
|
||||
|
||||
// Update the mesh.
|
||||
pcl::toPCLPointCloud2 (*meshCloud, mesh.cloud);
|
||||
|
||||
|
||||
// Iterate over each part of the mesh (one per material), to make texture file paths relative the .mtl file.
|
||||
for(size_t t = 0; t < mesh.tex_materials.size(); ++t)
|
||||
{
|
||||
// The material of the current submesh.
|
||||
pcl::TexMaterial& material = mesh.tex_materials[t];
|
||||
|
||||
size_t find = material.tex_file.find_last_of("/\\");
|
||||
if(std::string::npos != find)
|
||||
{
|
||||
material.tex_file = material.tex_file.substr(find + 1);
|
||||
}
|
||||
}
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Saving mesh file to \'" << outputObjFilename_ << "\'...\n";
|
||||
saveOBJFile(outputObjFilename_, mesh, 8);
|
||||
log_ << ".. mesh file saved.\n";
|
||||
|
||||
printGeorefSystem();
|
||||
}
|
||||
|
||||
void Georef::chooseBestCameraTriplet(size_t &cam0, size_t &cam1, size_t &cam2)
|
||||
{
|
||||
double minTotError = std::numeric_limits<double>::infinity();
|
||||
|
||||
for(size_t t = 0; t < cameras_.size(); ++t)
|
||||
{
|
||||
for(size_t s = t; s < cameras_.size(); ++s)
|
||||
{
|
||||
for(size_t p = s; p < cameras_.size(); ++p)
|
||||
{
|
||||
FindTransform trans;
|
||||
trans.findTransform(cameras_[t].getPos(), cameras_[s].getPos(), cameras_[p].getPos(),
|
||||
cameras_[t].getReferencedPos(), cameras_[s].getReferencedPos(), cameras_[p].getReferencedPos());
|
||||
|
||||
// The total error for the curren camera triplet.
|
||||
double totError = 0.0;
|
||||
|
||||
for(size_t r = 0; r < cameras_.size(); ++r)
|
||||
{
|
||||
totError += trans.error(cameras_[r].getPos(), cameras_[r].getReferencedPos());
|
||||
}
|
||||
|
||||
if(minTotError > totError)
|
||||
{
|
||||
minTotError = totError;
|
||||
cam0 = t;
|
||||
cam1 = s;
|
||||
cam2 = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log_ << "Mean georeference error " << minTotError / static_cast<double>(cameras_.size()) << '\n';
|
||||
}
|
||||
|
||||
void Georef::printGeorefSystem()
|
||||
{
|
||||
if(outputObjFilename_.empty())
|
||||
{
|
||||
throw GeorefException("Output file path empty!.");
|
||||
}
|
||||
|
||||
std::string tmp = outputObjFilename_;
|
||||
size_t findPos = tmp.find_last_of(".");
|
||||
|
||||
if(std::string::npos == findPos)
|
||||
{
|
||||
throw GeorefException("Tried to generate default ouptut file, could not find .obj in the output file:\n\'"+outputObjFilename_+"\'");
|
||||
}
|
||||
|
||||
tmp = tmp.substr(0, findPos);
|
||||
|
||||
tmp = tmp + "_georef_system.txt";
|
||||
log_ << '\n';
|
||||
log_ << "Saving georeference system file to \'" << tmp << "\'...\n";
|
||||
std::ofstream geoStream(tmp.c_str());
|
||||
geoStream << georefSystem_ << std::endl;
|
||||
geoStream.close();
|
||||
log_ << "... georeference system saved.\n";
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
#pragma once
|
||||
|
||||
// C++
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
|
||||
// PCL
|
||||
#include <pcl/common/eigen.h>
|
||||
#include <pcl/common/common.h>
|
||||
|
||||
// Logger
|
||||
#include "Logger.hpp"
|
||||
|
||||
// Transformation
|
||||
#include "FindTransform.hpp"
|
||||
|
||||
/*!
|
||||
* \brief The GeorefSystem struct is used to store information about a georeference system.
|
||||
*/
|
||||
struct GeorefSystem
|
||||
{
|
||||
std::string system_; /**< The name of the system. **/
|
||||
double falseEasting_; /**< The false easting of the cameras. **/
|
||||
double falseNorthing_; /**< The false northing of the cameras. **/
|
||||
|
||||
friend std::ostream& operator<<(std::ostream &os, const GeorefSystem &geo);
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The GeorefCamera struct is used to store information about a camera.
|
||||
*/
|
||||
struct GeorefCamera
|
||||
{
|
||||
GeorefCamera();
|
||||
GeorefCamera(const GeorefCamera &other);
|
||||
~GeorefCamera();
|
||||
|
||||
/*!
|
||||
* \brief extractCamera Extracts a camera's intrinsic and extrinsic parameters from a stream.
|
||||
*/
|
||||
void extractCamera(std::ifstream &bundleStream);
|
||||
|
||||
/*!
|
||||
* \brief extractCameraGeoref Extracts a camera's world position from a stream.
|
||||
*/
|
||||
void extractCameraGeoref(std::istringstream &coordStream);
|
||||
|
||||
/*!
|
||||
* \brief getReferencedPos Get the local position of the camera.
|
||||
*/
|
||||
Vec3 getPos();
|
||||
|
||||
/*!
|
||||
* \brief getReferencedPos Get the georeferenced position of the camera.
|
||||
*/
|
||||
Vec3 getReferencedPos();
|
||||
|
||||
double focalLength_; /**< The focal length of the camera. */
|
||||
double k1_; /**< The k1 lens distortion parameter. **/
|
||||
double k2_; /**< The k2 lens distortion parameter. **/
|
||||
|
||||
double easting_; /**< The easting of the camera. **/
|
||||
double northing_; /**< The northing of the camera. **/
|
||||
double altitude_; /**< The altitude of the camera. **/
|
||||
|
||||
Eigen::Affine3f* transform_; /**< The rotation of the camera. **/
|
||||
Eigen::Vector3f* position_; /**< The position of the camera. **/
|
||||
|
||||
friend std::ostream& operator<<(std::ostream &os, const GeorefCamera &cam);
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The OdmOrthoPhoto class is used to transform a mesh into a georeferenced system.
|
||||
* The class reads camera positions from a bundle file.
|
||||
* The class reads the georefenced camera positions from a coords file.
|
||||
* The class reads a textured mesh from an OBJ-file.
|
||||
* The class writes the georeferenced textured mesh to an OBJ-file.
|
||||
* The class uses file read and write from pcl.
|
||||
*/
|
||||
class Georef
|
||||
{
|
||||
public:
|
||||
Georef();
|
||||
~Georef();
|
||||
|
||||
int run(int argc, char* argv[]);
|
||||
|
||||
private:
|
||||
|
||||
/*!
|
||||
* \brief parseArguments Parses command line arguments.
|
||||
* \param argc Application argument count.
|
||||
* \param argv Argument values.
|
||||
*/
|
||||
void parseArguments(int argc, char* argv[]);
|
||||
|
||||
/*!
|
||||
* \brief printHelp Prints help, explaining usage. Can be shown by calling the program with argument: "-help".
|
||||
*/
|
||||
void printHelp();
|
||||
|
||||
/*!
|
||||
* \brief makeDefaultOutput Setup the output file name given the input file name.
|
||||
*/
|
||||
void makeDefaultOutput();
|
||||
|
||||
/*!
|
||||
* \brief makeGeoreferencedModel Makes the input file georeferenced and saves it to the output file.
|
||||
*/
|
||||
void makeGeoreferencedModel();
|
||||
|
||||
/*!
|
||||
* \brief chooseBestCameraTriplet Chooses the best triplet of cameras to use when makin gthe model georeferenced.
|
||||
*/
|
||||
void chooseBestCameraTriplet(size_t &cam0, size_t &cam1, size_t &cam2);
|
||||
|
||||
/*!
|
||||
* \brief printGeorefSystem Prints a file containing information about the georeference system, next to the ouptut file.
|
||||
**/
|
||||
void printGeorefSystem();
|
||||
|
||||
Logger log_; /**< Logging object. */
|
||||
std::string logFile_; /**< The path to the output log file. */
|
||||
|
||||
std::string bundleFilename_; /**< The path to the cameras bundle file. **/
|
||||
std::string coordFilename_; /**< The path to the cameras georeference file. **/
|
||||
std::string inputObjFilename_; /**< The path to the input mesh obj file. **/
|
||||
std::string outputObjFilename_; /**< The path to the output mesh obj file. **/
|
||||
|
||||
std::vector<GeorefCamera> cameras_; /**< A vector of all cameras. **/
|
||||
|
||||
GeorefSystem georefSystem_; /**< Contains the georeference system. **/
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The Georef class
|
||||
*/
|
||||
class GeorefException : public std::exception
|
||||
{
|
||||
|
||||
public:
|
||||
GeorefException() : message("Error in Georef") {}
|
||||
GeorefException(std::string msgInit) : message("Error in Georef:\n" + msgInit) {}
|
||||
~GeorefException() throw() {}
|
||||
virtual const char* what() const throw() {return message.c_str(); }
|
||||
|
||||
private:
|
||||
std::string message; /**< The error message **/
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
#include "Logger.hpp"
|
||||
|
||||
|
||||
Logger::Logger(bool isPrintingInCout) : isPrintingInCout_(isPrintingInCout)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Logger::~Logger()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Logger::print(std::string filePath)
|
||||
{
|
||||
std::ofstream file(filePath.c_str(), std::ios::binary);
|
||||
file << logStream_.str();
|
||||
file.close();
|
||||
}
|
||||
|
||||
bool Logger::isPrintingInCout() const
|
||||
{
|
||||
return isPrintingInCout_;
|
||||
}
|
||||
|
||||
void Logger::setIsPrintingInCout(bool isPrintingInCout)
|
||||
{
|
||||
isPrintingInCout_ = isPrintingInCout;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
// STL
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
/*!
|
||||
* \brief The Logger class is used to store program messages in a log file.
|
||||
* \details By using the << operator while printInCout is set, the class writes both to
|
||||
* cout and to file, if the flag is not set, output is written to file only.
|
||||
*/
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* \brief Logger Contains functionality for printing and displaying log information.
|
||||
* \param printInCout Flag toggling if operator << also writes to cout.
|
||||
*/
|
||||
Logger(bool isPrintingInCout = true);
|
||||
|
||||
/*!
|
||||
* \brief Destructor.
|
||||
*/
|
||||
~Logger();
|
||||
|
||||
/*!
|
||||
* \brief print Prints the contents of the log to file.
|
||||
* \param filePath Path specifying where to write the log.
|
||||
*/
|
||||
void print(std::string filePath);
|
||||
|
||||
/*!
|
||||
* \brief isPrintingInCout Check if console printing flag is set.
|
||||
* \return Console printing flag.
|
||||
*/
|
||||
bool isPrintingInCout() const;
|
||||
|
||||
/*!
|
||||
* \brief setIsPrintingInCout Set console printing flag.
|
||||
* \param isPrintingInCout Value, if true, messages added to the log are also printed in cout.
|
||||
*/
|
||||
void setIsPrintingInCout(bool isPrintingInCout);
|
||||
|
||||
/*!
|
||||
* Operator for printing messages to log and in the standard output stream if desired.
|
||||
*/
|
||||
template<class T>
|
||||
friend Logger& operator<< (Logger &log, T t)
|
||||
{
|
||||
// If console printing is enabled.
|
||||
if (log.isPrintingInCout_)
|
||||
{
|
||||
std::cout << t;
|
||||
std::cout.flush();
|
||||
}
|
||||
// Write to log.
|
||||
log.logStream_ << t;
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
private:
|
||||
bool isPrintingInCout_; /*!< If flag is set, log is printed in cout and written to the log. */
|
||||
|
||||
std::stringstream logStream_; /*!< Stream for storing the log. */
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
#include "Georef.hpp"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
Georef ref;
|
||||
return ref.run(argc, argv);
|
||||
}
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
* Software License Agreement (BSD License)
|
||||
*
|
||||
* Point Cloud Library (PCL) - www.pointclouds.org
|
||||
* Copyright (c) 2012-, Open Perception, Inc.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
* * Neither the name of the copyright holder(s) nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "modifiedPclFunctions.hpp"
|
||||
|
||||
int saveOBJFile(const std::string &file_name, const pcl::TextureMesh &tex_mesh, unsigned precision)
|
||||
{
|
||||
if (tex_mesh.cloud.data.empty ())
|
||||
{
|
||||
PCL_ERROR ("[pcl::io::saveOBJFile] Input point cloud has no data!\n");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
// Open file
|
||||
std::ofstream fs;
|
||||
fs.precision (precision);
|
||||
fs.open (file_name.c_str ());
|
||||
|
||||
// Define material file
|
||||
std::string mtl_file_name = file_name.substr (0, file_name.find_last_of (".")) + ".mtl";
|
||||
// Strip path for "mtllib" command
|
||||
std::string mtl_file_name_nopath = mtl_file_name;
|
||||
//std::cout << mtl_file_name_nopath << std::endl;
|
||||
mtl_file_name_nopath.erase (0, mtl_file_name.find_last_of ('/') + 1);
|
||||
|
||||
/* Write 3D information */
|
||||
// number of points
|
||||
int nr_points = tex_mesh.cloud.width * tex_mesh.cloud.height;
|
||||
int point_size = tex_mesh.cloud.data.size () / nr_points;
|
||||
|
||||
// mesh size
|
||||
int nr_meshes = tex_mesh.tex_polygons.size ();
|
||||
// number of faces for header
|
||||
int nr_faces = 0;
|
||||
for (int m = 0; m < nr_meshes; ++m)
|
||||
nr_faces += tex_mesh.tex_polygons[m].size ();
|
||||
|
||||
// Write the header information
|
||||
fs << "####" << std::endl;
|
||||
fs << "# OBJ dataFile simple version. File name: " << file_name << std::endl;
|
||||
fs << "# Vertices: " << nr_points << std::endl;
|
||||
fs << "# Faces: " <<nr_faces << std::endl;
|
||||
fs << "# Material information:" << std::endl;
|
||||
fs << "mtllib " << mtl_file_name_nopath << std::endl;
|
||||
fs << "####" << std::endl;
|
||||
|
||||
// Write vertex coordinates
|
||||
fs << "# Vertices" << std::endl;
|
||||
for (int i = 0; i < nr_points; ++i)
|
||||
{
|
||||
int xyz = 0;
|
||||
// "v" just be written one
|
||||
bool v_written = false;
|
||||
for (size_t d = 0; d < tex_mesh.cloud.fields.size (); ++d)
|
||||
{
|
||||
int count = tex_mesh.cloud.fields[d].count;
|
||||
if (count == 0)
|
||||
count = 1; // we simply cannot tolerate 0 counts (coming from older converter code)
|
||||
int c = 0;
|
||||
// adding vertex
|
||||
if ((tex_mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) /*sensor_msgs::PointField::FLOAT32)*/ && (
|
||||
tex_mesh.cloud.fields[d].name == "x" ||
|
||||
tex_mesh.cloud.fields[d].name == "y" ||
|
||||
tex_mesh.cloud.fields[d].name == "z"))
|
||||
{
|
||||
if (!v_written)
|
||||
{
|
||||
// write vertices beginning with v
|
||||
fs << "v ";
|
||||
v_written = true;
|
||||
}
|
||||
float value;
|
||||
memcpy (&value, &tex_mesh.cloud.data[i * point_size + tex_mesh.cloud.fields[d].offset + c * sizeof (float)], sizeof (float));
|
||||
fs << value;
|
||||
if (++xyz == 3)
|
||||
break;
|
||||
fs << " ";
|
||||
}
|
||||
}
|
||||
if (xyz != 3)
|
||||
{
|
||||
PCL_ERROR ("[pcl::io::saveOBJFile] Input point cloud has no XYZ data!\n");
|
||||
return (-2);
|
||||
}
|
||||
fs << std::endl;
|
||||
}
|
||||
fs << "# "<< nr_points <<" vertices" << std::endl;
|
||||
|
||||
// // Write vertex normals
|
||||
// for (int i = 0; i < nr_points; ++i)
|
||||
// {
|
||||
// int xyz = 0;
|
||||
// // "vn" just be written one
|
||||
// bool v_written = false;
|
||||
// for (size_t d = 0; d < tex_mesh.cloud.fields.size (); ++d)
|
||||
// {
|
||||
// int count = tex_mesh.cloud.fields[d].count;
|
||||
// if (count == 0)
|
||||
// count = 1; // we simply cannot tolerate 0 counts (coming from older converter code)
|
||||
// int c = 0;
|
||||
// // adding vertex
|
||||
// if ((tex_mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) && (
|
||||
// tex_mesh.cloud.fields[d].name == "normal_x" ||
|
||||
// tex_mesh.cloud.fields[d].name == "normal_y" ||
|
||||
// tex_mesh.cloud.fields[d].name == "normal_z"))
|
||||
// {
|
||||
// if (!v_written)
|
||||
// {
|
||||
// // write vertices beginning with vn
|
||||
// fs << "vn ";
|
||||
// v_written = true;
|
||||
// }
|
||||
// float value;
|
||||
// memcpy (&value, &tex_mesh.cloud.data[i * point_size + tex_mesh.cloud.fields[d].offset + c * sizeof (float)], sizeof (float));
|
||||
// fs << value;
|
||||
// if (++xyz == 3)
|
||||
// break;
|
||||
// fs << " ";
|
||||
// }
|
||||
// }
|
||||
// if (xyz != 3)
|
||||
// {
|
||||
// //PCL_ERROR ("[pcl::io::saveOBJFile] Input point cloud has no normals!\n");
|
||||
// //return (-2);
|
||||
// }
|
||||
// fs << std::endl;
|
||||
// }
|
||||
// Write vertex texture with "vt" (adding latter)
|
||||
|
||||
for (int m = 0; m < nr_meshes; ++m)
|
||||
{
|
||||
if(tex_mesh.tex_coordinates.size() == 0)
|
||||
continue;
|
||||
|
||||
//PCL_INFO ("%d vertex textures in submesh %d\n", tex_mesh.tex_coordinates[m].size (), m);
|
||||
fs << "# " << tex_mesh.tex_coordinates[m].size() << " vertex textures in submesh " << m << std::endl;
|
||||
for (size_t i = 0; i < tex_mesh.tex_coordinates[m].size (); ++i)
|
||||
{
|
||||
fs << "vt ";
|
||||
fs << tex_mesh.tex_coordinates[m][i][0] << " " << tex_mesh.tex_coordinates[m][i][1] << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int f_idx = 0;
|
||||
|
||||
// int idx_vt =0;
|
||||
//PCL_INFO ("Writting faces...\n");
|
||||
for (int m = 0; m < nr_meshes; ++m)
|
||||
{
|
||||
if (m > 0)
|
||||
f_idx += tex_mesh.tex_polygons[m-1].size ();
|
||||
|
||||
if(tex_mesh.tex_materials.size() !=0)
|
||||
{
|
||||
fs << "# The material will be used for mesh " << m << std::endl;
|
||||
//TODO pbl here with multi texture and unseen faces
|
||||
fs << "usemtl " << tex_mesh.tex_materials[m].tex_name << std::endl;
|
||||
fs << "# Faces" << std::endl;
|
||||
}
|
||||
for (size_t i = 0; i < tex_mesh.tex_polygons[m].size(); ++i)
|
||||
{
|
||||
// Write faces with "f"
|
||||
fs << "f";
|
||||
size_t j = 0;
|
||||
// There's one UV per vertex per face, i.e., the same vertex can have
|
||||
// different UV depending on the face.
|
||||
for (j = 0; j < tex_mesh.tex_polygons[m][i].vertices.size (); ++j)
|
||||
{
|
||||
unsigned int idx = tex_mesh.tex_polygons[m][i].vertices[j] + 1;
|
||||
fs << " " << idx
|
||||
<< "/" << 3*(i+f_idx) +j+1;
|
||||
//<< "/" << idx; // vertex index in obj file format starting with 1
|
||||
}
|
||||
fs << std::endl;
|
||||
}
|
||||
//PCL_INFO ("%d faces in mesh %d \n", tex_mesh.tex_polygons[m].size () , m);
|
||||
fs << "# "<< tex_mesh.tex_polygons[m].size() << " faces in mesh " << m << std::endl;
|
||||
}
|
||||
fs << "# End of File";
|
||||
|
||||
// Close obj file
|
||||
//PCL_INFO ("Closing obj file\n");
|
||||
fs.close ();
|
||||
|
||||
/* Write material defination for OBJ file*/
|
||||
// Open file
|
||||
//PCL_INFO ("Writing material files\n");
|
||||
//dont do it if no material to write
|
||||
if(tex_mesh.tex_materials.size() ==0)
|
||||
return (0);
|
||||
|
||||
std::ofstream m_fs;
|
||||
m_fs.precision (precision);
|
||||
m_fs.open (mtl_file_name.c_str ());
|
||||
//std::cout << "MTL file is located at_ " << mtl_file_name << std::endl;
|
||||
// default
|
||||
m_fs << "#" << std::endl;
|
||||
m_fs << "# Wavefront material file" << std::endl;
|
||||
m_fs << "#" << std::endl;
|
||||
for(int m = 0; m < nr_meshes; ++m)
|
||||
{
|
||||
m_fs << "newmtl " << tex_mesh.tex_materials[m].tex_name << std::endl;
|
||||
m_fs << "Ka "<< tex_mesh.tex_materials[m].tex_Ka.r << " " << tex_mesh.tex_materials[m].tex_Ka.g << " " << tex_mesh.tex_materials[m].tex_Ka.b << std::endl; // defines the ambient color of the material to be (r,g,b).
|
||||
m_fs << "Kd "<< tex_mesh.tex_materials[m].tex_Kd.r << " " << tex_mesh.tex_materials[m].tex_Kd.g << " " << tex_mesh.tex_materials[m].tex_Kd.b << std::endl; // defines the diffuse color of the material to be (r,g,b).
|
||||
m_fs << "Ks "<< tex_mesh.tex_materials[m].tex_Ks.r << " " << tex_mesh.tex_materials[m].tex_Ks.g << " " << tex_mesh.tex_materials[m].tex_Ks.b << std::endl; // defines the specular color of the material to be (r,g,b). This color shows up in highlights.
|
||||
m_fs << "d " << tex_mesh.tex_materials[m].tex_d << std::endl; // defines the transparency of the material to be alpha.
|
||||
m_fs << "Ns "<< tex_mesh.tex_materials[m].tex_Ns << std::endl; // defines the shininess of the material to be s.
|
||||
m_fs << "illum "<< tex_mesh.tex_materials[m].tex_illum << std::endl; // denotes the illumination model used by the material.
|
||||
// illum = 1 indicates a flat material with no specular highlights, so the value of Ks is not used.
|
||||
// illum = 2 denotes the presence of specular highlights, and so a specification for Ks is required.
|
||||
m_fs << "map_Kd " << tex_mesh.tex_materials[m].tex_file << std::endl;
|
||||
m_fs << "###" << std::endl;
|
||||
}
|
||||
m_fs.close ();
|
||||
return (0);
|
||||
}
|
||||
|
||||
bool getPixelCoordinates(const pcl::PointXYZ &pt, const pcl::TextureMapping<pcl::PointXYZ>::Camera &cam, pcl::PointXY &UV_coordinates)
|
||||
{
|
||||
if (pt.z > 0)
|
||||
{
|
||||
// compute image center and dimension
|
||||
double sizeX = cam.width;
|
||||
double sizeY = cam.height;
|
||||
double cx, cy;
|
||||
if (cam.center_w > 0)
|
||||
cx = cam.center_w;
|
||||
else
|
||||
cx = sizeX / 2.0;
|
||||
if (cam.center_h > 0)
|
||||
cy = cam.center_h;
|
||||
else
|
||||
cy = sizeY / 2.0;
|
||||
|
||||
double focal_x, focal_y;
|
||||
if (cam.focal_length_w > 0)
|
||||
focal_x = cam.focal_length_w;
|
||||
else
|
||||
focal_x = cam.focal_length;
|
||||
if (cam.focal_length_h > 0)
|
||||
focal_y = cam.focal_length_h;
|
||||
else
|
||||
focal_y = cam.focal_length;
|
||||
|
||||
// project point on camera's image plane
|
||||
UV_coordinates.x = static_cast<float> ((focal_x * (pt.x / pt.z) + cx)); //horizontal
|
||||
UV_coordinates.y = static_cast<float> ((focal_y * (pt.y / pt.z) + cy)); //vertical
|
||||
|
||||
// point is visible!
|
||||
if (UV_coordinates.x >= 15.0 && UV_coordinates.x <= (sizeX - 15.0) && UV_coordinates.y >= 15.0 && UV_coordinates.y <= (sizeY - 15.0))
|
||||
{
|
||||
return (true); // point was visible by the camera
|
||||
}
|
||||
}
|
||||
|
||||
// point is NOT visible by the camera
|
||||
UV_coordinates.x = -1.0f;
|
||||
UV_coordinates.y = -1.0f;
|
||||
return (false); // point was not visible by the camera
|
||||
}
|
||||
|
||||
bool isFaceProjected (const pcl::TextureMapping<pcl::PointXYZ>::Camera &camera, const pcl::PointXYZ &p1, const pcl::PointXYZ &p2, const pcl::PointXYZ &p3, pcl::PointXY &proj1, pcl::PointXY &proj2, pcl::PointXY &proj3)
|
||||
{
|
||||
return (getPixelCoordinates(p1, camera, proj1) && getPixelCoordinates(p2, camera, proj2) && getPixelCoordinates(p3, camera, proj3));
|
||||
}
|
||||
|
||||
void getTriangleCircumscribedCircleCentroid( const pcl::PointXY &p1, const pcl::PointXY &p2, const pcl::PointXY &p3, pcl::PointXY &circumcenter, double &radius)
|
||||
{
|
||||
// compute centroid's coordinates (translate back to original coordinates)
|
||||
circumcenter.x = static_cast<float> (p1.x + p2.x + p3.x ) / 3;
|
||||
circumcenter.y = static_cast<float> (p1.y + p2.y + p3.y ) / 3;
|
||||
double r1 = (circumcenter.x - p1.x) * (circumcenter.x - p1.x) + (circumcenter.y - p1.y) * (circumcenter.y - p1.y) ;
|
||||
double r2 = (circumcenter.x - p2.x) * (circumcenter.x - p2.x) + (circumcenter.y - p2.y) * (circumcenter.y - p2.y) ;
|
||||
double r3 = (circumcenter.x - p3.x) * (circumcenter.x - p3.x) + (circumcenter.y - p3.y) * (circumcenter.y - p3.y) ;
|
||||
|
||||
// radius
|
||||
radius = std::sqrt( std::max( r1, std::max( r2, r3) )) ;
|
||||
}
|
||||
|
||||
bool checkPointInsideTriangle(const pcl::PointXY &p1, const pcl::PointXY &p2, const pcl::PointXY &p3, const pcl::PointXY &pt)
|
||||
{
|
||||
// Compute vectors
|
||||
Eigen::Vector2d v0, v1, v2;
|
||||
v0(0) = p3.x - p1.x; v0(1) = p3.y - p1.y; // v0= C - A
|
||||
v1(0) = p2.x - p1.x; v1(1) = p2.y - p1.y; // v1= B - A
|
||||
v2(0) = pt.x - p1.x; v2(1) = pt.y - p1.y; // v2= P - A
|
||||
|
||||
// Compute dot products
|
||||
double dot00 = v0.dot(v0); // dot00 = dot(v0, v0)
|
||||
double dot01 = v0.dot(v1); // dot01 = dot(v0, v1)
|
||||
double dot02 = v0.dot(v2); // dot02 = dot(v0, v2)
|
||||
double dot11 = v1.dot(v1); // dot11 = dot(v1, v1)
|
||||
double dot12 = v1.dot(v2); // dot12 = dot(v1, v2)
|
||||
|
||||
// Compute barycentric coordinates
|
||||
double invDenom = 1.0 / (dot00*dot11 - dot01*dot01);
|
||||
double u = (dot11*dot02 - dot01*dot12) * invDenom;
|
||||
double v = (dot00*dot12 - dot01*dot02) * invDenom;
|
||||
|
||||
// Check if point is in triangle
|
||||
return ((u >= 0) && (v >= 0) && (u + v < 1));
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
// STL
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
// PCL
|
||||
#include <pcl/point_types.h>
|
||||
#include <pcl/surface/texture_mapping.h>
|
||||
#include <pcl/io/ply_io.h>
|
||||
|
||||
int saveOBJFile(const std::string &file_name, const pcl::TextureMesh &tex_mesh, unsigned precision);
|
||||
|
||||
bool getPixelCoordinates(const pcl::PointXYZ &pt, const pcl::TextureMapping<pcl::PointXYZ>::Camera &cam, pcl::PointXY &UV_coordinates);
|
||||
|
||||
bool isFaceProjected (const pcl::TextureMapping<pcl::PointXYZ>::Camera &camera, const pcl::PointXYZ &p1, const pcl::PointXYZ &p2, const pcl::PointXYZ &p3, pcl::PointXY &proj1, pcl::PointXY &proj2, pcl::PointXY &proj3);
|
||||
|
||||
void getTriangleCircumscribedCircleCentroid(const pcl::PointXY &p1, const pcl::PointXY &p2, const pcl::PointXY &p3, pcl::PointXY &circumcenter, double &radius);
|
||||
|
||||
bool checkPointInsideTriangle(const pcl::PointXY &p1, const pcl::PointXY &p2, const pcl::PointXY &p3, const pcl::PointXY &pt);
|
|
@ -0,0 +1,26 @@
|
|||
project(odm_meshing)
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
# Set pcl dir to the input spedified with option -DPCL_DIR="path"
|
||||
set(PCL_DIR "PCL_DIR-NOTFOUND" CACHE "PCL_DIR" "Path to the pcl installation directory")
|
||||
|
||||
# Add compiler options.
|
||||
add_definitions(-Wall -Wextra)
|
||||
|
||||
# Find pcl at the location specified by PCL_DIR
|
||||
find_package(PCL 1.7 HINTS "${PCL_DIR}/share/pcl-1.7")
|
||||
|
||||
# Add the PCL and Eigen include dirs.
|
||||
# Necessary since the PCL_INCLUDE_DIR variable set by find_package is broken.)
|
||||
include_directories(${PCL_ROOT}/include/pcl-${PCL_VERSION_MAJOR}.${PCL_VERSION_MINOR})
|
||||
include_directories(${EIGEN_ROOT})
|
||||
|
||||
# Add source directory
|
||||
aux_source_directory("./src" SRC_LIST)
|
||||
|
||||
# Add exectuteable
|
||||
add_executable(${PROJECT_NAME} ${SRC_LIST})
|
||||
|
||||
# Link
|
||||
target_link_libraries(odm_meshing ${PCL_COMMON_LIBRARIES} ${PCL_IO_LIBRARIES} ${PCL_SURFACE_LIBRARIES})
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#include "Logger.hpp"
|
||||
|
||||
|
||||
Logger::Logger(bool isPrintingInCout) : isPrintingInCout_(isPrintingInCout)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Logger::~Logger()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Logger::printToFile(std::string filePath)
|
||||
{
|
||||
std::ofstream file(filePath.c_str(), std::ios::binary);
|
||||
file << logStream_.str();
|
||||
file.close();
|
||||
}
|
||||
|
||||
bool Logger::isPrintingInCout() const
|
||||
{
|
||||
return isPrintingInCout_;
|
||||
}
|
||||
|
||||
void Logger::setIsPrintingInCout(bool isPrintingInCout)
|
||||
{
|
||||
isPrintingInCout_ = isPrintingInCout;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
// STL
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
/*!
|
||||
* \brief The Logger class is used to store program messages in a log file.
|
||||
* \details By using the << operator while printInCout is set, the class writes both to
|
||||
* cout and to file, if the flag is not set, output is written to file only.
|
||||
*/
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* \brief Logger Contains functionality for printing and displaying log information.
|
||||
* \param printInCout Flag toggling if operator << also writes to cout.
|
||||
*/
|
||||
Logger(bool isPrintingInCout = true);
|
||||
|
||||
/*!
|
||||
* \brief Destructor.
|
||||
*/
|
||||
~Logger();
|
||||
|
||||
/*!
|
||||
* \brief print Prints the contents of the log to file.
|
||||
* \param filePath Path specifying where to write the log.
|
||||
*/
|
||||
void printToFile(std::string filePath);
|
||||
|
||||
/*!
|
||||
* \brief isPrintingInCout Check if console printing flag is set.
|
||||
* \return Console printing flag.
|
||||
*/
|
||||
bool isPrintingInCout() const;
|
||||
|
||||
/*!
|
||||
* \brief setIsPrintingInCout Set console printing flag.
|
||||
* \param isPrintingInCout Value, if true, messages added to the log are also printed in cout.
|
||||
*/
|
||||
void setIsPrintingInCout(bool isPrintingInCout);
|
||||
|
||||
/*!
|
||||
* Operator for printing messages to log and in the standard output stream if desired.
|
||||
*/
|
||||
template<class T>
|
||||
friend Logger& operator<< (Logger &log, T t)
|
||||
{
|
||||
// If console printing is enabled.
|
||||
if (log.isPrintingInCout_)
|
||||
{
|
||||
std::cout << t;
|
||||
std::cout.flush();
|
||||
}
|
||||
// Write to log.
|
||||
log.logStream_ << t;
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
private:
|
||||
bool isPrintingInCout_; /*!< If flag is set, log is printed in cout and written to the log. */
|
||||
|
||||
std::stringstream logStream_; /*!< Stream for storing the log. */
|
||||
};
|
|
@ -0,0 +1,361 @@
|
|||
#include "OdmMeshing.hpp"
|
||||
|
||||
|
||||
OdmMeshing::OdmMeshing() : log_(false)
|
||||
{
|
||||
meshCreator_ = pcl::Poisson<pcl::PointNormal>::Ptr(new pcl::Poisson<pcl::PointNormal>());
|
||||
points_ = pcl::PointCloud<pcl::PointNormal>::Ptr(new pcl::PointCloud<pcl::PointNormal>());
|
||||
mesh_ = pcl::PolygonMeshPtr(new pcl::PolygonMesh);
|
||||
decimatedMesh_ = pcl::PolygonMeshPtr(new pcl::PolygonMesh);
|
||||
|
||||
// Set default values
|
||||
outputFile_ = "";
|
||||
logFilePath_ = "";
|
||||
|
||||
maxVertexCount_ = 0;
|
||||
treeDepth_ = 0;
|
||||
|
||||
solverDivide_ = 9.0;
|
||||
samplesPerNode_ = 1.0;
|
||||
decimationFactor_ = 0.0;
|
||||
|
||||
logFilePath_ = "odm_meshing_log.txt";
|
||||
log_ << logFilePath_ << "\n";
|
||||
}
|
||||
|
||||
OdmMeshing::~OdmMeshing()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int OdmMeshing::run(int argc, char **argv)
|
||||
{
|
||||
// If no arguments were passed, print help and return early.
|
||||
if (argc <= 1)
|
||||
{
|
||||
printHelp();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
parseArguments(argc, argv);
|
||||
|
||||
loadPoints();
|
||||
|
||||
createMesh();
|
||||
|
||||
decimateMesh();
|
||||
|
||||
writePlyFile();
|
||||
|
||||
}
|
||||
catch (const OdmMeshingException& e)
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
log_ << e.what() << "\n";
|
||||
log_.printToFile(logFilePath_);
|
||||
log_ << "For more detailed information, see log file." << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
log_ << "Error in OdmMeshing:\n";
|
||||
log_ << e.what() << "\n";
|
||||
log_.printToFile(logFilePath_);
|
||||
log_ << "For more detailed information, see log file." << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
log_ << "Unknwon error in OdmMeshing:\n";
|
||||
log_.printToFile(logFilePath_);
|
||||
log_ << "For more detailed information, see log file." << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
log_.printToFile(logFilePath_);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
void OdmMeshing::parseArguments(int argc, char **argv)
|
||||
{
|
||||
|
||||
for(int argIndex = 1; argIndex < argc; ++argIndex)
|
||||
{
|
||||
// The argument to be parsed.
|
||||
std::string argument = std::string(argv[argIndex]);
|
||||
|
||||
if(argument == "-help")
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
else if(argument == "-verbose")
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
}
|
||||
else if(argument == "-maxVertexCount" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
std::stringstream ss(argv[argIndex]);
|
||||
ss >> maxVertexCount_;
|
||||
if (ss.bad())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
|
||||
}
|
||||
log_ << "Vertex count was manually set to: " << maxVertexCount_ << "\n";
|
||||
}
|
||||
else if(argument == "-octreeDepth" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
std::stringstream ss(argv[argIndex]);
|
||||
ss >> treeDepth_;
|
||||
if (ss.bad())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
|
||||
}
|
||||
log_ << "Octree depth was manually set to: " << treeDepth_ << "\n";
|
||||
}
|
||||
else if(argument == "-solverDivide" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
std::stringstream ss(argv[argIndex]);
|
||||
ss >> solverDivide_;
|
||||
if (ss.bad())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
|
||||
}
|
||||
log_ << "Numerical solver divisions was manually set to: " << treeDepth_ << "\n";
|
||||
}
|
||||
else if(argument == "-samplesPerNode" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
std::stringstream ss(argv[argIndex]);
|
||||
ss >> samplesPerNode_;
|
||||
if (ss.bad())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
|
||||
}
|
||||
log_ << "The number of samples per octree node was manually set to: " << samplesPerNode_ << "\n";
|
||||
}
|
||||
else if(argument == "-inputFile" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
inputFile_ = std::string(argv[argIndex]);
|
||||
std::ifstream testFile(inputFile_.c_str(), std::ios::binary);
|
||||
if (!testFile.is_open())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value. (file not accessible)");
|
||||
}
|
||||
testFile.close();
|
||||
log_ << "Reading point cloud at: " << inputFile_ << "\n";
|
||||
}
|
||||
else if(argument == "-outputFile" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
outputFile_ = std::string(argv[argIndex]);
|
||||
std::ofstream testFile(outputFile_.c_str());
|
||||
if (!testFile.is_open())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value.");
|
||||
}
|
||||
testFile.close();
|
||||
log_ << "Writing output to: " << outputFile_ << "\n";
|
||||
}
|
||||
else if(argument == "-logFile" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
logFilePath_ = std::string(argv[argIndex]);
|
||||
std::ofstream testFile(outputFile_.c_str());
|
||||
if (!testFile.is_open())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value.");
|
||||
}
|
||||
testFile.close();
|
||||
log_ << "Writing log information to: " << logFilePath_ << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
printHelp();
|
||||
throw OdmMeshingException("Unrecognised argument '" + argument + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OdmMeshing::loadPoints()
|
||||
{
|
||||
|
||||
if(pcl::io::loadPLYFile<pcl::PointNormal> (inputFile_.c_str(), *points_.get()) == -1) {
|
||||
throw OdmMeshingException("Error when reading points and normals from:\n" + inputFile_ + "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
log_ << "Successfully loaded " << points_->size() << " points with corresponding normals from file.\n";
|
||||
}
|
||||
}
|
||||
|
||||
void OdmMeshing::printHelp()
|
||||
{
|
||||
bool printInCoutPop = log_.isPrintingInCout();
|
||||
log_.setIsPrintingInCout(true);
|
||||
|
||||
log_ << "OpenDroneMapMeshing.exe\n\n";
|
||||
|
||||
log_ << "Purpose:" << "\n";
|
||||
log_ << "Create a mesh from an oriented point cloud (points with normals) using the Poisson surface reconstruction method." << "\n";
|
||||
|
||||
log_ << "Usage:" << "\n";
|
||||
log_ << "The program requires a path to an input PLY point cloud file, all other input parameters are optional." << "\n\n";
|
||||
|
||||
log_ << "The following flags are available\n";
|
||||
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: \"-<argument name> <argument>\", (without <>), and the following parameters are configureable: " << "\n";
|
||||
log_ << "\"-inputFile <path>\" (mandatory)" << "\n";
|
||||
log_ << "\"Input ascii ply file that must contain a point cloud with normals.\n\n";
|
||||
|
||||
log_ << "\"-outputFile <path>\" (optional, default: odm_mesh.ply)" << "\n";
|
||||
log_ << "\"Target file in which the mesh is saved.\n\n";
|
||||
|
||||
log_ << "\"-logFile <path>\" (optional, default: odm_meshing_log.txt)" << "\n";
|
||||
log_ << "\"Target file in which the mesh is saved.\n\n";
|
||||
|
||||
log_ << "\"-maxVertexCount <integer>\" (optional, default: 100,000)" << "\n";
|
||||
log_ << "Desired final vertex count (after decimation), set to 0 to disable decimation.\n\n";
|
||||
|
||||
log_ << "\"-treeDepth <integer>\" (optional, default: 0 (automatic))" << "\n";
|
||||
log_ << "Controls octree depth used for poisson reconstruction. Recommended values (9-11).\n"
|
||||
<< "Increasing the value on this parameter will raise initial vertex count."
|
||||
<< "If omitted or zero, the depth is calculated automatically from the input point count.\n\n";
|
||||
|
||||
log_ << "\"-samplesPerNode <float>\" (optional, default: 1)" << "\n";
|
||||
log_ << "Average number of samples (points) per octree node. Increasing this value might help if data is very noisy.\n\n";
|
||||
|
||||
log_ << "\"-solverDivide <integer>\" (optional, default: 9)" << "\n";
|
||||
log_ << "Ocree depth at which the Laplacian equation is solved in the surface reconstruction step.\n";
|
||||
log_ << "Increasing this value increases computation times slightly but helps reduce memory usage.\n\n";
|
||||
|
||||
log_.setIsPrintingInCout(printInCoutPop);
|
||||
}
|
||||
|
||||
void OdmMeshing::createMesh()
|
||||
{
|
||||
|
||||
// Attempt to calculate the depth of the tree if unspecified
|
||||
if (treeDepth_ == 0)
|
||||
{
|
||||
treeDepth_ = calcTreeDepth(points_->size());
|
||||
}
|
||||
|
||||
log_ << "Octree depth used for reconstruction is: " << treeDepth_ << "\n";
|
||||
log_ << "Estimated initial vertex count: " << pow(4, treeDepth_) << "\n\n";
|
||||
|
||||
meshCreator_->setDepth(treeDepth_);
|
||||
meshCreator_->setSamplesPerNode(samplesPerNode_);
|
||||
meshCreator_->setInputCloud(points_);
|
||||
|
||||
// Guarantee manifold mesh.
|
||||
meshCreator_->setManifold(true);
|
||||
|
||||
// Begin reconstruction
|
||||
meshCreator_->reconstruct(*mesh_.get());
|
||||
|
||||
log_ << "Reconstruction complete:\n";
|
||||
log_ << "Vertex count: " << mesh_->cloud.width << "\n";
|
||||
log_ << "Triangle count: " << mesh_->polygons.size() << "\n\n";
|
||||
|
||||
}
|
||||
|
||||
void OdmMeshing::decimateMesh()
|
||||
{
|
||||
if (maxVertexCount_ <= 0)
|
||||
{
|
||||
log_ << "Vertex count not specified, decimation cancelled.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxVertexCount_ > mesh_->cloud.height*mesh_->cloud.width)
|
||||
{
|
||||
log_ << "Vertex count in mesh lower than initially generated mesh, unable to decimate.\n";
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
decimatedMesh_ = pcl::PolygonMeshPtr(new pcl::PolygonMesh);
|
||||
|
||||
double reductionFactor = 1.0 - double(maxVertexCount_)/double(mesh_->cloud.height*mesh_->cloud.width);
|
||||
|
||||
log_ << "Decimating mesh, removing " << reductionFactor*100 << " percent of vertices.\n";
|
||||
|
||||
pcl::MeshQuadricDecimationVTK decimator;
|
||||
decimator.setInputMesh(mesh_);
|
||||
decimator.setTargetReductionFactor(reductionFactor);
|
||||
decimator.process(*decimatedMesh_.get());
|
||||
|
||||
log_ << "Decimation complete.\n";
|
||||
log_ << "Decimated vertex count: " << decimatedMesh_->cloud.width << "\n";
|
||||
log_ << "Decimated triangle count: " << decimatedMesh_->polygons.size() << "\n\n";
|
||||
|
||||
mesh_ = decimatedMesh_;
|
||||
}
|
||||
}
|
||||
|
||||
int OdmMeshing::calcTreeDepth(size_t nPoints)
|
||||
{
|
||||
// Assume points are located (roughly) in a plane.
|
||||
double squareSide = std::sqrt(double(nPoints));
|
||||
|
||||
// Calculate octree depth such that if points were equally distributed in
|
||||
// a quadratic plane, there would be at least 1 point per octree node.
|
||||
int depth = 0;
|
||||
while(std::pow<double>(2,depth) < squareSide/2)
|
||||
{
|
||||
depth++;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
void OdmMeshing::writePlyFile()
|
||||
{
|
||||
log_ << "Saving mesh to file.\n";
|
||||
if (pcl::io::savePLYFile(outputFile_.c_str(), *mesh_.get()) == -1) {
|
||||
throw OdmMeshingException("Error when saving mesh to file:\n" + outputFile_ + "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
log_ << "Successfully wrote mesh to:\n"
|
||||
<< outputFile_ << "\n";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,361 @@
|
|||
#include "OdmMeshing.hpp"
|
||||
|
||||
|
||||
OdmMeshing::OdmMeshing() : log_(false)
|
||||
{
|
||||
meshCreator_ = pcl::Poisson<pcl::PointNormal>::Ptr(new pcl::Poisson<pcl::PointNormal>());
|
||||
points_ = pcl::PointCloud<pcl::PointNormal>::Ptr(new pcl::PointCloud<pcl::PointNormal>());
|
||||
mesh_ = pcl::PolygonMeshPtr(new pcl::PolygonMesh);
|
||||
decimatedMesh_ = pcl::PolygonMeshPtr(new pcl::PolygonMesh);
|
||||
|
||||
// Set default values
|
||||
outputFile_ = "";
|
||||
logFilePath_ = "";
|
||||
|
||||
maxVertexCount_ = 0;
|
||||
treeDepth_ = 0;
|
||||
|
||||
solverDivide_ = 9.0;
|
||||
samplesPerNode_ = 1.0;
|
||||
decimationFactor_ = 0.0;
|
||||
|
||||
logFilePath_ = "odm_meshing_log.txt";
|
||||
log_ << logFilePath_ << "\n";
|
||||
}
|
||||
|
||||
OdmMeshing::~OdmMeshing()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int OdmMeshing::run(int argc, char **argv)
|
||||
{
|
||||
// If no arguments were passed, print help and return early.
|
||||
if (argc <= 1)
|
||||
{
|
||||
printHelp();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
parseArguments(argc, argv);
|
||||
|
||||
loadPoints();
|
||||
|
||||
createMesh();
|
||||
|
||||
decimateMesh();
|
||||
|
||||
writePlyFile();
|
||||
|
||||
}
|
||||
catch (const OdmMeshingException& e)
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
log_ << e.what() << "\n";
|
||||
log_.printToFile(logFilePath_);
|
||||
log_ << "For more detailed information, see log file." << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
log_ << "Error in OdmMeshing:\n";
|
||||
log_ << e.what() << "\n";
|
||||
log_.printToFile(logFilePath_);
|
||||
log_ << "For more detailed information, see log file." << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
log_ << "Unknwon error in OdmMeshing:\n";
|
||||
log_.printToFile(logFilePath_);
|
||||
log_ << "For more detailed information, see log file." << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
log_.printToFile(logFilePath_);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
void OdmMeshing::parseArguments(int argc, char **argv)
|
||||
{
|
||||
|
||||
for(int argIndex = 1; argIndex < argc; ++argIndex)
|
||||
{
|
||||
// The argument to be parsed.
|
||||
std::string argument = std::string(argv[argIndex]);
|
||||
|
||||
if(argument == "-help")
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
else if(argument == "-verbose")
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
}
|
||||
else if(argument == "-maxVertexCount" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
std::stringstream ss(argv[argIndex]);
|
||||
ss >> maxVertexCount_;
|
||||
if (ss.bad())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
|
||||
}
|
||||
log_ << "Vertex count was manually set to: " << maxVertexCount_ << "\n";
|
||||
}
|
||||
else if(argument == "-octreeDepth" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
std::stringstream ss(argv[argIndex]);
|
||||
ss >> treeDepth_;
|
||||
if (ss.bad())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
|
||||
}
|
||||
log_ << "Octree depth was manually set to: " << treeDepth_ << "\n";
|
||||
}
|
||||
else if(argument == "-solverDivide" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
std::stringstream ss(argv[argIndex]);
|
||||
ss >> solverDivide_;
|
||||
if (ss.bad())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
|
||||
}
|
||||
log_ << "Numerical solver divisions was manually set to: " << treeDepth_ << "\n";
|
||||
}
|
||||
else if(argument == "-samplesPerNode" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
std::stringstream ss(argv[argIndex]);
|
||||
ss >> samplesPerNode_;
|
||||
if (ss.bad())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
|
||||
}
|
||||
log_ << "The number of samples per octree node was manually set to: " << samplesPerNode_ << "\n";
|
||||
}
|
||||
else if(argument == "-inputFile" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
inputFile_ = std::string(argv[argIndex]);
|
||||
std::ifstream testFile(inputFile_.c_str(), std::ios::binary);
|
||||
if (!testFile.is_open())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value. (file not accessible)");
|
||||
}
|
||||
testFile.close();
|
||||
log_ << "Reading point cloud at: " << inputFile_ << "\n";
|
||||
}
|
||||
else if(argument == "-outputFile" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
outputFile_ = std::string(argv[argIndex]);
|
||||
std::ofstream testFile(outputFile_.c_str());
|
||||
if (!testFile.is_open())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value.");
|
||||
}
|
||||
testFile.close();
|
||||
log_ << "Writing output to: " << outputFile_ << "\n";
|
||||
}
|
||||
else if(argument == "-logFile" && argIndex < argc)
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
logFilePath_ = std::string(argv[argIndex]);
|
||||
std::ofstream testFile(outputFile_.c_str());
|
||||
if (!testFile.is_open())
|
||||
{
|
||||
throw OdmMeshingException("Argument '" + argument + "' has a bad value.");
|
||||
}
|
||||
testFile.close();
|
||||
log_ << "Writing log information to: " << logFilePath_ << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
printHelp();
|
||||
throw OdmMeshingException("Unrecognised argument '" + argument + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OdmMeshing::loadPoints()
|
||||
{
|
||||
|
||||
if(pcl::io::loadPLYFile<pcl::PointNormal> (inputFile_.c_str(), *points_.get()) == -1) {
|
||||
throw OdmMeshingException("Error when reading points and normals from:\n" + inputFile_ + "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
log_ << "Successfully loaded " << points_->size() << " points with corresponding normals from file.\n";
|
||||
}
|
||||
}
|
||||
|
||||
void OdmMeshing::printHelp()
|
||||
{
|
||||
bool printInCoutPop = log_.isPrintingInCout();
|
||||
log_.setIsPrintingInCout(true);
|
||||
|
||||
log_ << "OpenDroneMapMeshing.exe\n\n";
|
||||
|
||||
log_ << "Purpose:" << "\n";
|
||||
log_ << "Create a mesh from an oriented point cloud (points with normals) using the Poisson surface reconstruction method." << "\n";
|
||||
|
||||
log_ << "Usage:" << "\n";
|
||||
log_ << "The program requires a path to an input PLY point cloud file, all other input parameters are optional." << "\n\n";
|
||||
|
||||
log_ << "The following flags are available\n";
|
||||
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: \"-<argument name> <argument>\", (without <>), and the following parameters are configureable: " << "\n";
|
||||
log_ << "\"-inputFile <path>\" (mandatory)" << "\n";
|
||||
log_ << "\"Input ascii ply file that must contain a point cloud with normals.\n\n";
|
||||
|
||||
log_ << "\"-outputFile <path>\" (optional, default: odm_mesh.ply)" << "\n";
|
||||
log_ << "\"Target file in which the mesh is saved.\n\n";
|
||||
|
||||
log_ << "\"-logFile <path>\" (optional, default: odm_meshing_log.txt)" << "\n";
|
||||
log_ << "\"Target file in which the mesh is saved.\n\n";
|
||||
|
||||
log_ << "\"-maxVertexCount <integer>\" (optional, default: 100,000)" << "\n";
|
||||
log_ << "Desired final vertex count (after decimation), set to 0 to disable decimation.\n\n";
|
||||
|
||||
log_ << "\"-treeDepth <integer>\" (optional, default: 0 (automatic))" << "\n";
|
||||
log_ << "Controls octree depth used for poisson reconstruction. Recommended values (9-11).\n"
|
||||
<< "Increasing the value on this parameter will raise initial vertex count."
|
||||
<< "If omitted or zero, the depth is calculated automatically from the input point count.\n\n";
|
||||
|
||||
log_ << "\"-samplesPerNode <float>\" (optional, default: 1)" << "\n";
|
||||
log_ << "Average number of samples (points) per octree node. Increasing this value might help if data is very noisy.\n\n";
|
||||
|
||||
log_ << "\"-solverDivide <integer>\" (optional, default: 9)" << "\n";
|
||||
log_ << "Ocree depth at which the Laplacian equation is solved in the surface reconstruction step.\n";
|
||||
log_ << "Increasing this value increases computation times slightly but helps reduce memory usage.\n\n";
|
||||
|
||||
log_.setIsPrintingInCout(printInCoutPop);
|
||||
}
|
||||
|
||||
void OdmMeshing::createMesh()
|
||||
{
|
||||
|
||||
// Attempt to calculate the depth of the tree if unspecified
|
||||
if (treeDepth_ == 0)
|
||||
{
|
||||
treeDepth_ = calcTreeDepth(points_->size());
|
||||
}
|
||||
|
||||
log_ << "Octree depth used for reconstruction is: " << treeDepth_ << "\n";
|
||||
log_ << "Estimated initial vertex count: " << pow(4, treeDepth_) << "\n\n";
|
||||
|
||||
meshCreator_->setDepth(treeDepth_);
|
||||
meshCreator_->setSamplesPerNode(samplesPerNode_);
|
||||
meshCreator_->setInputCloud(points_);
|
||||
|
||||
// Guarantee manifold mesh.
|
||||
meshCreator_->setManifold(true);
|
||||
|
||||
// Begin reconstruction
|
||||
meshCreator_->reconstruct(*mesh_.get());
|
||||
|
||||
log_ << "Reconstruction complete:\n";
|
||||
log_ << "Vertex count: " << mesh_->cloud.width << "\n";
|
||||
log_ << "Triangle count: " << mesh_->polygons.size() << "\n\n";
|
||||
|
||||
}
|
||||
|
||||
void OdmMeshing::decimateMesh()
|
||||
{
|
||||
if (maxVertexCount_ <= 0)
|
||||
{
|
||||
log_ << "Vertex count not specified, decimation cancelled.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxVertexCount_ > mesh_->cloud.height*mesh_->cloud.width)
|
||||
{
|
||||
log_ << "Vertex count in mesh lower than initially generated mesh, unable to decimate.\n";
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
decimatedMesh_ = pcl::PolygonMeshPtr(new pcl::PolygonMesh);
|
||||
|
||||
double reductionFactor = 1.0 - double(maxVertexCount_)/double(mesh_->cloud.height*mesh_->cloud.width);
|
||||
|
||||
log_ << "Decimating mesh, removing " << reductionFactor*100 << " percent of vertices.\n";
|
||||
|
||||
pcl::MeshQuadricDecimationVTK decimator;
|
||||
decimator.setInputMesh(mesh_);
|
||||
decimator.setTargetReductionFactor(reductionFactor);
|
||||
decimator.process(*decimatedMesh_.get());
|
||||
|
||||
log_ << "Decimation complete.\n";
|
||||
log_ << "Decimated vertex count: " << decimatedMesh_->cloud.width << "\n";
|
||||
log_ << "Decimated triangle count: " << decimatedMesh_->polygons.size() << "\n\n";
|
||||
|
||||
mesh_ = decimatedMesh_;
|
||||
}
|
||||
}
|
||||
|
||||
int OdmMeshing::calcTreeDepth(size_t nPoints)
|
||||
{
|
||||
// Assume points are located (roughly) in a plane.
|
||||
double squareSide = std::sqrt(double(nPoints));
|
||||
|
||||
// Calculate octree depth such that if points were equally distributed in
|
||||
// a quadratic plane, there would be at least 1 point per octree node.
|
||||
int depth = 0;
|
||||
while(std::pow<double>(2,depth) < squareSide/2)
|
||||
{
|
||||
depth++;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
void OdmMeshing::writePlyFile()
|
||||
{
|
||||
log_ << "Saving mesh to file.\n";
|
||||
if (pcl::io::savePLYFile(outputFile_.c_str(), *mesh_.get()) == -1) {
|
||||
throw OdmMeshingException("Error when saving mesh to file:\n" + outputFile_ + "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
log_ << "Successfully wrote mesh to:\n"
|
||||
<< outputFile_ << "\n";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
// STL
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
// PCL
|
||||
#include <pcl/io/ply_io.h>
|
||||
#include <pcl/surface/poisson.h>
|
||||
#include <pcl/surface/vtk_smoothing/vtk_mesh_quadric_decimation.h>
|
||||
|
||||
// Logging
|
||||
#include "Logger.hpp"
|
||||
|
||||
/*!
|
||||
* \brief The OdmMeshing class is used to create a triangulated mesh using the Poisson method.
|
||||
* The class reads an oriented point cloud (coordinates and normals) from a PLY ascii
|
||||
* file and outputs the resulting welded manifold mesh on the form of an ASCII PLY-file.
|
||||
* The class uses file read and write functions from pcl.
|
||||
*/
|
||||
class OdmMeshing
|
||||
{
|
||||
public:
|
||||
OdmMeshing();
|
||||
~OdmMeshing();
|
||||
|
||||
/*!
|
||||
* \brief run Runs the meshing functionality using the provided input arguments.
|
||||
* For a list of accepted arguments, please see the main page documentation or
|
||||
* call the program with parameter "-help".
|
||||
* \param argc Application argument count.
|
||||
* \param argv Argument values.
|
||||
* \return 0 If successful.
|
||||
*/
|
||||
int run(int argc, char **argv);
|
||||
|
||||
private:
|
||||
|
||||
/*!
|
||||
* \brief parseArguments Parses command line arguments.
|
||||
* \param argc Application argument count.
|
||||
* \param argv Argument values.
|
||||
*/
|
||||
void parseArguments(int argc, char** argv);
|
||||
|
||||
/*!
|
||||
* \brief createMesh Sets up the pcl::Poisson meshing class using provided arguments and calls
|
||||
* it to start the meshing.
|
||||
*/
|
||||
void createMesh();
|
||||
|
||||
/*!
|
||||
* \brief loadPoints Loads a PLY ascii file with points and normals from file.
|
||||
*/
|
||||
void loadPoints();
|
||||
|
||||
/*!
|
||||
* \brief decimateMesh Performs post-processing on the form of quadric decimation to generate a mesh
|
||||
* that has a higher density in areas with a lot of structure.
|
||||
*/
|
||||
void decimateMesh();
|
||||
|
||||
/*!
|
||||
* \brief writePlyFile Writes the mesh to file on the Ply format.
|
||||
*/
|
||||
void writePlyFile();
|
||||
|
||||
/*!
|
||||
* \brief printHelp Prints help, explaining usage. Can be shown by calling the program with argument: "-help".
|
||||
*/
|
||||
void printHelp();
|
||||
|
||||
/*!
|
||||
* \brief calcTreeDepth Attepts to calculate the depth of the tree using the point cloud.
|
||||
* The function makes the assumption points are located roughly in a plane
|
||||
* (fairly reasonable for ortho-terrain photos) and tries to generate a mesh using
|
||||
* an octree with an appropriate resolution.
|
||||
* \param nPoints The total number of points in the input point cloud.
|
||||
* \return The calcualated octree depth.
|
||||
*/
|
||||
int calcTreeDepth(size_t nPoints);
|
||||
|
||||
Logger log_; /**< Logging object. */
|
||||
|
||||
pcl::Poisson<pcl::PointNormal>::Ptr meshCreator_; /**< PCL poisson meshing class. */
|
||||
|
||||
pcl::PointCloud<pcl::PointNormal>::Ptr points_; /**< Input point and normals. */
|
||||
pcl::PolygonMeshPtr mesh_; /**< PCL polygon mesh. */
|
||||
pcl::PolygonMeshPtr decimatedMesh_; /**< Decimated polygon mesh. */
|
||||
|
||||
std::string inputFile_; /**< Path to a file containing points and normals. */
|
||||
std::string outputFile_; /**< Path to the destination file. */
|
||||
std::string logFilePath_; /**< Path to the log file. */
|
||||
|
||||
unsigned int maxVertexCount_; /**< Desired output vertex count. */
|
||||
unsigned int treeDepth_; /**< Depth of octree used for reconstruction. */
|
||||
|
||||
double samplesPerNode_; /**< Samples per octree node.*/
|
||||
double solverDivide_; /**< Depth at which the Laplacian equation solver is run during surface estimation.*/
|
||||
double decimationFactor_; /**< Percentage of points to remove when decimating the mesh. */
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The OdmMeshingException class
|
||||
*/
|
||||
class OdmMeshingException : public std::exception
|
||||
{
|
||||
|
||||
public:
|
||||
OdmMeshingException() : message("Error in OdmMeshing") {}
|
||||
OdmMeshingException(std::string msgInit) : message("Error in OdmMeshing:\n" + msgInit) {}
|
||||
~OdmMeshingException() throw() {}
|
||||
virtual const char* what() const throw() {return message.c_str(); }
|
||||
|
||||
private:
|
||||
std::string message; /**< The error message **/
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
// Insert license here.
|
||||
|
||||
// Include meshing source code.
|
||||
#include "OdmMeshing.hpp"
|
||||
|
||||
/*!
|
||||
* \mainpage main OpenDroneMap Meshing Module
|
||||
*
|
||||
* The OpenDroneMap Meshing Module generates a welded, manifold mesh using the Poisson
|
||||
* surface reconstruction algorithm from any oriented point cloud (points with corresponding normals).
|
||||
*
|
||||
*/
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
|
||||
OdmMeshing meshCreator;
|
||||
return meshCreator.run(argc, argv);
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
project(odm_orthophoto)
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
# Set pcl dir to the input spedified with option -DPCL_DIR="path"
|
||||
set(PCL_DIR "PCL_DIR-NOTFOUND" CACHE "PCL_DIR" "Path to the pcl installation directory")
|
||||
|
||||
# Add compiler options.
|
||||
add_definitions(-Wall -Wextra)
|
||||
|
||||
# Find pcl at the location specified by PCL_DIR
|
||||
find_package(PCL 1.7 HINTS "${PCL_DIR}/share/pcl-1.7" REQUIRED)
|
||||
|
||||
# Find OpenCV at the default location
|
||||
find_package(OpenCV REQUIRED)
|
||||
|
||||
# Only link with required opencv modules.
|
||||
set(OpenCV_LIBS opencv_core opencv_imgproc opencv_highgui)
|
||||
|
||||
# Add the PCL, Eigen and OpenCV include dirs.
|
||||
# Necessary since the PCL_INCLUDE_DIR variable set by find_package is broken.)
|
||||
include_directories(${PCL_ROOT}/include/pcl-${PCL_VERSION_MAJOR}.${PCL_VERSION_MINOR})
|
||||
include_directories(${EIGEN_ROOT})
|
||||
include_directories(${OpenCV_INCLUDE_DIRS})
|
||||
|
||||
#library_directories(${OpenCV_LIBRARY_DIRS})
|
||||
|
||||
# Add source directory
|
||||
aux_source_directory("./src" SRC_LIST)
|
||||
|
||||
# Add exectuteable
|
||||
add_executable(${PROJECT_NAME} ${SRC_LIST})
|
||||
target_link_libraries(odm_orthophoto ${PCL_COMMON_LIBRARIES} ${PCL_IO_LIBRARIES} ${PCL_SURFACE_LIBRARIES} ${OpenCV_LIBS})
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#include "Logger.hpp"
|
||||
|
||||
|
||||
Logger::Logger(bool isPrintingInCout) : isPrintingInCout_(isPrintingInCout)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Logger::~Logger()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Logger::print(std::string filePath)
|
||||
{
|
||||
std::ofstream file(filePath.c_str(), std::ios::binary);
|
||||
file << logStream_.str();
|
||||
file.close();
|
||||
}
|
||||
|
||||
bool Logger::isPrintingInCout() const
|
||||
{
|
||||
return isPrintingInCout_;
|
||||
}
|
||||
|
||||
void Logger::setIsPrintingInCout(bool isPrintingInCout)
|
||||
{
|
||||
isPrintingInCout_ = isPrintingInCout;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
// STL
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
/*!
|
||||
* \brief The Logger class is used to store program messages in a log file.
|
||||
* \details By using the << operator while printInCout is set, the class writes both to
|
||||
* cout and to file, if the flag is not set, output is written to file only.
|
||||
*/
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* \brief Logger Contains functionality for printing and displaying log information.
|
||||
* \param printInCout Flag toggling if operator << also writes to cout.
|
||||
*/
|
||||
Logger(bool isPrintingInCout = true);
|
||||
|
||||
/*!
|
||||
* \brief Destructor.
|
||||
*/
|
||||
~Logger();
|
||||
|
||||
/*!
|
||||
* \brief print Prints the contents of the log to file.
|
||||
* \param filePath Path specifying where to write the log.
|
||||
*/
|
||||
void print(std::string filePath);
|
||||
|
||||
/*!
|
||||
* \brief isPrintingInCout Check if console printing flag is set.
|
||||
* \return Console printing flag.
|
||||
*/
|
||||
bool isPrintingInCout() const;
|
||||
|
||||
/*!
|
||||
* \brief setIsPrintingInCout Set console printing flag.
|
||||
* \param isPrintingInCout Value, if true, messages added to the log are also printed in cout.
|
||||
*/
|
||||
void setIsPrintingInCout(bool isPrintingInCout);
|
||||
|
||||
/*!
|
||||
* Operator for printing messages to log and in the standard output stream if desired.
|
||||
*/
|
||||
template<class T>
|
||||
friend Logger& operator<< (Logger &log, T t)
|
||||
{
|
||||
// If console printing is enabled.
|
||||
if (log.isPrintingInCout_)
|
||||
{
|
||||
std::cout << t;
|
||||
std::cout.flush();
|
||||
}
|
||||
// Write to log.
|
||||
log.logStream_ << t;
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
private:
|
||||
bool isPrintingInCout_; /*!< If flag is set, log is printed in cout and written to the log. */
|
||||
|
||||
std::stringstream logStream_; /*!< Stream for storing the log. */
|
||||
};
|
|
@ -0,0 +1,646 @@
|
|||
|
||||
|
||||
// This
|
||||
#include "OdmOrthoPhoto.hpp"
|
||||
|
||||
OdmOrthoPhoto::OdmOrthoPhoto()
|
||||
:log_(false)
|
||||
{
|
||||
inputFile_ = "";
|
||||
outputFile_ = "ortho.jpg";
|
||||
logFile_ = "log.txt";
|
||||
|
||||
resolution_ = 0.0f;
|
||||
boundryPoint1_[0] = 0.0f; boundryPoint1_[1] = 0.0f;
|
||||
boundryPoint2_[0] = 0.0f; boundryPoint2_[1] = 0.0f;
|
||||
boundryPoint3_[0] = 0.0f; boundryPoint3_[1] = 0.0f;
|
||||
boundryPoint4_[0] = 0.0f; boundryPoint4_[1] = 0.0f;
|
||||
}
|
||||
|
||||
OdmOrthoPhoto::~OdmOrthoPhoto()
|
||||
{
|
||||
}
|
||||
|
||||
int OdmOrthoPhoto::run(int argc, char *argv[])
|
||||
{
|
||||
try
|
||||
{
|
||||
parseArguments(argc, argv);
|
||||
createOrthoPhoto();
|
||||
}
|
||||
catch (const OdmOrthoPhotoException& e)
|
||||
{
|
||||
log_ << e.what() << "\n";
|
||||
log_.print(logFile_);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
log_ << "Error in OdmOrthoPhoto:\n";
|
||||
log_ << e.what() << "\n";
|
||||
log_.print(logFile_);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
log_ << "Unknown error, terminating:\n";
|
||||
log_.print(logFile_);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
log_.print(logFile_);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void OdmOrthoPhoto::parseArguments(int argc, char *argv[])
|
||||
{
|
||||
logFile_ = std::string(argv[0]) + "_log.txt";
|
||||
log_ << logFile_ << "\n";
|
||||
|
||||
// If no arguments were passed, print help.
|
||||
if (argc == 1)
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
|
||||
log_ << "Arguments given\n";
|
||||
for(int argIndex = 1; argIndex < argc; ++argIndex)
|
||||
{
|
||||
log_ << argv[argIndex] << '\n';
|
||||
}
|
||||
|
||||
log_ << '\n';
|
||||
for(int argIndex = 1; argIndex < argc; ++argIndex)
|
||||
{
|
||||
// The argument to be parsed.
|
||||
std::string argument = std::string(argv[argIndex]);
|
||||
|
||||
if(argument == "-help")
|
||||
{
|
||||
printHelp();
|
||||
}
|
||||
else if(argument == "-resolution")
|
||||
{
|
||||
++argIndex;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmOrthoPhotoException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
std::stringstream ss(argv[argIndex]);
|
||||
ss >> resolution_;
|
||||
log_ << "Resolution count was set to: " << resolution_ << "pixels/meter\n";
|
||||
}
|
||||
else if(argument == "-boundry")
|
||||
{
|
||||
if(argIndex+8 >= argc)
|
||||
{
|
||||
throw OdmOrthoPhotoException("Argument '" + argument + "' expects 8 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
ss << argv[argIndex+1] << " " << argv[argIndex+2] << " " << argv[argIndex+3] << " " << argv[argIndex+4] << " " << argv[argIndex+5] << " " << argv[argIndex+6] << " " << argv[argIndex+7] << " " << argv[argIndex+8];
|
||||
ss >> boundryPoint1_[0] >> boundryPoint1_[1] >> boundryPoint2_[0] >> boundryPoint2_[1] >> boundryPoint3_[0] >> boundryPoint3_[1] >> boundryPoint4_[0] >> boundryPoint4_[1];
|
||||
argIndex += 8;
|
||||
|
||||
log_ << "Boundry point 1 was set to: " << boundryPoint1_[0] << ", " << boundryPoint1_[1] << '\n';
|
||||
log_ << "Boundry point 2 was set to: " << boundryPoint2_[0] << ", " << boundryPoint2_[1] << '\n';
|
||||
log_ << "Boundry point 3 was set to: " << boundryPoint3_[0] << ", " << boundryPoint3_[1] << '\n';
|
||||
log_ << "Boundry point 4 was set to: " << boundryPoint4_[0] << ", " << boundryPoint4_[1] << '\n';
|
||||
}
|
||||
else if(argument == "-verbose")
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
}
|
||||
else if(argument == "-inputFile" && argIndex < argc)
|
||||
{
|
||||
argIndex++;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmOrthoPhotoException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
inputFile_ = std::string(argv[argIndex]);
|
||||
log_ << "Reading textured mesh from: " << inputFile_ << "\n";
|
||||
}
|
||||
else if(argument == "-outputFile" && argIndex < argc)
|
||||
{
|
||||
argIndex++;
|
||||
if (argIndex >= argc)
|
||||
{
|
||||
throw OdmOrthoPhotoException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
|
||||
}
|
||||
outputFile_ = std::string(argv[argIndex]);
|
||||
log_ << "Writing output to: " << outputFile_ << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
printHelp();
|
||||
throw OdmOrthoPhotoException("Unrecognised argument '" + argument + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OdmOrthoPhoto::printHelp()
|
||||
{
|
||||
log_.setIsPrintingInCout(true);
|
||||
|
||||
log_ << "OpenDroneMapOrthoPhoto.exe\n\n";
|
||||
|
||||
log_ << "Purpose:" << "\n";
|
||||
log_ << "Create an orthograpical photo from an oriented textured mesh." << "\n";
|
||||
|
||||
log_ << "Usage:" << "\n";
|
||||
log_ << "The program requires a path to an input OBJ mesh file, resolution and boundry points to define the area. All other input parameters are optional." << "\n\n";
|
||||
|
||||
log_ << "The following flags are available\n";
|
||||
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: \"-<argument name> <argument>\", (without <>), and the following parameters are configureable: " << "\n";
|
||||
log_ << "\"-inputFile <path>\" (mandatory)" << "\n";
|
||||
log_ << "\"Input obj file that must contain a textured mesh.\n\n";
|
||||
|
||||
log_ << "\"-outputFile <path>\" (optional, default: ortho.jpg)" << "\n";
|
||||
log_ << "\"Target file in which the orthophoto is saved.\n\n";
|
||||
|
||||
log_ << "\"-resolution <pixels/m>\" (mandatory)" << "\n";
|
||||
log_ << "\"The number of pixels used per meter.\n\n";
|
||||
|
||||
log_ << "\"-boundry <Point1x Point1y Point2x Point2y Point3x Point3y Point4x Point4y >\" (mandatory)" << "\n";
|
||||
log_ << "\"Describes the area which should be covered in the ortho photo. The area will be a bounding box containing all four points.\n\n";
|
||||
|
||||
log_.setIsPrintingInCout(false);
|
||||
}
|
||||
|
||||
void OdmOrthoPhoto::createOrthoPhoto()
|
||||
{
|
||||
if(inputFile_.empty())
|
||||
{
|
||||
throw OdmOrthoPhotoException("Failed to create ortho photo, no texture mesh given.");
|
||||
return;
|
||||
}
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Reading mesh file...\n";
|
||||
// The textureds mesh.
|
||||
pcl::TextureMesh mesh;
|
||||
pcl::io::loadOBJFile(inputFile_, mesh);
|
||||
log_ << ".. mesh file read.\n";
|
||||
|
||||
// Does the model have more than one material?
|
||||
multiMaterial_ = 1 < mesh.tex_materials.size();
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
// The minimum and maximum boundry values.
|
||||
float xMax, xMin, yMax, yMin;
|
||||
xMin = std::min(std::min(boundryPoint1_[0], boundryPoint2_[0]), std::min(boundryPoint3_[0], boundryPoint4_[0]));
|
||||
xMax = std::max(std::max(boundryPoint1_[0], boundryPoint2_[0]), std::max(boundryPoint3_[0], boundryPoint4_[0]));
|
||||
yMin = std::min(std::min(boundryPoint1_[1], boundryPoint2_[1]), std::min(boundryPoint3_[1], boundryPoint4_[1]));
|
||||
yMax = std::max(std::max(boundryPoint1_[1], boundryPoint2_[1]), std::max(boundryPoint3_[1], boundryPoint4_[1]));
|
||||
|
||||
log_ << "Ortho photo area x : " << xMin << " -> " << xMax << '\n';
|
||||
log_ << "Ortho photo area y : " << yMin << " -> " << yMax << '\n';
|
||||
|
||||
// The size of the area.
|
||||
float xDiff = xMax - xMin;
|
||||
float yDiff = yMax - yMin;
|
||||
|
||||
// The resolution neccesary to fit the area with the given resolution.
|
||||
int rowRes = static_cast<int>(std::ceil(resolution_*xDiff));
|
||||
int colRes = static_cast<int>(std::ceil(resolution_*yDiff));
|
||||
log_ << "Ortho photo resolution, width x height : " << colRes << "x" << rowRes << '\n';
|
||||
|
||||
if(0 >= rowRes*colRes)
|
||||
{
|
||||
if(0 >= rowRes)
|
||||
{
|
||||
log_ << "Warning: ortho photo has zero area, height = " << rowRes << ". Forcing height = 1.\n";
|
||||
rowRes = 1;
|
||||
}
|
||||
if(0 >= colRes)
|
||||
{
|
||||
log_ << "Warning: ortho photo has zero area, width = " << colRes << ". Forcing width = 1.\n";
|
||||
colRes = 1;
|
||||
}
|
||||
log_ << "New ortho photo resolution, width x height : " << colRes << "x" << rowRes << '\n';
|
||||
}
|
||||
|
||||
// Init ortho photo
|
||||
photo_ = cv::Mat::zeros(rowRes, colRes, CV_8UC3) + cv::Scalar(255, 255, 255);
|
||||
depth_ = cv::Mat::zeros(rowRes, colRes, CV_32F) - std::numeric_limits<float>::infinity();
|
||||
|
||||
// Contains the vertices of the mesh.
|
||||
pcl::PointCloud<pcl::PointXYZ>::Ptr meshCloud (new pcl::PointCloud<pcl::PointXYZ>);
|
||||
pcl::fromPCLPointCloud2 (mesh.cloud, *meshCloud);
|
||||
|
||||
// Creates a transformation which aligns the area for the ortho photo.
|
||||
Eigen::Transform<float, 3, Eigen::Affine> transform = getROITransform(xMin, -yMax);
|
||||
|
||||
log_ << "Translating and scaling mesh...\n";
|
||||
|
||||
// Move the mesh into position.
|
||||
pcl::transformPointCloud(*meshCloud, *meshCloud, transform);
|
||||
log_ << ".. mesh translated and scaled.\n";
|
||||
|
||||
// Flatten texture coordiantes.
|
||||
std::vector<Eigen::Vector2f> uvs;
|
||||
for(size_t t = 0; t < mesh.tex_coordinates.size(); ++t)
|
||||
{
|
||||
uvs.insert(uvs.end(), mesh.tex_coordinates[t].begin(), mesh.tex_coordinates[t].end());
|
||||
}
|
||||
|
||||
// The current material texture
|
||||
cv::Mat texture;
|
||||
|
||||
// Used to keep track of the global face index.
|
||||
size_t faceOff = 0;
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Rendering the ortho photo...\n";
|
||||
|
||||
// Iterate over each part of the mesh (one per material).
|
||||
for(size_t t = 0; t < mesh.tex_materials.size(); ++t)
|
||||
{
|
||||
// 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())
|
||||
{
|
||||
log_ << "Material texture could not be read:\n";
|
||||
log_ << material.tex_file << '\n';
|
||||
log_ << "Could not be read as image, does the file exist?\n";
|
||||
continue; // Skip to next material.
|
||||
}
|
||||
|
||||
// The faces of the current submesh.
|
||||
std::vector<pcl::Vertices> faces = mesh.tex_polygons[t];
|
||||
|
||||
// Iterate over each face...
|
||||
for(size_t faceIndex = 0; faceIndex < faces.size(); ++faceIndex)
|
||||
{
|
||||
// The current polygon.
|
||||
pcl::Vertices polygon = faces[faceIndex];
|
||||
|
||||
// ... and draw it into the ortho photo.
|
||||
drawTexturedTriangle(texture, polygon, meshCloud, uvs, faceIndex+faceOff);
|
||||
}
|
||||
faceOff += faces.size();
|
||||
log_ << "Material " << t << " rendered.\n";
|
||||
}
|
||||
log_ << "...ortho photo rendered\n";
|
||||
|
||||
log_ << '\n';
|
||||
log_ << "Writing ortho photo to " << outputFile_ << "\n";
|
||||
cv::imwrite(outputFile_, photo_);
|
||||
log_ << "Orthophoto generation done.\n";
|
||||
}
|
||||
|
||||
Eigen::Transform<float, 3, Eigen::Affine> OdmOrthoPhoto::getROITransform(float xMin, float yMin) const
|
||||
{
|
||||
// The tranform used to move the chosen area into the ortho photo.
|
||||
Eigen::Transform<float, 3, Eigen::Affine> transform;
|
||||
|
||||
transform(0, 0) = resolution_;
|
||||
transform(1, 0) = 0.0f;
|
||||
transform(2, 0) = 0.0f;
|
||||
transform(3, 0) = 0.0f;
|
||||
|
||||
transform(0, 1) = 0.0f;
|
||||
transform(1, 1) = -resolution_;
|
||||
transform(2, 1) = 0.0f;
|
||||
transform(3, 1) = 0.0f;
|
||||
|
||||
transform(0, 2) = 0.0f;
|
||||
transform(1, 2) = 0.0f;
|
||||
transform(2, 2) = 1.0f;
|
||||
transform(3, 2) = 0.0f;
|
||||
|
||||
transform(0, 3) = -xMin*resolution_;
|
||||
transform(1, 3) = -yMin*resolution_;
|
||||
transform(2, 3) = 0.0f;
|
||||
transform(3, 3) = 1.0f;
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
void OdmOrthoPhoto::drawTexturedTriangle(const cv::Mat &texture, const pcl::Vertices &polygon, const pcl::PointCloud<pcl::PointXYZ>::Ptr &meshCloud, const std::vector<Eigen::Vector2f> &uvs, size_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];
|
||||
|
||||
if(isSliverPolygon(v1, v2, v3))
|
||||
{
|
||||
log_ << "Warning: Sliver polygon found at face index " << faceIndex << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
// The face data. Position v*{x,y,z}. Texture coordinate v*{u,v}. * is the vertex number in the polygon.
|
||||
float v1x, v1y, v1z, v1u, v1v;
|
||||
float v2x, v2y, v2z, v2u, v2v;
|
||||
float v3x, v3y, v3z, v3u, v3v;
|
||||
|
||||
// Barycentric coordinates of the currently rendered point.
|
||||
float l1, l2, l3;
|
||||
|
||||
// The size of the photo, as float.
|
||||
float fRows, fCols;
|
||||
fRows = static_cast<float>(texture.rows);
|
||||
fCols = static_cast<float>(texture.cols);
|
||||
|
||||
// Get vertex position.
|
||||
v1x = v1.x; v1y = v1.y; v1z = v1.z;
|
||||
v2x = v2.x; v2y = v2.y; v2z = v2.z;
|
||||
v3x = v3.x; v3y = v3.y; v3z = v3.z;
|
||||
|
||||
// Get texture coorinates.
|
||||
if(multiMaterial_)
|
||||
{
|
||||
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
|
||||
{
|
||||
v1u = uvs[v1i][0]; v1v = uvs[v1i][1];
|
||||
v2u = uvs[v2i][0]; v2v = uvs[v2i][1];
|
||||
v3u = uvs[v3i][0]; v3v = uvs[v3i][1];
|
||||
}
|
||||
|
||||
// Check bounding box overlap.
|
||||
int xMin = static_cast<int>(std::min(std::min(v1x, v2x), v3x));
|
||||
if(xMin > photo_.cols)
|
||||
{
|
||||
return; // Outside to the right.
|
||||
}
|
||||
int xMax = static_cast<int>(std::max(std::max(v1x, v2x), v3x));
|
||||
if(xMax < 0)
|
||||
{
|
||||
return; // Outside to the left.
|
||||
}
|
||||
int yMin = static_cast<int>(std::min(std::min(v1y, v2y), v3y));
|
||||
if(yMin > photo_.rows)
|
||||
{
|
||||
return; // Outside to the top.
|
||||
}
|
||||
int yMax = static_cast<int>(std::max(std::max(v1y, v2y), v3y));
|
||||
if(yMax < 0)
|
||||
{
|
||||
return; // Outside to the bottom.
|
||||
}
|
||||
|
||||
// Top point row and column positions
|
||||
float topR, topC;
|
||||
// Middle point row and column positions
|
||||
float midR, midC;
|
||||
// Bottom point row and column positions
|
||||
float botR, botC;
|
||||
|
||||
// Find top, middle and bottom points.
|
||||
if(v1y < v2y)
|
||||
{
|
||||
if(v1y < v3y)
|
||||
{
|
||||
if(v2y < v3y)
|
||||
{
|
||||
// 1 -> 2 -> 3
|
||||
topR = v1y; topC = v1x;
|
||||
midR = v2y; midC = v2x;
|
||||
botR = v3y; botC = v3x;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 1 -> 3 -> 2
|
||||
topR = v1y; topC = v1x;
|
||||
midR = v3y; midC = v3x;
|
||||
botR = v2y; botC = v2x;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3 -> 1 -> 2
|
||||
topR = v3y; topC = v3x;
|
||||
midR = v1y; midC = v1x;
|
||||
botR = v2y; botC = v2x;
|
||||
}
|
||||
}
|
||||
else // v2y <= v1y
|
||||
{
|
||||
if(v2y < v3y)
|
||||
{
|
||||
if(v1y < v3y)
|
||||
{
|
||||
// 2 -> 1 -> 3
|
||||
topR = v2y; topC = v2x;
|
||||
midR = v1y; midC = v1x;
|
||||
botR = v3y; botC = v3x;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 2 -> 3 -> 1
|
||||
topR = v2y; topC = v2x;
|
||||
midR = v3y; midC = v3x;
|
||||
botR = v1y; botC = v1x;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 3 -> 2 -> 1
|
||||
topR = v3y; topC = v3x;
|
||||
midR = v2y; midC = v2x;
|
||||
botR = v1y; botC = v1x;
|
||||
}
|
||||
}
|
||||
|
||||
// The step along column for every step along r. Top to middle.
|
||||
float ctmdr;
|
||||
// The step along column for every step along r. Top to bottom.
|
||||
float ctbdr;
|
||||
// The step along column for every step along r. Middle to bottom.
|
||||
float cmbdr;
|
||||
|
||||
ctbdr = (botC-topC)/(botR-topR);
|
||||
|
||||
// The current column position, from top to middle.
|
||||
float ctm = topC;
|
||||
// The current column position, from top to bottom.
|
||||
float ctb = topC;
|
||||
|
||||
// Check for vertical line between middle and top.
|
||||
if(FLT_EPSILON < midR-topR)
|
||||
{
|
||||
ctmdr = (midC-topC)/(midR-topR);
|
||||
|
||||
// Describes the offset from the pixel coordiante to the triangle point.
|
||||
float rRoundOff = topR - std::floor(topR);
|
||||
|
||||
// Travers along row from top to middle.
|
||||
for(int rq = static_cast<int>(std::floor(topR))+1; rq <= static_cast<int>(std::floor(midR)); ++rq)
|
||||
{
|
||||
// Set the current column positions.
|
||||
ctm = topC + ctmdr*(static_cast<float>(rq)-topR);
|
||||
ctb = topC + ctbdr*(static_cast<float>(rq)-topR);
|
||||
|
||||
// Describes the offset from the pixel coordiante to the triangle point.
|
||||
float cRoundOff = std::min(ctm, ctb) - std::floor(std::min(ctm, ctb));
|
||||
|
||||
for(int cq = static_cast<int>(std::floor(std::min(ctm, ctb))); cq < static_cast<int>(std::floor(std::max(ctm, ctb))); ++cq)
|
||||
{
|
||||
if(0 <= rq && rq < photo_.rows && 0 <= cq && cq < photo_.cols)
|
||||
{
|
||||
// Get barycentric coordinates for the current point.
|
||||
getBarycentricCoordiantes(v1, v2, v3, static_cast<float>(cq)+cRoundOff, static_cast<float>(rq)+rRoundOff, l1, l2, l3);
|
||||
|
||||
// The z value for the point.
|
||||
float z = v1z*l1+v2z*l2+v3z*l3;
|
||||
|
||||
// Check depth
|
||||
float depthValue = depth_.at<float>(rq, cq);
|
||||
if(z < depthValue)
|
||||
{
|
||||
// Current is behind last, don't draw.
|
||||
continue;
|
||||
}
|
||||
|
||||
// The uv values of the point.
|
||||
float u, v;
|
||||
u = v1u*l1+v2u*l2+v3u*l3;
|
||||
v = v1v*l1+v2v*l2+v3v*l3;
|
||||
|
||||
// Nearest neighbour texture interpolation.
|
||||
int uq = static_cast<int>(u*fCols);
|
||||
int vq = static_cast<int>((1.0f-v)*fRows);
|
||||
photo_.at<cv::Vec3b>(rq,cq) = texture.at<cv::Vec3b>(vq, uq);
|
||||
|
||||
// Update depth buffer.
|
||||
depth_.at<float>(rq, cq) = z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(FLT_EPSILON < botR-midR)
|
||||
{
|
||||
cmbdr = (botC-midC)/(botR-midR);
|
||||
|
||||
// The current column position, from middle to bottom.
|
||||
float cmb = midC;
|
||||
|
||||
// Describes the offset from the pixel coordiante to the triangle point.
|
||||
float rRoundOff = midR - std::floor(midR);
|
||||
|
||||
// Travers along row from middle to bottom.
|
||||
for(int rq = static_cast<int>(std::floor(midR))+1; rq <= static_cast<int>(std::floor(botR)); ++rq)
|
||||
{
|
||||
// Set the current column positions.
|
||||
ctb = topC + ctbdr*(static_cast<float>(rq)-topR);
|
||||
cmb = midC + cmbdr*(static_cast<float>(rq)-midR);
|
||||
|
||||
// Describes the offset from the pixel coordiante to the triangle point.
|
||||
float cRoundOff = std::min(cmb, ctb) - std::floor(std::min(cmb, ctb));
|
||||
|
||||
for(int cq = static_cast<int>(std::floor(std::min(cmb, ctb))); cq < static_cast<int>(std::floor(std::max(cmb, ctb))); ++cq)
|
||||
{
|
||||
if(0 <= rq && rq < photo_.rows && 0 <= cq && cq < photo_.cols)
|
||||
{
|
||||
// Get barycentric coordinates for the current point.
|
||||
getBarycentricCoordiantes(v1, v2, v3, static_cast<float>(cq)+cRoundOff, static_cast<float>(rq)+rRoundOff, l1, l2, l3);
|
||||
|
||||
// The z value for the point.
|
||||
float z = v1z*l1+v2z*l2+v3z*l3;
|
||||
|
||||
// Check depth
|
||||
float depthValue = depth_.at<float>(rq, cq);
|
||||
if(z < depthValue)
|
||||
{
|
||||
// Current is behind last, don't draw.
|
||||
continue;
|
||||
}
|
||||
|
||||
// The uv values of the point.
|
||||
float u, v;
|
||||
u = v1u*l1+v2u*l2+v3u*l3;
|
||||
v = v1v*l1+v2v*l2+v3v*l3;
|
||||
|
||||
// Nearest neighbour texture interpolation.
|
||||
int uq = static_cast<int>(u*fCols);
|
||||
int vq = static_cast<int>((1.0f-v)*fRows);
|
||||
photo_.at<cv::Vec3b>(rq,cq) = texture.at<cv::Vec3b>(vq, uq);
|
||||
|
||||
// Update depth buffer.
|
||||
depth_.at<float>(rq, cq) = z;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OdmOrthoPhoto::getBarycentricCoordiantes(pcl::PointXYZ v1, pcl::PointXYZ v2, pcl::PointXYZ v3, float x, float y, float &l1, float &l2, float &l3) const
|
||||
{
|
||||
// Diff along y.
|
||||
float y2y3 = v2.y-v3.y;
|
||||
float y1y3 = v1.y-v3.y;
|
||||
float y3y1 = v3.y-v1.y;
|
||||
float yy3 = y -v3.y;
|
||||
|
||||
// Diff along x.
|
||||
float x3x2 = v3.x-v2.x;
|
||||
float x1x3 = v1.x-v3.x;
|
||||
float xx3 = x -v3.x;
|
||||
|
||||
// Normalization factor.
|
||||
float norm = (y2y3*x1x3 + x3x2*y1y3);
|
||||
|
||||
l1 = (y2y3*(xx3) + x3x2*(yy3)) / norm;
|
||||
l2 = (y3y1*(xx3) + x1x3*(yy3)) / norm;
|
||||
l3 = 1 - l1 - l2;
|
||||
}
|
||||
|
||||
bool OdmOrthoPhoto::isSliverPolygon(pcl::PointXYZ v1, pcl::PointXYZ v2, pcl::PointXYZ v3) const
|
||||
{
|
||||
// Calculations are made using doubles, to minize rounding errors.
|
||||
Eigen::Vector3d a = Eigen::Vector3d(static_cast<double>(v1.x), static_cast<double>(v1.y), static_cast<double>(v1.z));
|
||||
Eigen::Vector3d b = Eigen::Vector3d(static_cast<double>(v2.x), static_cast<double>(v2.y), static_cast<double>(v2.z));
|
||||
Eigen::Vector3d c = Eigen::Vector3d(static_cast<double>(v3.x), static_cast<double>(v3.y), static_cast<double>(v3.z));
|
||||
Eigen::Vector3d dummyVec = (a-b).cross(c-b);
|
||||
|
||||
// Area smaller than, or equal to, floating-point epsilon.
|
||||
return std::numeric_limits<float>::epsilon() >= static_cast<float>(std::sqrt(dummyVec.dot(dummyVec))/2.0);
|
||||
}
|
||||
|
||||
bool OdmOrthoPhoto::isModelOk(const pcl::TextureMesh &mesh)
|
||||
{
|
||||
// The number of texture coordinates in the model.
|
||||
size_t nTextureCoordinates = 0;
|
||||
// The number of faces in the model.
|
||||
size_t nFaces = 0;
|
||||
|
||||
for(size_t t = 0; t < mesh.tex_coordinates.size(); ++t)
|
||||
{
|
||||
nTextureCoordinates += mesh.tex_coordinates[t].size();
|
||||
}
|
||||
for(size_t t = 0; t < mesh.tex_polygons.size(); ++t)
|
||||
{
|
||||
nFaces += mesh.tex_polygons[t].size();
|
||||
}
|
||||
|
||||
log_ << "Number of faces in the model " << nFaces << '\n';
|
||||
|
||||
return 3*nFaces == nTextureCoordinates;
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
#pragma once
|
||||
|
||||
// C++
|
||||
#include <limits.h>
|
||||
|
||||
// PCL
|
||||
#include <pcl/io/obj_io.h>
|
||||
#include <pcl/common/transforms.h>
|
||||
|
||||
// OpenCV
|
||||
#include <opencv2/imgproc/imgproc.hpp>
|
||||
#include <opencv2/highgui/highgui.hpp>
|
||||
|
||||
// PCL
|
||||
#include <pcl/common/eigen.h>
|
||||
#include <pcl/common/common.h>
|
||||
|
||||
// OpenCV
|
||||
#include <opencv2/core/core.hpp>
|
||||
|
||||
// Logger
|
||||
#include "Logger.hpp"
|
||||
|
||||
/*!
|
||||
* \brief The OdmOrthoPhoto class is used to create an orthograpic photo over a given area.
|
||||
* The class reads an oriented textured mesh from an OBJ-file.
|
||||
* The class uses file read from pcl.
|
||||
* The class uses image read and write from opencv.
|
||||
*/
|
||||
class OdmOrthoPhoto
|
||||
{
|
||||
public:
|
||||
OdmOrthoPhoto();
|
||||
~OdmOrthoPhoto();
|
||||
|
||||
/*!
|
||||
* \brief run Runs the ortho photo functionality using the provided input arguments.
|
||||
* For a list of accepted arguments, pleas see the main page documentation or
|
||||
* call the program with parameter "-help".
|
||||
* \param argc Application argument count.
|
||||
* \param argv Argument values.
|
||||
* \return 0 if successful.
|
||||
*/
|
||||
int run(int argc, char* argv[]);
|
||||
|
||||
private:
|
||||
|
||||
/*!
|
||||
* \brief parseArguments Parses command line arguments.
|
||||
* \param argc Application argument count.
|
||||
* \param argv Argument values.
|
||||
*/
|
||||
void parseArguments(int argc, char* argv[]);
|
||||
|
||||
/*!
|
||||
* \brief printHelp Prints help, explaining usage. Can be shown by calling the program with argument: "-help".
|
||||
*/
|
||||
void printHelp();
|
||||
|
||||
/*!
|
||||
* \brief Create the ortho photo using the current settings.
|
||||
*/
|
||||
void createOrthoPhoto();
|
||||
|
||||
/*!
|
||||
* \brief Creates a transformation which aligns the area for the orthophoto.
|
||||
*/
|
||||
Eigen::Transform<float, 3, Eigen::Affine> getROITransform(float xMin, float yMin) const;
|
||||
|
||||
/*!
|
||||
* \brief Renders a triangle into the ortho photo.
|
||||
* \param texture The texture of the polygon.
|
||||
* \param polygon The polygon as athree indices relative meshCloud.
|
||||
* \param meshCloud Contains all vertices.
|
||||
* \param uvs Contains the texture coordiantes for the active material.
|
||||
* \param faceIndex The index of the face.
|
||||
*/
|
||||
void drawTexturedTriangle(const cv::Mat &texture, const pcl::Vertices &polygon, const pcl::PointCloud<pcl::PointXYZ>::Ptr &meshCloud, const std::vector<Eigen::Vector2f> &uvs, size_t faceIndex);
|
||||
|
||||
/*!
|
||||
* \brief Calcualtes the barycentric coordinates of a point in a triangle.
|
||||
* \param v1 The first triangle vertex.
|
||||
* \param v2 The second triangle vertex.
|
||||
* \param v3 The third triangle vertex.
|
||||
* \param x The x coordinate of the point.
|
||||
* \param y The y coordinate of the point.
|
||||
* \param l1 The first vertex weight.
|
||||
* \param l2 The second vertex weight.
|
||||
* \param l3 The third vertex weight.
|
||||
*/
|
||||
void getBarycentricCoordiantes(pcl::PointXYZ v1, pcl::PointXYZ v2, pcl::PointXYZ v3, float x, float y, float &l1, float &l2, float &l3) const;
|
||||
|
||||
/*!
|
||||
* \brief Check if a given polygon is a sliver polygon.
|
||||
* \param v1 The first vertex of the polygon.
|
||||
* \param v2 The second vertex of the polygon.
|
||||
* \param v3 The third vertex of the polygon.
|
||||
*/
|
||||
bool isSliverPolygon(pcl::PointXYZ v1, pcl::PointXYZ v2, pcl::PointXYZ v3) const;
|
||||
|
||||
/*!
|
||||
* \brief Check if the model is suitable for ortho photo generation.
|
||||
* \param mesh The
|
||||
*/
|
||||
bool isModelOk(const pcl::TextureMesh &mesh);
|
||||
|
||||
Logger log_; /**< Logging object. */
|
||||
|
||||
std::string inputFile_; /**< Path to the textured mesh as an obj-file. */
|
||||
std::string outputFile_; /**< Path to the destination file. */
|
||||
std::string logFile_; /**< Path to the log file. */
|
||||
|
||||
float resolution_; /**< The number of pixels per meter in the ortho photo. */
|
||||
|
||||
Eigen::Vector2f boundryPoint1_; /**< The first boundry point for the ortho photo. */
|
||||
Eigen::Vector2f boundryPoint2_; /**< The second boundry point for the ortho photo. */
|
||||
Eigen::Vector2f boundryPoint3_; /**< The third boundry point for the ortho photo. */
|
||||
Eigen::Vector2f boundryPoint4_; /**< The fourth boundry point for the ortho photo. */
|
||||
|
||||
cv::Mat photo_; /**< The ortho photo as an OpenCV matrix, CV_8UC3. */
|
||||
cv::Mat depth_; /**< The depth of the ortho photo as an OpenCV matrix, CV_32F. */
|
||||
|
||||
bool multiMaterial_; /**< True if the mesh has multiple materials. **/
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The OdmOrthoPhoto class
|
||||
*/
|
||||
class OdmOrthoPhotoException : public std::exception
|
||||
{
|
||||
|
||||
public:
|
||||
OdmOrthoPhotoException() : message("Error in OdmOrthoPhoto") {}
|
||||
OdmOrthoPhotoException(std::string msgInit) : message("Error in OdmOrthoPhoto:\n" + msgInit) {}
|
||||
~OdmOrthoPhotoException() throw() {}
|
||||
virtual const char* what() const throw() {return message.c_str(); }
|
||||
|
||||
private:
|
||||
std::string message; /**< The error message **/
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
// Ortho photo generator.
|
||||
#include "OdmOrthoPhoto.hpp"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
OdmOrthoPhoto orthoPhotoGenerator;
|
||||
return orthoPhotoGenerator.run(argc, argv);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
project(odm_texturing)
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
# Set pcl dir to the input spedified with option -DPCL_DIR="path"
|
||||
set(PCL_DIR "PCL_DIR-NOTFOUND" CACHE "PCL_DIR" "Path to the pcl installation directory")
|
||||
|
||||
# Add compiler options.
|
||||
add_definitions(-Wall -Wextra)
|
||||
|
||||
# Find pcl at the location specified by PCL_DIR
|
||||
find_package(PCL 1.7 HINTS "${PCL_DIR}/share/pcl-1.7" REQUIRED)
|
||||
|
||||
# Find OpenCV at the default location
|
||||
find_package(OpenCV REQUIRED)
|
||||
|
||||
# Only link with required opencv modules.
|
||||
set(OpenCV_LIBS opencv_core opencv_imgproc opencv_highgui)
|
||||
|
||||
# Add the PCL, Eigen and OpenCV include dirs.
|
||||
# Necessary since the PCL_INCLUDE_DIR variable set by find_package is broken.)
|
||||
include_directories(${PCL_ROOT}/include/pcl-${PCL_VERSION_MAJOR}.${PCL_VERSION_MINOR})
|
||||
include_directories(${EIGEN_ROOT})
|
||||
include_directories(${OpenCV_INCLUDE_DIRS})
|
||||
|
||||
#library_directories(${OpenCV_LIBRARY_DIRS})
|
||||
|
||||
# Add source directory
|
||||
aux_source_directory("./src" SRC_LIST)
|
||||
|
||||
# Add exectuteable
|
||||
add_executable(${PROJECT_NAME} ${SRC_LIST})
|
||||
target_link_libraries(odm_texturing ${PCL_COMMON_LIBRARIES} ${PCL_IO_LIBRARIES} ${PCL_SURFACE_LIBRARIES} ${OpenCV_LIBS})
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#include "Logger.hpp"
|
||||
|
||||
|
||||
Logger::Logger(bool isPrintingInCout) : isPrintingInCout_(isPrintingInCout)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Logger::~Logger()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Logger::printToFile(std::string filePath)
|
||||
{
|
||||
std::ofstream file(filePath.c_str(), std::ios::binary);
|
||||
file << logStream_.str();
|
||||
file.close();
|
||||
}
|
||||
|
||||
bool Logger::isPrintingInCout() const
|
||||
{
|
||||
return isPrintingInCout_;
|
||||
}
|
||||
|
||||
void Logger::setIsPrintingInCout(bool isPrintingInCout)
|
||||
{
|
||||
isPrintingInCout_ = isPrintingInCout;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
// STL
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
/*!
|
||||
* \brief The Logger class is used to store program messages in a log file.
|
||||
* \details By using the << operator while printInCout is set, the class writes both to
|
||||
* cout and to file, if the flag is not set, output is written to file only.
|
||||
*/
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* \brief Logger Contains functionality for printing and displaying log information.
|
||||
* \param printInCout Flag toggling if operator << also writes to cout.
|
||||
*/
|
||||
Logger(bool isPrintingInCout = true);
|
||||
|
||||
/*!
|
||||
* \brief Destructor.
|
||||
*/
|
||||
~Logger();
|
||||
|
||||
/*!
|
||||
* \brief print Prints the contents of the log to file.
|
||||
* \param filePath Path specifying where to write the log.
|
||||
*/
|
||||
void printToFile(std::string filePath);
|
||||
|
||||
/*!
|
||||
* \brief isPrintingInCout Check if console printing flag is set.
|
||||
* \return Console printing flag.
|
||||
*/
|
||||
bool isPrintingInCout() const;
|
||||
|
||||
/*!
|
||||
* \brief setIsPrintingInCout Set console printing flag.
|
||||
* \param isPrintingInCout Value, if true, messages added to the log are also printed in cout.
|
||||
*/
|
||||
void setIsPrintingInCout(bool isPrintingInCout);
|
||||
|
||||
/*!
|
||||
* Operator for printing messages to log and in the standard output stream if desired.
|
||||
*/
|
||||
template<class T>
|
||||
friend Logger& operator<< (Logger &log, T t)
|
||||
{
|
||||
// If console printing is enabled.
|
||||
if (log.isPrintingInCout_)
|
||||
{
|
||||
std::cout << t;
|
||||
std::cout.flush();
|
||||
}
|
||||
// Write to log.
|
||||
log.logStream_ << t;
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
private:
|
||||
bool isPrintingInCout_; /*!< If flag is set, log is printed in cout and written to the log. */
|
||||
|
||||
std::stringstream logStream_; /*!< Stream for storing the log. */
|
||||
};
|
Plik diff jest za duży
Load Diff
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,211 @@
|
|||
#pragma once
|
||||
|
||||
// STL
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
// PCL
|
||||
#include <pcl/point_types.h>
|
||||
#include <pcl/search/kdtree.h>
|
||||
#include <pcl/surface/texture_mapping.h>
|
||||
|
||||
// OpenCV
|
||||
#include <opencv2/imgproc/imgproc.hpp>
|
||||
#include <opencv2/highgui/highgui.hpp>
|
||||
|
||||
// Modified PCL functions
|
||||
#include "modifiedPclFunctions.hpp"
|
||||
|
||||
// Logging
|
||||
#include "Logger.hpp"
|
||||
|
||||
/*!
|
||||
* \brief The Coords struct Coordinate class used in recursiveFindCoordinates for OdmTexturing::sortPatches().
|
||||
*/
|
||||
struct Coords
|
||||
{
|
||||
// Coordinates for row and column
|
||||
float r_, c_;
|
||||
|
||||
// If coordinates have been placed
|
||||
bool success_;
|
||||
|
||||
Coords()
|
||||
{
|
||||
r_ = 0.0;
|
||||
c_ = 0.0;
|
||||
success_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The Patch struct Struct to hold all faces connected and with the same optimal camera.
|
||||
*/
|
||||
struct Patch
|
||||
{
|
||||
std::vector<size_t> faces_;
|
||||
float minu_, minv_, maxu_, maxv_;
|
||||
Coords c_;
|
||||
bool placed_;
|
||||
int materialIndex_;
|
||||
int optimalCameraIndex_;
|
||||
|
||||
Patch()
|
||||
{
|
||||
placed_ = false;
|
||||
faces_ = std::vector<size_t>(0);
|
||||
minu_ = std::numeric_limits<double>::infinity();
|
||||
minv_ = std::numeric_limits<double>::infinity();
|
||||
maxu_ = 0.0;
|
||||
maxv_ = 0.0;
|
||||
optimalCameraIndex_ = -1;
|
||||
materialIndex_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The Node struct Node class for acceleration structure in OdmTexturing::sortPatches().
|
||||
*/
|
||||
struct Node
|
||||
{
|
||||
float r_, c_, width_, height_;
|
||||
bool used_;
|
||||
Node* rgt_;
|
||||
Node* lft_;
|
||||
|
||||
Node()
|
||||
{
|
||||
r_ = 0.0;
|
||||
c_ = 0.0;
|
||||
width_ = 1.0;
|
||||
height_ = 1.0;
|
||||
used_ = false;
|
||||
rgt_ = NULL;
|
||||
lft_ = NULL;
|
||||
}
|
||||
|
||||
Node(const Node &n)
|
||||
{
|
||||
r_ = n.r_;
|
||||
c_ = n.c_;
|
||||
used_ = n.used_;
|
||||
width_ = n.width_;
|
||||
height_ = n.height_;
|
||||
rgt_ = n.rgt_;
|
||||
lft_ = n.lft_;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The OdmTexturing class is used to create textures to a welded ply-mesh using the camera
|
||||
* positions from pmvs as input. The result is stored in an obj-file with corresponding
|
||||
* mtl-file and the textures saved as jpg.
|
||||
*/
|
||||
class OdmTexturing
|
||||
{
|
||||
public:
|
||||
OdmTexturing();
|
||||
~OdmTexturing();
|
||||
|
||||
|
||||
/*!
|
||||
* \brief run Runs the texturing functionality using the provided input arguments.
|
||||
* For a list of the accepted arguments, please see the main page documentation or
|
||||
* call the program with parameter "-help".
|
||||
* \param argc Application argument count.
|
||||
* \param argv Argument values.
|
||||
* \return 0 if successful.
|
||||
*/
|
||||
int run(int argc, char **argv);
|
||||
|
||||
private:
|
||||
|
||||
/*!
|
||||
* \brief parseArguments Parses command line arguments.
|
||||
* \param argc Application argument count.
|
||||
* \param argv Argument values.
|
||||
*/
|
||||
void parseArguments(int argc, char** argv);
|
||||
|
||||
/*!
|
||||
* \brief loadMesh Loads a PLY-file containing vertices and faces.
|
||||
*/
|
||||
void loadMesh();
|
||||
|
||||
/*!
|
||||
* \brief loadCameras Loads cameras from a bundle.out file with corresponding image list file.
|
||||
*/
|
||||
void loadCameras();
|
||||
|
||||
/*!
|
||||
* \brief triangleToImageAssignment Assigns optimal camera to faces for the faces that are visible.
|
||||
*/
|
||||
void triangleToImageAssignment();
|
||||
|
||||
/*!
|
||||
* \brief calculatePatches Arrange faces into patches as a prestep to arranging UV-mapping.
|
||||
*/
|
||||
void calculatePatches();
|
||||
|
||||
/*!
|
||||
* \brief recursiveFindCoords Recursive function used in sortPatches() to find free area to place patch.
|
||||
* \param n The container in which to check for free space in.
|
||||
* \param w The width of the box to place.
|
||||
* \param h The height of the box to place.
|
||||
* \return The coordinates where the patch has been placed.
|
||||
*/
|
||||
Coords recursiveFindCoords(Node &n, float w, float h);
|
||||
|
||||
/*!
|
||||
* \brief sortPatches Sorts patches into UV-containers to be used in createTextures() using a rectangle packer approach.
|
||||
*/
|
||||
void sortPatches();
|
||||
|
||||
/*!
|
||||
* \brief createTextures Creates textures to the mesh.
|
||||
*/
|
||||
void createTextures();
|
||||
|
||||
/*!
|
||||
* \brief writeObjFile Writes the textured mesh to file on the OBJ format.
|
||||
*/
|
||||
void writeObjFile();
|
||||
|
||||
/*!
|
||||
* \brief printHelp Prints help, explaining usage. Can be shown by calling the program with arguments: "-help".
|
||||
*/
|
||||
void printHelp();
|
||||
|
||||
Logger log_; /**< Logging object. */
|
||||
std::string logFilePath_; /**< Path to store the log file. */
|
||||
|
||||
std::string bundlePath_; /**< Path to the bundle.out file. */
|
||||
std::string imagesPath_; /**< Path to the folder with all images in the image list. */
|
||||
std::string imagesListPath_; /**< Path to the image list. */
|
||||
std::string inputModelPath_; /**< Path to the ply-file containing the mesh to be textured. */
|
||||
std::string outputFolder_; /**< Path to the folder to store the output mesh and textures. */
|
||||
|
||||
double bundleResizedTo_; /**< The size used in the previous steps to calculate the camera focal_length. */
|
||||
double textureWithSize_; /**< The desired size of the images to texture with. */
|
||||
double textureResolution_; /**< The resolution of each texture. */
|
||||
double padding_; /**< A padding used to handle edge cases. */
|
||||
int nrTextures_; /**< The number of textures created. */
|
||||
|
||||
pcl::TextureMesh::Ptr mesh_; /**< PCL Texture Mesh */
|
||||
std::vector<Patch> patches_; /**< The vector containing all patches */
|
||||
pcl::texture_mapping::CameraVector cameras_; /**< The vector containing all cameras. */
|
||||
std::vector<int> tTIA_; /**< The vector containing the optimal cameras for all faces. */
|
||||
};
|
||||
|
||||
class OdmTexturingException : public std::exception
|
||||
{
|
||||
|
||||
public:
|
||||
OdmTexturingException() : message("Error in OdmTexturing") {}
|
||||
OdmTexturingException(std::string msgInit) : message("Error in OdmTexturing:\n" + msgInit) {}
|
||||
~OdmTexturingException() throw() {}
|
||||
virtual const char* what() const throw() {return message.c_str(); }
|
||||
|
||||
private:
|
||||
std::string message; /**< The error message. */
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
// Include texturing source code
|
||||
#include "OdmTexturing.hpp"
|
||||
|
||||
/*!
|
||||
* \mainpage main OpenDroneMap Texturing Module
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
int main (int argc, char** argv)
|
||||
{
|
||||
OdmTexturing textureCreator;
|
||||
return textureCreator.run(argc, argv);
|
||||
}
|
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
* Software License Agreement (BSD License)
|
||||
*
|
||||
* Point Cloud Library (PCL) - www.pointclouds.org
|
||||
* Copyright (c) 2012-, Open Perception, Inc.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
* * Neither the name of the copyright holder(s) nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "modifiedPclFunctions.hpp"
|
||||
|
||||
int saveOBJFile(const std::string &file_name, const pcl::TextureMesh &tex_mesh, unsigned precision)
|
||||
{
|
||||
if (tex_mesh.cloud.data.empty ())
|
||||
{
|
||||
PCL_ERROR ("[pcl::io::saveOBJFile] Input point cloud has no data!\n");
|
||||
return (-1);
|
||||
}
|
||||
|
||||
// Open file
|
||||
std::ofstream fs;
|
||||
fs.precision (precision);
|
||||
fs.open (file_name.c_str ());
|
||||
|
||||
// Define material file
|
||||
std::string mtl_file_name = file_name.substr (0, file_name.find_last_of (".")) + ".mtl";
|
||||
// Strip path for "mtllib" command
|
||||
std::string mtl_file_name_nopath = mtl_file_name;
|
||||
//std::cout << mtl_file_name_nopath << std::endl;
|
||||
mtl_file_name_nopath.erase (0, mtl_file_name.find_last_of ('/') + 1);
|
||||
|
||||
/* Write 3D information */
|
||||
// number of points
|
||||
int nr_points = tex_mesh.cloud.width * tex_mesh.cloud.height;
|
||||
int point_size = tex_mesh.cloud.data.size () / nr_points;
|
||||
|
||||
// mesh size
|
||||
int nr_meshes = tex_mesh.tex_polygons.size ();
|
||||
// number of faces for header
|
||||
int nr_faces = 0;
|
||||
for (int m = 0; m < nr_meshes; ++m)
|
||||
nr_faces += tex_mesh.tex_polygons[m].size ();
|
||||
|
||||
// Write the header information
|
||||
fs << "####" << std::endl;
|
||||
fs << "# OBJ dataFile simple version. File name: " << file_name << std::endl;
|
||||
fs << "# Vertices: " << nr_points << std::endl;
|
||||
fs << "# Faces: " <<nr_faces << std::endl;
|
||||
fs << "# Material information:" << std::endl;
|
||||
fs << "mtllib " << mtl_file_name_nopath << std::endl;
|
||||
fs << "####" << std::endl;
|
||||
|
||||
// Write vertex coordinates
|
||||
fs << "# Vertices" << std::endl;
|
||||
for (int i = 0; i < nr_points; ++i)
|
||||
{
|
||||
int xyz = 0;
|
||||
// "v" just be written one
|
||||
bool v_written = false;
|
||||
for (size_t d = 0; d < tex_mesh.cloud.fields.size (); ++d)
|
||||
{
|
||||
int count = tex_mesh.cloud.fields[d].count;
|
||||
if (count == 0)
|
||||
count = 1; // we simply cannot tolerate 0 counts (coming from older converter code)
|
||||
int c = 0;
|
||||
// adding vertex
|
||||
if ((tex_mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) /*sensor_msgs::PointField::FLOAT32)*/ && (
|
||||
tex_mesh.cloud.fields[d].name == "x" ||
|
||||
tex_mesh.cloud.fields[d].name == "y" ||
|
||||
tex_mesh.cloud.fields[d].name == "z"))
|
||||
{
|
||||
if (!v_written)
|
||||
{
|
||||
// write vertices beginning with v
|
||||
fs << "v ";
|
||||
v_written = true;
|
||||
}
|
||||
float value;
|
||||
memcpy (&value, &tex_mesh.cloud.data[i * point_size + tex_mesh.cloud.fields[d].offset + c * sizeof (float)], sizeof (float));
|
||||
fs << value;
|
||||
if (++xyz == 3)
|
||||
break;
|
||||
fs << " ";
|
||||
}
|
||||
}
|
||||
if (xyz != 3)
|
||||
{
|
||||
PCL_ERROR ("[pcl::io::saveOBJFile] Input point cloud has no XYZ data!\n");
|
||||
return (-2);
|
||||
}
|
||||
fs << std::endl;
|
||||
}
|
||||
fs << "# "<< nr_points <<" vertices" << std::endl;
|
||||
|
||||
// // Write vertex normals
|
||||
// for (int i = 0; i < nr_points; ++i)
|
||||
// {
|
||||
// int xyz = 0;
|
||||
// // "vn" just be written one
|
||||
// bool v_written = false;
|
||||
// for (size_t d = 0; d < tex_mesh.cloud.fields.size (); ++d)
|
||||
// {
|
||||
// int count = tex_mesh.cloud.fields[d].count;
|
||||
// if (count == 0)
|
||||
// count = 1; // we simply cannot tolerate 0 counts (coming from older converter code)
|
||||
// int c = 0;
|
||||
// // adding vertex
|
||||
// if ((tex_mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) && (
|
||||
// tex_mesh.cloud.fields[d].name == "normal_x" ||
|
||||
// tex_mesh.cloud.fields[d].name == "normal_y" ||
|
||||
// tex_mesh.cloud.fields[d].name == "normal_z"))
|
||||
// {
|
||||
// if (!v_written)
|
||||
// {
|
||||
// // write vertices beginning with vn
|
||||
// fs << "vn ";
|
||||
// v_written = true;
|
||||
// }
|
||||
// float value;
|
||||
// memcpy (&value, &tex_mesh.cloud.data[i * point_size + tex_mesh.cloud.fields[d].offset + c * sizeof (float)], sizeof (float));
|
||||
// fs << value;
|
||||
// if (++xyz == 3)
|
||||
// break;
|
||||
// fs << " ";
|
||||
// }
|
||||
// }
|
||||
// if (xyz != 3)
|
||||
// {
|
||||
// //PCL_ERROR ("[pcl::io::saveOBJFile] Input point cloud has no normals!\n");
|
||||
// //return (-2);
|
||||
// }
|
||||
// fs << std::endl;
|
||||
// }
|
||||
// Write vertex texture with "vt" (adding latter)
|
||||
|
||||
for (int m = 0; m < nr_meshes; ++m)
|
||||
{
|
||||
if(tex_mesh.tex_coordinates.size() == 0)
|
||||
continue;
|
||||
|
||||
//PCL_INFO ("%d vertex textures in submesh %d\n", tex_mesh.tex_coordinates[m].size (), m);
|
||||
fs << "# " << tex_mesh.tex_coordinates[m].size() << " vertex textures in submesh " << m << std::endl;
|
||||
for (size_t i = 0; i < tex_mesh.tex_coordinates[m].size (); ++i)
|
||||
{
|
||||
fs << "vt ";
|
||||
fs << tex_mesh.tex_coordinates[m][i][0] << " " << tex_mesh.tex_coordinates[m][i][1] << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int f_idx = 0;
|
||||
|
||||
// int idx_vt =0;
|
||||
//PCL_INFO ("Writting faces...\n");
|
||||
for (int m = 0; m < nr_meshes; ++m)
|
||||
{
|
||||
if (m > 0)
|
||||
f_idx += tex_mesh.tex_polygons[m-1].size ();
|
||||
|
||||
if(tex_mesh.tex_materials.size() !=0)
|
||||
{
|
||||
fs << "# The material will be used for mesh " << m << std::endl;
|
||||
//TODO pbl here with multi texture and unseen faces
|
||||
fs << "usemtl " << tex_mesh.tex_materials[m].tex_name << std::endl;
|
||||
fs << "# Faces" << std::endl;
|
||||
}
|
||||
for (size_t i = 0; i < tex_mesh.tex_polygons[m].size(); ++i)
|
||||
{
|
||||
// Write faces with "f"
|
||||
fs << "f";
|
||||
size_t j = 0;
|
||||
// There's one UV per vertex per face, i.e., the same vertex can have
|
||||
// different UV depending on the face.
|
||||
for (j = 0; j < tex_mesh.tex_polygons[m][i].vertices.size (); ++j)
|
||||
{
|
||||
unsigned int idx = tex_mesh.tex_polygons[m][i].vertices[j] + 1;
|
||||
fs << " " << idx
|
||||
<< "/" << 3*(i+f_idx) +j+1;
|
||||
//<< "/" << idx; // vertex index in obj file format starting with 1
|
||||
}
|
||||
fs << std::endl;
|
||||
}
|
||||
//PCL_INFO ("%d faces in mesh %d \n", tex_mesh.tex_polygons[m].size () , m);
|
||||
fs << "# "<< tex_mesh.tex_polygons[m].size() << " faces in mesh " << m << std::endl;
|
||||
}
|
||||
fs << "# End of File";
|
||||
|
||||
// Close obj file
|
||||
//PCL_INFO ("Closing obj file\n");
|
||||
fs.close ();
|
||||
|
||||
/* Write material defination for OBJ file*/
|
||||
// Open file
|
||||
//PCL_INFO ("Writing material files\n");
|
||||
//dont do it if no material to write
|
||||
if(tex_mesh.tex_materials.size() ==0)
|
||||
return (0);
|
||||
|
||||
std::ofstream m_fs;
|
||||
m_fs.precision (precision);
|
||||
m_fs.open (mtl_file_name.c_str ());
|
||||
//std::cout << "MTL file is located at_ " << mtl_file_name << std::endl;
|
||||
// default
|
||||
m_fs << "#" << std::endl;
|
||||
m_fs << "# Wavefront material file" << std::endl;
|
||||
m_fs << "#" << std::endl;
|
||||
for(int m = 0; m < nr_meshes; ++m)
|
||||
{
|
||||
m_fs << "newmtl " << tex_mesh.tex_materials[m].tex_name << std::endl;
|
||||
m_fs << "Ka "<< tex_mesh.tex_materials[m].tex_Ka.r << " " << tex_mesh.tex_materials[m].tex_Ka.g << " " << tex_mesh.tex_materials[m].tex_Ka.b << std::endl; // defines the ambient color of the material to be (r,g,b).
|
||||
m_fs << "Kd "<< tex_mesh.tex_materials[m].tex_Kd.r << " " << tex_mesh.tex_materials[m].tex_Kd.g << " " << tex_mesh.tex_materials[m].tex_Kd.b << std::endl; // defines the diffuse color of the material to be (r,g,b).
|
||||
m_fs << "Ks "<< tex_mesh.tex_materials[m].tex_Ks.r << " " << tex_mesh.tex_materials[m].tex_Ks.g << " " << tex_mesh.tex_materials[m].tex_Ks.b << std::endl; // defines the specular color of the material to be (r,g,b). This color shows up in highlights.
|
||||
m_fs << "d " << tex_mesh.tex_materials[m].tex_d << std::endl; // defines the transparency of the material to be alpha.
|
||||
m_fs << "Ns "<< tex_mesh.tex_materials[m].tex_Ns << std::endl; // defines the shininess of the material to be s.
|
||||
m_fs << "illum "<< tex_mesh.tex_materials[m].tex_illum << std::endl; // denotes the illumination model used by the material.
|
||||
// illum = 1 indicates a flat material with no specular highlights, so the value of Ks is not used.
|
||||
// illum = 2 denotes the presence of specular highlights, and so a specification for Ks is required.
|
||||
m_fs << "map_Kd " << tex_mesh.tex_materials[m].tex_file << std::endl;
|
||||
m_fs << "###" << std::endl;
|
||||
}
|
||||
m_fs.close ();
|
||||
return (0);
|
||||
}
|
||||
|
||||
bool getPixelCoordinates(const pcl::PointXYZ &pt, const pcl::TextureMapping<pcl::PointXYZ>::Camera &cam, pcl::PointXY &UV_coordinates)
|
||||
{
|
||||
if (pt.z > 0)
|
||||
{
|
||||
// compute image center and dimension
|
||||
double sizeX = cam.width;
|
||||
double sizeY = cam.height;
|
||||
double cx, cy;
|
||||
if (cam.center_w > 0)
|
||||
cx = cam.center_w;
|
||||
else
|
||||
cx = sizeX / 2.0;
|
||||
if (cam.center_h > 0)
|
||||
cy = cam.center_h;
|
||||
else
|
||||
cy = sizeY / 2.0;
|
||||
|
||||
double focal_x, focal_y;
|
||||
if (cam.focal_length_w > 0)
|
||||
focal_x = cam.focal_length_w;
|
||||
else
|
||||
focal_x = cam.focal_length;
|
||||
if (cam.focal_length_h > 0)
|
||||
focal_y = cam.focal_length_h;
|
||||
else
|
||||
focal_y = cam.focal_length;
|
||||
|
||||
// project point on camera's image plane
|
||||
UV_coordinates.x = static_cast<float> ((focal_x * (pt.x / pt.z) + cx)); //horizontal
|
||||
UV_coordinates.y = static_cast<float> ((focal_y * (pt.y / pt.z) + cy)); //vertical
|
||||
|
||||
// point is visible!
|
||||
if (UV_coordinates.x >= 15.0 && UV_coordinates.x <= (sizeX - 15.0) && UV_coordinates.y >= 15.0 && UV_coordinates.y <= (sizeY - 15.0))
|
||||
{
|
||||
return (true); // point was visible by the camera
|
||||
}
|
||||
}
|
||||
|
||||
// point is NOT visible by the camera
|
||||
UV_coordinates.x = -1.0f;
|
||||
UV_coordinates.y = -1.0f;
|
||||
return (false); // point was not visible by the camera
|
||||
}
|
||||
|
||||
bool isFaceProjected (const pcl::TextureMapping<pcl::PointXYZ>::Camera &camera, const pcl::PointXYZ &p1, const pcl::PointXYZ &p2, const pcl::PointXYZ &p3, pcl::PointXY &proj1, pcl::PointXY &proj2, pcl::PointXY &proj3)
|
||||
{
|
||||
return (getPixelCoordinates(p1, camera, proj1) && getPixelCoordinates(p2, camera, proj2) && getPixelCoordinates(p3, camera, proj3));
|
||||
}
|
||||
|
||||
void getTriangleCircumscribedCircleCentroid( const pcl::PointXY &p1, const pcl::PointXY &p2, const pcl::PointXY &p3, pcl::PointXY &circumcenter, double &radius)
|
||||
{
|
||||
// compute centroid's coordinates (translate back to original coordinates)
|
||||
circumcenter.x = static_cast<float> (p1.x + p2.x + p3.x ) / 3;
|
||||
circumcenter.y = static_cast<float> (p1.y + p2.y + p3.y ) / 3;
|
||||
double r1 = (circumcenter.x - p1.x) * (circumcenter.x - p1.x) + (circumcenter.y - p1.y) * (circumcenter.y - p1.y) ;
|
||||
double r2 = (circumcenter.x - p2.x) * (circumcenter.x - p2.x) + (circumcenter.y - p2.y) * (circumcenter.y - p2.y) ;
|
||||
double r3 = (circumcenter.x - p3.x) * (circumcenter.x - p3.x) + (circumcenter.y - p3.y) * (circumcenter.y - p3.y) ;
|
||||
|
||||
// radius
|
||||
radius = std::sqrt( std::max( r1, std::max( r2, r3) )) ;
|
||||
}
|
||||
|
||||
bool checkPointInsideTriangle(const pcl::PointXY &p1, const pcl::PointXY &p2, const pcl::PointXY &p3, const pcl::PointXY &pt)
|
||||
{
|
||||
// Compute vectors
|
||||
Eigen::Vector2d v0, v1, v2;
|
||||
v0(0) = p3.x - p1.x; v0(1) = p3.y - p1.y; // v0= C - A
|
||||
v1(0) = p2.x - p1.x; v1(1) = p2.y - p1.y; // v1= B - A
|
||||
v2(0) = pt.x - p1.x; v2(1) = pt.y - p1.y; // v2= P - A
|
||||
|
||||
// Compute dot products
|
||||
double dot00 = v0.dot(v0); // dot00 = dot(v0, v0)
|
||||
double dot01 = v0.dot(v1); // dot01 = dot(v0, v1)
|
||||
double dot02 = v0.dot(v2); // dot02 = dot(v0, v2)
|
||||
double dot11 = v1.dot(v1); // dot11 = dot(v1, v1)
|
||||
double dot12 = v1.dot(v2); // dot12 = dot(v1, v2)
|
||||
|
||||
// Compute barycentric coordinates
|
||||
double invDenom = 1.0 / (dot00*dot11 - dot01*dot01);
|
||||
double u = (dot11*dot02 - dot01*dot12) * invDenom;
|
||||
double v = (dot00*dot12 - dot01*dot02) * invDenom;
|
||||
|
||||
// Check if point is in triangle
|
||||
return ((u >= 0) && (v >= 0) && (u + v < 1));
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
// STL
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
// PCL
|
||||
#include <pcl/point_types.h>
|
||||
#include <pcl/surface/texture_mapping.h>
|
||||
#include <pcl/io/ply_io.h>
|
||||
|
||||
int saveOBJFile(const std::string &file_name, const pcl::TextureMesh &tex_mesh, unsigned precision);
|
||||
|
||||
bool getPixelCoordinates(const pcl::PointXYZ &pt, const pcl::TextureMapping<pcl::PointXYZ>::Camera &cam, pcl::PointXY &UV_coordinates);
|
||||
|
||||
bool isFaceProjected (const pcl::TextureMapping<pcl::PointXYZ>::Camera &camera, const pcl::PointXYZ &p1, const pcl::PointXYZ &p2, const pcl::PointXYZ &p3, pcl::PointXY &proj1, pcl::PointXY &proj2, pcl::PointXY &proj3);
|
||||
|
||||
void getTriangleCircumscribedCircleCentroid(const pcl::PointXY &p1, const pcl::PointXY &p2, const pcl::PointXY &p3, pcl::PointXY &circumcenter, double &radius);
|
||||
|
||||
bool checkPointInsideTriangle(const pcl::PointXY &p1, const pcl::PointXY &p2, const pcl::PointXY &p3, const pcl::PointXY &pt);
|
168
run.pl
168
run.pl
|
@ -67,14 +67,14 @@ sub parseArgs {
|
|||
## defaults
|
||||
$args{"--match-size"} = "200";
|
||||
|
||||
$args{"--resize-to"} = "3000";
|
||||
$args{"--resize-to"} = "1200";
|
||||
|
||||
$args{"--start-with"} = "resize";
|
||||
$args{"--end-with"} = "pmvs";
|
||||
$args{"--end-with"} = "odm_texturing";
|
||||
|
||||
$args{"--cmvs-maxImages"} = 100;
|
||||
|
||||
$args{"--matcher-ratio"} = 0.6;
|
||||
$args{"--matcher-ratio"} = 0.6;
|
||||
$args{"--matcher-threshold"} = 2.0;
|
||||
|
||||
$args{"--pmvs-level"} = 1;
|
||||
|
@ -82,6 +82,14 @@ sub parseArgs {
|
|||
$args{"--pmvs-threshold"} = 0.7;
|
||||
$args{"--pmvs-wsize"} = 7;
|
||||
$args{"--pmvs-minImageNum"} = 3;
|
||||
|
||||
$args{"--odm_meshing-maxVertexCount"} = 100000;
|
||||
$args{"--odm_meshing-octreeDepth"} = 9;
|
||||
$args{"--odm_meshing-samplesPerNode"} = 1;
|
||||
$args{"--odm_meshing-solverDivide"} = 9;
|
||||
|
||||
$args{"--odm_texturing-textureResolution"} = 4096;
|
||||
$args{"--odm_texturing-textureWithSize"} = 3600;
|
||||
|
||||
for($i = 0; $i <= $#ARGV; $i++) {
|
||||
if($ARGV[$i] =~ /^--[^a-z\-]*/){
|
||||
|
@ -188,13 +196,55 @@ sub parseArgs {
|
|||
die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
|
||||
}
|
||||
}
|
||||
if($ARGV[$i] eq "--odm_meshing-maxVertexCount"){
|
||||
if($ARGV[$i+1] =~ /^[0-9]*$/){
|
||||
$args{$ARGV[$i]} = $ARGV[$i+1];
|
||||
} else {
|
||||
die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
|
||||
}
|
||||
}
|
||||
if($ARGV[$i] eq "--odm_meshing-octreeDepth"){
|
||||
if($ARGV[$i+1] =~ /^[0-9]*$/){
|
||||
$args{$ARGV[$i]} = $ARGV[$i+1];
|
||||
} else {
|
||||
die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
|
||||
}
|
||||
}
|
||||
if($ARGV[$i] eq "--odm_meshing-samplesPerNode"){
|
||||
if($ARGV[$i+1] =~ /^[0-9]*\.?[0-9]*$/){
|
||||
$args{$ARGV[$i]} = $ARGV[$i+1];
|
||||
} else {
|
||||
die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
|
||||
}
|
||||
}
|
||||
if($ARGV[$i] eq "--odm_meshing-solverDivide"){
|
||||
if($ARGV[$i+1] =~ /^[0-9]*$/){
|
||||
$args{$ARGV[$i]} = $ARGV[$i+1];
|
||||
} else {
|
||||
die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
|
||||
}
|
||||
}
|
||||
if($ARGV[$i] eq "--odm_texturing-textureResolution"){
|
||||
if($ARGV[$i+1] =~ /^[0-9]*$/){
|
||||
$args{$ARGV[$i]} = $ARGV[$i+1];
|
||||
} else {
|
||||
die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
|
||||
}
|
||||
}
|
||||
if($ARGV[$i] eq "--odm_texturing-textureWithSize"){
|
||||
if($ARGV[$i+1] =~ /^[0-9]*$/){
|
||||
$args{$ARGV[$i]} = $ARGV[$i+1];
|
||||
} else {
|
||||
die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($args{"--help"}){
|
||||
print "\nusage: run.pl [options]";
|
||||
print "\nit should be run from the folder containing the images to be reconstructed";
|
||||
print "\nusgae: run.pl [options]";
|
||||
print "\nit should be run from the folder contining the images to which should reconstructed";
|
||||
print "\n";
|
||||
print "\noptions:";
|
||||
print "\n --help: ";
|
||||
|
@ -202,7 +252,7 @@ sub parseArgs {
|
|||
print "\n ";
|
||||
|
||||
print "\n --resize-to: <positive integer|\"orig\">";
|
||||
print "\n default: 3000";
|
||||
print "\n default: 1200";
|
||||
print "\n will resize the images so that the maximum width/height of the images are smaller or equal to the specified number";
|
||||
print "\n if \"--resize-to orig\" is used it will use the images without resizing";
|
||||
print "\n ";
|
||||
|
@ -232,7 +282,7 @@ sub parseArgs {
|
|||
|
||||
print "\n --matcher-threshold: <float> (percent)";
|
||||
print "\n default: 2.0";
|
||||
print "\n ignore matched keypoints if the two images share less than <float> percent of keypoints";
|
||||
print "\n ignore matched keypoints if the two images share less then <float> percent of keypoints";
|
||||
print "\n ";
|
||||
|
||||
print "\n --matcher-ratio: <float";
|
||||
|
@ -264,6 +314,18 @@ sub parseArgs {
|
|||
print "\n see http://grail.cs.washington.edu/software/pmvs/documentation.html for an explanation of these parameters";
|
||||
print "\n";
|
||||
|
||||
print "\n --odm_meshing-maxVertexCount: <positive integer>";
|
||||
print "\n default: 100000";
|
||||
print "\n The maximum vertex count of the output mesh.";
|
||||
|
||||
print "\n --odm_meshing-octreeDepth: <positive integer>";
|
||||
print "\n default: 9";
|
||||
print "\n Octree depth used in the mesh reconstruction, increase to get more vertices, recommended values are 8-12";
|
||||
|
||||
print "\n --odm_meshing-samplesPerNode: <float: 1.0 <= x>";
|
||||
print "\n default: 1";
|
||||
print "\n Number of points per octree node, recommended value: 1.0";
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
|
@ -452,14 +514,14 @@ sub getKeypoints {
|
|||
|
||||
if($fileObject->{isOk}){
|
||||
if($args{"--lowe-sift"}){
|
||||
$vlsiftJobs .= "echo -n \" $c/$objectStats{good} - \" && convert -format pgm \"$fileObject->{step_0_resizedImage}\" \"$fileObject->{step_1_pgmFile}\"";
|
||||
$vlsiftJobs .= "echo -n \"$c/$objectStats{good} - \" && convert -format pgm \"$fileObject->{step_0_resizedImage}\" \"$fileObject->{step_1_pgmFile}\"";
|
||||
$vlsiftJobs .= " && \"$BIN_PATH/sift\" < \"$fileObject->{step_1_pgmFile}\" > \"$fileObject->{step_1_keyFile}\"";
|
||||
$vlsiftJobs .= " && gzip -f \"$fileObject->{step_1_keyFile}\"";
|
||||
$vlsiftJobs .= " && rm -f \"$fileObject->{step_1_pgmFile}\"";
|
||||
$vlsiftJobs .= " && rm -f \"$fileObject->{step_1_keyFile}.sift\"\n";
|
||||
} else {
|
||||
unless (-e "$jobOptions{jobDir}/$fileObject->{base}.key.bin") {
|
||||
$vlsiftJobs .= "echo -n \" $c/$objectStats{good} - \" && convert -format pgm \"$fileObject->{step_0_resizedImage}\" \"$fileObject->{step_1_pgmFile}\"";
|
||||
$vlsiftJobs .= "echo -n \"$c/$objectStats{good} - \" && convert -format pgm \"$fileObject->{step_0_resizedImage}\" \"$fileObject->{step_1_pgmFile}\"";
|
||||
$vlsiftJobs .= " && \"$BIN_PATH/vlsift\" \"$fileObject->{step_1_pgmFile}\" -o \"$fileObject->{step_1_keyFile}.sift\" > /dev/null && perl \"$BIN_PATH/../convert_vlsift_to_lowesift.pl\" \"$jobOptions{jobDir}/$fileObject->{base}\"";
|
||||
$vlsiftJobs .= " && gzip -f \"$fileObject->{step_1_keyFile}\"";
|
||||
$vlsiftJobs .= " && rm -f \"$fileObject->{step_1_pgmFile}\"";
|
||||
|
@ -575,7 +637,7 @@ sub bundler {
|
|||
$bundlerOptions .= "--variable_focal_length\n";
|
||||
$bundlerOptions .= "--use_focal_estimate\n";
|
||||
$bundlerOptions .= "--constrain_focal\n";
|
||||
$bundlerOptions .= "--constrain_focal_weight 0.01\n";
|
||||
$bundlerOptions .= "--constrain_focal_weight 0.0\n";
|
||||
$bundlerOptions .= "--estimate_distortion\n";
|
||||
$bundlerOptions .= "--run_bundle";
|
||||
|
||||
|
@ -636,6 +698,76 @@ sub pmvs {
|
|||
run("\"$BIN_PATH/pmvs2\" pmvs/ option-0000");
|
||||
|
||||
system("cp -Rf \"$jobOptions{jobDir}/pmvs/models\" \"$jobOptions{jobDir}-results\"");
|
||||
|
||||
if($args{"--end-with"} ne "pmvs"){
|
||||
odm_meshing();
|
||||
}
|
||||
}
|
||||
|
||||
sub odm_meshing {
|
||||
print "\n";
|
||||
print "\n - running meshing - ";
|
||||
print "\n";
|
||||
|
||||
chdir($jobOptions{jobDir});
|
||||
mkdir($jobOptions{jobDir}."/odm_meshing");
|
||||
|
||||
|
||||
run("\"$BIN_PATH/odm_meshing\" -inputFile $jobOptions{jobDir}-results/option-0000.ply -outputFile $jobOptions{jobDir}-results/odm_mesh-0000.ply -logFile $jobOptions{jobDir}/odm_meshing/odm_meshing_log.txt -maxVertexCount $args{'--odm_meshing-maxVertexCount'} -octreeDepth $args{'--odm_meshing-octreeDepth'} -samplesPerNode $args{'--odm_meshing-samplesPerNode'}" );
|
||||
|
||||
if($args{"--end-with"} ne "odm_meshing"){
|
||||
odm_texturing();
|
||||
}
|
||||
}
|
||||
|
||||
sub odm_texturing {
|
||||
print "\n";
|
||||
print "\n - running texturing - ";
|
||||
print "\n";
|
||||
|
||||
chdir($jobOptions{jobDir});
|
||||
mkdir($jobOptions{jobDir}."/odm_texturing");
|
||||
mkdir("$jobOptions{jobDir}-results/odm_texturing");
|
||||
|
||||
|
||||
run("\"$BIN_PATH/odm_texturing\" -bundleFile $jobOptions{jobDir}/pmvs/bundle.rd.out -imagesPath $jobOptions{srcDir}/ -imagesListPath $jobOptions{jobDir}/pmvs/list.rd.txt -inputModelPath $jobOptions{jobDir}-results/odm_mesh-0000.ply -outputFolder $jobOptions{jobDir}-results/odm_texturing/ -textureResolution $args{'--odm_texturing-textureResolution'} -bundleResizedTo $jobOptions{resizeTo} -textureWithSize $args{'--odm_texturing-textureWithSize'} -logFile $jobOptions{jobDir}/odm_texturing/odm_texturing_log.txt" );
|
||||
|
||||
if($args{"--end-with"} ne "odm_texturing"){
|
||||
odm_georeferencing();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub odm_georeferencing {
|
||||
print "\n";
|
||||
print "\n - running georeferencing - ";
|
||||
print "\n";
|
||||
|
||||
chdir($jobOptions{jobDir});
|
||||
mkdir($jobOptions{jobDir}."/odm_georeferencing");
|
||||
|
||||
run("\"$BIN_PATH/odm_extract_utm\" -imagesPath $jobOptions{srcDir}/ -imageListFile $jobOptions{jobDir}/pmvs/list.rd.txt -outputCoordFile $jobOptions{jobDir}/odm_georeferencing/coordFile.txt");
|
||||
|
||||
run("\"$BIN_PATH/odm_georef\" -bundleFile $jobOptions{jobDir}/pmvs/bundle.rd.out -coordFile $jobOptions{jobDir}/odm_georeferencing/coordFile.txt -inputFile $jobOptions{jobDir}-results/odm_texturing/odm_textured_model.obj -outputFile $jobOptions{jobDir}-results/odm_texturing/odm_textured_model_geo.obj");
|
||||
|
||||
if($args{"--end-with"} ne "odm_georeferencing"){
|
||||
odm_orthophoto();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
sub odm_orthophoto {
|
||||
print "\n";
|
||||
print "\n - running orthophoto generation - ";
|
||||
print "\n";
|
||||
|
||||
chdir($jobOptions{jobDir});
|
||||
mkdir($jobOptions{jobDir}."/odm_orthophoto");
|
||||
|
||||
|
||||
run("\"$BIN_PATH/odm_orthophoto\" -inputFile $jobOptions{jobDir}-results/odm_texturing/odm_textured_model_geo.obj -outputFile $jobOptions{jobDir}-results/odm_orthphoto.png -resolution 20.0 -boundry -200 -200 -200 200 200 200 200 -200");
|
||||
|
||||
}
|
||||
|
||||
parseArgs();
|
||||
|
@ -644,12 +776,16 @@ prepareObjects();
|
|||
chdir($jobOptions{jobDir});
|
||||
|
||||
switch ($args{"--start-with"}) {
|
||||
case "resize" { resize(); }
|
||||
case "getKeypoints" { getKeypoints(); }
|
||||
case "match" { match(); }
|
||||
case "bundler" { bundler(); }
|
||||
case "cmvs" { cmvs(); }
|
||||
case "pmvs" { pmvs(); }
|
||||
case "resize" { resize(); }
|
||||
case "getKeypoints" { getKeypoints(); }
|
||||
case "match" { match(); }
|
||||
case "bundler" { bundler(); }
|
||||
case "cmvs" { cmvs(); }
|
||||
case "pmvs" { pmvs(); }
|
||||
case "odm_meshing" { odm_meshing(); }
|
||||
case "odm_texturing" { odm_texturing(); }
|
||||
case "odm_georeferencing" { odm_georeferencing(); }
|
||||
case "odm_orthophoto" { odm_orthophoto(); }
|
||||
}
|
||||
|
||||
print "\n";
|
||||
|
|
Ładowanie…
Reference in New Issue