web-dev-qa-db-de.com

UIImagePickerController und Extrahieren von EXIF-Daten aus vorhandenen Fotos

Es ist bekannt, dass UIImagePickerController die Metadaten des Fotos nach der Auswahl nicht zurückgibt. Einige Apps im App Store (Mobile Fotos, PixelPipe) scheinen jedoch in der Lage zu sein, die Originaldateien und die darin gespeicherten EXIF-Daten zu lesen, sodass die App die Geodaten aus dem ausgewählten Foto extrahieren kann.

Sie scheinen dies zu tun, indem sie die Originaldatei aus dem Ordner/private/var/mobile/Media/DCIM/100Apple/lesen und über eine EXIF-Bibliothek ausführen.

Ich kann jedoch nicht herausfinden, wie ein von UIImagePickerController zurückgegebenes Foto mit einer Datei auf der Festplatte abgeglichen werden kann. Ich habe die Dateigrößen erforscht, aber die Originaldatei ist ein JPEG-Bild, während das zurückgegebene Bild ein unformatierter UIImage ist, der es unmöglich macht, die Dateigröße des ausgewählten Bildes zu kennen.

Ich erwäge, eine Tabelle mit Hashwerten zu erstellen und die ersten x Pixel jedes Bildes abzugleichen. Dies scheint jedoch etwas übertrieben und wahrscheinlich ziemlich langsam.

Irgendwelche Vorschläge?

56
tomtaylor

Haben Sie sich diese Exif-iPhone-Bibliothek angesehen?

http://code.google.com/p/iphone-exif/

Ich werde es auf meiner Seite versuchen. Ich möchte die GPS-Koordinaten (Geotags) aus dem mit dem UIImagePickerController aufgenommenen Bild erhalten: /

Nach einem genaueren Blick scheint diese Bibliothek NSData-Informationen als Eingabe zu verwenden, und der UIImagePickerController gibt nach dem Erstellen eines Snapshots ein UIImage zurück. Theoretisch, wenn wir das aus der UIkit-Kategorie ausgewählte UIImage verwenden

NSData * UIImageJPEGRepresentation (
   UIImage *image,
   CGFloat compressionQuality
);

Dann können wir die UIImage in eine NSData-Instanz konvertieren und dann mit der iPhone-Exif-Bibliothek verwenden.

AKTUALISIEREN:
Ich habe die oben erwähnte Bibliothek getestet und es scheint zu funktionieren. Aufgrund meines begrenzten Wissens über das EXIF-Format und der fehlenden API auf hohem Niveau in der Bibliothek gelingt es mir jedoch nicht, die Werte für die EXIF-Tags zu ermitteln. Hier ist mein Code, falls einer von Ihnen weiter gehen kann: 


