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

时间:2009-08-06 13:19:34

标签: iphone cocoa-touch gps uiimagepickercontroller exif

众所周知,UIImagePickerController在选择后不会返回照片的元数据。但是,应用程序商店中的一些应用程序(Mobile Fotos,PixelPipe)似乎能够读取原始文件和存储在其中的EXIF数据,使应用程序能够从所选照片中提取地理数据。

他们似乎是通过从 / private / var / mobile / Media / DCIM / 100APPLE / 文件夹中读取原始文件并通过EXIF库运行它来完成此操作。

但是,我无法找到一种方法将从UIImagePickerController返回的照片与磁盘上的文件进行匹配。我已经探索了文件大小,但原始文件是JPEG,而返回的图像是原始UIImage,因此无法知道所选图像的文件大小。

我正在考虑制作一个哈希表并匹配每个图像的前x个像素。这似乎有点过头了,可能很慢。

有什么建议吗?

18 个答案:

答案 0 :(得分:29)

您是否看过这个exif iPhone库?

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

要试试我的一面。我想从UIImagePickerController拍摄的照片中获取GPS(地理标记)坐标:/

经过深入研究后,这个库似乎将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 - access location information from a photo UIImage没有封装元信息,因此我们陷入困境:当然,不会通过此界面提供EXIF信息。

最终更新
好吧,我设法让它工作,至少是对选择器返回的图片进行地理标记 在触发UIImagePickerController之前,您可以使用CLLocationManager来检索当前的CLocation
获得后,您可以使用此方法使用exif-iPhone库从CLLocation对UIImage进行地理标记:


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

答案 1 :(得分:23)

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

答案 2 :(得分:17)

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
}

答案 3 :(得分:5)

Apple在iOS4中添加了一个Image I / O Framework,可用于从图片中读取EXIF数据。我不知道UIImagePickerController是否会返回嵌入了EXIF数据的图片。

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

答案 4 :(得分: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";
}

无论如何,希望能节省一些时间。

答案 5 :(得分:2)

我花了一些时间来研究这个我签约建立的应用程序。基本上,因为API目前是不可能的。基本问题是UIImage类STRIPS所有EXIF数据除了方向输出。此外,保存到相机胶卷的功能也会删除此数据。因此,基本上获取和维护任何额外EXIF数据的唯一方法是将其保存在应用程序中的私有“相机胶卷”中。我也向苹果提交了这个错误,并强调了我们一直与之联系的应用评论员代表的需求。希望总有一天他们会加入它。否则它会使GEO标记完全无用,因为它只适用于“库存”相机应用程序。

注意应用程序商店中的某些应用程序 hack 。通过我发现,直接访问相机胶卷和SAVING照片直接到它以节省GEO数据。但是,这仅适用于相机胶卷/已保存的照片,而不适用于照片库的其余部分。从计算机“同步”到手机的照片包含所有EXIF数据,除了方向剥离。

我仍然无法理解为什么这些应用程序被批准(哎呀他们甚至从相机卷中删除)而我们的应用程序没有做到这一点仍然被阻止。

答案 6 :(得分:2)

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

答案 7 :(得分:1)

这是公共API未提供的内容,但可能对许多人有用。您的主要追索权是file a bug with Apple,它描述了您的需求(并且解释您为什么需要它也会有所帮助)。希望您的请求能够成为未来的版本。

提交错误后,您还可以使用iPhone开发人员计划会员资格附带的开发人员技术支持(DTS)事件之一。如果有公开的方式来做这件事,Apple工程师就会知道。否则,它至少可以帮助你在母舰中更多地关注你的困境。祝你好运!

答案 8 :(得分: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];
    }
}

答案 9 :(得分:0)

适用于iOS 10 - Swift 3

选择器的回调有一个info字典,其中有一个带有metadata的密钥:UIImagePickerControllerMediaMetadata

Image picker metadata example

答案 10 :(得分:0)

您可以对UIImagePickerController返回的图像数据和目录中的每个图像进行散列并进行比较。

答案 11 :(得分:0)

如果你仍然想要支持iOS 8,那么这是在Swift 3中:

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

}

答案 12 :(得分:0)

我将其用于相机胶卷图像

-(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元信息

,它显然有效

希望有所帮助

答案 13 :(得分:0)

为了获得这些元数据,你必须使用较低级别的框架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

}

答案 14 :(得分:0)

似乎UIImagePickerControllerMediaURL获得的照片根本没有exif标签

答案 15 :(得分:0)

您是否有特定原因需要从图像中提取位置数据?另一种方法是使用CoreLocation框架单独获取位置。如果它只是您需要的地理数据,这可能会让您感到头疼。

答案 16 :(得分:0)

只是一个想法,但你在GitHub上的Three20项目中尝试过TTPhotoViewController吗?

这提供了一个可以从多个来源读取的图像选择器。您可以将它用作UIImagePickerController的替代品,或者源代码可能会为您提供如何获取所需信息的线索。

答案 17 :(得分:-2)

执行此操作的顽皮方法是遍历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会检测到这些方法名称​​ 。所以不要这样做,好吗?