UIImagePickerController и извлечение данных EXIF из существующих фотографий
Хорошо известно, что UIImagePickerController не возвращает метаданные фотографии после выбора. Тем не менее, пара приложений в магазине приложений (Mobile Fotos, PixelPipe), похоже, могут читать исходные файлы и данные EXIF, хранящиеся в них, что позволяет приложению извлекать геоданные из выбранной фотографии.
Похоже, что они делают это, читая оригинальный файл из папки /private/var/mobile/Media/DCIM/100APPLE/ и пропуская его через библиотеку EXIF.
Однако я не могу найти способ сопоставления фотографии, возвращенной из UIImagePickerController, с файлом на диске. Я исследовал размеры файлов, но исходный файл представляет собой JPEG, в то время как возвращаемое изображение является необработанным UIImage, что делает невозможным узнать размер файла изображения, которое было выбрано.
Я рассматриваю возможность создания таблицы хэшей и сопоставления с первыми пикселями х каждого изображения. Это кажется немного чрезмерным, и, вероятно, довольно медленно.
Какие-либо предложения?
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 после создания снимка. Теоретически, если мы используем выбранный из категории 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
:(
Если вы хотите попробовать библиотеку, вам нужно определить глобальную переменную, чтобы она заработала (как объяснено в документе, но гудит..:/)
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];
}
// 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 нужно ввести 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];
}
}
Есть способ в 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 добавила Image I/O Framework в 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 STRIPS все данные EXIF за исключением ориентации. Также функция сохранения в рулон камеры удаляет эти данные. Таким образом, по сути, единственный способ получить и сохранить любые дополнительные данные EXIF - это сохранить их в отдельном "списке камер" в вашем приложении. Я также поделился этой ошибкой с яблоком и подчеркнул необходимость того, чтобы с нами связывались представители рецензента. Надеюсь, когда-нибудь они добавят его. В противном случае это делает использование тегов GEO совершенно бесполезным, поскольку это работает только в "стандартном" приложении камеры.
ПРИМЕЧАНИЕ Некоторые приложения в магазине приложений взломать это. Благодаря тому, что я нашел, прямой доступ к рулону камеры и СОХРАНЕНИЕ фотографий прямо к нему для сохранения данных GEO. Однако это работает только с фотоаппаратом / сохраненными фотографиями, а НЕ с остальной библиотекой фотографий. На фотографиях, "синхронизированных" с телефоном с вашего компьютера, есть все данные EXIF, за исключением снятой ориентации.
Я до сих пор не могу понять, почему эти приложения были одобрены (черт возьми, они даже УДАЛЯЮТ с рулона камеры), и наше приложение, которое ничего не делает, все еще удерживается.
Для 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)
}
}
Это то, что публичный 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];
}
}
Я использую это для изображений с камеры
-(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
Надеюсь, поможет
Есть ли конкретная причина, по которой вы хотите извлечь данные о местоположении из изображения? Альтернативой может быть получение местоположения отдельно с использованием инфраструктуры CoreLocation. Если вам нужны только геоданные, это может избавить вас от головной боли.
Просто мысль, но вы пробовали TTPhotoViewController в проекте Three20 на GitHub?
Это обеспечивает средство выбора изображений, которое может читать из нескольких источников. Вы можете использовать его в качестве альтернативы UIImagePickerController, или источник может дать вам подсказку, как решить, как получить необходимую информацию.
Кажется, что фотография, полученная UIImagePickerControllerMediaURL, вообще не имеет тегов exif
Возможно, вы сможете хэшировать данные изображения, возвращаемые UIImagePickerController, и каждое из изображений в каталоге и сравнивать их.
Это в 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!)
})
}
}
Для того, чтобы получить эти метаданные, вы должны будете использовать структуру нижнего уровня 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
}
Для iOS 10 - Swift 3
Обратный звонок сборщика имеет info
укажите, где находится ключ с метаданными: UIImagePickerControllerMediaMetadata