diff --git a/opendm/config.py b/opendm/config.py index 47e1bd5c..2fcbf8a2 100755 --- a/opendm/config.py +++ b/opendm/config.py @@ -707,6 +707,17 @@ def config(argv=None): help=('Use images\' GPS exif data for reconstruction, even if there are GCPs present.' 'This flag is useful if you have high precision GPS measurements. ' 'If there are no GCPs, this flag does nothing. Default: %(default)s')) + + parser.add_argument('--gps-accuracy', + type=float, + action=StoreValue, + metavar='', + default=15, + help='Set a value in meters for the GPS Dilution of Precision (DOP) ' + 'information for all images. If your images are tagged ' + 'with high precision GPS information (RTK), this value will be automatically ' + 'set accordingly. You can use this option to manually set it in case the reconstruction ' + 'fails. Lowering this option can sometimes help control bowling-effects over large areas. Default: %(default)s') parser.add_argument('--optimize-disk-space', action=StoreTrue, diff --git a/opendm/osfm.py b/opendm/osfm.py index 9fc1daf5..d8ec9e05 100644 --- a/opendm/osfm.py +++ b/opendm/osfm.py @@ -103,6 +103,35 @@ class OSFMContext: log.ODM_INFO("Multi-camera setup, using BOW matching") use_bow = True + # GPSDOP override if we have GPS accuracy information (such as RTK) + override_gps_dop = 'gps_accuracy_is_set' in args + for p in photos: + if p.get_gps_dop() is not None: + override_gps_dop = True + break + + if override_gps_dop: + if 'gps_accuracy_is_set' in args: + log.ODM_INFO("Forcing GPS DOP to %s for all images" % args.gps_accuracy) + else: + log.ODM_INFO("Looks like we have RTK accuracy info for some photos. Good! We'll use it.") + + exif_overrides = {} + for p in photos: + dop = args.gps_accuracy if 'gps_accuracy_is_set' in args else p.get_gps_dop() + if dop is not None and p.latitude is not None and p.longitude is not None: + exif_overrides[p.filename] = { + 'gps': { + 'latitude': p.latitude, + 'longitude': p.longitude, + 'altitude': p.altitude if p.altitude is not None else 0, + 'dop': dop, + } + } + + with open(os.path.join(self.opensfm_project_path, "exif_overrides.json"), 'w') as f: + f.write(json.dumps(exif_overrides)) + # create config file for OpenSfM config = [ "use_exif_size: no", diff --git a/opendm/photo.py b/opendm/photo.py index 4f97c2fb..ecb25a4f 100644 --- a/opendm/photo.py +++ b/opendm/photo.py @@ -60,6 +60,10 @@ class ODM_Photo: # self.center_wavelength = None # self.bandwidth = None + # RTK + self.gps_xy_stddev = None # Dilution of Precision X/Y + self.gps_z_stddev = None # Dilution of Precision Z + # parse values from metadata self.parse_exif_values(path_file) @@ -183,6 +187,22 @@ class ODM_Photo: 'Camera:Irradiance', ], float) + # Phantom 4 RTK + if '@drone-dji:RtkStdLon' in tags: + y = float(self.get_xmp_tag(tags, '@drone-dji:RtkStdLon')) + x = float(self.get_xmp_tag(tags, '@drone-dji:RtkStdLat')) + self.gps_xy_stddev = max(x, y) + + if '@drone-dji:RtkStdHgt' in tags: + self.gps_z_stddev = float(self.get_xmp_tag(tags, '@drone-dji:RtkStdHgt')) + else: + self.set_attr_from_xmp_tag('gps_xy_stddev', tags, [ + '@Camera:GPSXYAccuracy' + ], float) + self.set_attr_from_xmp_tag('gps_z_stddev', tags, [ + '@Camera:GPSZAccuracy' + ], float) + if 'DLS:Yaw' in tags: self.set_attr_from_xmp_tag('dls_yaw', tags, ['DLS:Yaw'], float) self.set_attr_from_xmp_tag('dls_pitch', tags, ['DLS:Pitch'], float) @@ -239,6 +259,7 @@ class ODM_Photo: if xmp_start < xmp_end: xmp_str = img_str[xmp_start:xmp_end + 12] + xmp_str = xmp_str.replace("rdf:about=''xmlns", "rdf:about='' xmlns") xdict = x2d.parse(xmp_str) xdict = xdict.get('x:xmpmeta', {}) xdict = xdict.get('rdf:RDF', {}) @@ -361,4 +382,15 @@ class ODM_Photo: if self.bits_per_sample: return float(2 ** self.bits_per_sample) + return None + + def get_gps_dop(self): + val = -9999 + if self.gps_xy_stddev is not None: + val = self.gps_xy_stddev + if self.gps_z_stddev is not None: + val = max(val, self.gps_z_stddev) + if val > 0: + return val + return None \ No newline at end of file