using MetadataExtractor; using MetadataExtractor.Formats.Exif; using MetadataExtractor.Formats.Xmp; using System; using System.IO; using System.Linq; using System.Text; using XmpCore; using ZeroLevel.ML.LocationMath; namespace ZeroLevel.ML { public sealed class DewarpData { /* For the parameter values conversion I used the following formulas (from Luhmann et al., 2019): k1 pixel units = k1 focal units / focal length^2 k2 pixel units = k2 focal units / focal length^4 k3 pixel units = k3 focal units / focal length^6 p1 pixel units = p1 focal units / focal length p2 pixel units = -p2 focal units / focal length */ public string Date = null!; /// /// Focal length /// public float Fx; /// /// Focal length /// public float Fy; /// /// Principal points /// public float Cx; /// /// Principal points /// public float Cy; /// /// Lens distortion /// public float K1; /// /// Lens distortion /// public float K2; /// /// Lens distortion /// public float P1; /// /// Lens distortion /// public float P2; /// /// Lens distortion /// public float K3; public DewarpData(string data) { if (!string.IsNullOrWhiteSpace(data)) { var parts = data.Split(';'); if (parts.Length == 2) { Date = parts[0]; var par_parts = parts[1].Split(","); if (par_parts.Length >= 9) { Fx = ToFloat(par_parts[0]); Fy = ToFloat(par_parts[1]); Cx = ToFloat(par_parts[2]); Cy = ToFloat(par_parts[3]); K1 = ToFloat(par_parts[4]); K2 = ToFloat(par_parts[5]); P1 = ToFloat(par_parts[6]); P2 = ToFloat(par_parts[7]); K3 = ToFloat(par_parts[8]); } } } } private static float ToFloat(string s) { var ns = ImageMetainfo.GetNumber(s); if (float.TryParse(ns, out var f)) { return f; } return 0f; } public float CalculateDistortedX(float x) { return x * (1 + K1); } } public sealed class ImageMetainfo { private CameraMath _camera = null; public CameraMath CreateCamera() { if (_camera == null) { _camera = new CameraMath(CameraPixelSizes.GetPixelSizeByModel(this.Model), this.FocalLength, this.ImageWidth, this.ImageHeight); } return _camera; } public DateTime Created { get; set; } public string Make { get; set; } = null!; public string Model { get; set; } = null!; public string SerialNumber { get; set; } = null!; public double FocalLength { get; set; } public double ImageWidth { get; set; } public double ImageHeight { get; set; } public double GPSLatitude { get; set; } public double GPSLongitude { get; set; } public double GPSAltitude { get; set; } public double DJILatitude { get; set; } public double DJILongitude { get; set; } public double DJIAltitude { get; set; } private double GimbalYawDegree { get; set; } private double FlightYawDegree { get; set; } private static double Diff(double a, double b) { var a1 = Math.Abs(a - b); var a2 = 360 - Math.Max(a, b) + Math.Min(a, b); return Math.Min(a1, a2); } public double Yaw { get { var fn = (FlightYawDegree + 360) % 360; var gn = (GimbalYawDegree + 360) % 360; var gni = (GimbalYawDegree + 540) % 360; var gy = GimbalYawDegree; if (gy < 0) { gy = 180 + GimbalYawDegree; } else { gy = -180 + GimbalYawDegree; } var d1 = Diff(fn, gn); var d2 = Diff(fn, gni); if (d2 < d1) { return gy; } return GimbalYawDegree; } } public double CalibratedFocalLength { get; set; } public DewarpData Dewarp { get; set; } = null!; public double Latitude => Math.Max(DJILatitude, GPSLatitude); public double Longitude => Math.Max(DJILongitude, GPSLongitude); public double Altitude => DJIAltitude; internal static string GetNumber(string line) { var ns = new StringBuilder(); var sep = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; bool started = false; bool separated = false; foreach (var ch in line) { if (char.IsDigit(ch)) { started = true; ns.Append(ch); } else if (ch == '.' || ch == ',' && started) { if (separated) { break; } separated = true; ns.Append(sep); } else if (ch == '-' && !started) { started = true; ns.Append(ch); } else if (started) { break; } } return ns.ToString(); } public void ReadMetadata(Stream stream) { double dBuf; try { var metadata = ImageMetadataReader.ReadMetadata(stream); var exif0 = metadata.OfType().FirstOrDefault(); if (exif0 != null) { foreach (var t in exif0.Tags) { if (t.Name.Equals("Make")) { this.Make = t.Description!; } else if (t.Name.Equals("Model")) { this.Model = t.Description!; } } } var exif = metadata.OfType().FirstOrDefault(); if (exif != null) { foreach (var t in exif.Tags) { if (t.Name.Equals("Focal Length")) { if (double.TryParse(GetNumber(t.Description ?? string.Empty), out dBuf)) { this.FocalLength = dBuf; } } else if (t.Name.Equals("Exif Image Width")) { if (double.TryParse(GetNumber(t.Description ?? string.Empty), out dBuf)) { this.ImageWidth = dBuf; } } else if (t.Name.Equals("Exif Image Height")) { if (double.TryParse(GetNumber(t.Description ?? string.Empty), out dBuf)) { this.ImageHeight = dBuf; } } else if (t.Name.Equals("Body Serial Number")) { this.SerialNumber = t.Description!; } else if (t.Name.Equals("CreateDate")) { this.Created = DateTime.Parse(t.Description ?? string.Empty); } } } var gps = metadata.OfType().FirstOrDefault(); if (gps != null) { if (gps.TryGetGeoLocation(out var location)) { this.GPSLatitude = location.Latitude; this.GPSLongitude = location.Longitude; double alt = 0.0d; if (gps.TryGetDouble(GpsDirectory.TagAltitude, out alt)) { this.GPSAltitude = alt; } } } var xmp = metadata.OfType().FirstOrDefault(); if (xmp != null) { foreach (var p in xmp.XmpMeta?.Properties ?? Enumerable.Empty()) { if (!string.IsNullOrEmpty(p.Path)) { if (p.Path.IndexOf("RelativeAltitude") >= 0) { if (double.TryParse(GetNumber(p.Value), out dBuf)) { this.DJIAltitude = dBuf; } } else if (p.Path.IndexOf("GpsLatitude") >= 0) { if (double.TryParse(GetNumber(p.Value), out dBuf)) { this.DJILatitude = dBuf; } } else if (p.Path.IndexOf("GpsLongitude") >= 0) { if (double.TryParse(GetNumber(p.Value), out dBuf)) { this.DJILongitude = dBuf; } } else if (p.Path.IndexOf("GimbalYawDegree") >= 0) { if (double.TryParse(GetNumber(p.Value), out dBuf)) { this.GimbalYawDegree = dBuf; } } else if (p.Path.IndexOf("FlightYawDegree") >= 0) { if (double.TryParse(GetNumber(p.Value), out dBuf)) { this.FlightYawDegree = dBuf; } } else if (p.Path.IndexOf("CalibratedFocalLength") >= 0) { if (double.TryParse(GetNumber(p.Value), out dBuf)) { this.CalibratedFocalLength = dBuf; } } else if (p.Path.IndexOf("DewarpData") >= 0) { this.Dewarp = new DewarpData(p.Value); } } } } } catch { stream.Close(); throw; } } } }