Merge branch 'master' into default-opensfm

pull/444/head
Dakota Benjamin 2017-02-21 09:46:41 -05:00 zatwierdzone przez GitHub
commit 65fab9fccc
10 zmienionych plików z 333 dodań i 217 usunięć

Wyświetl plik

@ -23,7 +23,7 @@ So far, it does Point Clouds, Digital Surface Models, Textured Digital Surface M
## QUICKSTART ## QUICKSTART
Requires Ubuntu 14.04 or later, see https://github.com/OpenDroneMap/odm_vagrant for running on Windows in a VM OpenDroneMap can run natively on Ubuntu 14.04 or later, see [Build and Run Using Docker](#build-and-run-using-docker) for running on Windows / MacOS. A Vagrant VM is also available: https://github.com/OpenDroneMap/odm_vagrant.
*Support for Ubuntu 12.04 is currently BROKEN with the addition of OpenSfM and Ceres-Solver. It is likely to remain broken unless a champion is found to fix it.* *Support for Ubuntu 12.04 is currently BROKEN with the addition of OpenSfM and Ceres-Solver. It is likely to remain broken unless a champion is found to fix it.*
@ -40,7 +40,7 @@ Current version: 0.2 (this software is in beta)
- textured mesh model: odm_texturing/odm_textured_model_geo.obj - textured mesh model: odm_texturing/odm_textured_model_geo.obj
- point cloud (georeferenced): odm_georeferencing/odm_georeferenced_model.ply - point cloud (georeferenced): odm_georeferencing/odm_georeferenced_model.ply
See below for more detailed installation instructions. See [here](https://github.com/OpenDroneMap/OpenDroneMap/tree/ebaaf802a1fb50e335b3807a35d00cba1e106d11#installation) for more detailed installation instructions.
### Installation ### Installation
@ -65,22 +65,13 @@ Note that using `run.sh` sets these temporarily in the shell.
### Run OpenDroneMap ### Run OpenDroneMap
First you need a set of images, taken from a drone or otherwise. First you need a set of images, taken from a drone or otherwise. Example data can be cloned from https://github.com/OpenDroneMap/odm_data
Create a project folder and places your images in an "images" directory:
|-- /path/to/project/
|-- images/
|-- img-1234.jpg
|-- ...
Example data can be cloned from https://github.com/OpenDroneMap/odm_data
Then run: Then run:
python run.py --project-path /path/to/project python run.py --project-path /path/to/project -i /path/to/images
There are many options for tuning your project. See the [wiki](https://github.com/OpenDroneMap/OpenDroneMap/wiki/Run-Time-Parameters) or run `python run.py -h` The images will be copied over to the project path so you only need to specify the `-i /path/` once. There are many options for tuning your project. See the [wiki](https://github.com/OpenDroneMap/OpenDroneMap/wiki/Run-Time-Parameters) or run `python run.py -h`
### View Results ### View Results
@ -121,7 +112,7 @@ Any file ending in .obj or .ply can be opened and viewed in [MeshLab](http://mes
![](https://raw.githubusercontent.com/alexhagiopol/OpenDroneMap/feature-better-docker/toledo_dataset_example_mesh.jpg) ![](https://raw.githubusercontent.com/alexhagiopol/OpenDroneMap/feature-better-docker/toledo_dataset_example_mesh.jpg)
You can also view the orthophoto GeoTIFF in QGIS or other mapping software: You can also view the orthophoto GeoTIFF in [QGIS](http://www.qgis.org/) or other mapping software:
![](https://raw.githubusercontent.com/OpenDroneMap/OpenDroneMap/master/img/bellus_map.png) ![](https://raw.githubusercontent.com/OpenDroneMap/OpenDroneMap/master/img/bellus_map.png)
@ -133,16 +124,16 @@ has equivalent procedures for Mac OS X and Windows. See [docs.docker.com](docs.d
OpenDroneMap is Dockerized, meaning you can use containerization to build and run it without tampering with the configuration of libraries and packages already OpenDroneMap is Dockerized, meaning you can use containerization to build and run it without tampering with the configuration of libraries and packages already
installed on your machine. Docker software is free to install and use in this context. If you don't have it installed, installed on your machine. Docker software is free to install and use in this context. If you don't have it installed,
see the [Docker Ubuntu installation tutorial](https://docs.docker.com/engine/installation/linux/ubuntulinux/) and follow the see the [Docker Ubuntu installation tutorial](https://docs.docker.com/engine/installation/linux/ubuntulinux/) and follow the
instructions through "Create a Docker group". Once Docker is installed, an OpenDroneMap Docker image can be created instructions through "Create a Docker group". Once Docker is installed, the fastest way to use OpenDroneMap is to run a pre-built image by typing:
like so:
docker run -it --rm -v $(pwd)/images:/code/images -v $(pwd)/odm_orthophoto:/code/odm_orthophoto -v $(pwd)/odm_texturing:/code/odm_texturing opendronemap/opendronemap
If you want to build your own Docker image from sources, type:
docker build -t packages -f packages.Dockerfile . docker build -t packages -f packages.Dockerfile .
docker build -t odm_image .
docker run -it --user root\ docker build -t my_odm_image .
-v $(pwd)/images:/code/images\ docker run -it --rm -v $(pwd)/images:/code/images v $(pwd)/odm_orthophoto:/code/odm_orthophoto -v $(pwd)/odm_texturing:/code/odm_texturing my_odm_image
-v $(pwd)/odm_orthophoto:/code/odm_orthophoto\
-v $(pwd)/odm_texturing:/code/odm_texturing\
--rm odm_image
Using this method, the containerized ODM will process the images in the OpenDroneMap/images directory and output results Using this method, the containerized ODM will process the images in the OpenDroneMap/images directory and output results
to the OpenDroneMap/odm_orthophoto and OpenDroneMap/odm_texturing directories as described in the **Viewing Results** section. to the OpenDroneMap/odm_orthophoto and OpenDroneMap/odm_texturing directories as described in the **Viewing Results** section.
@ -150,13 +141,11 @@ If you want to view other results outside the Docker image simply add which dire
established above. For example, if you're interested in the dense cloud results generated by PMVS and in the orthophoto, established above. For example, if you're interested in the dense cloud results generated by PMVS and in the orthophoto,
simply use the following `docker run` command after building the image: simply use the following `docker run` command after building the image:
docker run -it \ docker run -it --rm -v $(pwd)/images:/code/images -v $(pwd)/pmvs:/code/pmvs -v $(pwd)/odm_orthophoto:/code/odm_orthophoto my_odm_image
-v $(pwd)/images:/code/images\
-v $(pwd)/pmvs:/code/pmvs\
-v $(pwd)/odm_orthophoto:/code/odm_orthophoto\
--rm odm_image
To pass in custom parameters to the run.py script, simply pass it as arguments to the `docker run` command. To pass in custom parameters to the run.py script, simply pass it as arguments to the `docker run` command. For example:
docker run -it --rm -v $(pwd)/images:/code/images v $(pwd)/odm_orthophoto:/code/odm_orthophoto -v $(pwd)/odm_texturing:/code/odm_texturing opendronemap/opendronemap --resize-to 1800 --force-ccd 6.16
## User Interface ## User Interface

Wyświetl plik

@ -1141,9 +1141,40 @@ void Georef::createGeoreferencedModelFromExifData()
void Georef::chooseBestGCPTriplet(size_t &gcp0, size_t &gcp1, size_t &gcp2) void Georef::chooseBestGCPTriplet(size_t &gcp0, size_t &gcp1, size_t &gcp2)
{ {
double minTotError = std::numeric_limits<double>::infinity(); size_t numThreads = boost::thread::hardware_concurrency();
boost::thread_group threads;
std::vector<GeorefBestTriplet*> triplets;
for(size_t t = 0; t < numThreads; ++t)
{
GeorefBestTriplet* triplet = new GeorefBestTriplet();
triplets.push_back(triplet);
threads.create_thread(boost::bind(&Georef::findBestGCPTriplet, this, boost::ref(triplet->t_), boost::ref(triplet->s_), boost::ref(triplet->p_), t, numThreads, boost::ref(triplet->err_)));
}
for(size_t t = 0; t < gcps_.size(); ++t) threads.join_all();
double minTotError = std::numeric_limits<double>::infinity();
for(size_t t = 0; t<numThreads; t++)
{
GeorefBestTriplet* triplet = triplets[t];
if(minTotError > triplet->err_)
{
minTotError = triplet->err_;
gcp0 = triplet->t_;
gcp1 = triplet->s_;
gcp2 = triplet->p_;
}
delete triplet;
}
log_ << "Mean georeference error " << minTotError / static_cast<double>(gcps_.size()) << '\n';
}
void Georef::findBestGCPTriplet(size_t &gcp0, size_t &gcp1, size_t &gcp2, size_t offset, size_t stride, double &minTotError)
{
minTotError = std::numeric_limits<double>::infinity();
for(size_t t = offset; t < gcps_.size(); t+=stride)
{ {
if (gcps_[t].use_) if (gcps_[t].use_)
{ {
@ -1180,14 +1211,47 @@ void Georef::chooseBestGCPTriplet(size_t &gcp0, size_t &gcp1, size_t &gcp2)
} }
} }
} }
log_ << "Mean georeference error " << minTotError / static_cast<double>(cameras_.size()) << '\n';
log_ << '[' << offset+1 << " of " << stride << "] Mean georeference error " << minTotError / static_cast<double>(gcps_.size());
log_ << " (" << gcp0 << ", " << gcp1 << ", " << gcp2 << ")\n";
} }
void Georef::chooseBestCameraTriplet(size_t &cam0, size_t &cam1, size_t &cam2) void Georef::chooseBestCameraTriplet(size_t &cam0, size_t &cam1, size_t &cam2)
{ {
double minTotError = std::numeric_limits<double>::infinity(); size_t numThreads = boost::thread::hardware_concurrency();
boost::thread_group threads;
std::vector<GeorefBestTriplet*> triplets;
for(size_t t = 0; t < numThreads; ++t)
{
GeorefBestTriplet* triplet = new GeorefBestTriplet();
triplets.push_back(triplet);
threads.create_thread(boost::bind(&Georef::findBestCameraTriplet, this, boost::ref(triplet->t_), boost::ref(triplet->s_), boost::ref(triplet->p_), t, numThreads, boost::ref(triplet->err_)));
}
for(size_t t = 0; t < cameras_.size(); ++t) threads.join_all();
double minTotError = std::numeric_limits<double>::infinity();
for(size_t t = 0; t<numThreads; t++)
{
GeorefBestTriplet* triplet = triplets[t];
if(minTotError > triplet->err_)
{
minTotError = triplet->err_;
cam0 = triplet->t_;
cam1 = triplet->s_;
cam2 = triplet->p_;
}
delete triplet;
}
log_ << "Mean georeference error " << minTotError / static_cast<double>(cameras_.size()) << '\n';
}
void Georef::findBestCameraTriplet(size_t &cam0, size_t &cam1, size_t &cam2, size_t offset, size_t stride, double &minTotError)
{
minTotError = std::numeric_limits<double>::infinity();
for(size_t t = offset; t < cameras_.size(); t+=stride)
{ {
for(size_t s = t; s < cameras_.size(); ++s) for(size_t s = t; s < cameras_.size(); ++s)
{ {
@ -1216,7 +1280,8 @@ void Georef::chooseBestCameraTriplet(size_t &cam0, size_t &cam1, size_t &cam2)
} }
} }
log_ << "Mean georeference error " << minTotError / static_cast<double>(cameras_.size()) << '\n'; log_ << '[' << offset+1 << " of " << stride << "] Mean georeference error " << minTotError / static_cast<double>(cameras_.size());
log_ << " (" << cam0 << ", " << cam1 << ", " << cam2 << ")\n";
} }
void Georef::printGeorefSystem() void Georef::printGeorefSystem()

Wyświetl plik

@ -111,6 +111,17 @@ struct GeorefCamera
friend std::ostream& operator<<(std::ostream &os, const GeorefCamera &cam); friend std::ostream& operator<<(std::ostream &os, const GeorefCamera &cam);
}; };
/*!
* \brief The GeorefBestTriplet struct is used to store the best triplet found.
*/
struct GeorefBestTriplet
{
size_t t_; /**< First ordinate of the best triplet found. **/
size_t s_; /**< Second ordinate of the best triplet found. **/
size_t p_; /**< Third ordinate of the best triplet found. **/
double err_; /**< Error of this triplet. **/
};
/*! /*!
* \brief The Georef class is used to transform a mesh into a georeferenced system. * \brief The Georef class is used to transform a mesh into a georeferenced system.
* The class reads camera positions from a bundle file. * The class reads camera positions from a bundle file.
@ -196,11 +207,21 @@ private:
*/ */
void chooseBestGCPTriplet(size_t &gcp0, size_t &gcp1, size_t &gcp2); void chooseBestGCPTriplet(size_t &gcp0, size_t &gcp1, size_t &gcp2);
/*!
* \brief findBestGCPTriplet Partitioned version of chooseBestGCPTriplet.
*/
void findBestGCPTriplet(size_t &gcp0, size_t &gcp1, size_t &gcp2, size_t offset, size_t stride, double &minTotError);
/*! /*!
* \brief chooseBestCameraTriplet Chooses the best triplet of cameras to use when making the model georeferenced. * \brief chooseBestCameraTriplet Chooses the best triplet of cameras to use when making the model georeferenced.
*/ */
void chooseBestCameraTriplet(size_t &cam0, size_t &cam1, size_t &cam2); void chooseBestCameraTriplet(size_t &cam0, size_t &cam1, size_t &cam2);
/*!
* \brief findBestCameraTriplet Partitioned version of chooseBestCameraTriplet.
*/
void findBestCameraTriplet(size_t &cam0, size_t &cam1, size_t &cam2, size_t offset, size_t stride, double &minTotError);
/*! /*!
* \brief printGeorefSystem Prints a file containing information about the georeference system, next to the ouptut file. * \brief printGeorefSystem Prints a file containing information about the georeference system, next to the ouptut file.
**/ **/

Wyświetl plik

@ -16,6 +16,10 @@ parser = argparse.ArgumentParser(description='OpenDroneMap')
def config(): def config():
parser.add_argument('--images', '-i',
metavar='<string>',
help='Path to input images'),
parser.add_argument('--project-path', parser.add_argument('--project-path',
metavar='<string>', metavar='<string>',
help='Path to the project to process') help='Path to the project to process')
@ -118,6 +122,13 @@ def config():
'images based on GPS exif data. Set to 0 to skip ' 'images based on GPS exif data. Set to 0 to skip '
'pre-matching. Default: %(default)s') 'pre-matching. Default: %(default)s')
parser.add_argument('--opensfm-processes',
metavar='<positive integer>',
default=context.num_cores,
type=int,
help=('The maximum number of processes to use in dense '
'reconstruction. Default: %(default)s'))
parser.add_argument('--use-pmvs', parser.add_argument('--use-pmvs',
action='store_true', action='store_true',
default=False, default=False,
@ -166,7 +177,7 @@ def config():
'value leads to more stable reconstructions, but ' 'value leads to more stable reconstructions, but '
'the program becomes slower. Default: %(default)s') 'the program becomes slower. Default: %(default)s')
parser.add_argument('--pmvs-minImageNum', parser.add_argument('--pmvs-min-images',
metavar='<positive integer>', metavar='<positive integer>',
default=3, default=3,
type=int, type=int,
@ -181,14 +192,14 @@ def config():
help=('The maximum number of cores to use in dense ' help=('The maximum number of cores to use in dense '
'reconstruction. Default: %(default)s')) 'reconstruction. Default: %(default)s'))
parser.add_argument('--odm_meshing-maxVertexCount', parser.add_argument('--mesh-size',
metavar='<positive integer>', metavar='<positive integer>',
default=100000, default=100000,
type=int, type=int,
help=('The maximum vertex count of the output mesh ' help=('The maximum vertex count of the output mesh '
'Default: %(default)s')) 'Default: %(default)s'))
parser.add_argument('--odm_meshing-octreeDepth', parser.add_argument('--mesh-octree-depth',
metavar='<positive integer>', metavar='<positive integer>',
default=9, default=9,
type=int, type=int,
@ -196,14 +207,14 @@ def config():
'increase to get more vertices, recommended ' 'increase to get more vertices, recommended '
'values are 8-12. Default: %(default)s')) 'values are 8-12. Default: %(default)s'))
parser.add_argument('--odm_meshing-samplesPerNode', parser.add_argument('--mesh-samples',
metavar='<float >= 1.0>', metavar='<float >= 1.0>',
default=1.0, default=1.0,
type=float, type=float,
help=('Number of points per octree node, recommended ' help=('Number of points per octree node, recommended '
'and default value: %(default)s')) 'and default value: %(default)s'))
parser.add_argument('--odm_meshing-solverDivide', parser.add_argument('--mesh-solver-divide',
metavar='<positive integer>', metavar='<positive integer>',
default=9, default=9,
type=int, type=int,
@ -213,78 +224,64 @@ def config():
'times slightly but helps reduce memory usage. ' 'times slightly but helps reduce memory usage. '
'Default: %(default)s')) 'Default: %(default)s'))
parser.add_argument('--mvs_texturing-dataTerm', parser.add_argument('--texturing-data-term',
metavar='<string>', metavar='<string>',
default='gmi', default='gmi',
help=('Data term: [area, gmi]. Default: %(default)s')) help=('Data term: [area, gmi]. Default: '
'%(default)s'))
parser.add_argument('--mvs_texturing-outlierRemovalType', parser.add_argument('--texturing-outlier-removal-type',
metavar='<string>', metavar='<string>',
default='none', default='none',
help=('Type of photometric outlier removal method: ' help=('Type of photometric outlier removal method: '
'[none, gauss_damping, gauss_clamping]. Default: ' '[none, gauss_damping, gauss_clamping]. Default: '
'%(default)s')) '%(default)s'))
parser.add_argument('--mvs_texturing-skipGeometricVisibilityTest', parser.add_argument('--texturing-skip-visibility-test',
action='store_true', action='store_true',
default=False, default=False,
help=('Skip geometric visibility test. Default: %(default)s')) help=('Skip geometric visibility test. Default: '
' %(default)s'))
parser.add_argument('--mvs_texturing-skipGlobalSeamLeveling', parser.add_argument('--texturing-skip-global-seam-leveling',
action='store_true', action='store_true',
default=False, default=False,
help=('Skip geometric visibility test. Default: %(default)s')) help=('Skip global seam leveling. Useful for IR data.'
'Default: %(default)s'))
parser.add_argument('--mvs_texturing-skipLocalSeamLeveling', parser.add_argument('--texturing-skip-local-seam-leveling',
action='store_true', action='store_true',
default=False, default=False,
help=('Skip local seam blending. Default: %(default)s')) help='Skip local seam blending. Default: %(default)s')
parser.add_argument('--mvs_texturing-skipHoleFilling', parser.add_argument('--texturing-skip-hole-filling',
action='store_true', action='store_true',
default=False, default=False,
help=('Skip filling of holes in the mesh. Default: %(default)s')) help=('Skip filling of holes in the mesh. Default: '
' %(default)s'))
parser.add_argument('--mvs_texturing-keepUnseenFaces', parser.add_argument('--texturing-keep-unseen-faces',
action='store_true', action='store_true',
default=False, default=False,
help=('Keep faces in the mesh that are not seen in any camera. ' help=('Keep faces in the mesh that are not seen in any camera. '
'Default: %(default)s')) 'Default: %(default)s'))
# Old odm_texturing arguments parser.add_argument('--gcp',
parser.add_argument('--odm_texturing-textureResolution',
metavar='<positive integer>',
default=4096,
type=int,
help=('The resolution of the output textures. Must be '
'greater than textureWithSize. Default: %(default)s'))
parser.add_argument('--odm_texturing-textureWithSize',
metavar='<positive integer>',
default=3600,
type=int,
help=('The resolution to rescale the images performing '
'the texturing. Default: %(default)s'))
# End of old odm_texturing arguments
parser.add_argument('--odm_georeferencing-gcpFile',
metavar='<path string>', metavar='<path string>',
default='gcp_list.txt', default=None,
help=('path to the file containing the ground control ' help=('path to the file containing the ground control '
'points used for georeferencing. Default: ' 'points used for georeferencing. Default: '
'%(default)s. The file needs to ' '%(default)s. The file needs to '
'be on the following line format: \neasting ' 'be on the following line format: \neasting '
'northing height pixelrow pixelcol imagename')) 'northing height pixelrow pixelcol imagename'))
parser.add_argument('--odm_georeferencing-useGcp', parser.add_argument('--use-exif',
action='store_true', action='store_true',
default=False, default=False,
help='Enabling GCPs from the file above. The GCP file ' help=('Use this tag if you have a gcp_list.txt but '
'is not used by default.') 'want to use the exif geotags instead'))
parser.add_argument('--odm_orthophoto-resolution', parser.add_argument('--orthophoto-resolution',
metavar='<float > 0.0>', metavar='<float > 0.0>',
default=20.0, default=20.0,
type=float, type=float,

Wyświetl plik

@ -331,9 +331,13 @@ class ODM_GeoRef(object):
class ODM_Tree(object): class ODM_Tree(object):
def __init__(self, root_path): def __init__(self, root_path, images_path):
# root path to the project # root path to the project
self.root_path = io.absolute_path_file(root_path) self.root_path = io.absolute_path_file(root_path)
if not images_path:
self.input_images = io.join_paths(self.root_path, 'images')
else:
self.input_images = io.absolute_path_file(images_path)
# modules paths # modules paths

5
run.py
Wyświetl plik

@ -3,6 +3,7 @@
from opendm import log from opendm import log
from opendm import config from opendm import config
from opendm import system from opendm import system
from opendm import io
import sys import sys
import ecto import ecto
@ -26,8 +27,12 @@ if __name__ == '__main__':
# Force to provide the images path # Force to provide the images path
if args.project_path is None: if args.project_path is None:
usage() usage()
elif not io.dir_exists(args.project_path):
log.ODM_WARNING('Directory %s does not exist. Creating it now.' % args.project_path)
system.mkdir_p(os.path.abspath(args.project_path))
#If user asks to rerun everything, delete all of the existing progress directories. #If user asks to rerun everything, delete all of the existing progress directories.
# TODO: Move this somewhere it's not hard-coded
if args.rerun_all: if args.rerun_all:
os.system("rm -rf " os.system("rm -rf "
+ args.project_path + "images_resize/ " + args.project_path + "images_resize/ "

Wyświetl plik

@ -7,6 +7,8 @@ from opendm import context
from opendm import io from opendm import io
from opendm import types from opendm import types
from opendm import log from opendm import log
from opendm import system
from shutil import copyfile
def make_odm_photo(force_focal, force_ccd, path_file): def make_odm_photo(force_focal, force_ccd, path_file):
@ -28,32 +30,41 @@ class ODMLoadDatasetCell(ecto.Cell):
outputs.declare("photos", "list of ODMPhotos", []) outputs.declare("photos", "list of ODMPhotos", [])
def process(self, inputs, outputs): def process(self, inputs, outputs):
# check if the extension is sopported # check if the extension is supported
def supported_extension(file_name): def supported_extension(file_name):
(pathfn, ext) = os.path.splitext(file_name) (pathfn, ext) = os.path.splitext(file_name)
return ext.lower() in context.supported_extensions return ext.lower() in context.supported_extensions
# Get supported images from dir
def get_images(in_dir):
# filter images for its extension type
log.ODM_DEBUG(in_dir)
return [f for f in io.get_files_list(in_dir) if supported_extension(f)]
log.ODM_INFO('Running ODM Load Dataset Cell') log.ODM_INFO('Running ODM Load Dataset Cell')
# get inputs # get inputs
tree = self.inputs.tree tree = self.inputs.tree
# set images directory # get images directory
images_dir = tree.dataset_resize input_dir = tree.input_images
if not io.dir_exists(images_dir):
images_dir = tree.dataset_raw images_dir = tree.dataset_raw
resize_dir = tree.dataset_resize
# Check first if a project already exists. This is a mediocre way to check, by checking the resize dir
if io.dir_exists(resize_dir):
log.ODM_DEBUG("resize dir: %s" % resize_dir)
images_dir = resize_dir
# if first time running, create project directory and copy images over to project/images
else:
if not io.dir_exists(images_dir): if not io.dir_exists(images_dir):
log.ODM_ERROR("You must put your pictures into an <images> directory") log.ODM_INFO("Project directory %s doesn't exist. Creating it now. " % images_dir)
return ecto.QUIT system.mkdir_p(images_dir)
copied = [copyfile(io.join_paths(input_dir, f), io.join_paths(images_dir, f)) for f in get_images(input_dir)]
log.ODM_DEBUG('Loading dataset from: %s' % images_dir) log.ODM_DEBUG('Loading dataset from: %s' % images_dir)
# find files in the given directory files = get_images(images_dir)
files = io.get_files_list(images_dir)
# filter images for its extension type
files = [f for f in files if supported_extension(f)]
if files: if files:
# create ODMPhoto list # create ODMPhoto list

Wyświetl plik

@ -3,7 +3,6 @@ import os
from opendm import context from opendm import context
from opendm import types from opendm import types
from opendm import config
from opendm import io from opendm import io
from opendm import system from opendm import system
@ -45,7 +44,7 @@ class ODMApp(ecto.BlackBox):
'opensfm': ODMOpenSfMCell(use_exif_size=False, 'opensfm': ODMOpenSfMCell(use_exif_size=False,
feature_process_size=p.args.resize_to, feature_process_size=p.args.resize_to,
feature_min_frames=p.args.min_num_features, feature_min_frames=p.args.min_num_features,
processes=context.num_cores, processes=p.args.opensfm_processes,
matching_gps_neighbors=p.args.matcher_neighbors, matching_gps_neighbors=p.args.matcher_neighbors,
matching_gps_distance=p.args.matcher_distance), matching_gps_distance=p.args.matcher_distance),
'slam': ODMSlamCell(), 'slam': ODMSlamCell(),
@ -54,36 +53,32 @@ class ODMApp(ecto.BlackBox):
csize=p.args.pmvs_csize, csize=p.args.pmvs_csize,
thresh=p.args.pmvs_threshold, thresh=p.args.pmvs_threshold,
wsize=p.args.pmvs_wsize, wsize=p.args.pmvs_wsize,
min_imgs=p.args.pmvs_minImageNum, min_imgs=p.args.pmvs_min_images,
cores=p.args.pmvs_num_cores), cores=p.args.pmvs_num_cores),
'meshing': ODMeshingCell(max_vertex=p.args.odm_meshing_maxVertexCount, 'meshing': ODMeshingCell(max_vertex=p.args.mesh_size,
oct_tree=p.args.odm_meshing_octreeDepth, oct_tree=p.args.mesh_octree_depth,
samples=p.args.odm_meshing_samplesPerNode, samples=p.args.mesh_samples,
solver=p.args.odm_meshing_solverDivide, solver=p.args.mesh_solver_divide,
verbose=p.args.verbose), verbose=p.args.verbose),
'texturing': ODMMvsTexCell(data_term=p.args.mvs_texturing_dataTerm, 'texturing': ODMMvsTexCell(data_term=p.args.texturing_data_term,
outlier_rem_type=p.args.mvs_texturing_outlierRemovalType, outlier_rem_type=p.args.texturing_outlier_removal_type,
skip_vis_test=p.args.mvs_texturing_skipGeometricVisibilityTest, skip_vis_test=p.args.texturing_skip_visibility_test,
skip_glob_seam_leveling=p.args.mvs_texturing_skipGlobalSeamLeveling, skip_glob_seam_leveling=p.args.texturing_skip_global_seam_leveling,
skip_loc_seam_leveling=p.args.mvs_texturing_skipLocalSeamLeveling, skip_loc_seam_leveling=p.args.texturing_skip_local_seam_leveling,
skip_hole_fill=p.args.mvs_texturing_skipHoleFilling, skip_hole_fill=p.args.texturing_skip_hole_filling,
keep_unseen_faces=p.args.mvs_texturing_keepUnseenFaces), keep_unseen_faces=p.args.texturing_keep_unseen_faces),
# Old odm_texturing
# 'texturing': ODMTexturingCell(resize=p.args['resize_to'],
# resolution=p.args['odm_texturing_textureResolution'],
'georeferencing': ODMGeoreferencingCell(img_size=p.args.resize_to, 'georeferencing': ODMGeoreferencingCell(img_size=p.args.resize_to,
gcp_file=p.args.odm_georeferencing_gcpFile, gcp_file=p.args.gcp,
use_gcp=p.args.odm_georeferencing_useGcp, use_exif=p.args.use_exif,
verbose=p.args.verbose), verbose=p.args.verbose),
'orthophoto': ODMOrthoPhotoCell(resolution=p.args.odm_orthophoto_resolution, 'orthophoto': ODMOrthoPhotoCell(resolution=p.args.orthophoto_resolution,
verbose=p.args.verbose) verbose=p.args.verbose)
} }
return cells return cells
def configure(self, p, _i, _o): def configure(self, p, _i, _o):
tree = types.ODM_Tree(p.args.project_path) tree = types.ODM_Tree(p.args.project_path, p.args.images)
self.tree = ecto.Constant(value=tree) self.tree = ecto.Constant(value=tree)
# TODO(dakota) put this somewhere better maybe # TODO(dakota) put this somewhere better maybe

