Domanda

È noto che UIImagePickerController non restituisce i metadati della foto dopo la selezione.Tuttavia, un paio di app nell'app store (Mobile Fotos, PixelPipe) sembrano essere in grado di leggere i file originali e i dati EXIF ​​memorizzati al loro interno, consentendo all'app di estrarre i dati geografici dalla foto selezionata.

Sembra che lo facciano leggendo il file originale dal file /privato/var/mobile/Media/DCIM/100APPLE/ cartella ed eseguirlo tramite una libreria EXIF.

Tuttavia, non riesco a trovare un modo per abbinare una foto restituita da UIImagePickerController a un file su disco.Ho esplorato le dimensioni dei file, ma il file originale è un JPEG, mentre l'immagine restituita è un UIImage non elaborato, rendendo impossibile conoscere la dimensione del file dell'immagine selezionata.

Sto pensando di creare una tabella di hash e di abbinarli ai primi x pixel di ciascuna immagine.Questo sembra un po' esagerato e probabilmente piuttosto lento.

Eventuali suggerimenti?

È stato utile?

Soluzione 18

Il modo cattivo per farlo è attraversare le visualizzazioni di UIImagePickerViewController e selezionare l'immagine selezionata nel callback del delegato.

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

}

Questo ti darà il percorso per l'immagine a dimensione intera, che potrai quindi aprire con una libreria EXIF ​​​​di tua scelta.

Ma questo chiama un'API privata e questi nomi di metodo Volere essere rilevato da Apple se invii questa app.Quindi non farlo, ok?

Altri suggerimenti

Hai dato un'occhiata a questa libreria EXIF ​​​​iPhone?

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

Lo proverò dalla mia parte.Mi piacerebbe ottenere le coordinate GPS (geotag) dall'immagine scattata con UIImagePickerController :/

Dopo uno sguardo più approfondito, questa libreria sembra prendere le informazioni NSData come input e UIImagePickerController restituisce un UIImage dopo aver acquisito uno snapshot.In teoria, se utilizziamo la selezione dalla categoria UIkit per UIImage

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

Quindi possiamo convertire UIImage in un'istanza NSData e quindi utilizzarla con la libreria EXIF ​​dell'iPhone.

AGGIORNAMENTO:
Ho fatto un test alla libreria menzionata sopra e sembra funzionare.Tuttavia, a causa della mia conoscenza limitata del formato EXIF ​​e della mancanza di API di alto livello nella libreria, non riesco a ottenere i valori per i tag EXIF.Ecco il mio codice nel caso qualcuno di voi possa andare oltre:


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

Il recupero della definizione dei tag è OK, ma vengono restituiti tutti i valori dei tag nil :(

Nel caso in cui desideri provare la libreria, devi definire una variabile globale per farla funzionare (come spiegato nel documento ma hum..:/)

BOOL gLogging = FALSE;

AGGIORNAMENTO 2
Rispondi qui: iPhone: accedi alle informazioni sulla posizione da una foto Un UIImage non incapsula le meta informazioni, quindi siamo bloccati:di sicuro, nessuna informazione EXIF ​​verrà fornita tramite questa interfaccia.

AGGIORNAMENTO FINALE
Ok, sono riuscito a farlo funzionare, almeno a geotaggare correttamente le immagini restituite dal selettore.
Prima di attivare UIImagePickerController, spetta a te utilizzare CLLocationManager per recuperare la CLocation corrente
Una volta ottenuto, puoi utilizzare questo metodo che utilizza la libreria EXIF-iPhone per geotaggare l'UIImage da 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]; }

Funziona con iOS5 (beta 4) e il rullino fotografico (è necessario digitare defs per i blocchi nel file .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];
  }
}

C'è un modo per entrare iOS 8

Senza utilizzando qualsiasi terza parte EXIF biblioteca.

#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 ha aggiunto un Image I/O Framework in iOS4 che può essere utilizzato per leggere i dati EXIF ​​dalle immagini.Non so se il UIImagePickerController restituisce però un'immagine con i dati EXIF ​​incorporati.

Modificare: In iOS4 puoi recuperare i dati EXIF ​​afferrando il valore di UIImagePickerControllerMediaMetadata chiave nel dizionario info che viene passato al file UIImagePickerControllerDelegate delegare.

Avevo una domanda simile in cui volevo solo la data in cui è stata scattata una foto e nessuna delle risposte precedenti sembra risolvere il mio problema in modo semplice (ad es.nessuna libreria esterna), quindi ecco tutti i dati che ho trovato che puoi estrarre da un'immagine dopo averla selezionata con il selettore:

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