#import "EXFJpeg.h"

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo {
    NSLog(@"image picked %@ with info %@", image, editingInfo);
    NSData* jpegData = UIImageJPEGRepresentation (image,0.5);
    EXFJpeg* jpegScanner = [[EXFJpeg alloc] init];
    [jpegScanner scanImageData: jpegData];
    EXFMetaData* exifData = jpegScanner.exifMetaData;
    EXFJFIF* jfif = jpegScanner.jfif;
    EXFTag* tagDefinition = [exifData tagDefinition: [NSNumber numberWithInt:EXIF_DateTime]];
    //EXFTag* latitudeDef = [exifData tagDefinition: [NSNumber numberWithInt:EXIF_GPSLatitude]];
    //EXFTag* longitudeDef = [exifData tagDefinition: [NSNumber numberWithInt:EXIF_GPSLongitude]];
    id latitudeValue = [exifData tagValue:[NSNumber numberWithInt:EXIF_GPSLatitude]];
    id longitudeValue = [exifData tagValue:[NSNumber numberWithInt:EXIF_GPSLongitude]];
    id datetime = [exifData tagValue:[NSNumber numberWithInt:EXIF_DateTime]];
    id t = [exifData tagValue:[NSNumber numberWithInt:EXIF_Model]];
....
....

Das Abrufen der Tag-Definition ist in Ordnung, aber alle Tag-Werte geben nil :( zurück.

Für den Fall, dass Sie die Bibliothek ausprobieren möchten, müssen Sie eine globale Variable definieren, um sie zum Laufen zu bringen (wie im Dokument beschrieben, aber hum ..: /)

BOOL gLogging = FALSE;

UPDATE 2
Antwort hier: iPhone - Auf Standortinformationen von einem Foto aus zugreifenEin UIImage kapselt die Metainformationen nichtWir stecken also fest: Über diese Schnittstelle werden keine EXIF-Informationen übermittelt.

Letzte Aktualisierung
Ok, es ist mir gelungen, es zum Laufen zu bringen, zumindest die vom Picker zurückgegebenen Bilder richtig zu geotaggen.
Bevor Sie den UIImagePickerController auslösen, müssen Sie den CLLocationManager verwenden, um den aktuellen CLocation abzurufen
Wenn Sie es haben, können Sie diese Methode verwenden, die die Exif-iPhone-Bibliothek verwendet, um den UIImage aus der CLLocation zu geotagieren:


-(NSData*) geotagImage:(UIImage*)image withLocation:(CLLocation*)imageLocation {
    NSData* jpegData =  UIImageJPEGRepresentation(image, 0.8);
    EXFJpeg* jpegScanner = [[EXFJpeg alloc] init];
    [jpegScanner scanImageData: jpegData];
    EXFMetaData* exifMetaData = jpegScanner.exifMetaData;
    // end of helper methods 
    // adding GPS data to the Exif object 
    NSMutableArray* locArray = [self createLocArray:imageLocation.coordinate.latitude]; 
    EXFGPSLoc* gpsLoc = [[EXFGPSLoc alloc] init]; 
    [self populateGPS: gpsLoc :locArray]; 
    [exifMetaData addTagValue:gpsLoc forKey:[NSNumber numberWithInt:EXIF_GPSLatitude] ]; 
    [gpsLoc release]; 
    [locArray release]; 
    locArray = [self createLocArray:imageLocation.coordinate.longitude]; 
    gpsLoc = [[EXFGPSLoc alloc] init]; 
    [self populateGPS: gpsLoc :locArray]; 
    [exifMetaData addTagValue:gpsLoc forKey:[NSNumber numberWithInt:EXIF_GPSLongitude] ]; 
    [gpsLoc release]; 
    [locArray release];
    NSString* ref;
    if (imageLocation.coordinate.latitude <0.0)
        ref = @"S"; 
    else
        ref [email protected]"N"; 
    [exifMetaData addTagValue: ref forKey:[NSNumber numberWithInt:EXIF_GPSLatitudeRef] ]; 
    if (imageLocation.coordinate.longitude <0.0)
        ref = @"W"; 
    else
        ref [email protected]"E"; 
    [exifMetaData addTagValue: ref forKey:[NSNumber numberWithInt:EXIF_GPSLongitudeRef] ]; 
    NSMutableData* taggedJpegData = [[NSMutableData alloc] init];
    [jpegScanner populateImageData:taggedJpegData];
    [jpegScanner release];
    return [taggedJpegData autorelease];
}

// Helper methods for location conversion -(NSMutableArray*) createLocArray:(double) val{ val = fabs(val); NSMutableArray* array = [[NSMutableArray alloc] init]; double deg = (int)val; [array addObject:[NSNumber numberWithDouble:deg]]; val = val - deg; val = val*60; double minutes = (int) val; [array addObject:[NSNumber numberWithDouble:minutes]]; val = val - minutes; val = val*60; double seconds = val; [array addObject:[NSNumber numberWithDouble:seconds]]; return array; } -(void) populateGPS:(EXFGPSLoc* ) gpsLoc :(NSArray*) locArray{ long numDenumArray[2]; long* arrPtr = numDenumArray; [EXFUtils convertRationalToFraction:&arrPtr :[locArray objectAtIndex:0]]; EXFraction* fract = [[EXFraction alloc] initWith:numDenumArray[0]:numDenumArray[1]]; gpsLoc.degrees = fract; [fract release]; [EXFUtils convertRationalToFraction:&arrPtr :[locArray objectAtIndex:1]]; fract = [[EXFraction alloc] initWith:numDenumArray[0] :numDenumArray[1]]; gpsLoc.minutes = fract; [fract release]; [EXFUtils convertRationalToFraction:&arrPtr :[locArray objectAtIndex:2]]; fract = [[EXFraction alloc] initWith:numDenumArray[0] :numDenumArray[1]]; gpsLoc.seconds = fract; [fract release]; }

29
yonel

Dies funktioniert mit iOS5 (Beta 4) und der Kamerarolle (Sie benötigen defs für die Blöcke in der .h):

-(void) imagePickerController:(UIImagePickerController *)picker 
           didFinishPickingMediaWithInfo:(NSDictionary *)info
{
  NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
  if ([mediaType isEqualToString:(NSString*)kUTTypeImage]) {
    NSURL *url = [info objectForKey:UIImagePickerControllerReferenceURL];
    if (url) {
      ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset) {
      CLLocation *location = [myasset valueForProperty:ALAssetPropertyLocation];
      // location contains lat/long, timestamp, etc
      // extracting the image is more tricky and 5.x beta ALAssetRepresentation has bugs!
    };
    ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror) {
      NSLog(@"cant get image - %@", [myerror localizedDescription]);
    };
    ALAssetsLibrary *assetsLib = [[ALAssetsLibrary alloc] init];
    [assetsLib assetForURL:url resultBlock:resultblock failureBlock:failureblock];
  }
}
23
RonC

Es gibt einen Weg in iOS 8 

Ohne Verwendung einer DrittanbieterEXIFBibliothek. 

#import <Photos/Photos.h>

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

    NSURL *url = [info objectForKey:UIImagePickerControllerReferenceURL];
    PHFetchResult *fetchResult = [PHAsset fetchAssetsWithALAssetURLs:@[url] options:nil];
    PHAsset *asset = fetchResult.firstObject;

    //All you need is
    //asset.location.coordinate.latitude
    //asset.location.coordinate.longitude

    //Other useful properties of PHAsset
    //asset.favorite
    //asset.modificationDate
    //asset.creationDate
}
17

