New to WebODM, processed obliques to 3D?

I have access to a processed set of oblique imagery (4 cardinal directions) that was flown from an airplane, would I be able to process these into a 3D model? Or would I need something unprocessed?

The processed obliques have a .psi file type and the webODM file uploader would not accept them. I’m a first time user to webODM and am not very experienced. Please let me know what other information would be useful!

You could see if the PSI file is some other format in disguise like a jpeg. It could very well be… . But make sure you understand the license on the data. :smiley:

2 Likes

You’re a genius. They were masquerading.

Thank you!

1 Like

Post some screenshots from the processing. I have been very curious to try this.

2 Likes

I’m still trying to figure everything out. When I ran it the first time I just used the obliques and I got an image that was nightmarish. There were also flat images which I added to the second processing and then everything turned out looking alright. I’m checking out the .laz right now. There are approximately 285 contiguous sections that I have access to each with 113 images each (32205 total images). How should I approach this task?

Full results here: https://drive.google.com/file/d/1StMg_sPYuqeTgoUbRFDnwGS4ao0b61Uv/view?usp=sharing

oblique_and_orthos_01 Obliques_Only.PNG

3 Likes

As a follow-up, what should/can I do to spatially correct the position? These are currently showing up in Null Island.

I have a shapefile with polygons that display the extent of each photo in WGS84, I’m just not sure how to exploit that information appropriately.

You will need to extract the centroid of each of those locations and apply them back to the exif data in the jpegs. Exiftool can do this, and you will need to set the following flags:

                    -GPSLongitude
                    -GPSLatitude
                    -GPSAltitude
                    -GPSLongitudeRef= (set to E or W)
                    -GPSLatitudeRef= (set to N or S)
                    -GPSAltitudeRef="above sea level"

Then OpenDroneMap will take those values and use them to initiate the model in real geographic coordinates.

2 Likes

I am using the library piexif to re-write the exif data according to the spatial coordinates contained in the companion shapefile. I am using the following codeblock to write the exif data:

import arcpy
import os.path
import piexif
from fractions import Fraction
from PIL import Image

def to_deg(value):
    """convert decimal coordinates into degrees, minutes and seconds tuple
    Keyword arguments: value is float gps-value
    return: tuple like (25, 13, 48.343)
    """
    abs_value = abs(value)
    deg =  int(abs_value)
    t1 = (abs_value-deg)*60
    min = int(t1)
    sec = round((t1 - min)* 60, 5)
    f = Fraction(str(sec))
    return [(deg, 1), (min, 1), (f.numerator, f.denominator)]

images_NOR = "TXCOLL19_NOR"
images_fields = ["ImageName", "ImagePath", "CENTROID_X", "CENTROID_Y"]
#Centroid Fields are decimal coordinates

with arcpy.da.SearchCursor(images_NOR, images_fields) as scursor:
    for srow in scursor:
        img = f"{srow[1]}{srow[0]}.jpg"
        if os.path.isfile(img):
            exif_dict = piexif.load(img)
            dms_long = to_deg(srow[2])
            dms_lat = to_deg(srow[3])

            exif_dict['GPS'][piexif.GPSIFD.GPSAltitude] = (609, 1)
            exif_dict['GPS'][piexif.GPSIFD.GPSLongitude] = dms_long
            exif_dict['GPS'][piexif.GPSIFD.GPSLatitude] = dms_lat
            exif_dict['GPS'][piexif.GPSIFD.GPSAltitudeRef] = 0
            exif_dict['GPS'][piexif.GPSIFD.GPSLongitudeRef] = 'W'
            exif_dict['GPS'][piexif.GPSIFD.GPSLatitudeRef] = 'N'
            
            exif_bytes = piexif.dump(exif_dict)
            piexif.insert(exif_bytes, img)

print("Completed NOR")

After I processed a sample set, I got the following error message:

Traceback (most recent call last):
File "/code/run.py", line 61, in <module>
app.execute()
File "/code/stages/odm_app.py", line 92, in execute
self.first_stage.run()
File "/code/opendm/types.py", line 325, in run
self.process(self.args, outputs)
File "/code/stages/dataset.py", line 89, in process
photos += [types.ODM_Photo(f)]
File "/code/opendm/photo.py", line 68, in __init__
self.parse_exif_values(path_file)
File "/code/opendm/photo.py", line 95, in parse_exif_values
self.latitude = self.dms_to_decimal(tags['GPS GPSLatitude'], tags['GPS GPSLatitudeRef'])
File "/code/opendm/photo.py", line 275, in dms_to_decimal
degrees, minutes, seconds = self.float_values(dms)
ValueError: need more than 1 value to unpack

