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
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.*
@ -40,7 +40,7 @@ Current version: 0.2 (this software is in beta)
- textured mesh model: odm_texturing/odm_textured_model_geo.obj
- 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
@ -65,22 +65,13 @@ Note that using `run.sh` sets these temporarily in the shell.
### Run OpenDroneMap
First you need a set of images, taken from a drone or otherwise.
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
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
Then run:
python run.py --project-path /path/to/project
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`
python run.py --project-path /path/to/project -i /path/to/images
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
@ -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)
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)
@ -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
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
instructions through "Create a Docker group". Once Docker is installed, an OpenDroneMap Docker image can be created
like so:
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:
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 odm_image .
docker run -it --user root\
-v $(pwd)/images:/code/images\
-v $(pwd)/odm_orthophoto:/code/odm_orthophoto\
-v $(pwd)/odm_texturing:/code/odm_texturing\
--rm odm_image
docker build -t my_odm_image .
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
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.
@ -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,
simply use the following `docker run` command after building the image:
docker run -it \
-v $(pwd)/images:/code/images\
-v $(pwd)/pmvs:/code/pmvs\
-v $(pwd)/odm_orthophoto:/code/odm_orthophoto\
--rm odm_image
docker run -it --rm -v $(pwd)/images:/code/images -v $(pwd)/pmvs:/code/pmvs -v $(pwd)/odm_orthophoto:/code/odm_orthophoto my_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

Wyświetl plik

@ -1141,9 +1141,40 @@ void Georef::createGeoreferencedModelFromExifData()
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_)
{
@ -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)
{
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_)));
}
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 = 0; t < cameras_.size(); ++t)
for(size_t t = offset; t < cameras_.size(); t+=stride)
{
for(size_t s = t; s < cameras_.size(); ++s)
{
@ -1215,8 +1279,9 @@ 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()

Wyświetl plik

@ -111,6 +111,17 @@ struct GeorefCamera
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.
* The class reads camera positions from a bundle file.
@ -196,10 +207,20 @@ private:
*/
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.
*/
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.

Wyświetl plik

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

Wyświetl plik

@ -331,9 +331,13 @@ class ODM_GeoRef(object):
class ODM_Tree(object):
def __init__(self, root_path):
def __init__(self, root_path, images_path):
# root path to the project
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

5
run.py
Wyświetl plik

@ -3,6 +3,7 @@
from opendm import log
from opendm import config
from opendm import system
from opendm import io
import sys
import ecto
@ -26,8 +27,12 @@ if __name__ == '__main__':
# Force to provide the images path
if args.project_path is None:
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.
# TODO: Move this somewhere it's not hard-coded
if args.rerun_all:
os.system("rm -rf "
+ args.project_path + "images_resize/ "

Wyświetl plik

@ -7,6 +7,8 @@ from opendm import context
from opendm import io
from opendm import types
from opendm import log
from opendm import system
from shutil import copyfile
def make_odm_photo(force_focal, force_ccd, path_file):
@ -28,32 +30,41 @@ class ODMLoadDatasetCell(ecto.Cell):
outputs.declare("photos", "list of ODMPhotos", [])
def process(self, inputs, outputs):
# check if the extension is sopported
# check if the extension is supported
def supported_extension(file_name):
(pathfn, ext) = os.path.splitext(file_name)
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')
# get inputs
tree = self.inputs.tree
# set images directory
images_dir = tree.dataset_resize
# get images directory
input_dir = tree.input_images
images_dir = tree.dataset_raw
resize_dir = tree.dataset_resize
if not io.dir_exists(images_dir):
images_dir = tree.dataset_raw
# 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):
log.ODM_ERROR("You must put your pictures into an <images> directory")
return ecto.QUIT
log.ODM_INFO("Project directory %s doesn't exist. Creating it now. " % images_dir)
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)
# find files in the given directory
files = io.get_files_list(images_dir)
# filter images for its extension type
files = [f for f in files if supported_extension(f)]
files = get_images(images_dir)
if files:
# create ODMPhoto list

Wyświetl plik

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

Wyświetl plik

@ -1,5 +1,6 @@
import ecto
import csv
import os
from opendm import io
from opendm import log
@ -14,8 +15,8 @@ class ODMGeoreferencingCell(ecto.Cell):
'points used for georeferencing.The file needs to '
'be on the following line format: \neasting '
'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("use_exif", 'use exif', False)
params.declare("verbose", 'print additional messages to console', False)
def declare_io(self, params, inputs, outputs):
@ -26,6 +27,12 @@ class ODMGeoreferencingCell(ecto.Cell):
outputs.declare("reconstruction", "list of ODMReconstructions", [])
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
start_time = system.now_raw()
@ -34,7 +41,9 @@ class ODMGeoreferencingCell(ecto.Cell):
# get inputs
args = self.inputs.args
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 ''
# define paths and create working directories
@ -42,34 +51,34 @@ class ODMGeoreferencingCell(ecto.Cell):
# in case a gcp file it's not provided, let's try to generate it using
# images metadata. Internally calls jhead.
if not self.params.use_gcp and \
not io.file_exists(tree.odm_georeferencing_coords):
log.ODM_DEBUG(self.params.gcp_file)
if not self.params.gcp_file: # and \
# not io.file_exists(tree.odm_georeferencing_coords):
log.ODM_WARNING('Warning: No coordinates file. '
'Generating coordinates file in: %s'
log.ODM_WARNING('No coordinates file. '
'Generating coordinates file: %s'
% tree.odm_georeferencing_coords)
try:
# odm_georeference definitions
kwargs = {
'bin': context.odm_modules_path,
'imgs': tree.dataset_resize,
'imgs_list': tree.opensfm_bundle_list,
'coords': tree.odm_georeferencing_coords,
'log': tree.odm_georeferencing_utm_log,
'verbose': verbose
}
# run UTM extraction binary
system.run('{bin}/odm_extract_utm -imagesPath {imgs}/ '
'-imageListFile {imgs_list} -outputCoordFile {coords} {verbose} '
'-logFile {log}'.format(**kwargs))
# odm_georeference definitions
kwargs = {
'bin': context.odm_modules_path,
'imgs': tree.dataset_resize,
'imgs_list': tree.opensfm_bundle_list,
'coords': tree.odm_georeferencing_coords,
'log': tree.odm_georeferencing_utm_log,
'verbose': verbose
}
# run UTM extraction binary
extract_utm = system.run_and_return('{bin}/odm_extract_utm -imagesPath {imgs}/ '
'-imageListFile {imgs_list} -outputCoordFile {coords} {verbose} '
'-logFile {log}'.format(**kwargs))
if extract_utm != '':
log.ODM_WARNING('Could not generate coordinates file. '
'Ignore if there is a GCP file. Error: %s'
% extract_utm)
except Exception, e:
log.ODM_ERROR('Could not generate GCP file from images metadata.'
'Consider rerunning with argument --odm_georeferencing-useGcp'
' and provide a proper GCP file')
log.ODM_ERROR(e)
return ecto.QUIT
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
@ -103,47 +112,59 @@ class ODMGeoreferencingCell(ecto.Cell):
else:
kwargs['pc'] = tree.pmvs_model
if self.params.use_gcp and \
io.file_exists(gcpfile):
# Check to see if the GCP file exists
system.run('{bin}/odm_georef -bundleFile {bundle} -imagesPath {imgs} -imagesListPath {imgs_list} '
'-bundleResizedTo {size} -inputFile {model} -outputFile {model_geo} '
'-inputPointCloudFile {pc} -outputPointCloudFile {pc_geo} {verbose} '
'-logFile {log} -georefFileOutputPath {geo_sys} -gcpFile {gcp} '
'-outputCoordFile {coords}'.format(**kwargs))
else:
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} '
'-bundleResizedTo {size} -inputFile {model} -outputFile {model_geo} '
'-inputPointCloudFile {pc} -outputPointCloudFile {pc_geo} {verbose} '
'-logFile {log} -georefFileOutputPath {geo_sys} -gcpFile {gcp} '
'-outputCoordFile {coords}'.format(**kwargs))
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} '
'-inputFile {model} -outputFile {model_geo} '
'-inputPointCloudFile {pc} -outputPointCloudFile {pc_geo} {verbose} '
'-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
# update images metadata
geo_ref = types.ODM_GeoRef()
geo_ref.parse_coordinate_system(tree.odm_georeferencing_coords)
if geocreated:
# update images metadata
geo_ref = types.ODM_GeoRef()
geo_ref.parse_coordinate_system(tree.odm_georeferencing_coords)
for idx, photo in enumerate(self.inputs.photos):
geo_ref.utm_to_latlon(tree.odm_georeferencing_latlon, photo, idx)
for idx, photo in enumerate(self.inputs.photos):
geo_ref.utm_to_latlon(tree.odm_georeferencing_latlon, photo, idx)
# convert ply model to LAS reference system
geo_ref.convert_to_las(tree.odm_georeferencing_model_ply_geo,
tree.odm_georeferencing_pdal)
# convert ply model to LAS reference system
geo_ref.convert_to_las(tree.odm_georeferencing_model_ply_geo,
tree.odm_georeferencing_pdal)
# XYZ point cloud output
log.ODM_INFO("Creating geo-referenced CSV file (XYZ format, can be used with GRASS to create DEM)")
with open(tree.odm_georeferencing_xyz_file, "wb") as csvfile:
csvfile_writer = csv.writer(csvfile, delimiter=",")
reachedpoints = False
with open(tree.odm_georeferencing_model_ply_geo) as f:
for lineNumber, line in enumerate(f):
if reachedpoints:
tokens = line.split(" ")
csv_line = [float(tokens[0])+geo_ref.utm_east_offset,
float(tokens[1])+geo_ref.utm_north_offset,
tokens[2]]
csvfile_writer.writerow(csv_line)
if line.startswith("end_header"):
reachedpoints = True
csvfile.close()
# XYZ point cloud output
log.ODM_INFO("Creating geo-referenced CSV file (XYZ format, can be used with GRASS to create DEM)")
with open(tree.odm_georeferencing_xyz_file, "wb") as csvfile:
csvfile_writer = csv.writer(csvfile, delimiter=",")
reachedpoints = False
with open(tree.odm_georeferencing_model_ply_geo) as f:
for lineNumber, line in enumerate(f):
if reachedpoints:
tokens = line.split(" ")
csv_line = [float(tokens[0])+geo_ref.utm_east_offset,
float(tokens[1])+geo_ref.utm_north_offset,
tokens[2]]
csvfile_writer.writerow(csv_line)
if line.startswith("end_header"):
reachedpoints = True
csvfile.close()
else:
log.ODM_WARNING('Found a valid georeferenced model in: %s'

Wyświetl plik

@ -22,7 +22,7 @@ class ODMOrthoPhotoCell(ecto.Cell):
# Benchmarking
start_time = system.now_raw()
log.ODM_INFO('Running OMD OrthoPhoto Cell')
log.ODM_INFO('Running ODM Orthophoto Cell')
# get inputs
args = self.inputs.args
@ -44,7 +44,6 @@ class ODMOrthoPhotoCell(ecto.Cell):
# odm_orthophoto definitions
kwargs = {
'bin': context.odm_modules_path,
'model_geo': tree.odm_georeferencing_model_obj_geo,
'log': tree.odm_orthophoto_log,
'ortho': tree.odm_orthophoto_file,
'corners': tree.odm_orthophoto_corners,
@ -52,57 +51,66 @@ class ODMOrthoPhotoCell(ecto.Cell):
'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
system.run('{bin}/odm_orthophoto -inputFile {model_geo} '
'-logFile {log} -outputFile {ortho} -resolution {res} {verbose} '
'-outputCornerFile {corners}'.format(**kwargs))
# Create georeferenced GeoTiff
geotiffcreated = False
georef = types.ODM_GeoRef()
# creates the coord refs # TODO I don't want to have to do this twice- after odm_georef
georef.parse_coordinate_system(tree.odm_georeferencing_coords)
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
geotiffcreated = False
georef = types.ODM_GeoRef()
# creates the coord refs # TODO I don't want to have to do this twice- after odm_georef
georef.parse_coordinate_system(tree.odm_georeferencing_coords)
if georef.epsg and georef.utm_east_offset and georef.utm_north_offset:
ulx = uly = lrx = lry = 0.0
with open(tree.odm_orthophoto_corners) as f:
for lineNumber, line in enumerate(f):
if lineNumber == 0:
tokens = line.split(' ')
if len(tokens) == 4:
ulx = float(tokens[0]) + \
float(georef.utm_east_offset)
lry = float(tokens[1]) + \
float(georef.utm_north_offset)
lrx = float(tokens[2]) + \
float(georef.utm_east_offset)
uly = float(tokens[3]) + \
float(georef.utm_north_offset)
log.ODM_INFO('Creating GeoTIFF')
if georef.epsg and georef.utm_east_offset and georef.utm_north_offset:
ulx = uly = lrx = lry = 0.0
with open(tree.odm_orthophoto_corners) as f:
for lineNumber, line in enumerate(f):
if lineNumber == 0:
tokens = line.split(' ')
if len(tokens) == 4:
ulx = float(tokens[0]) + \
float(georef.utm_east_offset)
lry = float(tokens[1]) + \
float(georef.utm_north_offset)
lrx = float(tokens[2]) + \
float(georef.utm_east_offset)
uly = float(tokens[3]) + \
float(georef.utm_north_offset)
log.ODM_INFO('Creating GeoTIFF')
kwargs = {
'ulx': ulx,
'uly': uly,
'lrx': lrx,
'lry': lry,
'epsg': georef.epsg,
'png': tree.odm_orthophoto_file,
'tiff': tree.odm_orthophoto_tif,
'log': tree.odm_orthophoto_tif_log
}
kwargs = {
'ulx': ulx,
'uly': uly,
'lrx': lrx,
'lry': lry,
'epsg': georef.epsg,
'png': tree.odm_orthophoto_file,
'tiff': tree.odm_orthophoto_tif,
'log': tree.odm_orthophoto_tif_log
}
system.run('gdal_translate -a_ullr {ulx} {uly} {lrx} {lry} '
'-co TILED=yes '
'-co COMPRESS=DEFLATE '
'-co PREDICTOR=2 '
'-co BLOCKXSIZE=512 '
'-co BLOCKYSIZE=512 '
'-co NUM_THREADS=ALL_CPUS '
'-a_srs \"EPSG:{epsg}\" {png} {tiff} > {log}'.format(**kwargs))
geotiffcreated = True
if not geotiffcreated:
log.ODM_WARNING('No geo-referenced orthophoto created due '
'to missing geo-referencing or corner coordinates.')
system.run('gdal_translate -a_ullr {ulx} {uly} {lrx} {lry} '
'-co TILED=yes '
'-co COMPRESS=DEFLATE '
'-co PREDICTOR=2 '
'-co BLOCKXSIZE=512 '
'-co BLOCKYSIZE=512 '
'-co NUM_THREADS=ALL_CPUS '
'-a_srs \"EPSG:{epsg}\" {png} {tiff} > {log}'.format(**kwargs))
geotiffcreated = True
if not geotiffcreated:
log.ODM_WARNING('No geo-referenced orthophoto created due '
'to missing geo-referencing or corner coordinates.')
else:
log.ODM_WARNING('Found a valid orthophoto in: %s' % tree.odm_orthophoto_file)