Apple hat in iOS4 ein Image-I/O-Framework hinzugefügt, mit dem EXIF-Daten aus Bildern gelesen werden können. Ich weiß nicht, ob die Variable UIImagePickerController ein Bild mit eingebetteten EXIF-Daten zurückgibt.

Edit: In iOS4 können Sie die EXIF-Daten abrufen, indem Sie den Wert des Schlüssels UIImagePickerControllerMediaMetadata im Info-Wörterbuch abrufen, das an den Delegaten UIImagePickerControllerDelegate übergeben wird. 

5

Ich hatte eine ähnliche Frage, wo ich nur das Datum wollte, an dem ein Bild aufgenommen wurde, und keines der oben genannten Punkte scheint mein Problem auf einfache Weise zu lösen (z. B. keine externen Bibliotheken). Hier sind also alle Daten, die ich finden konnte, die Sie extrahieren können aus einem Bild nach Auswahl mit dem Picker:

// Inside whatever implements UIImagePickerControllerDelegate
@import AssetsLibrary;

// ... your other code here ...

@implementation MYImagePickerDelegate

- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    NSString *mediaType = info[UIImagePickerControllerMediaType];
    UIImage *originalImage = info[UIImagePickerControllerOriginalImage];
    UIImage *editedImage = info[UIImagePickerControllerEditedImage];
    NSValue *cropRect = info[UIImagePickerControllerCropRect];
    NSURL *mediaUrl = info[UIImagePickerControllerMediaURL];
    NSURL *referenceUrl = info[UIImagePickerControllerReferenceURL];
    NSDictionary *mediaMetadata = info[UIImagePickerControllerMediaMetadata];

    NSLog(@"mediaType=%@", mediaType);
    NSLog(@"originalImage=%@", originalImage);
    NSLog(@"editedImage=%@", editedImage);
    NSLog(@"cropRect=%@", cropRect);
    NSLog(@"mediaUrl=%@", mediaUrl);
    NSLog(@"referenceUrl=%@", referenceUrl);
    NSLog(@"mediaMetadata=%@", mediaMetadata);

    if (!referenceUrl) {
        NSLog(@"Media did not have reference URL.");
    } else {
        ALAssetsLibrary *assetsLib = [[ALAssetsLibrary alloc] init];
        [assetsLib assetForURL:referenceUrl
                   resultBlock:^(ALAsset *asset) {
                       NSString *type = 
                           [asset valueForProperty:ALAssetPropertyType];
                       CLLocation *location = 
                           [asset valueForProperty:ALAssetPropertyLocation];
                       NSNumber *duration = 
                           [asset valueForProperty:ALAssetPropertyDuration];
                       NSNumber *orientation = 
                           [asset valueForProperty:ALAssetPropertyOrientation];
                       NSDate *date = 
                           [asset valueForProperty:ALAssetPropertyDate];
                       NSArray *representations = 
                           [asset valueForProperty:ALAssetPropertyRepresentations];
                       NSDictionary *urls = 
                           [asset valueForProperty:ALAssetPropertyURLs];
                       NSURL *assetUrl = 
                           [asset valueForProperty:ALAssetPropertyAssetURL];

                       NSLog(@"type=%@", type);
                       NSLog(@"location=%@", location);
                       NSLog(@"duration=%@", duration);
                       NSLog(@"assetUrl=%@", assetUrl);
                       NSLog(@"orientation=%@", orientation);
                       NSLog(@"date=%@", date);
                       NSLog(@"representations=%@", representations);
                       NSLog(@"urls=%@", urls);
                   }
                  failureBlock:^(NSError *error) {
                      NSLog(@"Failed to get asset: %@", error);
                  }];
    }

    [picker dismissViewControllerAnimated:YES
                               completion:nil];
}

