Gestione Exif

Tutorial_20220131

Introduzione

In questo approfondimento parleremo della specifica Exif di foto (.jpeg, .png, etc..) e video. Ne parleremo approfondendo un caso studio, dato che tutorial e documentazione per gli strumenti spiegati abbondano. Questa specifica che sta per Exchangeable image file format (fonte: wikipedia ) è utile per analizzare i metadati di una foto, un’immagine o un video. Prima di qualche mese fa non mi ero mai posto il problema di dover estrarre e manipolare questo tipo di informazioni, ma nel caso che vi propongo tra poco si sono rivelate necessarie (fondamentali) per risolvere i problemi che avevo di fronte.

Per calarci nel pratico, prima di partire con il caso studio (molto semplice) per cui ho usato questi metadati, mostro cosa intendo. La foto seguente è geolocalizzata e ha una serie di caratteristiche di dimensioni e via discorrendo.

italy-garda-lake-sailing-club

I metadati introdotti di sopra sono i seguenti:
File Name                       : italy-garda-lake-sailing-club.jpg
Directory                       : .
File Size                       : 794 KiB
Zone Identifier                 : Exists
File Modification Date/Time     : 2022:01:26 23:44:16+01:00
File Access Date/Time           : 2022:01:26 23:45:19+01:00
File Creation Date/Time         : 2022:01:26 23:44:15+01:00
File Permissions                : -rw-rw-rw-
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : inches
X Resolution                    : 72
Y Resolution                    : 72
Exif Byte Order                 : Little-endian (Intel, II)
Exposure Time                   : 1/3906
F Number                        : 1.8
Exposure Program                : Program AE
ISO                             : 52
Exif Version                    : 0220
Date/Time Original              : 2018:09:16 11:08:41
Create Date                     : 2018:09:16 11:08:41
Components Configuration        : Y, Cb, Cr, -
Shutter Speed Value             : 1/3902
Aperture Value                  : 1.8
Brightness Value                : 9.57
Exposure Compensation           : 0
Max Aperture Value              : 1.8
Subject Distance                : 1.686 m
Metering Mode                   : Center-weighted average
Flash                           : Off, Did not fire
Focal Length                    : 4.4 mm
Warning                         : [minor] Unrecognized MakerNotes
Sub Sec Time                    : 714773
Sub Sec Time Original           : 714773
Sub Sec Time Digitized          : 714773
Flashpix Version                : 0100
Color Space                     : sRGB
Exif Image Width                : 4032
Exif Image Height               : 3024
Interoperability Index          : R98 - DCF basic file (sRGB)
Interoperability Version        : 0100
Sensing Method                  : One-chip color area
Scene Type                      : Directly photographed
Custom Rendered                 : Custom
Exposure Mode                   : Auto
White Balance                   : Auto
Digital Zoom Ratio              : 0
Focal Length In 35mm Format     : 27 mm
Scene Capture Type              : Standard
Contrast                        : Normal
Saturation                      : Normal
Sharpness                       : Normal
Subject Distance Range          : Close
GPS Version ID                  : 2.2.0.0
GPS Latitude Ref                : North
GPS Latitude                    : 45 deg 52' 39.47"
GPS Longitude Ref               : East
GPS Longitude                   : 10 deg 51' 25.78"
GPS Altitude Ref                : Above Sea Level
GPS Altitude                    : 71.95 m
GPS Time Stamp                  : 09:08:36
GPS Dilution Of Precision       : 21.74
GPS Processing Method           : fused
GPS Date Stamp                  : 2018:09:16
Image Width                     : 4032
Image Height                    : 3024
Make                            : Google
Camera Model Name               : Pixel 2
Orientation                     : Horizontal (normal)
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Software                        : HDR+ 1.0.199571065z
Modify Date                     : 2018:09:16 11:08:41
Y Cb Cr Positioning             : Centered
Image Width                     : 252
Image Height                    : 189
Compression                     : JPEG (old-style)
Orientation                     : Horizontal (normal)
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Thumbnail Offset                : 21376
Thumbnail Length                : 8940
Profile CMM Type                :
Profile Version                 : 4.0.0
Profile Class                   : Display Device Profile
Color Space Data                : RGB
Profile Connection Space        : XYZ
Profile Date Time               : 2016:12:08 09:38:28
Profile File Signature          : acsp
Primary Platform                : Unknown ()
CMM Flags                       : Not Embedded, Independent
Device Manufacturer             : Google
Device Model                    :
Device Attributes               : Reflective, Glossy, Positive, Color
Rendering Intent                : Perceptual
Connection Space Illuminant     : 0.9642 1 0.82491
Profile Creator                 : Google
Profile ID                      : 75e1a6b13c34376310c8ab660632a28a
Profile Description             : sRGB IEC61966-2.1
Profile Copyright               : Copyright (c) 2016 Google Inc.
Media White Point               : 0.95045 1 1.08905
Media Black Point               : 0 0 0
Red Matrix Column               : 0.43604 0.22249 0.01392
Green Matrix Column             : 0.38512 0.7169 0.09706
Blue Matrix Column              : 0.14305 0.06061 0.71391
Red Tone Reproduction Curve     : (Binary data 32 bytes, use -b option to extract)
Chromatic Adaptation            : 1.04788 0.02292 -0.05019 0.02959 0.99048 -0.01704 -0.00922 0.01508 0.75168
Blue Tone Reproduction Curve    : (Binary data 32 bytes, use -b option to extract)
Green Tone Reproduction Curve   : (Binary data 32 bytes, use -b option to extract)
Image Width                     : 1600
Image Height                    : 1200
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Aperture                        : 1.8
Image Size                      : 1600x1200
Megapixels                      : 1.9
Scale Factor To 35 mm Equivalent: 6.1
Shutter Speed                   : 1/3906
Create Date                     : 2018:09:16 11:08:41.714773
Date/Time Original              : 2018:09:16 11:08:41.714773
Modify Date                     : 2018:09:16 11:08:41.714773
Thumbnail Image                 : (Binary data 8940 bytes, use -b option to extract)
GPS Altitude                    : 71.9 m Above Sea Level
GPS Date/Time                   : 2018:09:16 09:08:36Z
GPS Latitude                    : 45 deg 52' 39.47" N
GPS Longitude                   : 10 deg 51' 25.78" E
Circle Of Confusion             : 0.005 mm
Depth Of Field                  : 6.02 m (0.96 - 6.98 m)
Field Of View                   : 67.4 deg
Focal Length                    : 4.4 mm (35 mm equivalent: 27.0 mm)
GPS Position                    : 45 deg 52' 39.47" N, 10 deg 51' 25.78" E
Hyperfocal Distance             : 2.22 m
Light Value                     : 14.6