Wyświetl plik

@ -1,5 +1,6 @@
import ecto import ecto
import csv import csv
import os
from opendm import io from opendm import io
from opendm import log from opendm import log
@ -14,8 +15,8 @@ class ODMGeoreferencingCell(ecto.Cell):
'points used for georeferencing.The file needs to ' 'points used for georeferencing.The file needs to '
'be on the following line format: \neasting ' 'be on the following line format: \neasting '
'northing height pixelrow pixelcol imagename', 'gcp_list.txt') 'northing height pixelrow pixelcol imagename', 'gcp_list.txt')
params.declare("use_gcp", 'set to true for enabling GCPs from the file above', False)
params.declare("img_size", 'image size used in calibration', 2400) params.declare("img_size", 'image size used in calibration', 2400)
params.declare("use_exif", 'use exif', False)
params.declare("verbose", 'print additional messages to console', False) params.declare("verbose", 'print additional messages to console', False)
def declare_io(self, params, inputs, outputs): def declare_io(self, params, inputs, outputs):
@ -26,6 +27,12 @@ class ODMGeoreferencingCell(ecto.Cell):
outputs.declare("reconstruction", "list of ODMReconstructions", []) outputs.declare("reconstruction", "list of ODMReconstructions", [])
def process(self, inputs, outputs): def process(self, inputs, outputs):
# find a file in the root directory
def find(file, dir):
for root, dirs, files in os.walk(dir):
return '/'.join((root, file)) if file in files else None
# Benchmarking # Benchmarking
start_time = system.now_raw() start_time = system.now_raw()
@ -34,7 +41,9 @@ class ODMGeoreferencingCell(ecto.Cell):
# get inputs # get inputs
args = self.inputs.args args = self.inputs.args
tree = self.inputs.tree tree = self.inputs.tree
gcpfile = io.join_paths(tree.root_path, self.params.gcp_file) gcpfile = io.join_paths(tree.root_path, self.params.gcp_file) \
if self.params.gcp_file else find('gcp_list.txt', tree.root_path)
geocreated = True
verbose = '-verbose' if self.params.verbose else '' verbose = '-verbose' if self.params.verbose else ''
# define paths and create working directories # define paths and create working directories
@ -42,13 +51,14 @@ class ODMGeoreferencingCell(ecto.Cell):
# in case a gcp file it's not provided, let's try to generate it using # in case a gcp file it's not provided, let's try to generate it using
# images metadata. Internally calls jhead. # images metadata. Internally calls jhead.
if not self.params.use_gcp and \ log.ODM_DEBUG(self.params.gcp_file)
not io.file_exists(tree.odm_georeferencing_coords): if not self.params.gcp_file: # and \
# not io.file_exists(tree.odm_georeferencing_coords):
log.ODM_WARNING('Warning: No coordinates file. ' log.ODM_WARNING('No coordinates file. '
'Generating coordinates file in: %s' 'Generating coordinates file: %s'
% tree.odm_georeferencing_coords) % tree.odm_georeferencing_coords)
try:
# odm_georeference definitions # odm_georeference definitions
kwargs = { kwargs = {
'bin': context.odm_modules_path, 'bin': context.odm_modules_path,
@ -60,16 +70,15 @@ class ODMGeoreferencingCell(ecto.Cell):
} }
# run UTM extraction binary # run UTM extraction binary
system.run('{bin}/odm_extract_utm -imagesPath {imgs}/ ' extract_utm = system.run_and_return('{bin}/odm_extract_utm -imagesPath {imgs}/ '
'-imageListFile {imgs_list} -outputCoordFile {coords} {verbose} ' '-imageListFile {imgs_list} -outputCoordFile {coords} {verbose} '
'-logFile {log}'.format(**kwargs)) '-logFile {log}'.format(**kwargs))
except Exception, e: if extract_utm != '':
log.ODM_ERROR('Could not generate GCP file from images metadata.' log.ODM_WARNING('Could not generate coordinates file. '
'Consider rerunning with argument --odm_georeferencing-useGcp' 'Ignore if there is a GCP file. Error: %s'
' and provide a proper GCP file') % extract_utm)
log.ODM_ERROR(e)
return ecto.QUIT
# check if we rerun cell or not # check if we rerun cell or not
rerun_cell = (args.rerun is not None and rerun_cell = (args.rerun is not None and
@ -103,20 +112,32 @@ class ODMGeoreferencingCell(ecto.Cell):
else: else:
kwargs['pc'] = tree.pmvs_model kwargs['pc'] = tree.pmvs_model
if self.params.use_gcp and \ # Check to see if the GCP file exists
io.file_exists(gcpfile):
if not self.params.use_exif and (self.params.gcp_file or find('gcp_list.txt', tree.root_path)):
log.ODM_INFO('Found %s' % gcpfile)
try:
system.run('{bin}/odm_georef -bundleFile {bundle} -imagesPath {imgs} -imagesListPath {imgs_list} ' system.run('{bin}/odm_georef -bundleFile {bundle} -imagesPath {imgs} -imagesListPath {imgs_list} '
'-bundleResizedTo {size} -inputFile {model} -outputFile {model_geo} ' '-bundleResizedTo {size} -inputFile {model} -outputFile {model_geo} '
'-inputPointCloudFile {pc} -outputPointCloudFile {pc_geo} {verbose} ' '-inputPointCloudFile {pc} -outputPointCloudFile {pc_geo} {verbose} '
'-logFile {log} -georefFileOutputPath {geo_sys} -gcpFile {gcp} ' '-logFile {log} -georefFileOutputPath {geo_sys} -gcpFile {gcp} '
'-outputCoordFile {coords}'.format(**kwargs)) '-outputCoordFile {coords}'.format(**kwargs))
else: except Exception:
log.ODM_EXCEPTION('Georeferencing failed. ')
return ecto.QUIT
elif io.file_exists(tree.odm_georeferencing_coords):
log.ODM_INFO('Running georeferencing with generated coords file.')
system.run('{bin}/odm_georef -bundleFile {bundle} -inputCoordFile {coords} ' system.run('{bin}/odm_georef -bundleFile {bundle} -inputCoordFile {coords} '
'-inputFile {model} -outputFile {model_geo} ' '-inputFile {model} -outputFile {model_geo} '
'-inputPointCloudFile {pc} -outputPointCloudFile {pc_geo} {verbose} ' '-inputPointCloudFile {pc} -outputPointCloudFile {pc_geo} {verbose} '
'-logFile {log} -georefFileOutputPath {geo_sys}'.format(**kwargs)) '-logFile {log} -georefFileOutputPath {geo_sys}'.format(**kwargs))
else:
log.ODM_WARNING('Georeferencing failed. Make sure your '
'photos have geotags in the EXIF or you have '
'provided a GCP file. ')
geocreated = False # skip the rest of the georeferencing
if geocreated:
# update images metadata # update images metadata
geo_ref = types.ODM_GeoRef() geo_ref = types.ODM_GeoRef()
geo_ref.parse_coordinate_system(tree.odm_georeferencing_coords) geo_ref.parse_coordinate_system(tree.odm_georeferencing_coords)

Wyświetl plik

@ -22,7 +22,7 @@ class ODMOrthoPhotoCell(ecto.Cell):
# Benchmarking # Benchmarking
start_time = system.now_raw() start_time = system.now_raw()
log.ODM_INFO('Running OMD OrthoPhoto Cell') log.ODM_INFO('Running ODM Orthophoto Cell')
# get inputs # get inputs
args = self.inputs.args args = self.inputs.args
@ -44,7 +44,6 @@ class ODMOrthoPhotoCell(ecto.Cell):
# odm_orthophoto definitions # odm_orthophoto definitions
kwargs = { kwargs = {
'bin': context.odm_modules_path, 'bin': context.odm_modules_path,
'model_geo': tree.odm_georeferencing_model_obj_geo,
'log': tree.odm_orthophoto_log, 'log': tree.odm_orthophoto_log,
'ortho': tree.odm_orthophoto_file, 'ortho': tree.odm_orthophoto_file,
'corners': tree.odm_orthophoto_corners, 'corners': tree.odm_orthophoto_corners,
@ -52,11 +51,20 @@ class ODMOrthoPhotoCell(ecto.Cell):
'verbose': verbose 'verbose': verbose
} }
kwargs['model_geo'] = tree.odm_georeferencing_model_obj_geo \
if io.file_exists(tree.odm_georeferencing_coords) \
else tree.odm_textured_model_obj
# run odm_orthophoto # run odm_orthophoto
system.run('{bin}/odm_orthophoto -inputFile {model_geo} ' system.run('{bin}/odm_orthophoto -inputFile {model_geo} '
'-logFile {log} -outputFile {ortho} -resolution {res} {verbose} ' '-logFile {log} -outputFile {ortho} -resolution {res} {verbose} '
'-outputCornerFile {corners}'.format(**kwargs)) '-outputCornerFile {corners}'.format(**kwargs))
if not io.file_exists(tree.odm_georeferencing_coords):
log.ODM_WARNING('No coordinates file. A georeferenced raster '
'will not be created')
else:
# Create georeferenced GeoTiff # Create georeferenced GeoTiff
geotiffcreated = False geotiffcreated = False
georef = types.ODM_GeoRef() georef = types.ODM_GeoRef()