@end

Wenn Sie also ein Bild auswählen, erhalten Sie eine Ausgabe, die folgendermaßen aussieht (einschließlich Datum!):

mediaType=public.image
originalImage=<UIImage: 0x7fb38e00e870> size {1280, 850} orientation 0 scale 1.000000
editedImage=<UIImage: 0x7fb38e09e1e0> size {640, 424} orientation 0 scale 1.000000
cropRect=NSRect: {{0, 0}, {1280, 848}}
mediaUrl=(null)
referenceUrl=assets-library://asset/asset.JPG?id=AC072879-DA36-4A56-8A04-4D467C878877&ext=JPG
mediaMetadata=(null)
type=ALAssetTypePhoto
location=(null)
duration=ALErrorInvalidProperty
assetUrl=assets-library://asset/asset.JPG?id=AC072879-DA36-4A56-8A04-4D467C878877&ext=JPG
orientation=0
date=2014-07-14 04:28:18 +0000
representations=(
    "public.jpeg"
)
urls={
    "public.jpeg" = "assets-library://asset/asset.JPG?id=AC072879-DA36-4A56-8A04-4D467C878877&ext=JPG";
}

Wie auch immer, hoffentlich spart das jemandem etwas Zeit.

3
plowman

Ich habe eine Weile daran gearbeitet, auch für eine Anwendung zu arbeiten, deren Bau ich beauftragt hatte. Grundsätzlich ist das API derzeit nicht möglich. Das grundlegende Problem ist die UIImage-Klasse STRIPS, mit Ausnahme der Orientierung, alle EXIF-Daten. Auch die Funktion zum Speichern auf der Kamerarolle entfernt diese Daten. Grundsätzlich besteht die einzige Möglichkeit, zusätzliche EXIF-Daten zu erfassen und zu verwalten, darin, diese in einer privaten "Kamerarolle" in Ihrer Anwendung zu speichern. Ich habe diesen Fehler auch bei Apple eingereicht und betont, dass die Wiederholer der App-Prüfer, mit denen wir in Kontakt gekommen sind, unbedingt erforderlich sind. Hoffentlich fügen sie es eines Tages hinzu. Andernfalls macht es das GEO-Tagging völlig nutzlos, da es nur in der Kamera-Anwendung "stock" funktioniert.