Pertanto, quando selezioni un'immagine, ottieni un output simile a questo (inclusa la data!):

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

Ad ogni modo, spero che questo faccia risparmiare tempo a qualcun altro.

Trascorro un po' di tempo lavorando anche su questo per un'applicazione che mi è stato assegnato il compito di creare.Fondamentalmente allo stato attuale dell'API non è possibile.Il problema di base è che la classe UIImage STRIPS tutti i dati EXIF ​​tranne l'orientamento.Anche la funzione di salvataggio sul rullino fotografico rimuove questi dati.Quindi, in pratica, l'unico modo per acquisire e conservare eventuali dati EXIF ​​aggiuntivi è salvarli in un "rullino fotografico" privato nella tua applicazione.Ho segnalato questo bug anche ad Apple e ho sottolineato la necessità ai rappresentanti dei revisori dell'app con cui siamo stati in contatto.Speriamo che un giorno lo aggiungano..Altrimenti rende completamente inutile il tagging GEO poiché funziona solo nell'applicazione fotocamera "di serie".

NOTA Alcune applicazioni sull'App Store hackerare intorno a questo.In base a ciò che ho scoperto, accedo direttamente al rullino fotografico e SALVO le foto direttamente su di esso per salvare i dati GEO.Tuttavia, funziona solo con il rullino fotografico/le foto salvate e NON con il resto della libreria fotografica.Le foto "sincronizzate" sul telefono dal computer hanno tutti i dati EXIF ​​esclusi l'orientamento.

Non riesco ancora a capire perché quelle applicazioni siano state approvate (diamine, le hanno addirittura CANCELLATE dal rullino fotografico) e la nostra applicazione che non fa nulla di tutto ciò è ancora trattenuta.

Per iOS 8 e versioni successive puoi utilizzare Quadro Foto.

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

Questo è qualcosa che l'API pubblica non fornisce, ma potrebbe essere utile a molte persone.La tua risorsa principale è: segnalare un bug ad Apple che descrive ciò di cui hai bisogno (e può essere utile spiegare anche perché ne hai bisogno).Speriamo che la tua richiesta possa essere inclusa in una versione futura.

Dopo aver segnalato un bug, puoi anche utilizzare uno degli interventi di supporto tecnico per sviluppatori (DTS) forniti con l'iscrizione al programma per sviluppatori iPhone.Se esiste un modo pubblico per farlo, un ingegnere Apple lo saprà.Altrimenti, potrebbe almeno aiutarti a ottenere un po' più di attenzione sulla tua situazione all'interno della nave madre.Buona fortuna!

Usa il UIImagePickerControllerMediaURL chiave del dizionario per ottenere l'URL del file originale.Nonostante ciò che dice la documentazione, puoi ottenere l'URL del file per le foto e non solo per i film.

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

Potresti essere in grado di eseguire l'hashing dei dati dell'immagine restituiti da UIImagePickerController e ciascuna delle immagini nella directory e confrontarli.

Solo un pensiero, ma hai provato TTPhotoViewController nel progetto Three20 su GitHub?

Ciò fornisce un selettore di immagini in grado di leggere da più fonti.Potresti essere in grado di usarlo come alternativa a UIImagePickerController oppure la fonte potrebbe darti un indizio su come ottenere le informazioni di cui hai bisogno.

C'è un motivo specifico per cui desideri estrarre i dati sulla posizione dall'immagine?Un'alternativa potrebbe essere quella di ottenere la posizione separatamente utilizzando il framework CoreLocation.Se ti servono solo i dati geografici, questo potrebbe risparmiarti qualche grattacapo.

sembra che la foto ottenuta da UIImagePickerControllerMediaURL non abbia affatto tag EXIF

Per ottenere questi metadati dovrai utilizzare il framework di livello inferiore AVFoundation.

Dai un'occhiata all'esempio Squarecam di Apple (http://developer.apple.com/library/ios/#samplecode/SquareCam/Introduction/Intro.html)

Trova il metodo qui sotto e aggiungi la riga che ho aggiunto al codice.Il dizionario dei metadati restituito contiene anche un oggetto diagnostico NSDictionary.

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

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

}

Lo sto usando per le immagini del rullino fotografico

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

Funziona ovviamente se l'immagine contiene metainformazioni gps

Spero che sia d'aiuto

Questo è in Swift 3 se desideri ancora il supporto per 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!)
        })
    }

}

Per iOS 10-Swift 3

La richiamata del selezionatore ha un info detta dove c'è una chiave con metadati: UIImagePickerControllerMediaMetadata

Image picker metadata example

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top