EXIF-Informationen in C/C++ auslesen

Aus toolbox_interaktion
Wechseln zu: Navigation, Suche

Im Rahmen unserer Projektarbeit "Geotagging von Bildern" mussten wir Exif-Informationen aus einer JPEG Datei in C++ auslesen. Im Folgenden ein Auszug zum Thema Exif aus unserer Projektdokumentantion, da dies auch für andere Projekte interessant sein kann.

Aufbau einer JPEG Datei

Alle Bildinformationen die die Kamera zur Verfügung stellt, wie zum Beispiel Kameramodell, Datum und Uhrzeit, werden im Exif Dateiformat abgelegt. Die Exif Daten befinden sich dabei im Header einer Bilddatei wie zum Beispiel eine JPEG Datei. Neben den oben genannten Informationen werden auch die GPS Koordinaten im Exif Teil abgelegt. Der Aufbau der JPEG Datei ist dabei wie folgt. Zuerst kommt ein SOI (Start of Image) Marker. Vor jedem Marker ist ein Marker Präfix mit dem Hex-Code 0xFF. Der SOI Marker hat den Hex-Code 0xD8.

Um den Aufbau einer JPEG Datei zu sehen, kann man die Datei mit einem Hex-Editor öffnen und die Hex-Werte nachvollziehen Anschließend kommt ein APP1 Marker der den Anfang des Application Segment 1 markiert. Im APP1 Segment sind die Exif Daten hinterlegt. Nach dem APP1 Marker kann der APP2 oder andere APPn Marker kommen, jedoch nur im APP2 Segment können sich weitere Exif Daten befinden. Somit muss das APP2 Segment beim Auslesen der Exif Daten ebenfalls berücksichtigt werden. Wie in nachfolgender Grafik (Abbildung 2: Grundstruktur einer JPEG Datei aus Exif Spezifikation JEITA CP-3451B) ersichtlich, folgen noch weitere Segmente. Details können zum Beispiel aus Exchangeable image file format for digital still cameras: Exif Version 2.3 (siehe Weblinks) entnommen werden.

Das APP1 Segment ist wie folgt aufgebaut. Zuerst kommt wieder ein Marker inklusive Marker Präfix (0xFFE1). Die folgenden zwei Byte definieren die Größe bzw. die Länge des Feldes. Um eindeutig zu erkennen dass es bei der Information in diesem Segment um Exif Daten handelt, folgt nun der ASCII Code für „Exif“ 0x45786966 gefolgt von 2 Byte 0x00. In Abbildung 1 kann man in der rechten Spalte den ASCII Code erkenne.

Im jetzt folgenden TIFF Header steht entweder „II“ (0x4949) oder „MM“ (0x4D4D). Bei „MM“ sind die Daten als big endian und bei „II“ als little endian abgelegt. Ebenfalls zum TIFF Header gehören die folgenden fixen 2 Byte mit dem Wert 0x002A und der Offset zum 0ten IFD. IFD steht für Image File Directory. In diesen Verzeichnissen befinden sich die Informationen die später für unser Projekt wichtig sind.

Die IFDs sind wie folgt aufgebaut:

  • Bytes 0-1 Tag
  • Bytes 2-3 Typ
  • Bytes 4-7 Anzahl
  • Bytes 8-11 Wert oder Offset

Ein Tag ist eine eindeutige 2-byte Nummer um die Felder zu identifizieren. Die Typen Identifizieren die Art der Information die in diesem IFD steht.

Dabei werden folgende Typen unterschieden
  • BYTE 8-bit unsigned integer
  • ASCII 8-bit Byte mit ASCII Code
  • SHORT 16-bit unsigned integer
  • LONG 32-bit unsigned integer
  • RATIONAL Zwei LONGs wobei der erste LONG Wert der Zähler und der zweite der Nenner ist
  • UNDEFINED Ein 8-bit Byte welches jeden Wert abhängig von der Definition annehmen kann
  • SLONG 32-bit signed integer
  • SRATIONAL Zwei SLONGs wobei der erste SLONG Wert der Zähler und der zweite der Nenner ist

Die Anzahl gibt an, wie viele Bytes sich in diesem Verzeichnis befinden. In den Bytes 8-11 steht entweder der eigentliche Wert falls er in die 4 Byte passt oder der Offset zum eigentlichen Wert.

Es gibt 3 unterschiedliche Exif-spezifische IFDs
  • Das Exif IFD welches allgemeine Informationen zum Bild enthält,
  • das GPS IFD worin alle Daten bezüglich des Standortes gespeichert werden
  • und Interoperability IFD worin Kompatibilitätsinformationen stehen.

Auslesen der Exif Informationen

Bei unserer Recherche fanden wir eine schlanke Klasse zum Auslesen von Exif Daten, die vom Autor "Cexif" genannt wird.

Hinweise zur Verwendung unter Windows

Nach dem einbinden in unser Projekt, gelang es zunächst nicht, wie gewünscht die Exif Attribute zu extrahieren. Nach intensivem Debuggen und Fehlersuche fanden wir heraus, dass wir die Datei falsch öffneten. Dabei verwendeten wir die Funktion FILE *fopen(const char *path, const char *mode) aus der stdio.h Bibliothek. Für das Argument const char *mode übergaben wir anfangs „r“ für read. Hierbei wird die Datei auf einigen Systemen als Textdatei und nicht als Binärdatei geöffnet wodurch es von der Cexif Klasse nicht bearbeitet werden konnte. Durch das Ändern des Arguments auf „rb“ wird die Datei richtig geöffnet und die Exif Informationen konnten erfolgreich ausgelesen werden.

Bei POSIX konformen Systemen, wie zum Beispiel Linux, wird die Datei mit oder ohne „b“ im Argument mode gleich behandelt. Das „b“ wird beim Funktionsaufruf ignoriert.