NOTEEinige Anwendungen im App Store hack um dieses herum. Durch das, was ich gefunden habe, greift man direkt auf die Kamerarolle zu und speichert Fotos direkt darauf, um GEO-Daten zu speichern. Dies funktioniert jedoch nur bei der Kamerarolle/gespeicherten Fotos und NICHT dem Rest der Fotobibliothek. Bei Fotos, die von Ihrem Computer aus mit Ihrem Telefon synchronisiert wurden, sind alle EXIF-Daten mit Ausnahme der Ausrichtung entfernt.

Ich kann immer noch nicht verstehen, warum diese Anwendungen genehmigt wurden (sie löschten sie sogar von der Kamerarolle) und unsere Anwendung, die nichts davon tut, wird immer noch zurückgehalten.

2
Urkle

Für iOS 8 und höher können Sie Photos Framework verwenden.

 func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
      let url = info[UIImagePickerControllerReferenceURL] as? URL
            if url != nil {

                let fetchResult = PHAsset.fetchAssets(withALAssetURLs: [url!], options: nil)
                let asset = fetchResult.firstObject
                print(asset?.location?.coordinate.latitude)
                print(asset?.creationDate)
            }
    }
2
Aravindhan

Verwenden Sie die UIImagePickerControllerMediaURL-Dictionary-Taste, um die Datei-URL zur Originaldatei zu erhalten. Trotz der Informationen in der Dokumentation können Sie die Datei-URL für Fotos und nicht nur für Filme erhalten.

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

    // Try to get the original file.
    NSURL *originalFile = [info objectForKey:UIImagePickerControllerMediaURL];
    if (originalFile) {
        NSData *fileData = [NSData dataWithContentsOfURL:originalFile];
    }
}
1
lucius

Dies ist etwas, was die öffentliche API nicht bietet, aber für viele Menschen nützlich sein könnte. Ihr primäres Mittel ist, dass Sie einen Fehler mit Apple ablegen der beschreibt, was Sie benötigen (und es kann hilfreich sein, zu erklären, warum Sie ihn auch brauchen). Hoffentlich könnte Ihre Anfrage es in eine zukünftige Version schaffen.

Nachdem Sie einen Fehler eingereicht haben, können Sie auch einen Vorfall des technischen DTS (Developer Technical Support) verwenden, der mit Ihrer Mitgliedschaft beim iPhone Developer Program geliefert wurde. Wenn es einen öffentlichen Weg gibt, dies zu tun, weiß ein Apple-Ingenieur Bescheid. Ansonsten kann es zumindest helfen, Ihre Notlage im Mutterschiff etwas mehr Aufmerksamkeit zu wecken. Viel Glück!

1
natevw

Um diese Metadaten zu erhalten, müssen Sie das untergeordnete Framework AVFoundation verwenden.

