diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d55149..1d153ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.10) project(TinyEXIF) include(GNUInstallDirs) diff --git a/Samples/dji_phantom4.jpg b/Samples/dji_phantom4.jpg new file mode 100644 index 0000000..8305fb8 Binary files /dev/null and b/Samples/dji_phantom4.jpg differ diff --git a/Samples/dji_phantom4_2.jpg b/Samples/dji_phantom4_2.jpg new file mode 100644 index 0000000..3826200 Binary files /dev/null and b/Samples/dji_phantom4_2.jpg differ diff --git a/TinyEXIF.cpp b/TinyEXIF.cpp index c69fb42..a619151 100644 --- a/TinyEXIF.cpp +++ b/TinyEXIF.cpp @@ -32,18 +32,18 @@ */ #include "TinyEXIF.h" - -#ifndef TINYEXIF_NO_XMP_SUPPORT -#include -#endif // TINYEXIF_NO_XMP_SUPPORT - -#include +#include #include #include #include #include #include #include +#include + +#ifndef TINYEXIF_NO_XMP_SUPPORT +#include +#endif // TINYEXIF_NO_XMP_SUPPORT #ifdef _MSC_VER namespace { @@ -1114,8 +1114,19 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) { if (element == NULL || (szAttribute = element->GetText()) == NULL) return false; } - value = strtoul(szAttribute, NULL, 0); return true; - return false; + value = strtoul(szAttribute, NULL, 0); + return true; + } + // same as previous function but with std::string + static bool Value(const tinyxml2::XMLElement* document, const char* name, std::string& value) { + const char* szAttribute = document->Attribute(name); + if (szAttribute == NULL) { + const tinyxml2::XMLElement* const element(document->FirstChildElement(name)); + if (element == NULL || (szAttribute = element->GetText()) == NULL) + return false; + } + value = std::string(szAttribute); + return true; } }; const char* szAbout(document->Attribute("rdf:about")); @@ -1128,6 +1139,28 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) { ParseXMP::Value(document, "drone-dji:CalibratedFocalLength", Calibration.FocalLength); ParseXMP::Value(document, "drone-dji:CalibratedOpticalCenterX", Calibration.OpticalCenterX); ParseXMP::Value(document, "drone-dji:CalibratedOpticalCenterY", Calibration.OpticalCenterY); + std::string dewarpData; + ParseXMP::Value(document, "drone-dji:DewarpFlag", Distortion.DewarpFlag); + ParseXMP::Value(document, "drone-dji:DewarpData", dewarpData); + std::vector distortionParams; + size_t pos = dewarpData.find(';'); + if (pos != std::string::npos) { + std::stringstream ss(dewarpData.substr(pos + 1)); + std::string item; + while (std::getline(ss, item, ',')) { + distortionParams.push_back(std::stod(item)); + } + } + // The DewarpData string has the following format: + // date;Fx,Fy,Cx,Cy,K1,K2,P1,P2,K3 + // , where Fx, Fy are focal lengths in pixels, Cx, Cy are optical center offsets from the image center in pixels + if (distortionParams.size() == 9) { + Distortion.K1 = distortionParams[4]; + Distortion.K2 = distortionParams[5]; + Distortion.P1 = distortionParams[6]; + Distortion.P2 = distortionParams[7]; + Distortion.K3 = distortionParams[8]; + } } else if (0 == strcasecmp(Make.c_str(), "senseFly") || 0 == strcasecmp(Make.c_str(), "Sentera")) { ParseXMP::Value(document, "Camera:Roll", GeoLocation.RollDegree); @@ -1165,6 +1198,17 @@ int EXIFInfo::parseFromXMPSegmentXML(const char* szXML, unsigned len) { #endif // TINYEXIF_NO_XMP_SUPPORT +bool EXIFInfo::Calibration_t::hasCalibration() const { + return FocalLength > 0.0 && OpticalCenterX > 0.0 && OpticalCenterY > 0.0; +} + +bool EXIFInfo::Distortion_t::hasDewarpFlag() const { + return DewarpFlag != UINT32_MAX; +} +bool EXIFInfo::Distortion_t::hasDistortion() const { + return K1 != 0.0 || K2 != 0.0 || P1 != 0.0 || P2 != 0.0 || K3 != 0.0; +} + void EXIFInfo::Geolocation_t::parseCoords() { // Convert GPS latitude if (LatComponents.degrees != DBL_MAX || @@ -1210,6 +1254,9 @@ bool EXIFInfo::Geolocation_t::hasOrientation() const { bool EXIFInfo::Geolocation_t::hasSpeed() const { return SpeedX != DBL_MAX && SpeedY != DBL_MAX && SpeedZ != DBL_MAX; } +bool EXIFInfo::Geolocation_t::hasAccuracy() const { + return AccuracyXY != 0 && AccuracyZ != 0; +} bool EXIFInfo::GPano_t::hasPosePitchDegrees() const { return PosePitchDegrees != DBL_MAX; @@ -1306,6 +1353,14 @@ void EXIFInfo::clear() { GeoLocation.LonComponents.seconds = 0; GeoLocation.LonComponents.direction = 0; + // Distortion + Distortion.DewarpFlag = UINT32_MAX; + Distortion.K1 = 0; + Distortion.K2 = 0; + Distortion.P1 = 0; + Distortion.P2 = 0; + Distortion.K3 = 0; + // GPano GPano.PosePitchDegrees = DBL_MAX; GPano.PoseRollDegrees = DBL_MAX; diff --git a/TinyEXIF.h b/TinyEXIF.h index a3a878e..4082336 100644 --- a/TinyEXIF.h +++ b/TinyEXIF.h @@ -251,7 +251,21 @@ public: double FocalLength; // Focal length (pixels) double OpticalCenterX; // Principal point X (pixels) double OpticalCenterY; // Principal point Y (pixels) + bool hasCalibration() const; // Return true if FocalLength, OpticalCenterX, OpticalCenterY are available (nonzero) } Calibration; + struct TINYEXIF_LIB Distortion_t { // Lens distortion information + uint32_t DewarpFlag; // Dewarp flag - indicates whether undistortion has been applied to the image + // UINT32_MAX: flag missing from EXIF data + // 0: no dewarp (raw image) - the image is distorted, should also have coefficients + // 1: dewarp applied (undistorted image) + double K1; + double K2; + double P1; + double P2; + double K3; + bool hasDewarpFlag() const; // Return true if DewarpFlag is available + bool hasDistortion() const; // Return true if any of K1, K2, P1, P2, K3 are available + } Distortion; struct TINYEXIF_LIB LensInfo_t { // Lens information double FStopMin; // Min aperture (f-stop) double FStopMax; // Max aperture (f-stop) @@ -302,6 +316,7 @@ public: bool hasRelativeAltitude()const;// Return true if (rel_alt) is available bool hasOrientation() const; // Return true if (roll,yaw,pitch) is available bool hasSpeed() const; // Return true if (speedX,speedY,speedZ) is available + bool hasAccuracy() const; // Return true if (accuracyXY,accuracyZ) is available } GeoLocation; struct TINYEXIF_LIB GPano_t { // Spherical metadata. https://developers.google.com/streetview/spherical-metadata double PosePitchDegrees; // Pitch, measured in degrees above the horizon, for the center in the image. Value must be >= -90 and <= 90. diff --git a/main.cpp b/main.cpp index ddd5212..7e57fad 100644 --- a/main.cpp +++ b/main.cpp @@ -130,5 +130,11 @@ int main(int argc, const char** argv) std::cout << "GPano.PosePitchDegrees " << imageEXIF.GPano.PosePitchDegrees << "\n"; if (imageEXIF.GPano.hasPoseRollDegrees()) std::cout << "GPano.PoseRollDegrees " << imageEXIF.GPano.PoseRollDegrees << "\n"; + if (imageEXIF.Distortion.hasDewarpFlag()) + std::cout << "Distortion.DewarpFlag " << imageEXIF.Distortion.DewarpFlag << "\n"; + if (imageEXIF.Distortion.hasDistortion()) + std::cout << "Distortion [K1 K2 P1 P2 K3] " << std::setprecision(6) + << imageEXIF.Distortion.K1 << " " << imageEXIF.Distortion.K2 << " " << imageEXIF.Distortion.P1 + << " " << imageEXIF.Distortion.P2 << " " << imageEXIF.Distortion.K3 << "\n"; return EXIT_SUCCESS; }