Änderungen an der existierenden Klasse

Da sich herausstellte dass diese Klasse keine GPS Daten auslesen kann, mussten wir uns zuerst mit dem Aufbau einer JPEG Datei und der Exif Erweiterung auseinandersetzten um zu verstehen wie die verwendete Klasse funktioniert und wie sie erweitert werden muss. Siehe Kapitel 2.4 Aufbau des Exif Dateiformats.

Nach intensiver Analyse stellte sich heraus, dass die Klasse eine Funktion sowohl für das Exif Verzeichnis als auch das Interoperability Verzeichnis verwendet. Daher lag es nahe, diese Funktion auch für das GPS Verzeichnis zu erweitern.

Dazu musste zuerst anhand der Spezifikation überprüft werden ob es Attribute gibt, die sowohl für das Exif, das Interoperability als auch für das GPS Verzeichnis verwendet werden. Dabei viel auf, dass es einen Tag gibt der sowohl im Interoperability als auch im GPS Verzeichnis verwendet wird. Da das Interoperability Attribut in unserer Klasse nicht abgefragt wird, können wir die Klasse trotzdem so erweitern um die GPS Attribute abzufragen.

Um dies zu verwirklichen, wurde eine Struktur innerhalb der Klasse um die entsprechenden Variablen erweitert um später auf die Informationen zugreifen zu können. Weiterhin wurden Konstanten für das GPS IFD und Konstanten für alle neu hinzukommenden GPS Tag IDs angelegt.

Das Auslesen der Tags funktioniert mit einer switch case Anweisung bei der die Tag IDs abgefragt werden. Für alle GPS Tag IDs wurde ein eigener case festgelegt, jedoch werden nur die von uns benötigten Tags ausgelesen und ausgewertet.

Nachfolgend der Ausschnitt mit den von uns erstellten und ausgewerteten case Anweisungen:

case TAG_GPSVERSIONID:
	m_exifinfo->GPSVersionID1 = (unsigned char)ConvertAnyFormat(ValuePtr, Format);
	m_exifinfo->GPSVersionID2 = (unsigned char)ConvertAnyFormat(ValuePtr+1, Format);
	m_exifinfo->GPSVersionID3 = (unsigned char)ConvertAnyFormat(ValuePtr+2, Format);
	m_exifinfo->GPSVersionID4 = (unsigned char)ConvertAnyFormat(ValuePtr+3, Format);
	break;
case TAG_GPSLATITUDEREF:
	strncpy(m_exifinfo->GPSLatitudeRef, (char*)ValuePtr, 2);
	break;
case TAG_GPSLATITUDE:
	m_exifinfo->GPSLatitudeH = (float)ConvertAnyFormat(ValuePtr, Format);
	m_exifinfo->GPSLatitudeMin = (float)ConvertAnyFormat(ValuePtr + BytesPerFormat[Format], Format);
	m_exifinfo->GPSLatitudeSec = (float)ConvertAnyFormat(ValuePtr + 2 * BytesPerFormat[Format], Format);
	m_exifinfo->GPSLatitudeDeg = m_exifinfo->GPSLatitudeH + m_exifinfo->GPSLatitudeMin / 60 + m_exifinfo->GPSLatitudeSec / 3600;
	break;
case TAG_GPSLONGITUDEREF:
	strncpy(m_exifinfo->GPSLongitudeRef, (char*)ValuePtr, 2);
	break;
case TAG_GPSLONGITUDE:
	m_exifinfo->GPSLongitudeH = (float)ConvertAnyFormat(ValuePtr, Format);
	m_exifinfo->GPSLongitudeMin = (float)ConvertAnyFormat(ValuePtr + BytesPerFormat[Format], Format);
	m_exifinfo->GPSLongitudeSec = (float)ConvertAnyFormat(ValuePtr + 2 * BytesPerFormat[Format], Format);
	m_exifinfo->GPSLongitudeDeg = m_exifinfo->GPSLongitudeH + m_exifinfo->GPSLongitudeMin / 60 + m_exifinfo->GPSLongitudeSec / 3600;
	break;
case TAG_GPSALTITUDEREF:
	m_exifinfo->GPSAltitudeRef = (unsigned char)ConvertAnyFormat(ValuePtr, Format);
	break;
case TAG_GPSALTITUDE:
	m_exifinfo->GPSAltitude = (float)ConvertAnyFormat(ValuePtr, Format);
	break;
case TAG_GPSTIMESTAMP:
	m_exifinfo->GPSTimeStampH = (float)ConvertAnyFormat(ValuePtr, Format);
	m_exifinfo->GPSTimeStampMin = (float)ConvertAnyFormat(ValuePtr + BytesPerFormat[Format], Format);
	m_exifinfo->GPSTimeStampSec = (float)ConvertAnyFormat(ValuePtr + 2 * BytesPerFormat[Format], Format);
	break;
case TAG_GPSSATELLITES:
	strncpy(m_exifinfo->GPSSatellites, (char*)ValuePtr, 39);
	break;
case TAG_GPSSTATUS:
	strncpy(m_exifinfo->GPSStatus, (char*)ValuePtr, 2);
	break;

Funktionsausbau/Weiterverwendung

Falls für weitere Projekte die anderen GPS Tags ebenfalls benötigt werden, kann die Klasse so leicht wiederverwendet werden. Dafür müssen lediglich zusätzlich benötigte Variablen angelegt und in den entsprechenden case Anweisungen die Tags ausgewertet werden. Dazu muss man in die Spezifikation schauen um herauszulesen, in welchem Format die Informationen im Exif Verzeichnis abgelegt werden.

Weblinks


Interner Bereich: