Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example dataset link broken #4

Open
islandmonkey opened this issue Oct 2, 2018 · 9 comments
Open

Example dataset link broken #4

islandmonkey opened this issue Oct 2, 2018 · 9 comments

Comments

@islandmonkey
Copy link

islandmonkey commented Oct 2, 2018

@alexhagiopol,

unfortunately your example dataset link is broken. Is there any chance you could upload it again?

Cheers

@cmbasnett
Copy link

cmbasnett commented Jul 4, 2019

I have made a quick-and-dirty script that can generate an example data set from a set of DJI drone images (it extracts the EXIF/XMP data to get the XYZYPR values needed).

import os
from bs4 import BeautifulSoup
from PIL import Image, ExifTags
from pymap3d import ecef2enu, geodetic2ecef
import numpy as np


def dms_to_decimal(d, m, s):
    return d + (m / 60.0) + (s / 3600.0)


def get_gps_coords(im):
    """
    Gets latitude and longitude values from image EXIF data.
    :param im:
    :return:
    """
    exif = im.getexif()
    exif_data = dict()
    for tag, value in exif.items():
        decoded_tag = ExifTags.TAGS.get(tag, tag)
        exif_data[decoded_tag] = value
    gps_info = exif_data['GPSInfo']
    lat_dms = map(lambda x: x[0] / float(x[1]), gps_info[2])
    lat = dms_to_decimal(*lat_dms)
    if gps_info[1] == 'S':
        lat *= -1
    lng_dms = map(lambda x: x[0] / float(x[1]), gps_info[4])
    lng = dms_to_decimal(*lng_dms)
    if gps_info[3] == 'W':
        lng *= -1
    return lat, lng


def get_data(path):
    lat0 = None
    lon0 = None
    h0 = 0
    for root, dirs, files in os.walk(path):
        for filename in sorted(filter(lambda x: os.path.splitext(x)[1].lower() == '.jpg', files)):
            filepath = os.path.join(root, filename)
            with Image.open(filepath) as im:
                for segment, content in im.applist:
                    marker, body = content.split('\x00', 1)
                    if segment == 'APP1' and marker == 'http://ns.adobe.com/xap/1.0/':
                        soup = BeautifulSoup(body, features='html.parser')
                        description = soup.find('x:xmpmeta').find('rdf:rdf').find('rdf:description')
                        pitch = float(description['drone-dji:gimbalpitchdegree']) + 90
                        yaw = float(description['drone-dji:gimbalyawdegree'])
                        roll = float(description['drone-dji:gimbalrolldegree'])
                        alt = float(description['drone-dji:relativealtitude'])
                        lat, lon = get_gps_coords(im)
                        if lat0 is None:
                            lat0 = lat
                            lon0 = lon
                        x, y, z = geodetic2ecef(lat, lon, alt)
                        x, y, z = ecef2enu(x, y, z, lat0, lon0, h0)
                        yield filename, '{:f}'.format(x), '{:f}'.format(y), '{:f}'.format(z), yaw, pitch, roll


def main():
    data = [d for d in get_data('datasets/images')]
    data = sorted(data, key=lambda x: x[0])
    x = np.array(map(lambda d: d[1], data))
    y = np.array(map(lambda d: d[2], data))
    with open('datasets/imageData.txt', 'w+') as f:
        for datum in data:
            f.write(','.join([str(d) for d in datum]) + '\n')


if __name__ == '__main__':
    main()

To get the XYZ values expected by the dataset, the script uses GPS coordinates, converts them from WGS84->ECEF->ENU, with the "origin" of the ENU space being the first image.

If you put images in the datasets/images folder and run this script, you'll get a file called imageData.txt written out in a CSV format like this:

DJI_0023.JPG,0.000000,-0.000000,58.600000,56.2,0.0,0.0
DJI_0024.JPG,6.390588,4.473257,58.599995,56.1,0.0,0.0
DJI_0025.JPG,23.154435,15.434010,58.599939,55.5,0.0,0.0
DJI_0026.JPG,26.520979,17.642848,58.599921,55.7,0.1,0.0

An interesting note, the pitch value treats nadir (straight-down) as 0 degrees pitch, not -90.

Hope this helps some folks getting this working, here's some preliminary results from a few sets of photos:

Screen Shot 2019-07-04 at 11 19 44 AM

OpenCV4

I also had to do a few tweaks to get the script working with OpenCV4:

cv.SURF is deprecated

The call to cv.SURF(500)was deprecated, and is apparently a patented algorithm that they can't include anymore in modern versions of OpenCV. I simply changed it to use ORB: cv2.ORB_create().