Come vedete tra le tantissime informazioni che questa foto (i cui diritti vanno a Google: Profile Copyright : Copyright (c) 2016 Google Inc.) possiede ci sono anche quelle sulla geolocalizzazione, che interesseranno il caso che sto per descrivere (non tutte le immagini hanno le coordinate ovviamente, potrete provare con un .jpg che avete sul desktop per verificare. I dati sono stati ottenuti con un notevolissimo strumento che utilizzo da riga di comando https://exiftool.org/ : pagina minimale, informazioni e documentazione chiare e ricche di esempi, utilizzo semplice (almeno per chi ama la riga di comando) e facilmente replicabile. Per utilizzarlo ho semplicemente scaricato l’eseguibile in una cartella insieme alle immagini che volevo analizzare e, aprendo il Prompt dei comandi/Powershell/Bash nella cartella in cui è presente l’eseguibile ho lanciato il comando:

exiftool -a italy-garda-lake-sailing-club.jpg

dove exiftool è il nome dell’eseguibile che stiamo… eseguendo, -a sta per “all” e significa che si vogliono vedere elencati tutti i metadati e italy-garda-lake-sailing-club.jpg è semplicemente il nome del file (non voglio insultare nessuno ma potete usare il tasto TAB per completare il nome del file qualore sia lungo come in questo caso). Ad ogni modo la pagina dove potete scaricare il software riporta molti esempi ed esistono diversi tutorial che spiegano come si usa, attraverso comandi simili a quello appena lanciato, Exiftool.

Descrizione del problema

Per analizzare un caso più interessante della mera elencazione dei metadati riporto un’esperienza e una caso di studio che mi è recentemente capitato. Nell’ambito di un rilievo stradale con una macchina GoPro Fusion (macchina adatta a scattare foto panoramiche o a 360°) con successiva pubblicazione delle foto georiferite sulla piattaforma Mapillary, un passaggio del workflow consisteva nell’unire la foto frontale e la foto posteriore per creare l’unica foto panoramica.

Sublime's custom image

In questo passaggio (stitching delle foto), che non dettaglio e che potrà essere il tema di un altro approfondimento, per generare una foto panoramica che fosse georiferita, gli strumenti e le funzionalità che avevo sviluppato dovevano prendere le informazioni di posizione da una delle foto di partenza (quella frontale, nello schema di sopra GF010005.JPG) e riportarle nella foto panoramica finale. In sede di controllo del risultato mi sono accorto che in alcuni casi la foto finale mancava della posizione: non era stata copiata da una delle foto di partenza (frontale o posteriore). Avrei potuto georiferire la foto a mano o avrei potuto copiare i metadati da una foto all’altra, avendo il bakcup dei dati, ma si trattava di controllare più di 49.000 foto. Ho deciso dunque di automatizzare il processo con l’utilizzo di Python e di alcuni suoi pacchetti per il trattamento di immagini e del software che ho introdotto precedentemente (Exiftool).

Nota: Entrambi questi strumenti possono essere usati separatamente per raggiungere il task richiesto.

Estrazione metadati Exiftool

Provando a estrarre i metadati relativi al posizionamento dall’immagine GF010005.JPG con il seguente comando:

exiftool -a "-gps*" GF010005.JPG

Il comando si deve eseguire avendo cura di spostare l’eseguibile nella cartella desiderata o di richiamare il path corretto. L’opzione -a permette di ricevere in uput tutti i tag anche qualora esistessero dei duplicati mentre l’opzione "-gps*" ci permette di estrarre tutte le informazioni riguardanti i tag che coinvolgono la parola gps, per maggiori approfondimenti consiglio https://exiftool.org/faq.html. Dal comando di sopra otteniamo la seguente risposta:

GPS Latitude Ref                : North
GPS Longitude Ref               : East
GPS Altitude Ref                : Above Sea Level
GPS Time Stamp                  : 08:38:47
GPS Date Stamp                  : 2021:11:10
GPS Altitude                    : 83.8 m Above Sea Level
GPS Date/Time                   : 2021:11:10 08:38:47Z
GPS Latitude                    : 44 deg 24' 2.75" N
GPS Longitude                   : 8 deg 56' 18.58" E
GPS Position                    : 44 deg 24' 2.75" N, 8 deg 56' 18.58" E

Questi dati mancano nell’immagine finale e dobbiamo riportarli per poterla georiferire. E’ possibile usare l’opzione -TagsFromFile per copiare le informazioni da un’immagine all’altra e sarà dunque possibile risolvere il problema con il seguente comando:

  exiftool -TagsFromFile {filepath origin} -a "-gps*" {filepath destination}

Calandolo nel nostro caso e sostituendo il nome del file:

  exiftool -TagsFromFile GF010005.JPG -a "-gps*" stitched010005.JPG

In questo modo per un’immagine riusciamo a copiare tutti i dati che ci servono per georiferirla. Per fare in modo che tutti i file vengano processati in questa maniera si può ricorrere alla linea di comando e alle opzioni che presenta Exiftool oppure, per mia pigrizia nel non voler andare a vedere la documentazione del software, si può utilizzare un semplice script in Python per automatizzare il processo, probabilmente lo script sarà più lento del solo software da linea di comando ma mi permette di introdurre alcuni concetti e di fare alcune considerazioni. Partendo da una struttura delle cartelle come segue:

# 
folder
|     Exiftool.exe
|____ OUTPUT
     |       stitched010005.JPG
     |       stitched010006.JPG
     |       stitched010007.JPG
      ....

|____ FRONT
     |       GF010005.JPG
     |       GF010006.JPG
     |       GF010007.JPG
     ....

Lo script molto semplice a scopo didattico è il seguente:

from subprocess import check_call
import os
from os import path as oSpath


def main():

    cwd = os.getcwd()
    datiInput = oSpath.join(cwd, "FRONT")
    datiOutput = oSpath.join(cwd, "OUTPUT")

    for filename in os.listdir(datiInput):

        outputName = f"stitched{filename[2:]}"

        inputFilePath = oSpath.join(datiInput, filename)
        outputFilePath = oSpath.join(datiOutput, outputName)

        command_list = [
            "cmd",
            "/c",
            ".\exiftool.exe",
            "-TagsFromFile",
            inputFilePath,
            "-a",
            '"-gps*"',
            outputFilePath,
        ]
        check_call(command_list)


if __name__ == "__main__":
    main()

Il punto interessante dello script sta nel fatto che iterando per ogni file della cartella in cui sono contentuti i file frontali (denominati FRONT) per cui i metadati ci danno le informazioni di geolocalizzazione, richiamiamo il comando da cmd con il modulo subprocess. In questo caso il valore della variabile command_list varia a seconda del sistema operativo utilizzato ma ricostruendo il comando pezzo per pezzo si nota che non si fa altro che richiamare il comando di prima in Exiftool ma attraverso lo script.

Estrazione metadati Python

Avendo introdotto lo script in Python sorge spontaneo chiedersi se sia proprio necessario richiamare un software esterno come Exiftool e se non sia possibile rimanere in Python per estrarre i metadati. A seconda dello scopo del nostro lavoro la domanda può essere vista da punti diversi, probabilmente Exiftool è più performante in quanto a prestazioni ma se ci interessa lavorare con un linguaggio conosciuto a magari integrare le informazioni dei metadati con altre funzionalità che in Exiftool non sono disponibili, possiamo affidarci completamente a Python e in particolare a qualche suo pacchetto usato per trattare immagini. Anche in questo caso ci sono tanti pacchetti da poter utilizzare, per la semplice estrazione dei metadati di georeferenziazione utilizzerò il pacchetto PIL, lo script che segue è analogo a quello di sopra ma prende in considerazione solo le foto frontali (FRONT) per semplicità. Qualora qualcuno volesse approfondire l’utilizzo di Python per analisi dei metadati riporto questo interessante blogpost cui mi sono ispirato per la scrittura della funzione takeGeoInfo che ho scritto nel seguente script ( approfondimenti ):

    import os
    from os import path as oSpath
    from PIL.ExifTags import TAGS, GPSTAGS
    from PIL import Image

    def takeGeoInfo(exif):

        geotagging = {}
        for (id, tag) in TAGS.items():

            if tag == "GPSInfo":

                for (key, val) in GPSTAGS.items():

                    if key in exif[id]:
                        geotagging[val] = exif[id][key]

        return geotagging

    def main():

        cwd = os.getcwd()
        datiInput = oSpath.join(cwd, "INPUT")

        for filename in os.listdir(datiInput):

            inputFilePath = oSpath.join(datiInput, filename)

            immagine = Image.open(inputFilePath)
            immagine.verify()

            geoInfo = takeGeoInfo(immagine._getexif(), inputFilePath)

            print(geoInfo)

    if __name__ == "__main__":
        main()  

Attraverso il solo uso di pacchetti di Python è dunque possibile analizzare i metadati delle foto a nostra disposizione. Se analizziamo l’output di un file dopo aver fatto girare lo script troviamo qualcosa di simile (a seconda dei nostri dati):

{'GPSLatitudeRef': 'N', 'GPSLatitude': (44.0, 24.0, 7.35084), 'GPSLongitudeRef': 'E', 'GPSLongitude': (8.0, 56.0, 17.00088), 'GPSAltitudeRef': b'\x00', 'GPSAltitude': 90.378, 'GPSTimeStamp': (8.0, 45.0, 41.0), 'GPSDateStamp': '2021:11:10'}

Conclusione

Fra i molti strumenti presenti per analizzare i metadati di foto e video penso che Exiftool e Python siano degli ottimi alleati, con questi semplici comandi si riescono a reperire molte informazioni e talvolta ad aggiustare file in poche righe di codice. Gli script di Python sono ad uso didattico e hanno una serie di informazioni hardcoded che non li rendono di utilizzo generale, inoltre mancano una serie di controlli (ad esempio sul tipo di file processato) che potrebbero invalidare l’uso dello script. Rimangono un’ottima base di partenza almeno in ambiente di test e analisi.

 Date: February 14, 2021
 Tags:  metadata python command line

Next
Automatizzazione tasks ripetitivi con script semplici e pronti all'uso ⏩