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

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

Они, кажется, делают это, читая оригинальный файл из папки / private / var / mobile / Media / DCIM / 100APPLE / и пропуская его через библиотеку EXIF.

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

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

Есть предложения?

Ответы [ 18 ]

29 голосов
/ 28 декабря 2009

Вы смотрели на эту exif библиотеку iPhone?

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

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

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

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

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

UPDATE:
Я дал тест в библиотеку, упомянутую выше, и это похоже на работу. Однако из-за моего ограниченного знания о формате 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: (

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

BOOL gLogging = FALSE;

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

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

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

23 голосов
/ 28 июля 2011

Это работает с iOS5 (бета-версия 4) и прокруткой камеры (для блоков в .h нужно ввести defs):

-(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];
  }
}
17 голосов
/ 12 ноября 2015

Есть способ в 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
}
5 голосов
/ 28 сентября 2010

Apple добавила Image I / O Framework в iOS4, которую можно использовать для чтения данных EXIF ​​из картинок. Я не знаю, возвращает ли UIImagePickerController изображение со встроенными данными EXIF.

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

4 голосов
/ 03 февраля 2015

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

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

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

2 голосов
/ 11 ноября 2016

Для iOS 8 и более поздних версий вы можете использовать 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 голосов
/ 11 января 2010

Я также работаю над этим для приложения, на которое у меня был контракт. В основном, поскольку API в настоящее время стоит, это невозможно. Основной проблемой является UIImage класс STRIPS все данные EXIF ​​за исключением ориентации. Также функция сохранения в рулон камеры удаляет эти данные. Таким образом, по сути, единственный способ получить и сохранить любые дополнительные данные EXIF ​​- это сохранить их в отдельном «видеопленке» в вашем приложении. Я также поделился этой ошибкой с яблоком и подчеркнул необходимость того, чтобы с нами связывались представители рецензента. Надеемся, что когда-нибудь они добавят его. В противном случае использование тегов GEO станет бесполезным, так как оно работает только в «стандартной» камере.

ПРИМЕЧАНИЕ Некоторые приложения в магазине приложений взломать вокруг этого. Благодаря тому, что я нашел, прямой доступ к рулону камеры и СОХРАНЕНИЕ фотографий прямо на нее для сохранения данных GEO. Однако это работает только с фотоаппаратом / сохраненными фотографиями, а НЕ с остальной библиотекой фотографий. На фотографиях, «синхронизированных» с телефоном с вашего компьютера, содержатся все данные EXIF, за исключением ориентации.

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

1 голос
/ 03 августа 2010

Используйте ключ словаря 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];
    }
}
1 голос
/ 12 ноября 2009

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

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

0 голосов
/ 15 апреля 2014

Я использую это для изображений с камеры

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

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

...