I thought that I had written the GPS data correctly, would you mind providing some guidance? I’ve attached one of the photos that I inserted exif data.
TXXCCR026027NeighObliq6777E_191230
exif
and a link: https://drive.google.com/file/d/1-rFrY1g6ZFs61P8Mc6NDxO7RLizHYQ7N/view?usp=sharing

I’m fairly stumped on this. I’ve cross-referenced the exif tags that I wrote against some DJI Phantom 4 images that I had laying around; where the GPS is concerned the only difference was a GPSversionID, which I added into my script and re-ran.

I’m still receiving the above error, which appears to imply that my DMS values are not correct, but I they seem to check out. Should I re-post this into a different channel?

1 Like

Can you also post one from DJI for reference?

(Also, understand, this forum is staffed in people’s spare time. Sometimes, these follow-ups to posts get missed.)

2 Likes

Here is a DJI image for reference:

I don’t mean to be pushy, I’m both very excited about getting this working and a bit frustrated with my own lack of understanding. I’m going to work through the methods in photo.py and see if how exifread is working on the data.

2 Likes

Honk. Please cancel the goose chase.

I’m sheepishly guessing that I made some sort of error when I initially uploaded the photos, but didn’t reupload the photos in the task dashboard. Just now, I deleted the task, reuploaded the photos and they are processing without any errors.

3 Likes

:poop:happens, especially around geese :wink:

Glad you (most likely?) have it sorted!

Throw up some results when you get a chance.

3 Likes

Been there, done that. For years, almost every time I documented a “bug” in open source software, by the time I was done documenting it, I realized something I had done wrong. It was really annoying. :smiley:

3 Likes

Okay I need a sanity check now. Before pushing in the exif data, I was able to process both of these samples on my docker instance running 9gb RAM and 8 cores (I understand that it is below the minimum requirements but I wanted to have some sort of deliverable before asking my boss for more hardware to run the entire dataset) in about 1.5 - 2 hours each.

However, now I only get about 8-10 minutes in when I run into a worker termination. I don’t think that it is a memory issue unless the introduction of GPS information significantly increased the processing requirements?:

2020-05-23 22:52:10,079 DEBUG: No segmentation for TXXCCR026028NeighOrtho3074_191230.jpg, no features masked.
2020-05-23 22:52:10,084 DEBUG: No segmentation for TXXCCR026028NeighObliq2971N_191227.jpg, no features masked.
2020-05-23 22:57:42,765 ERROR: exception calling callback for <Future at 0x7f02d41628d0 state=finished raised TerminatedWorkerError>
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/joblib/externals/loky/_base.py", line 625, in _invoke_callbacks
callback(self)
File "/usr/local/lib/python2.7/dist-packages/joblib/parallel.py", line 309, in __call__
self.parallel.dispatch_next()
File "/usr/local/lib/python2.7/dist-packages/joblib/parallel.py", line 731, in dispatch_next
if not self.dispatch_one_batch(self._original_iterator):
File "/usr/local/lib/python2.7/dist-packages/joblib/parallel.py", line 759, in dispatch_one_batch
self._dispatch(tasks)
File "/usr/local/lib/python2.7/dist-packages/joblib/parallel.py", line 716, in _dispatch
job = self._backend.apply_async(batch, callback=cb)
File "/usr/local/lib/python2.7/dist-packages/joblib/_parallel_backends.py", line 510, in apply_async
future = self._workers.submit(SafeFunction(func))
File "/usr/local/lib/python2.7/dist-packages/joblib/externals/loky/reusable_executor.py", line 151, in submit
fn, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/joblib/externals/loky/process_executor.py", line 1022, in submit
raise self._flags.broken
TerminatedWorkerError: A worker process managed by the executor was unexpectedly terminated. This could be caused by a segmentation fault while calling the function or by an excessive memory usage causing the Operating System to kill the worker. The exit codes of the workers are {SIGSEGV(-11)}
Traceback (most recent call last):
File "/code/SuperBuild/src/opensfm/bin/opensfm", line 34, in <module>
command.run(args)
File "/code/SuperBuild/src/opensfm/opensfm/commands/match_features.py", line 29, in run
pairs_matches, preport = matching.match_images(data, images, images)
File "/code/SuperBuild/src/opensfm/opensfm/matching.py", line 43, in match_images

return match_images_with_pairs(data, exifs, ref_images, pairs), preport
File "/code/SuperBuild/src/opensfm/opensfm/matching.py", line 67, in match_images_with_pairs

