UIImagePickerController e extrair dados EXIF ​​das fotos existentes

É bem sabido que o UIImagePickerController não retorna os metadados da foto após a seleção. No entanto, alguns aplicativos na loja de aplicativos (Fotos de dispositivos móveis, PixelPipe) parecem conseguir ler os arquivos originais e os dados EXIF ​​armazenados neles, permitindo que o aplicativo extraia os dados geocharts da foto selecionada.

Eles parecem fazer isso lendo o arquivo original da pasta / private / var / mobile / Mídia / DCIM / 100APPLE / e o executando através de uma biblioteca EXIF.

No entanto, não consigo encontrar uma maneira de corresponder uma foto retornada do UIImagePickerController a um arquivo no disco. Eu explorei tamanhos de arquivo, mas o arquivo original é um JPEG, enquanto a imagem retornada é um UIImage bruto, tornando impossível saber o tamanho do arquivo da imagem que foi selecionada.

Eu estou pensando em fazer uma tabela de hashes e correspondência contra os primeiros x pixels de cada imagem. Isso parece um pouco exagerado, e provavelmente muito lento.

Alguma sugestão?

    Você já deu uma olhada nesta biblioteca do iPhone exif?

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

    Vou tentar do meu lado. Eu gostaria de obter as coordenadas GPS (geotags) da imagem que foi tirada com o UIImagePickerController: /

    Depois de uma análise mais profunda, esta biblioteca parece ter informações NSData como uma input e o UIImagePickerController retorna um UIImage depois de tirar um instantâneo. Em teoria, se usarmos o selecionado da categoria UIkit para UIImage

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

    Em seguida, podemos converter o UIImage em uma instância NSData e, em seguida, usá-lo com a biblioteca exif do iPhone.

    ATUALIZAR:
    Eu dei um teste para a biblioteca mencionada acima e parece funcionar. No entanto, devido ao meu conhecimento limitado sobre o formato EXIF ​​e a falta de API de alto nível na biblioteca, não consigo obter os valores para as tags EXIF. Aqui está o meu código no caso de qualquer um de vocês poder ir além:

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

    A recuperação da definição das tags é OK, mas todos os valores da tag retornam nil 🙁

    Caso você queira testar a biblioteca, você precisa definir uma variável global para executá-la (como explicado no documento, mas hum ..: /)

    BOOL gLogging = FALSE;

    ATUALIZAÇÃO 2
    Responda aqui: iPhone – acesse informações de localização de uma foto A UIImage não encapsula as metainformações , então estamos presos: com certeza, nenhuma informação EXIF ​​será dada através desta interface.

    ATUALIZAÇÃO FINAL
    Ok, eu consegui fazê-lo funcionar, pelo menos para geotag corretamente imagens retornadas pelo selecionador.
    Antes de acionar o UIImagePickerController, cabe a você usar o CLLocationManager para recuperar o atual CLocation
    Uma vez que você o tenha, você pode usar este método que usa a biblioteca exif-iPhone para georreferenciar o UIImage a partir 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]; } 

    // Métodos auxiliares para conversão de localização - (NSMutableArray *) createLocArray: (double) val {val = fabs (val); NSMutableArray * array = [[alocação NSMutableArray] init]; duplo grau = (int) val; [array addObject: [NSNumber numberWithDouble: deg]]; val = val - deg; val = val * 60; minutos duplos = (int) val; [array addObject: [NSNumber numberWithDouble: minutes]]; val = val - minutos; val = val * 60; duplo segundo = val; [array addObject: [NSNumber numberWithDouble: seconds]]; return array; } - (void) populateGPS: (EXFGPSLoc *) gpsLoc: (NSArray *) locArray {longo numDenumArray [2]; long * arrPtr = numDenumArray; [EXFUtils convertRationalToFraction: & arrPtr: [locArray objectAtIndex: 0]]; EXFraction * fract = [[alocação de EXFraction] initWith: numDenumArray [0]: numDenumArray [1]]; gpsLoc.degrees = fract; [liberação de fratura]; [EXFUtils convertRationalToFraction: & arrPtr: [locArray objectAtIndex: 1]]; fract = [[alocação EXFraction] initWith: numDenumArray [0]: numDenumArray [1]]; gpsLoc.minutes = fract; [liberação de fratura]; [EXFUtils convertRationalToFraction: & arrPtr: [locArray objectAtIndex: 2]]; fract = [[alocação EXFraction] initWith: numDenumArray [0]: numDenumArray [1]]; gpsLoc.seconds = fract; [liberação de fratura]; }

    Isso funciona com o iOS5 (beta 4) e o rolo da câmera (você precisa digitar defs para os blocos no .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]; } } 

    Existe uma maneira no iOS 8

    Sem usar qualquer biblioteca EXIF terceiros.

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

    A Apple adicionou um Image I / O Framework no iOS4, que pode ser usado para ler dados EXIF ​​de imagens. Eu não sei se o UIImagePickerController retorna uma imagem com os dados EXIF ​​incorporados.

    Editar: no iOS4, você pode buscar os dados EXIF, pegando o valor da chave UIImagePickerControllerMediaMetadata no dictionary de informações que é passado para o delegado UIImagePickerControllerDelegate .

    Eu tinha uma pergunta semelhante em que queria apenas a data em que uma foto foi tirada e nenhuma das opções acima parece resolver meu problema de maneira simples (por exemplo, sem bibliotecas externas), então aqui estão todos os dados que você pode extrair a partir de uma imagem depois de selecioná-la com o selecionador:

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

    Então, quando você seleciona uma imagem, você obtém uma saída semelhante a esta (incluindo a data!):

     mediaType=public.image originalImage= size {1280, 850} orientation 0 scale 1.000000 editedImage= 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"; } 

    De qualquer forma, espero que isso poupe outra pessoa algum tempo.

    Eu gasto um tempo trabalhando nisso também para um aplicativo que eu fui contratado para construir. Basicamente, como a API atualmente está, não é possível. O problema básico é que a class UIImage STRIPS todos os dados EXIF, exceto para a saída de orientação. Além disso, a function de salvar no rolo da câmera retira esses dados. Então, basicamente, a única maneira de pegar e manter qualquer dado EXIF ​​extra é salvá-lo em um “rolo de câmera” privado em seu aplicativo. Eu arquivei este bug com a apple também e enfatizei a necessidade dos representantes do aplicativo em que estivemos em contato. Espero que algum dia eles o adicionem. Caso contrário, isso fará com que o GEO seja completamente inútil, já que ele só funciona no aplicativo de câmera “stock”.

    OBSERVAÇÃO Alguns aplicativos na App Store hackeavam isso. Por, o que eu encontrei, acessando diretamente o rolo da câmera e salvando fotos diretamente para ele para salvar dados GEO. No entanto, isso só funciona com o rolo da câmera / fotos salvas e NÃO com o restante da biblioteca de fotos. As fotos “sincronizadas” para o seu telefone a partir do seu computador possuem todos os dados EXIF, exceto pela orientação despojada.

    Eu ainda não consigo entender por que esses aplicativos foram aprovados (até mesmo eles DELETE do rolo da câmera) e nosso aplicativo que não faz nada disso ainda está sendo retido.

    Isso é algo que a API pública não fornece, mas pode ser útil para muitas pessoas. Seu recurso principal é registrar um bug na Apple que descreve o que você precisa (e pode ser útil explicar por que você também precisa). Espero que o seu pedido possa ser lançado em breve.

    Depois de preencher um bug, você também pode usar um dos incidentes do Suporte Técnico ao Desenvolvedor (DTS) fornecidos com a associação ao seu Programa para Desenvolvedores do iPhone. Se houver uma maneira pública de fazer isso, um engenheiro da Apple saberá. Caso contrário, pode pelo menos ajudar o seu sofrimento um pouco mais de atenção dentro da nave-mãe. Boa sorte!

    Use a chave do dictionary UIImagePickerControllerMediaURL para obter o URL do arquivo para o arquivo original. Apesar do que a documentação diz, você pode obter o URL do arquivo para fotos e não apenas filmes.

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

    Para o iOS 8 e posterior, você pode usar o Fotos 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) } } 

    Você pode fazer o hash dos dados da imagem retornados pelo UIImagePickerController e cada uma das imagens no diretório e compará-los.

    Apenas um pensamento, mas você já tentou o TTPhotoViewController no projeto do Three20 no GitHub?

    Isso fornece um seletor de imagens que pode ler de várias fonts. Você pode usá-lo como uma alternativa ao UIImagePickerController, ou a fonte pode lhe dar uma pista de como descobrir como obter as informações necessárias.

    Existe algum motivo específico para você extrair os dados de localização da imagem? Uma alternativa poderia ser obter o local separadamente usando o framework CoreLocation. Se for apenas o geodata que você precisa, isso pode poupar algumas dores de cabeça.

    parece que a foto obtida pelo UIImagePickerControllerMediaURL não tem tags exif

    Para obter esses metadados, você precisará usar o AVFoundation de estrutura de nível inferior.

    Dê uma olhada no exemplo da Squarecam da Apple (http://developer.apple.com/library/ios/#samplecode/SquareCam/Introduction/Intro.html)

    Encontre o método abaixo e adicione a linha que adicionei ao código. O dictionary de metadados retornado também contém um object NSDictionary de diagnóstico.

     - (BOOL)writeCGImageToCameraRoll:(CGImageRef)cgImage withMetadata:(NSDictionary *)metadata { NSDictionary *Exif = [metadata objectForKey:@"Exif"]; // Add this line } 

    Eu estou usando isso para imagens de rolo de câmera

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

    Funciona, obviamente, se a imagem contiver meta-informações gps

    Espero que ajude

    Isso está no Swift 3 se você ainda quiser suporte para o 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!) }) } } 

    Para iOS 10 – Swift 3

    O retorno de chamada do selecionador possui uma info onde há uma chave com metadados : UIImagePickerControllerMediaMetadata

    Exemplo de metadados do seletor de imagens

    A maneira impertinente de fazer isso é percorrer as exibições do UIImagePickerViewController e selecionar a imagem selecionada no retorno de chamada delegado.

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

    Isso lhe dará o caminho para a imagem em tamanho real, que você pode abrir com uma biblioteca EXIF ​​de sua escolha.

    Mas isso chama uma API privada e esses nomes de método serão detectados pela Apple se você enviar este aplicativo. Então não faça isso, ok?