Schauen Sie sich das Beispiel von Apples Squarecam an (http://developer.Apple.com/library/ios/#samplecode/SquareCam/Introduction/Intro.html).

Finden Sie die Methode unten und fügen Sie die Zeile hinzu, die ich zum Code hinzugefügt habe. Das zurückgegebene Metadatenwörterbuch enthält außerdem ein Diagnose-NSDictionary-Objekt. 

- (BOOL)writeCGImageToCameraRoll:(CGImageRef)cgImage withMetadata:(NSDictionary *)metadata
{

   NSDictionary *Exif = [metadata objectForKey:@"Exif"];   // Add this line

}
0
Adam Roberts

Ich verwende dies für Rollbilder

-(CLLocation*)locationFromAsset:(ALAsset*)asset
{
    if (!asset)
        return nil;

    NSDictionary* pickedImageMetadata = [[asset defaultRepresentation] metadata];
    NSDictionary* gpsInfo = [pickedImageMetadata objectForKey:(__bridge NSString *)kCGImagePropertyGPSDictionary];
    if (gpsInfo){
        NSNumber* nLat = [gpsInfo objectForKey:(__bridge NSString *)kCGImagePropertyGPSLatitude];
        NSNumber* nLng = [gpsInfo objectForKey:(__bridge NSString *)kCGImagePropertyGPSLongitude];
        if (nLat && nLng)
            return [[CLLocation alloc]initWithLatitude:[nLat doubleValue] longitude:[nLng doubleValue]];
    }

    return nil;
}


-(void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    //UIImage *image =  [info objectForKey:UIImagePickerControllerOriginalImage];
    NSURL *assetURL = [info objectForKey:UIImagePickerControllerReferenceURL];

    // create the asset library in the init method of your custom object or view controller
    //self.library = [[ALAssetsLibrary alloc] init];
    //

    [self.library assetForURL:assetURL resultBlock:^(ALAsset *asset) {

        // try to retrieve gps metadata coordinates
        CLLocation* myLocation = [self locationFromAsset:asset];

        // Do your stuff....

    } failureBlock:^(NSError *error) {
        NSLog(@"Failed to get asset from library");
    }];
}

Es funktioniert offensichtlich, wenn das Bild GPS-Meta-Informationen enthält

Ich hoffe es hilft

0
Luca Iaco

Gibt es einen bestimmten Grund, aus dem Sie die Standortdaten extrahieren möchten? Eine Alternative könnte sein, den Standort separat mit dem CoreLocation-Framework abzurufen. Wenn Sie nur die Geodaten benötigen, kann dies einige Kopfschmerzen ersparen.

0
RupertP

Nur ein Gedanke, aber haben Sie TTPhotoViewController im Three20-Projekt auf GitHub ausprobiert?

Damit steht ein Bildwähler zur Verfügung, der aus mehreren Quellen lesen kann. Sie können es möglicherweise als Alternative zu UIImagePickerController verwenden, oder die Quelle gibt Ihnen möglicherweise Hinweise, wie Sie die benötigten Informationen erhalten.

0
Simon

Möglicherweise können Sie die vom UIImagePickerController zurückgegebenen Bilddaten und jedes der Bilder im Verzeichnis mit einem Hashwert versehen und vergleichen. 

0
Frank Schmitt

Dies ist in Swift 3, wenn Sie weiterhin Unterstützung für iOS 8 benötigen:

import AssetsLibrary

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

    if picker.sourceType == UIImagePickerControllerSourceType.photoLibrary,
        let url = info[UIImagePickerControllerReferenceURL] as? URL {

        let assetLibrary = ALAssetsLibrary()
        assetLibrary.asset(for: url, resultBlock: { (asset) in
            if let asset = asset {
                let assetRep: ALAssetRepresentation = asset.defaultRepresentation()
                let metaData: NSDictionary = assetRep.metadata() as NSDictionary
                print(metaData)
            }
        }, failureBlock: { (error) in
            print(error!)
        })
    }

}
0
Sam

es scheint, dass das von UIImagePickerControllerMediaURL erhaltene Foto überhaupt keine Exif-Tags hat

0
RolandasR

Für iOS 10 - Swift 3

Der Callback des Pickers hat ein info -Dict, bei dem ein Schlüssel mit Metadaten : UIImagePickerControllerMediaMetadata vorhanden ist.

 Image picker metadata example

0
Efren