matches = context.parallel_map(match_unwrap_args, args, processes, jobs_per_process)
File "/code/SuperBuild/src/opensfm/opensfm/context.py", line 41, in parallel_map
return Parallel(batch_size=batch_size)(delayed(func)(arg) for arg in args)
File "/usr/local/lib/python2.7/dist-packages/joblib/parallel.py", line 934, in __call__

self.retrieve()
File "/usr/local/lib/python2.7/dist-packages/joblib/parallel.py", line 833, in retrieve

self._output.extend(job.get(timeout=self.timeout))
File "/usr/local/lib/python2.7/dist-packages/joblib/_parallel_backends.py", line 521, in wrap_future_result
return future.result(timeout=timeout)
File "/usr/local/lib/python2.7/dist-packages/joblib/externals/loky/_base.py", line 433, in result

return self.__get_result()
File "/usr/local/lib/python2.7/dist-packages/joblib/externals/loky/_base.py", line 381, in __get_result

raise self._exception
joblib.externals.loky.process_executor
.TerminatedWorkerError:
A worker process managed by the executor was unexpectedly terminated. This could be caused by a segmentation fault while calling the function or by an excessive memory usage causing the Operating System to kill the worker. The exit codes of the workers are {SIGSEGV(-11)}
Traceback (most recent call last):
File "/code/run.py", line 61, in <module>
app.execute()
File "/code/stages/odm_app.py", line 92, in execute
self.first_stage.run()
File "/code/opendm/types.py", line 344, in run
self.next_stage.run(outputs)
File "/code/opendm/types.py", line 344, in run
self.next_stage.run(outputs)
File "/code/opendm/types.py", line 344, in run
self.next_stage.run(outputs)
File "/code/opendm/types.py", line 325, in run
self.process(self.args, outputs)
File "/code/stages/run_opensfm.py", line 30, in process
octx.feature_matching(self.rerun())
File "/code/opendm/osfm.py", line 239, in feature_matching
self.run('match_features')
File "/code/opendm/osfm.py", line 23, in run
(context.opensfm_path, command, self.opensfm_project_path))
File "/code/opendm/system.py", line 76, in run
raise Exception("Child returned {}".format(retcode))
Exception: Child returned 1
2020-05-24 02:00:44,351 DEBUG: Matching TXXCCR011015NeighObliq5588N_191230.jpg and TXXCCR011015NeighOrtho3885X_191230.jpg.  Matcher: FLANN (symmetric) T-desc: 1.140 Matches: FAILED
2020-05-24 02:00:44,406 DEBUG: No segmentation for TXXCCR011015NeighOrtho5692X_191230.jpg, no features masked.
2020-05-24 02:05:47,828 ERROR: exception calling callback for <Future at 0x7f287aba4050 state=finished raised TerminatedWorkerError>
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/joblib/externals/loky/_base.py", line 625, in _invoke_callbacks
callback(self)
File "/usr/local/lib/python2.7/dist-packages/joblib/parallel.py", line 309, in __call__
self.parallel.dispatch_next()
File "/usr/local/lib/python2.7/dist-packages/joblib/parallel.py", line 731, in dispatch_next
if not self.dispatch_one_batch(self._original_iterator):
File "/usr/local/lib/python2.7/dist-packages/joblib/parallel.py", line 759, in dispatch_one_batch
self._dispatch(tasks)
File "/usr/local/lib/python2.7/dist-packages/joblib/parallel.py", line 716, in _dispatch
job = self._backend.apply_async(batch, callback=cb)
File "/usr/local/lib/python2.7/dist-packages/joblib/_parallel_backends.py", line 510, in apply_async
future = self._workers.submit(SafeFunction(func))
File "/usr/local/lib/python2.7/dist-packages/joblib/externals/loky/reusable_executor.py", line 151, in submit
fn, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/joblib/externals/loky/process_executor.py", line 1022, in submit
raise self._flags.broken
TerminatedWorkerError: A worker process managed by the executor was unexpectedly terminated. This could be caused by a segmentation fault while calling the function or by an excessive memory usage causing the Operating System to kill the worker. The exit codes of the workers are {SIGSEGV(-11)}
Traceback (most recent call last):
File "/code/SuperBuild/src/opensfm/bin/opensfm", line 34, in <module>
command.run(args)
File "/code/SuperBuild/src/opensfm/opensfm/commands/match_features.py", line 29, in run
pairs_matches, preport = matching.match_images(data, images, images)
File "/code/SuperBuild/src/opensfm/opensfm/matching.py", line 43, in match_images
return match_images_with_pairs(data, exifs, ref_images, pairs), preport
File "/code/SuperBuild/src/opensfm/opensfm/matching.py", line 67, in match_images_with_pairs
matches = context.parallel_map(match_unwrap_args, args, processes, jobs_per_process)
File "/code/SuperBuild/src/opensfm/opensfm/context.py", line 41, in parallel_map
return Parallel(batch_size=batch_size)(delayed(func)(arg) for arg in args)
File "/usr/local/lib/python2.7/dist-packages/joblib/parallel.py", line 934, in __call__
self.retrieve()
File "/usr/local/lib/python2.7/dist-packages/joblib/parallel.py", line 833, in retrieve
self._output.extend(job.get(timeout=self.timeout))
File "/usr/local/lib/python2.7/dist-packages/joblib/_parallel_backends.py", line 521, in wrap_future_result
return future.result(timeout=timeout)
File "/usr/local/lib/python2.7/dist-packages/joblib/externals/loky/_base.py", line 433, in result
return self.__get_result()
File "/usr/local/lib/python2.7/dist-packages/joblib/externals/loky/_base.py", line 381, in __get_result
raise self._exception
joblib.externals.loky.process_executor.
TerminatedWorkerError: A worker process managed by the executor was unexpectedly terminated. This could be caused by a segmentation fault while calling the function or by an excessive memory usage causing the Operating System to kill the worker. The exit codes of the workers are {SIGSEGV(-11)}
Traceback (most recent call last):
File "/code/run.py", line 61, in <module>
app.execute()
File "/code/stages/odm_app.py", line 95, in execute
self.first_stage.run()
File "/code/opendm/types.py", line 350, in run
self.next_stage.run(outputs)
File "/code/opendm/types.py", line 350, in run
self.next_stage.run(outputs)
File "/code/opendm/types.py", line 350, in run
self.next_stage.run(outputs)
File "/code/opendm/types.py", line 331, in run
self.process(self.args, outputs)
File "/code/stages/run_opensfm.py", line 30, in process
octx.feature_matching(self.rerun())
File "/code/opendm/osfm.py", line 239, in feature_matching
self.run('match_features')
File "/code/opendm/osfm.py", line 23, in run
(context.opensfm_path, command, self.opensfm_project_path))
File "/code/opendm/system.py", line 76, in run
raise Exception("Child returned {}".format(retcode))
Exception: Child returned 1

