UIImagePickerController和从现有照片中提取EXIF数据

56
众所周知,UIImagePickerController在选择后不会返回照片的元数据。但是,应用商店中的一些应用程序(Mobile Fotos、PixelPipe)似乎能够读取原始文件以及其中存储的EXIF数据,从而使应用程序能够提取所选照片中的地理数据。
它们似乎是通过从/private/var/mobile/Media/DCIM/100APPLE/文件夹中读取原始文件并运行一个EXIF库来实现这一点的。
然而,我无法找到一种将从UIImagePickerController返回的照片与磁盘上的文件匹配的方法。我尝试了文件大小,但原始文件是JPEG格式,而返回的图像是原始UIImage格式,因此无法知道所选图像的文件大小。
我正在考虑制作哈希表,并对每个图像的前x像素进行匹配。然而,这似乎有点过头了,而且可能相当慢。
有什么建议吗?

你在这段时间里找到了解决方案或替代方法吗? - Ton
大多数人选择的解决方案似乎都是构建自己的Table View以从照片列表中进行选择。Mobile Fotos似乎可以访问相机选择器,但我无法弄清楚如何实现。 - tomtaylor
这个问题现在已经过时了,因为在iOS 4.0下,可以使用AssetLibrary框架提取图像元数据。 - tomtaylor
不,它仍然是相关的,因为您必须支持3G设备。 - harshalb
18个回答

29
你看过这个exif iPhone库吗?http://code.google.com/p/iphone-exif/。我打算在我的项目中试用一下。我想要从使用UIImagePickerController拍摄的照片中获取GPS(地理标记)坐标 :/。
经过深入了解,该库似乎将NSData信息作为输入,UIImagePickerController在拍摄快照后返回UIImage。理论上,如果我们为 UIImage 使用 UIkit 类别中选取的内容。
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 - access location information from a photo UIImage不包含元信息,所以我们被卡住了:当然,这个接口不会提供任何EXIF信息。

FINAL UPDATE
Ok I managed to get it working, at least to geotag properly pictures returned by the picker.
Before triggering the UIImagePickerController, it's up to you to use the CLLocationManager to retrieve the current CLocation
Once you have it, you can use this method that uses exif-iPhone library to geotag the UIImage from the 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];
}

// 位置转换的辅助方法 -(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; } // 将GPS地理位置信息填充到EXFGPSLoc对象中 -(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]; }


是的,那就是我最初尝试的方法。但还是感谢你的努力 - 如果你找到了另一种方法,在这里让我们知道就好了。 - tomtaylor
我将尝试使用exif iPhone库自己对照片进行地理标记,我会及时告知您(从用户指南上看似乎“简单”,但实际上并不是那么直接... :/) - yonel
谢谢你的回复,但我实际上是想从相机应用程序拍摄的照片中获取EXIF信息。 - tomtaylor
@tom:是的,我知道我有点偏离你原来的主题 :) - yonel
1
并没有真正回答原来的问题! - gak
3
有没有这个库的 iOS5/ARC 版本? - snez

23

这适用于iOS5(beta 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];
  }
}

RonC是正确的。他的脚本允许您从照片库中选择的图像获取位置。UIImagePickerController不会提供相机的位置。您必须在应用程序中添加CLLocationManager。启动/委托给自己。并调用“startUpdatingLocation”。然后在“-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation”中,您可以从“newLocation”获取您的位置。现在,如果您想将图像保存到照片库中,可以使用https://github.com/gpambrozio/GusUtils。 - Wayne Liu
这个解决方案对于从相机胶卷中拍摄的图像和视频完美适用! - Ruben Marin

18

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
}

我可以在这个方法中获取当前的纬度和经度吗? - Shivam Bhalla

6

苹果在iOS4中添加了一个Image I/O框架,可用于从图片中读取EXIF数据。但我不知道UIImagePickerController是否返回带有嵌入式EXIF数据的图片。

编辑:在iOS4中,您可以通过获取传递给UIImagePickerControllerDelegate委托的信息字典中UIImagePickerControllerMediaMetadata键的值来获取EXIF数据。


这是因为出于安全原因,所有位置信息都必须被剥离。如果您想要位置信息,您必须使用上述库或者ALAssetsLibrary,在其中用户将被提示允许启用定位服务以获取位置信息。 - Pochi
委托回调的信息字典中没有 UIImagePickerControllerMediaMetadata 这样的键。它是 nil - Can Poyrazoğlu
此密钥仅在使用源类型设置为UIImagePickerController.SourceType.camera的图像选择器时有效,并且仅适用于静态图像。 - benc

4

我有一个类似的问题,我只想知道一张照片拍摄的日期,但上面提到的方法都不能简单地解决我的问题(例如没有外部库),所以这里是我能找到的所有数据,你可以在使用选择器选中图片后从中提取:

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

希望这能为其他人节省时间。无论如何,希望如此。

3
我曾经在一次合同工作中,为了开发一个应用程序,花费了一段时间研究此问题。基本上,根据当前的API,这是不可能的。问题的基本原因是UIImage类去掉了除方向之外的所有EXIF数据。另外,保存到相机胶卷的函数也会剥离这些数据。所以,唯一的方法是将任何额外的EXIF数据保存在应用程序的私有“相机胶卷”中并维护它。我也已经向苹果公司报告了这个问题,并强调了我们与应用程序审查员代表所联系过的需求。希望有一天他们会加入这个功能...否则,地理标记就完全没有意义,因为它只在“默认”相机应用程序中起作用。
注意:App Store上的一些应用程序通过“黑客”方式规避了这个问题。根据我所发现的,直接访问相机胶卷并将照片直接保存到其中以保存GEO数据。然而,这仅适用于相机胶卷/已保存的照片,而不适用于其余的照片库。从电脑同步到您的手机的照片除了方向之外,所有的EXIF数据都被剥离了。
我仍然无法理解为什么那些应用程序得到了批准(甚至从相机胶卷中删除),而我们的应用程序没有这样做,却被拖延了。

3

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

1

使用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];
    }
}

似乎在4.2版本中不存在。 - Alex Brown

1

这是公共API没有提供的内容,但对许多人可能很有用。您的主要途径是向苹果提交错误报告,描述您需要什么(解释一下为什么需要也可能有帮助)。希望您的请求能够在未来的版本中得到满足。

提交错误报告后,您还可以使用iPhone开发者计划成员附带的开发技术支持(DTS)事件之一。如果有公共方法可以实现此功能,苹果工程师会知道。否则,它至少可以帮助您的困境在总部获得更多关注。祝您好运!


1
谢谢 - 我刚刚提交了一个Bug:#7388190。 - tomtaylor

0
为了获取此元数据,您将需要使用较低级别的框架 AVFoundation。
请查看苹果的 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

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接