web-dev-qa-db-fra.com

UIImagePickerController et extraction de données EXIF ​​à partir de photos existantes

Il est bien connu que UIImagePickerController ne renvoie pas les métadonnées de la photo après la sélection. Cependant, quelques applications de la boutique d'applications (Mobile Fotos, PixelPipe) semblent être en mesure de lire les fichiers d'origine et les données EXIF ​​stockées, ce qui permet à l'application d'extraire les données géographiques de la photo sélectionnée.

Ils semblent le faire en lisant le fichier d'origine dans le dossier/private/var/mobile/Media/DCIM/100Apple/et en l'exécutant dans une bibliothèque EXIF.

Cependant, je ne peux pas trouver un moyen de faire correspondre une photo renvoyée par UIImagePickerController à un fichier sur le disque. J'ai exploré les tailles de fichier, mais le fichier d'origine est un fichier JPEG, tandis que l'image renvoyée est une image UII brute, ce qui empêche de connaître la taille du fichier de l'image sélectionnée.

J'envisage de créer un tableau de hachages et de faire correspondre les x premiers pixels de chaque image. Cela semble toutefois un peu exagéré et probablement assez lent.

Aucune suggestion?

56
tomtaylor

Avez-vous jeté un œil à cette bibliothèque exif pour iPhone?

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

Je vais l'essayer de mon côté. J'aimerais obtenir les coordonnées GPS (géomarques) de la photo prise avec le UIImagePickerController: /

Après un examen plus approfondi, cette bibliothèque semble prendre les informations NSData en entrée et le UIImagePickerController renvoie un UIImage après avoir pris un instantané. En théorie, si nous utilisons les éléments sélectionnés dans la catégorie UIkit pour UIImage

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

Ensuite, nous pouvons convertir UIImage en une instance NSData, puis l’utiliser avec la bibliothèque exif d’iPhone.

METTRE À JOUR:
J'ai testé la bibliothèque mentionnée ci-dessus et cela semble fonctionner. Cependant, en raison de ma connaissance limitée du format EXIF ​​et de l'absence d'API de haut niveau dans la bibliothèque, je ne parviens pas à obtenir les valeurs des balises EXIF ​​. Voici mon code au cas où l'un de vous pourrait aller plus loin: 


#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]];
....
....