This is a link to each of the sample sets that I am using:
https://drive.google.com/open?id=1Zy-Tp8x6fSolDzY9_njUgvwYatQRDJEx

If someone would not mind trying to run this that would be great. I’m looking to see if they georeferenced correctly and a .las pointcloud so that I can compare it with some LiDAR data that we have. If this is just an under-powered machine issue then I don’t have any qualms asking for more hardware. But if this is a, I-messed-something-up-by-injecting-exif-data, then that’ll probably put an end to this testing for me.

I also deleted/pruned my container, re-cloned the webodm repo and recreated the contained.

1 Like

It runs to completion, but with some pretty weird effects:
https://webodm.cmparks.net/public/task/56d2a5b4-e483-49f8-9213-12184bd6367d/map/

I think the orthophotos need to have a different camera tag from the off nadir images. I also, wonder if the orthophotos should be included at all.

2 Likes

Thanks for running that for me! I’m going to remove those orthos and try to process again.

EDIT:

I processed them again and they are all warped and distorted. I have the direction information for the obliques in my other data file so I’m going to plug that in as well and see if it’ll smooth things out.

EDIT 2:

I processed them again with directional information, the farmland piece turned out looking okay in 2D my coordinates seem to be off somewhat and the texture model was cratered for the farmland, I’m guessing that’s because I’m using a static value for altitude? The city sample was totally mangled. I’ve updated all the datasets in my google drive with directional exifs now.

I’m slowly forming the opinion that without having access to the raw/original data that this is something of a fool’s errand. However, I am excited to go fly our drones and create some 3D and ortho products for myself.

image
image

1 Like

I think the issue here is that this isn’t really the raw data. I suspect that corrections have been already applied, including terrain corrections, which creates a strange very non-linear structure from motion result.

One last thing to try would be to set camera-lens to perspective to give minimal lens parameters for the structure from motion step to play with. But the odds are getting slimmer that anything will work without getting at the true raw imagery.

2 Likes

There were significant improvements, much smoother in the center, with the perspective setting but the georeferencing became jumbled up. I removed the orientation tags and that corrected the georeferencing similar to the pre-perspective setting. I’m going to throw in the towel on this and see what I can do about getting raw data from the vendor. Thanks again for all your help!

image
image

3 Likes