Drone2Map: How to adjust image altitude in EXIF metadata from DJI Drones

8333
3
05-03-2018 07:13 AM
DavidSchwab1
New Contributor

I just went through a workflow that I wanted to highlight here in case it helps anyone with post processing drone images in Drone2Map.  This is for those people using DJI drones.  Sorry if this has already been covered elsewhere.

When producing the EXIF metadata for each image, the image altitude is populated with the DJI aircrafts GPS altitude reading.  This can be several hundred feet off, which is obvious in Drone2Map when you view the images in 3D view.  For example, I was doing a 3D building survey and flew two loops at roughly 30 and 60 feet off the ground.  When I viewed the captured images in Drone2Map 3D view, they appeared more like 200-230 feet off of the ground and processing the images resulted in bizarre results even when running GCP's.  After browsing some DJI forums, I found the fix. 

Sidenote: While collecting data, first calibrate the IMU through the DJI go app so that you can be sure your barometer readings are as accurate as possible given changes in weather and elevation between different work sites.

There is another field in the EXIF metadata called RelativeAltitude which is given from the aircraft barometer.  This altitude is far more accurate, but you need to do some work to get Drone2Map to adjust image altitude to these readings.

Most flights with DJI and DroneDeploy will navigate to the flight altitude relative from takeoff, then stay there for the duration of the flight, not adjusting flight height relative to terrain.   So, you want to adjust flight height based on the elevation of the takeoff point.  You can get the elevation of the take off point in several ways. If you have elevations from GCP's use those.  If not, you can go to manage GCP, then add "from map".  Click where you took off and the GCP added will give you an elevation.

The Next step is to create a flat DEM with the takeoff elevation as the only elevation value across the whole raster.  I just created a polygon, added a field and put the takeoff elevation, then use the "Convert Raster to Polygon" tool to make it into a DEM.  Note that Drone2Map needs the DEM to be in .tif format.

So with that in place, I had two sets of images (Flown at different altitudes) I needed to adjust, and Drone2Map 1.3 added the necessary feature to do this.  First, I loaded one image into this tool Jeffrey Friedl's Image Metadata Viewer  in order to see the RelativeAltitude reading.  Next, I select all images that were flown at the same height and select "adjust image altitude". Enter the value from the Relative Altitude for all images flown at one height. Then select your custom DEM and hit Ok.  All of the images altitudes will be adjusted to the takeoff elevation.  If you use the elevation service, it will not work correctly and adjust the image altitude based on underlying terrain.  Repeat this process for any images that are at different flight heights.

Not sure if this makes perfect sense or not but there ya go.  I found this process easier than using command line EXIF readers and cheaper than using MapsMadeEasy image altitude adjuster.  Hope this helps someone, it improved my results immensely even with GCP's.

Cheers

3 Replies
MervynLotter
Occasional Contributor III

Hi David

This was an incredibly useful post, thank you. I have a DJI drone and I have been struggling with creating a 3D products from Drone2Map that are vertically aligned with the surrounding landscape. I have tried adding GCP points but that usually results in a very skewed final product. This ultimately results in me not wanting to share my Drone2Map products with others until such time as I sort out the altitude problem. Your workflow has solved that. 

To simplify your workflow, I have created a new idea for Drone2Map on the ArcGIS Ideas site. Do see https://community.esri.com/ideas/15040 

Thanks so much.

Regards

Mervyn

0 Kudos
SteffenDoering
New Contributor

Hi all,

also not being a command line king  I found an easy tool to adjust EXIF data - the ExifToolGui

With the following steps you can adjust eg. the wrong height and/or the shifted coordinates from the new DJI Mavic 2 Pro.

EXIFTOOLGUI
  • Save exiftools in -> C:\Windows\exiftool.exe and ExifToolGui in a folder of your choice (-> readme.txt)
  • in ExiftoolGui
    • Write the height of your starting position or any other height in the Exif file -> in the little central window (-> button Exiftool direct) with following commands
      •  exiftool -GPSAltitude=123 m DJI_123.jpg (Image path) or for more images

        exiftool -GPSAltitude=123 m a.jpg b.jpg c.jpg

      • exiftool -GPSAltitude=123 m G:\...\... (folder path -> for changing all images of a folder)
      • ExifTool Command-Line Examples 
    • Create a XMP_Sidecarfile -> Export/Import -> EXPORT Metadata into XMP = Sidecar file (Metadata Sidecar Files )
    • Finally again -> Export/Import -> Import GPS-Data from XMP -> the right coordinates are written from the XMP file to the Exif file
More exiftool commands
Best regards! 
Steffen 
0 Kudos
City_ofGlendale__CA
New Contributor

Late to the party, but i had a similar issue.  DJI drone had altitude reading offset by 50 meters.  I used this python script to adjust the exif tag instead of re-flying the mission.  Hope it can help other people.

   -Edit.  Provided additional script to adjust XMP header as well.  Turns out Drone2Map will read XMP data instead of EXIF when xmp is available.  However it should be run on Linux unless you want to compile exempi on windows.  I used a Vbox image for this.  

## piexif, python3.7

import glob 
import piexif
from fractions import Fraction

def to_deg(value, loc):
    """convert decimal coordinates into degrees, munutes and seconds tuple
    Keyword arguments: value is float gps-value, loc is direction list ["S", "N"] or ["W", "E"]
    return: tuple like (25, 13, 48.343 ,'N')
    """
    if value < 0:
        loc_value = loc[0]
    elif value > 0:
        loc_value = loc[1]
    else:
        loc_value = ""
    abs_value = abs(value)
    deg =  int(abs_value)
    t1 = (abs_value-deg)*60
    min = int(t1)
    sec = round((t1 - min)* 60, 5)
    return (deg, min, sec, loc_value)

def change_to_rational(number):
    """convert a number to rantional
    Keyword arguments: number
    return: tuple like (1, 2), (numerator, denominator)
    """
    f = Fraction(str(round(number,4)))
    return (f.numerator, f.denominator)
	
def change_to_decimal(fraction):
	"""convert fraction tuple to decimal
	keyword arguments: fraction -- tuple like (numerator, denominator)
	return: float"""
	return (fraction[0]/fraction[1])


def set_gps_location(file_name, lat, lng, altitude):
    """Adds GPS position as EXIF metadata
    Keyword arguments:
    file_name -- image file
    lat -- latitude (as float)
    lng -- longitude (as float)
    altitude -- altitude (as float)
    """
    lat_deg = to_deg(lat, ["S", "N"])
    lng_deg = to_deg(lng, ["W", "E"])

    exiv_lat = (change_to_rational(lat_deg[0]), change_to_rational(lat_deg[1]), change_to_rational(lat_deg[2]))
    exiv_lng = (change_to_rational(lng_deg[0]), change_to_rational(lng_deg[1]), change_to_rational(lng_deg[2]))

    gps_ifd = {
        piexif.GPSIFD.GPSVersionID: (2, 0, 0, 0),
        piexif.GPSIFD.GPSAltitudeRef: 1,
        piexif.GPSIFD.GPSAltitude: change_to_rational(round(altitude)),
        piexif.GPSIFD.GPSLatitudeRef: lat_deg[3],
        piexif.GPSIFD.GPSLatitude: exiv_lat,
        piexif.GPSIFD.GPSLongitudeRef: lng_deg[3],
        piexif.GPSIFD.GPSLongitude: exiv_lng,
    }

    exif_dict = {"GPS": gps_ifd}
    exif_bytes = piexif.dump(exif_dict)
    piexif.insert(exif_bytes, file_name)


def change_gps_altitude(file_name, delta_altitude):
	exif_dict = piexif.load(file_name)
	gps_altitude = exif_dict['GPS'][piexif.GPSIFD.GPSAltitude]
	gps_altitude = change_to_rational(change_to_decimal(gps_altitude)+delta_altitude)
	print("altitude: " + str(gps_altitude))
	exif_dict['GPS'][piexif.GPSIFD.GPSAltitude] = gps_altitude
	exif_bytes = piexif.dump(exif_dict)
	piexif.insert(exif_bytes, file_name)

##End Utility Functions 


delta_altitude = 47.97
path = "C:\\Users\\tli\\Pictures\\Drone2Map_Pilot\\"
file_list = [f for f in glob.glob(path + "**/*.JPG", recursive=True)]
for file_name in file_list: 
	print("Processing: " + file_name)
	change_gps_altitude(file_name, delta_altitude)

XMP adjust

# This script needs to run on linux due to dependencies on exempi library
# python-xmp-toolkit, python 3.6

from libxmp import XMPFiles, consts
import glob


def change_xmp_altitude(file_name, delta_altitude):
    xmpfile = XMPFiles(file_path=file_name, open_forupdate=True)
    xmp = xmpfile.get_xmp()
    # list the uri for prefix drone-dji
    # xmp_sub = xmp.get_namespace_for_prefix("drone-dji")
    dji_uri = "http://www.dji.com/drone-dji/1.0/"
    xmp_absalt = xmp.get_property(dji_uri, "AbsoluteAltitude")
    str_translation = dict.fromkeys(map(ord, '+'), None)
    xmp_absalt = xmp_absalt.translate(str_translation)
    xmp_absalt = "+" + str(round(float(xmp_absalt) + delta_altitude, 2))
    xmp.set_property(dji_uri, "AbsoluteAltitude", xmp_absalt)
    if xmpfile.can_put_xmp(xmp):
        xmpfile.put_xmp(xmp)
        xmpfile.close_file()
        print(xmp_absalt, "New XMP Altitude Entered for: ", file_name)


delta_altitude = 47.97
path = "/home/tli/COG/Drone2MapTest"
file_list = [f for f in glob.glob(path + "**/*.JPG", recursive=True)]
for file_name in file_list:
    print("Processing: " + file_name)
    change_xmp_altitude(file_name, delta_altitude)
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