La récupération de la définition des balises est OK, mais toutes les valeurs de balises retournent nil :(

Si vous voulez essayer la bibliothèque, vous devez définir une variable globale pour la faire fonctionner (comme expliqué dans la doc mais hum ..: /)

BOOL gLogging = FALSE;

MISE À JOUR 2
Répondez ici: iPhone - informations d’emplacement d’accès à partir d’une photoUn UIImage n'encapsule pas la méta-information, donc nous sommes bloqués: bien sûr, aucune information EXIF ​​ne sera transmise via cette interface.

MISE À JOUR FINALE
Ok, j’ai réussi à le faire fonctionner, au moins à géolocaliser correctement les images renvoyées par le sélecteur.
Avant de déclencher UIImagePickerController, il vous appartient d'utiliser le CLLocationManager pour récupérer le CLocation actuel.
Une fois que vous l'avez, vous pouvez utiliser cette méthode qui utilise la bibliothèque exif-iPhone pour géolocaliser le UIImage à partir de CLLocation:


-(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 =@"N"; 
    [exifMetaData addTagValue: ref forKey:[NSNumber numberWithInt:EXIF_GPSLatitudeRef] ]; 
    if (imageLocation.coordinate.longitude <0.0)
        ref = @"W"; 
    else
        ref =@"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

Cela fonctionne avec iOS5 (beta 4) et la pellicule (vous avez besoin de définir le type pour les blocs dans le fichier .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

Il y a un moyen dans iOS 8 

Sans en utilisant un tiersEXIFlibrary. 

#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
Warif Akhand Rishi

Apple a ajouté un cadre I/O Image dans iOS4 qui peut être utilisé pour lire des données EXIF ​​à partir d'images. Je ne sais pas si la UIImagePickerController renvoie une image avec les données EXIF ​​intégrées.

Éditer: Dans iOS4, vous pouvez récupérer les données EXIF ​​en récupérant la valeur de la clé UIImagePickerControllerMediaMetadata dans le dictionnaire d'informations transmis au délégué UIImagePickerControllerDelegate

5
Yannick Compernol

J'avais une question similaire où je voulais juste la date à laquelle une photo a été prise et aucune des réponses ci-dessus ne semble résoudre mon problème d'une manière simple (par exemple, aucune bibliothèque externe), voici donc toutes les données que je pourrais trouver que vous pouvez extraire à partir d'une image après l'avoir sélectionnée avec le sélecteur:

// 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

Ainsi, lorsque vous sélectionnez une image, vous obtenez une sortie qui ressemble à ceci (y compris la date!):

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";
}

Quoi qu'il en soit, j'espère que cela fera gagner un peu de temps à quelqu'un d'autre.

3
plowman

Je passe aussi quelque temps à travailler là-dessus pour une application que je devais construire. Fondamentalement, l’API n’est pas possible. Le problème de base est la classe UIImage STRIPS toutes les données EXIF ​​sauf pour l'orientation. La fonction de sauvegarde sur le rouleau de la caméra supprime également ces données. Donc, fondamentalement, la seule façon de récupérer et de conserver des données EXIF ​​supplémentaires est de les enregistrer dans un "film privé" dans votre application. J'ai également déposé ce bogue auprès d'Apple et souligné la nécessité pour les représentants des examinateurs des applications avec lesquels nous avons été en contact. Espérons qu’un jour, ils l’ajouteront. Sinon, le marquage GEO est totalement inutile, car il ne fonctionne que dans l’application "stock".

NOTECertaines applications sur l'App Store hack autour de cela. Par ce que j'ai trouvé, j'ai directement accédé au rouleau de la caméra et SAUVEGARDEZ les photos directement pour sauvegarder les données GEO. Cependant, cela ne fonctionne qu'avec le rouleau de la caméra/les photos enregistrées et PAS avec le reste de la bibliothèque de photos. Les photos "synchronisées" sur votre téléphone à partir de votre ordinateur contiennent toutes les données EXIF ​​sauf l'orientation.

Je ne comprends toujours pas pourquoi ces applications ont été approuvées (diable, elles ont même été supprimées du film) et notre application, qui ne fait rien de tout cela, est toujours bloquée.

2
Urkle

Pour iOS 8 et versions ultérieures, vous pouvez utiliser Photos Framework.

 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

Utilisez la clé de dictionnaire UIImagePickerControllerMediaURL pour obtenir l'URL du fichier vers le fichier d'origine. Malgré ce que dit la documentation, vous pouvez obtenir l’URL du fichier pour les photos et pas seulement les films.

- (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

C'est quelque chose que l'API publique ne fournit pas, mais pourrait être utile à beaucoup de gens. Votre principal recours consiste à signaler un bogue à Apple qui décrit ce dont vous avez besoin (et il peut être utile d'expliquer pourquoi vous en avez également besoin). J'espère que votre demande pourrait en faire une version ultérieure.

Après avoir déposé un bogue, vous pouvez également utiliser l’un des incidents DTS (Developer Technical Support) accompagnant votre abonnement au programme pour développeurs iPhone. S'il existe un moyen public de le faire, un ingénieur Apple le saura. Sinon, cela pourrait au moins vous aider à attirer un peu plus d'attention sur votre sort dans le ravitailleur. Bonne chance!

1
natevw

Pour obtenir ces métadonnées, vous devez utiliser le framework de niveau inférieur AVFoundation.

Jetez un coup d'oeil à l'exemple Squarecam d'Apple (http://developer.Apple.com/library/ios/#samplecode/SquareCam/Introduction/Intro.html)

Trouvez la méthode ci-dessous et ajoutez la ligne que j'ai ajoutée au code. Le dictionnaire de métadonnées renvoyé contient également un objet NSDictionary de diagnostic. 

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

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

}
0
Adam Roberts

J'utilise ceci pour les images en rouleau

-(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");
    }];
}

Cela fonctionne évidemment si l'image contient des méta informations gps

J'espère que ça aide

0
Luca Iaco

Existe-t-il une raison spécifique pour laquelle vous souhaitez extraire les données de localisation de l'image? Une alternative pourrait être d'obtenir l'emplacement séparément à l'aide de la structure CoreLocation. Si vous avez uniquement besoin des géodonnées, cela peut vous éviter des maux de tête.

0
RupertP

Juste une pensée, mais avez-vous essayé TTPhotoViewController dans le projet Three20 sur GitHub?

Cela fournit un sélecteur d'images capable de lire à partir de plusieurs sources. Vous pourrez peut-être l'utiliser comme alternative à UIImagePickerController, ou le code source pourrait vous donner un indice sur la manière d'obtenir l'information dont vous avez besoin.

0
Simon

Vous pourrez peut-être hacher les données d'image renvoyées par UIImagePickerController et chacune des images du répertoire et les comparer. 

0
Frank Schmitt

Ceci est dans Swift 3 si vous souhaitez toujours prendre en charge iOS 8:

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

il semble que la photo atteinte par UIImagePickerControllerMediaURL n'ait pas de balises exif

0
RolandasR

Pour iOS 10 - Swift 3

Le rappel du sélecteur a un info dict où il y a une clé avec métadonnées : UIImagePickerControllerMediaMetadata

 Image picker metadata example

0
Efren