estimateRigidTransform is deprecated

The new docs lay out that there are two new functions that do the same thing. In this case, we want to replace the call with A, _ = cv2.estimateAffinePartial2D(src_pts, dst_pts).

@alexhagiopol
Copy link
Owner

Hi @cmbasnett and @island-monkey . I apologize for neglecting this issue for so long. For some reason, I never got an email notification about problem this until Colin posted his message above.

It's true that the Dropbox account where I stored my example data 4 years ago no longer exists. I will try to locate the data, but it's possible it could be lost forever 😔.

Colin's code looks very useful though I have not had the chance to review in detail, and - without the data - I would not be able to test the results myself. I will post here if I am able to come up with a solution.

@selva221724
Copy link

I have made a quick-and-dirty script that can generate an example data set from a set of DJI drone images (it extracts the EXIF/XMP data to get the XYZYPR values needed).

import os
from bs4 import BeautifulSoup
from PIL import Image, ExifTags
from pymap3d import ecef2enu, geodetic2ecef
import numpy as np


def dms_to_decimal(d, m, s):
    return d + (m / 60.0) + (s / 3600.0)


def get_gps_coords(im):
    """
    Gets latitude and longitude values from image EXIF data.
    :param im:
    :return:
    """
    exif = im.getexif()
    exif_data = dict()
    for tag, value in exif.items():
        decoded_tag = ExifTags.TAGS.get(tag, tag)
        exif_data[decoded_tag] = value
    gps_info = exif_data['GPSInfo']
    lat_dms = map(lambda x: x[0] / float(x[1]), gps_info[2])
    lat = dms_to_decimal(*lat_dms)
    if gps_info[1] == 'S':
        lat *= -1
    lng_dms = map(lambda x: x[0] / float(x[1]), gps_info[4])
    lng = dms_to_decimal(*lng_dms)
    if gps_info[3] == 'W':
        lng *= -1
    return lat, lng


def get_data(path):
    lat0 = None
    lon0 = None
    h0 = 0
    for root, dirs, files in os.walk(path):
        for filename in sorted(filter(lambda x: os.path.splitext(x)[1].lower() == '.jpg', files)):
            filepath = os.path.join(root, filename)
            with Image.open(filepath) as im:
                for segment, content in im.applist:
                    marker, body = content.split('\x00', 1)
                    if segment == 'APP1' and marker == 'http://ns.adobe.com/xap/1.0/':
                        soup = BeautifulSoup(body, features='html.parser')
                        description = soup.find('x:xmpmeta').find('rdf:rdf').find('rdf:description')
                        pitch = float(description['drone-dji:gimbalpitchdegree']) + 90
                        yaw = float(description['drone-dji:gimbalyawdegree'])
                        roll = float(description['drone-dji:gimbalrolldegree'])
                        alt = float(description['drone-dji:relativealtitude'])
                        lat, lon = get_gps_coords(im)
                        if lat0 is None:
                            lat0 = lat
                            lon0 = lon
                        x, y, z = geodetic2ecef(lat, lon, alt)
                        x, y, z = ecef2enu(x, y, z, lat0, lon0, h0)
                        yield filename, '{:f}'.format(x), '{:f}'.format(y), '{:f}'.format(z), yaw, pitch, roll


def main():
    data = [d for d in get_data('datasets/images')]
    data = sorted(data, key=lambda x: x[0])
    x = np.array(map(lambda d: d[1], data))
    y = np.array(map(lambda d: d[2], data))
    with open('datasets/imageData.txt', 'w+') as f:
        for datum in data:
            f.write(','.join([str(d) for d in datum]) + '\n')


if __name__ == '__main__':
    main()

To get the XYZ values expected by the dataset, the script uses GPS coordinates, converts them from WGS84->ECEF->ENU, with the "origin" of the ENU space being the first image.

If you put images in the datasets/images folder and run this script, you'll get a file called imageData.txt written out in a CSV format like this:

DJI_0023.JPG,0.000000,-0.000000,58.600000,56.2,0.0,0.0
DJI_0024.JPG,6.390588,4.473257,58.599995,56.1,0.0,0.0
DJI_0025.JPG,23.154435,15.434010,58.599939,55.5,0.0,0.0
DJI_0026.JPG,26.520979,17.642848,58.599921,55.7,0.1,0.0

An interesting note, the pitch value treats nadir (straight-down) as 0 degrees pitch, not -90.

Hope this helps some folks getting this working, here's some preliminary results from a few sets of photos:

Screen Shot 2019-07-04 at 11 19 44 AM

OpenCV4

I also had to do a few tweaks to get the script working with OpenCV4:

cv.SURF is deprecated

The call to cv.SURF(500)was deprecated, and is apparently a patented algorithm that they can't include anymore in modern versions of OpenCV. I simply changed it to use ORB: cv2.ORB_create().

estimateRigidTransform is deprecated

The new docs lay out that there are two new functions that do the same thing. In this case, we want to replace the call with A, _ = cv2.estimateAffinePartial2D(src_pts, dst_pts).

HI mate, could you please share your Combiner.py script here... i am not able to come to a solution here, thanks

@cmbasnett
Copy link

@selva221724 I don't have the script on my machine anymore. The Combiner.py script I used is the same as the one that's in the repository except with the changes I laid out at the bottom of my post.

@Sidx369
Copy link

Sidx369 commented Jan 28, 2021

I have made a quick-and-dirty script that can generate an example data set from a set of DJI drone images (it extracts the EXIF/XMP data to get the XYZYPR values needed).

import os
from bs4 import BeautifulSoup
from PIL import Image, ExifTags
from pymap3d import ecef2enu, geodetic2ecef
import numpy as np


def dms_to_decimal(d, m, s):
    return d + (m / 60.0) + (s / 3600.0)


def get_gps_coords(im):
    """
    Gets latitude and longitude values from image EXIF data.
    :param im:
    :return:
    """
    exif = im.getexif()
    exif_data = dict()
    for tag, value in exif.items():
        decoded_tag = ExifTags.TAGS.get(tag, tag)
        exif_data[decoded_tag] = value
    gps_info = exif_data['GPSInfo']
    lat_dms = map(lambda x: x[0] / float(x[1]), gps_info[2])
    lat = dms_to_decimal(*lat_dms)
    if gps_info[1] == 'S':
        lat *= -1
    lng_dms = map(lambda x: x[0] / float(x[1]), gps_info[4])
    lng = dms_to_decimal(*lng_dms)
    if gps_info[3] == 'W':
        lng *= -1
    return lat, lng


def get_data(path):
    lat0 = None
    lon0 = None
    h0 = 0
    for root, dirs, files in os.walk(path):
        for filename in sorted(filter(lambda x: os.path.splitext(x)[1].lower() == '.jpg', files)):
            filepath = os.path.join(root, filename)
            with Image.open(filepath) as im:
                for segment, content in im.applist:
                    marker, body = content.split('\x00', 1)
                    if segment == 'APP1' and marker == 'http://ns.adobe.com/xap/1.0/':
                        soup = BeautifulSoup(body, features='html.parser')
                        description = soup.find('x:xmpmeta').find('rdf:rdf').find('rdf:description')
                        pitch = float(description['drone-dji:gimbalpitchdegree']) + 90
                        yaw = float(description['drone-dji:gimbalyawdegree'])
                        roll = float(description['drone-dji:gimbalrolldegree'])
                        alt = float(description['drone-dji:relativealtitude'])
                        lat, lon = get_gps_coords(im)
                        if lat0 is None:
                            lat0 = lat
                            lon0 = lon
                        x, y, z = geodetic2ecef(lat, lon, alt)
                        x, y, z = ecef2enu(x, y, z, lat0, lon0, h0)
                        yield filename, '{:f}'.format(x), '{:f}'.format(y), '{:f}'.format(z), yaw, pitch, roll


def main():
    data = [d for d in get_data('datasets/images')]
    data = sorted(data, key=lambda x: x[0])
    x = np.array(map(lambda d: d[1], data))
    y = np.array(map(lambda d: d[2], data))
    with open('datasets/imageData.txt', 'w+') as f:
        for datum in data:
            f.write(','.join([str(d) for d in datum]) + '\n')


if __name__ == '__main__':
    main()

To get the XYZ values expected by the dataset, the script uses GPS coordinates, converts them from WGS84->ECEF->ENU, with the "origin" of the ENU space being the first image.

If you put images in the datasets/images folder and run this script, you'll get a file called imageData.txt written out in a CSV format like this:

DJI_0023.JPG,0.000000,-0.000000,58.600000,56.2,0.0,0.0
DJI_0024.JPG,6.390588,4.473257,58.599995,56.1,0.0,0.0
DJI_0025.JPG,23.154435,15.434010,58.599939,55.5,0.0,0.0
DJI_0026.JPG,26.520979,17.642848,58.599921,55.7,0.1,0.0

An interesting note, the pitch value treats nadir (straight-down) as 0 degrees pitch, not -90.

Hope this helps some folks getting this working, here's some preliminary results from a few sets of photos:

Screen Shot 2019-07-04 at 11 19 44 AM

OpenCV4

I also had to do a few tweaks to get the script working with OpenCV4:

cv.SURF is deprecated

The call to cv.SURF(500)was deprecated, and is apparently a patented algorithm that they can't include anymore in modern versions of OpenCV. I simply changed it to use ORB: cv2.ORB_create().

estimateRigidTransform is deprecated

The new docs lay out that there are two new functions that do the same thing. In this case, we want to replace the call with A, _ = cv2.estimateAffinePartial2D(src_pts, dst_pts).

@cmbasnett Can you explain what these numbers separated by comma eg. DJI_0026.JPG,26.520979,17.642848,58.599921,55.7,0.1,0.0 means?

@islandmonkey
Copy link
Author

@Sidx369
from the code:
'{:f}'.format(x), '{:f}'.format(y), '{:f}'.format(z), yaw, pitch, roll
latitude, longitude, altitude, yaw, pitch, roll

where, in the case of the DJI Phantom, yaw, pitch and roll are the values of the gimbal.
If you don't have a gimbal (i.e. your camera is fixed to the drone frame) just use the yaw, pitch, roll of the drone.

@Sidx369
Copy link

Sidx369 commented Jan 31, 2021

Thanks @islandmonkey,
I have an error on running the above script, can anyone tell me how to correct it?
File "C:\Users\Downloads\Aerial repo\orthomosaic\generate_dataset.py", line 44, in get_data marker, body = content.split('\x00', 1) TypeError: a bytes-like object is required, not 'str'

@atharva-ak
Copy link

@Sidx369 use
marker, body = content.split(b'\x00',1)
for the error, b'\x00' this refers '\x00' as bytes-like object.

@AshutoshStark
Copy link

the is making the file but it's printing anything can anyone help

code:

import os
from bs4 import BeautifulSoup
from PIL import Image, ExifTags
from pymap3d import ecef2enu, geodetic2ecef
import numpy as np

def dms_to_decimal(d, m, s):
return d + (m / 60.0) + (s / 3600.0)

def get_gps_coords(im):
"""
Gets latitude and longitude values from image EXIF data.
:param im:
:return:
"""
exif = im.getexif()
exif_data = dict()
for tag, value in exif.items():
decoded_tag = ExifTags.TAGS.get(tag, tag)
exif_data[decoded_tag] = value
gps_info = exif_data['GPSInfo']
lat_dms = map(lambda x: x[0] / float(x[1]), gps_info[2])
lat = dms_to_decimal(*lat_dms)
if gps_info[1] == 'S':
lat *= -1
lng_dms = map(lambda x: x[0] / float(x[1]), gps_info[4])
lng = dms_to_decimal(*lng_dms)
if gps_info[3] == 'W':
lng *= -1
return lat, lng

def get_data(path):
lat0 = None
lon0 = None
h0 = 0
for root, dirs, files in os.walk(path):
for filename in sorted(filter(lambda x: os.path.splitext(x)[1].lower() == '.jpg', files)):
filepath = os.path.join(root, filename)
with Image.open(filepath) as im:
for segment, content in im.applist:
marker, body = content.split(b'\x00',1)
if segment == 'APP1' and marker == 'http://ns.adobe.com/xap/1.0/':
soup = BeautifulSoup(body, features='html.parser')
description = soup.find('x:xmpmeta').find('rdf:rdf').find('rdf:description')
pitch = float(description['drone-dji:gimbalpitchdegree']) + 90
yaw = float(description['drone-dji:gimbalyawdegree'])
roll = float(description['drone-dji:gimbalrolldegree'])
alt = float(description['drone-dji:relativealtitude'])
lat, lon = get_gps_coords(im)
if lat0 is None:
lat0 = lat
lon0 = lon
x, y, z = geodetic2ecef(lat, lon, alt)
x, y, z = ecef2enu(x, y, z, lat0, lon0, h0)
yield filename, '{:f}'.format(x), '{:f}'.format(y), '{:f}'.format(z), yaw, pitch, roll

def main():
data = [d for d in get_data("images")]
data = sorted(data, key=lambda x: x[0])
x = np.array(map(lambda d: d[1], data))
y = np.array(map(lambda d: d[2], data))
with open('imageData.txt', 'w+') as f:
for datum in data:
f.write(','.join([str(d) for d in datum]) + '\n')

if name == 'main':
main()
print(main())

Question:.
the is making the file but it's printing anything can anyone help?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants