UIImagePickerController и извлечение EXIF-данных из существующих фотографий

StackOverflow https://stackoverflow.com/questions/1238838

Вопрос

Хорошо известно, что UIImagePickerController не возвращает метаданные фотографии после выбора.Однако пара приложений в app Store (Mobile Fotos, PixelPipe), похоже, способны считывать исходные файлы и хранящиеся в них данные EXIF, что позволяет приложению извлекать геоданные из выбранной фотографии.

Похоже, они делают это, считывая исходный файл из /приватный/ var/ мобильный / Медиа/DCIM/100APPLE/ папку и запускаем ее через библиотеку EXIF.

Однако я не могу придумать способ сопоставления фотографии, возвращенной из UIImagePickerController, с файлом на диске.Я изучил размеры файлов, но исходный файл имеет формат JPEG, в то время как возвращаемое изображение представляет собой UIImage raw, что делает невозможным определение размера файла выбранного изображения.

Я подумываю о создании таблицы хэшей и сопоставлении с первыми x пикселями каждого изображения.Хотя это кажется немного перебором и, вероятно, довольно медленным.

Есть какие-нибудь предложения?

Это было полезно?

Решение 18

Самый простой способ сделать это - просмотреть представления UIImagePickerViewController и выбрать выбранное изображение в обратном вызове делегата.

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

}

Это даст вам путь к полноразмерному изображению, которое затем вы можете открыть с помощью библиотеки EXIF по вашему выбору.

Но это вызывает Частный API и эти имена методов будет быть обнаруженным Apple, если вы отправите это приложение.Так что не делай этого, хорошо?

Другие советы

Вы уже ознакомились с этой библиотекой exif для iPhone?

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

Собираюсь попробовать это со своей стороны.Я хотел бы получить координаты GPS (геотеги) из изображения, которое было сделано с помощью UIImagePickerController :/

После более глубокого изучения эта библиотека, похоже, принимает информацию NSData в качестве входных данных, а UIImagePickerController возвращает UIImage после создания моментального снимка.Теоретически, если мы используем selected из категории UIKit для UIImage

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

Затем мы можем преобразовать UIImage в экземпляр NSData, а затем использовать его с библиотекой iPhone exif.

Обновить:
Я провел тест для библиотеки, упомянутой выше, и, похоже, это работает.Однако из-за моей ограниченной осведомленности о формате EXIF и отсутствия высокоуровневого API в библиотеке мне не удается получить значения для тегов EXIF.Вот мой код на случай, если кто-нибудь из вас сможет пойти дальше :


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

Извлечение определения тегов выполняется нормально, но все значения тегов возвращают nil :(

В случае, если вы хотите попробовать библиотеку, вам нужно определить глобальную переменную, чтобы запустить ее (как описано в документе, но hum..:/)

BOOL gLogging = FALSE;

ОБНОВЛЕНИЕ 2
Ответьте здесь : iPhone - доступ к информации о местоположении с фотографии UIImage не инкапсулирует метаинформацию, так что мы застряли :конечно, никакая информация EXIF не будет предоставлена через этот интерфейс.

ОКОНЧАТЕЛЬНОЕ ОБНОВЛЕНИЕ
Хорошо, мне удалось заставить это работать, по крайней мере, правильно пометить фотографии, возвращенные сборщиком.
Перед запуском UIImagePickerController вы должны использовать CLLocationManager для извлечения текущей синхронизации
Получив его, вы можете использовать этот метод, который использует библиотеку exif-iPhone для геотегирования UIImage из 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]; }

Это работает с iOS5 (бета-версия 4) и фотопленкой (вам нужны определения типов для блоков в .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];
  }
}

Есть способ войти iOS 8

Без используя любую стороннюю EXIF библиотека.

#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 добавила в iOS4 платформу ввода-вывода изображений, которую можно использовать для чтения EXIF-данных с изображений.Я не знаю, является ли UIImagePickerController однако возвращает изображение со встроенными данными EXIF.

Редактировать: В iOS4 вы можете извлечь данные EXIF, взяв значение UIImagePickerControllerMediaMetadata ключ в информационном словаре, который передается в UIImagePickerControllerDelegate делегировать.

У меня был аналогичный вопрос, где я хотел просто указать дату, когда был сделан снимок, и ни одно из вышеперечисленных действий, похоже, не решает мою проблему простым способом (напримерникаких внешних библиотек), итак, вот все данные, которые я смог найти, которые вы можете извлечь из изображения после выбора его с помощью средства выбора:

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

Таким образом, когда вы выбираете изображение, вы получаете результат, который выглядит примерно так (включая дату!):

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

В любом случае, надеюсь, это сэкономит кому-то еще немного времени.

Я также некоторое время работаю над этим для приложения, на создание которого у меня был контракт.В принципе, в нынешнем виде API это невозможно.Основная проблема заключается в том, что класс UIImage УДАЛЯЕТ все данные EXIF, за исключением ориентации.Кроме того, функция сохранения на фотопленку удаляет эти данные.Таким образом, по сути, единственный способ получить и сохранить любые дополнительные данные EXIF - это сохранить их в частной "фотопленке" в вашем приложении.Я также сообщил об этой ошибке в Apple и подчеркнул необходимость обращения к представителям App reviewer, с которыми мы контактировали.Надеюсь, когда-нибудь они это добавят..В противном случае геотегирование становится совершенно бесполезным, поскольку оно работает только в приложении "стоковая" камера.

ПРИМЕЧАНИЕ Некоторые приложения в App Store взломать вокруг этого.С помощью того, что я нашел, можно напрямую получить доступ к фотопленке и СОХРАНЯТЬ фотографии прямо в нее, чтобы сохранить географические данные.Однако это работает только с фотопленкой / сохраненными фотографиями, а НЕ с остальной частью библиотеки фотографий.Фотографии, "синхронизированные" с вашего компьютера на ваш телефон, содержат все данные EXIF, за исключением удаленной ориентации.

Я все еще не могу понять, почему эти приложения были одобрены (черт возьми, они даже УДАЛЯЮТСЯ с фотопленки), а наше приложение, которое ничего из этого не делает, все еще сдерживается.

Для iOS 8 и более поздних версий вы можете использовать Рамки для фотографий.

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

Это то, что общедоступный API не предоставляет, но может быть полезно многим людям.Ваше основное средство - это отправьте сообщение об ошибке в Apple это описывает то, что вам нужно (и может быть полезно объяснить, зачем вам это нужно).Надеюсь, ваш запрос сможет попасть в будущий релиз.

После сообщения об ошибке вы также можете воспользоваться одним из обращений в службу технической поддержки разработчиков (DTS), связанных с вашим членством в программе разработчика iPhone.Если существует общедоступный способ сделать это, инженер Apple будет знать.В противном случае, это может, по крайней мере, помочь привлечь к вашему бедственному положению немного больше внимания на материнском корабле.Желаю удачи!

Используйте UIImagePickerControllerMediaURL ключ словаря для получения URL-адреса файла к исходному файлу.Несмотря на то, что говорится в документации, вы можете получить URL-адрес файла для фотографий, а не только для фильмов.

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

Возможно, вы сможете хэшировать данные изображения, возвращаемые UIImagePickerController, и каждое из изображений в каталоге и сравнивать их.

Просто мысль, но вы пробовали TTPhotoViewController в проекте Three20 на GitHub?

Это обеспечивает средство выбора изображений, которое может считывать данные из нескольких источников.Возможно, вы сможете использовать его как альтернативу UIImagePickerController, или исходный код может дать вам подсказку, как решить, как получить необходимую вам информацию.

Есть ли конкретная причина, по которой вы хотите извлечь данные о местоположении из изображения?Альтернативой может быть получение местоположения отдельно с помощью CoreLocation framework.Если вам нужны только геоданные, это может избавить вас от некоторых головных болей.

похоже, что фотография, полученная с помощью UIImagePickerControllerMediaURL, вообще не имеет тегов exif

Чтобы получить эти метаданные, вам придется использовать фреймворк более низкого уровня AVFoundation.

Взгляните на пример Squarecam от Apple (http://developer.apple.com/library/ios/#samplecode/SquareCam/Introduction/Intro.html)

Найдите метод ниже и добавьте строку, которую я добавил в код.Возвращаемый словарь метаданных также содержит объект diagnostics NSDictionary.

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

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

}

Я использую это для снимков с фотопленки

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

Очевидно, что это работает, если изображение содержит метаинформацию gps

Надеюсь, это поможет

Это в Swift 3, если вы все еще хотите поддерживать 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!)
        })
    }

}

Для iOS 10 - Swift 3

Обратный вызов сборщика имеет info укажите, где находится ключ с метаданные: UIImagePickerControllerMediaMetadata

Image picker metadata example

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top