Подтвердить что ты не робот

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

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

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

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

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

Любые предложения?

4b9b3361

Ответ 1

Непослушный способ сделать это - пересечь представления 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 ​​по вашему выбору.

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

Ответ 2

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

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

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

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

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

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

UPDATE:
Я дал тест в упомянутой выше библиотеке и, похоже, работает. Однако из-за моего ограниченного knwoledge о формате 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 для извлечения текущего 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 [email protected]"N"; 
    [exifMetaData addTagValue: ref forKey:[NSNumber numberWithInt:EXIF_GPSLatitudeRef] ]; 
    if (imageLocation.coordinate.longitude <0.0)
        ref = @"W"; 
    else
        ref [email protected]"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]; }

Ответ 3

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

Ответ 4

В 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

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

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

Ответ 6

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

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

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

Ответ 7

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

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

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

Для 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)
            }
    }

Ответ 11

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

Ответ 12

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

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

Ответ 13

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

Ответ 14

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

Ответ 15

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

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

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

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

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

}

Ответ 16

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

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

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

Ответ 17

Это в 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!)
        })
    }

}

Ответ 18

Для iOS 10 - Swift 3

Обратный вызов выбора имеет info dict, где есть ключ с metadata: UIImagePickerControllerMediaMetadata

Пример метаданных сборщика изображений