Question

Il est bien connu que UIImagePickerController ne renvoie pas les métadonnées de la photo après sélection.Cependant, quelques applications de l'App Store (Mobile Fotos, PixelPipe) semblent être capables de lire les fichiers originaux et les données EXIF ​​​​qui y sont stockées, permettant à l'application d'extraire les géodonnées de la photo sélectionnée.

Ils semblent faire cela en lisant le fichier original à partir du /privé/var/mobile/Media/DCIM/100APPLE/ dossier et l’exécuter via une bibliothèque EXIF.

Cependant, je n'arrive pas à trouver un moyen de faire correspondre une photo renvoyée par UIImagePickerController à un fichier sur le disque.J'ai exploré les tailles de fichiers, mais le fichier original est un JPEG, tandis que l'image renvoyée est une UIImage brute, ce qui rend impossible 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 cependant un peu exagéré et probablement assez lent.

Aucune suggestion?

Était-ce utile?

La solution 18

La façon la plus simple de procéder consiste à parcourir les vues de UIImagePickerViewController et à sélectionner l'image sélectionnée dans le rappel du délégué.

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

  id thumbnailView = [[[[[[[[[[picker.view subviews] 
                  objectAtIndex:0] subviews]
                  objectAtIndex:0] subviews]
                objectAtIndex:0] subviews]
                objectAtIndex:0] subviews]
              objectAtIndex:0];

  NSString *fullSizePath = [[[thumbnailView selectedPhoto] fileGroup] pathForFullSizeImage];
  NSString *thumbnailPath = [[[thumbnailView selectedPhoto] fileGroup] pathForThumbnailFile];

  NSLog(@"%@ and %@", fullSizePath, thumbnailPath);

}

Cela vous donnera le chemin d’accès à l’image en taille réelle, que vous pourrez ensuite ouvrir avec une bibliothèque EXIF ​​​​de votre choix.

Mais cela appelle une API privée et ces noms de méthodes volonté être détecté par Apple si vous soumettez cette application.Alors ne fais pas ça, d'accord ?

Autres conseils

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

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

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

Après un examen plus approfondi, cette bibliothèque semble prendre les informations NSData comme entrée et UIImagePickerController renvoie une UIImage après avoir pris un instantané.En théorie, si nous utilisons la sélection de la catégorie UIkit pour UIImage

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

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

MISE À JOUR:
J'ai fait un test à la bibliothèque mentionnée ci-dessus et cela semble fonctionner.Cependant en raison de mes connaissances limitées sur le format EXIF ​​et du manque d'API de haut niveau dans la bibliothèque, je n'arrive pas à obtenir les valeurs des balises EXIF.Voici mon code au cas où l'un d'entre 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 des balises sont renvoyées nil :(

Au cas où vous voudriez 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 - accéder aux informations de localisation à partir d'une photo Un UIImage n'encapsule pas les méta-informations, donc on est bloqué :bien sûr, aucune information EXIF ​​ne sera donnée 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 le UIImagePickerController, à vous 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 l'UIImage depuis le 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]; }

Cela fonctionne avec iOS5 (bêta 4) et la pellicule (vous avez besoin de taper defs pour les blocs dans le .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];
  }
}

Il y a un moyen d'entrer iOS 8

Sans en utilisant un tiers EXIF bibliothèque.

#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
}

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

Modifier: Dans iOS4, vous pouvez récupérer les données EXIF ​​en saisissant la valeur du UIImagePickerControllerMediaMetadata clé dans le dictionnaire d'informations qui est transmise au UIImagePickerControllerDelegate déléguer.

J'avais une question similaire où je voulais juste la date à laquelle une photo avait été prise et aucune des réponses ci-dessus ne semblait résoudre mon problème de manière simple (par ex.pas de bibliothèques externes), voici donc toutes les données que j'ai pu trouver et que vous pouvez extraire 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 un résultat qui ressemble à ceci (date incluse !) :

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 du temps à quelqu'un d'autre.

Je passe également du temps à travailler là-dessus pour une application pour laquelle j'ai été chargé de créer un contrat.Fondamentalement, dans l’état actuel de l’API, ce n’est pas possible.Le problème fondamental est que la classe UIImage supprime toutes les données EXIF, à l'exception de l'orientation vers l'extérieur.De plus, la fonction de sauvegarde sur la pellicule supprime 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 une « pellicule » privée dans votre application.J'ai également signalé ce bug à Apple et j'ai souligné la nécessité auprès des représentants des évaluateurs d'applications avec lesquels nous avons été en contact.J'espère qu'un jour ils l'ajouteront..Sinon, le marquage GEO est complètement inutile car il ne fonctionne que dans l'application caméra "stock".

NOTE Quelques applications sur l'App Store pirater autour de ça.Grâce à ce que j'ai trouvé, accéder directement à la pellicule et ENREGISTRER des photos directement dessus pour enregistrer les données GEO.Cependant, cela ne fonctionne qu'avec la pellicule/les photos enregistrées et NON avec le reste de la photothèque.Les photos "synchronisées" sur votre téléphone depuis votre ordinateur contiennent toutes les données EXIF, à l'exception de l'orientation supprimée.

Je n'arrive toujours pas à comprendre pourquoi ces applications ont été approuvées (bon sang, elles sont même SUPPRIMÉES de la pellicule) et notre application, qui ne fait rien de tout cela, est toujours retenue.

Pour iOS 8 et versions ultérieures, vous pouvez utiliser Cadre photo.

 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)
            }
    }

C’est quelque chose que l’API publique ne propose pas, mais qui pourrait être utile à de nombreuses personnes.Votre principal recours est de signaler un bug à Apple qui décrit ce dont vous avez besoin (et il peut également être utile d'expliquer pourquoi vous en avez besoin).J'espère que votre demande pourra figurer dans une future version.

Après avoir signalé un bogue, vous pouvez également utiliser l'un des incidents de support technique pour développeurs (DTS) fournis avec votre adhésion 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 aider à attirer un peu plus d’attention sur votre sort au sein du vaisseau mère.Bonne chance!

Utilisez le UIImagePickerControllerMediaURL clé de dictionnaire 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 pour 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];
    }
}

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

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 la source pourrait vous donner une idée sur la façon d'obtenir les informations dont vous avez besoin.

Y a-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 du framework CoreLocation.Si vous n’avez besoin que des géodonnées, cela pourrait vous éviter quelques maux de tête.

il semble que la photo obtenue par UIImagePickerControllerMediaURL n'ait pas du tout de balises exif

Afin d'obtenir ces métadonnées, vous devrez utiliser le framework de niveau inférieur AVFoundation.

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

Recherchez 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 de diagnostic NSDictionary.

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

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

}

Je l'utilise pour les images de pellicule

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

C'est dans Swift 3 si vous souhaitez toujours la prise en charge d'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!)
        })
    }

}

Pour iOS 10-Swift 3

Le rappel du sélecteur a un info dicter où se trouve une clé avec métadonnées: UIImagePickerControllerMediaMetadata

Image picker metadata example

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top