从UIImage中获取Exif数据 - UIImagePickerController

53

如何从选自UIImagePickerController的UIImage获取Exif信息?

我做了很多研究,并得到了很多答复,但仍然无法实现这一点。

我已经阅读了链接。

请帮我解决这个问题。

提前感谢您。


请查看此答案。iOS 8的解决方案 - Warif Akhand Rishi
6个回答

35

有趣的问题!我想出了以下解决方案,适用于从照片库中选择的图像(请注意,我的代码使用ARC):

导入AssetsLibrary.frameworkImageIO.framework

然后在您的.h文件中包含所需的类:

#import <AssetsLibrary/ALAsset.h>
#import <AssetsLibrary/ALAssetRepresentation.h>
#import <ImageIO/CGImageSource.h>
#import <ImageIO/CGImageProperties.h>

请将以下代码添加到您的imagePickerController:didFinishPickingMediaWithInfo:代理方法中:

ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:[info objectForKey:UIImagePickerControllerReferenceURL]
    resultBlock:^(ALAsset *asset) {

        ALAssetRepresentation *image_representation = [asset defaultRepresentation];

        // create a buffer to hold image data 
        uint8_t *buffer = (Byte*)malloc(image_representation.size);
        NSUInteger length = [image_representation getBytes:buffer fromOffset: 0.0  length:image_representation.size error:nil];

        if (length != 0)  {

            // buffer -> NSData object; free buffer afterwards
            NSData *adata = [[NSData alloc] initWithBytesNoCopy:buffer length:image_representation.size freeWhenDone:YES];

            // identify image type (jpeg, png, RAW file, ...) using UTI hint
            NSDictionary* sourceOptionsDict = [NSDictionary dictionaryWithObjectsAndKeys:(id)[image_representation UTI] ,kCGImageSourceTypeIdentifierHint,nil];

            // create CGImageSource with NSData
            CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef) adata,  (__bridge CFDictionaryRef) sourceOptionsDict);

            // get imagePropertiesDictionary
            CFDictionaryRef imagePropertiesDictionary;
            imagePropertiesDictionary = CGImageSourceCopyPropertiesAtIndex(sourceRef,0, NULL);

            // get exif data
            CFDictionaryRef exif = (CFDictionaryRef)CFDictionaryGetValue(imagePropertiesDictionary, kCGImagePropertyExifDictionary);
            NSDictionary *exif_dict = (__bridge NSDictionary*)exif;
            NSLog(@"exif_dict: %@",exif_dict);

            // save image WITH meta data
            NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
            NSURL *fileURL = nil;
            CGImageRef imageRef = CGImageSourceCreateImageAtIndex(sourceRef, 0, imagePropertiesDictionary);

            if (![[sourceOptionsDict objectForKey:@"kCGImageSourceTypeIdentifierHint"] isEqualToString:@"public.tiff"])
                     {
                         fileURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/%@.%@",
                                                           documentsDirectory,
                                                           @"myimage",
                                                           [[[sourceOptionsDict objectForKey:@"kCGImageSourceTypeIdentifierHint"] componentsSeparatedByString:@"."] objectAtIndex:1]
                                                           ]];

                         CGImageDestinationRef dr = CGImageDestinationCreateWithURL ((__bridge CFURLRef)fileURL,
                                                                                     (__bridge CFStringRef)[sourceOptionsDict objectForKey:@"kCGImageSourceTypeIdentifierHint"],
                                                                                     1,
                                                                                     NULL
                                                                                    );
              CGImageDestinationAddImage(dr, imageRef, imagePropertiesDictionary);
              CGImageDestinationFinalize(dr);
              CFRelease(dr);
            }
            else
            {
              NSLog(@"no valid kCGImageSourceTypeIdentifierHint found …");
            }

            // clean up
            CFRelease(imageRef);
            CFRelease(imagePropertiesDictionary);
            CFRelease(sourceRef);
        }
        else {
            NSLog(@"image_representation buffer length == 0");
        }
    }
    failureBlock:^(NSError *error) {
        NSLog(@"couldn't get asset: %@", error);
    }
];

我注意到的一件事是,如果iOS要求用户允许位置服务,而用户拒绝了,那么您将无法获取图像数据...

编辑

添加代码以保存包括元数据在内的图像。这是一种快速的方法,可能有更好的方法,但它有效!


请问您如何在获取exif数据后保存图像? - Mehul Mistri
请问你的代码中的 "__bridge" 是什么意思? :-) - Mehul Mistri
@Marvin 我正在使用ARC。 __bridge 是一组关键字之一,可以告诉ARC有关对象所有权的信息,以便正确清理它们。 最简单的情况是 __bridge 转换,对于这种转换,ARC 不会执行任何额外工作(它假定您自己处理对象的内存)。 - dom
buffer的内存是否被释放了?怀疑没有...? - Joe D'Andrea
1
请注意:您还需要包含 #import <ImageIO/CGImageDestination.h>。 - Arnaud
显示剩余2条评论

29

这些回答看起来都非常复杂。如果图像已保存到相机胶卷中,并且您拥有ALAsset(无论是从UIImagePicker还是ALAssetLibrary获取),则可以按以下方式获取元数据:

asset.defaultRepresentation.metadata;

如果您想将相机胶卷中的图像保存到另一个位置(比如沙盒/文档文件夹),只需执行以下操作:

CGImageDestinationRef imageDestinationRef   = CGImageDestinationCreateWithURL((__bridge CFURLRef)urlToSaveTo, kUTTypeJPEG, 1, NULL);
CFDictionaryRef imagePropertiesRef          = (__bridge CFDictionaryRef)asset.defaultRepresentation.metadata;

CGImageDestinationAddImage(imageDestinationRef, asset.defaultRepresentation.fullResolutionImage, imagePropertiesRef);
if (!CGImageDestinationFinalize(imageDestinationRef)) NSLog(@"Failed to copy photo on save to %@", urlToSaveTo);

CFRelease(imageDestinationRef);

1
这种方法比2012年的答案要好得多。 - broughten
1
它还返回比其他方式更多的信息:它们不会返回存储在{TIFF}中的信息,如型号、制造商、版权、艺术家等等。这应该被标记为答案! - bam

25

我已经在这里找到了解决方案并得到了答案。

从这里我们还可以获取GPS信息...

太棒了,感谢所有帮助我解决这个问题的人。

更新

这是另一个我自己创建的函数,也返回Exif数据以及GPS数据,在这个函数中我们不需要任何第三方库...但你必须打开位置服务并使用当前纬度和经度。所以必须使用CoreLocation.framework

//FOR CAMERA IMAGE

-(NSMutableData *)getImageWithMetaData:(UIImage *)pImage
{
    NSData* pngData =  UIImagePNGRepresentation(pImage);

    CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)pngData, NULL);
    NSDictionary *metadata = (NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);

    NSMutableDictionary *metadataAsMutable = [[metadata mutableCopy]autorelease];
    [metadata release];

    //For GPS Dictionary
    NSMutableDictionary *GPSDictionary = [[[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyGPSDictionary]mutableCopy]autorelease];
    if(!GPSDictionary) 
        GPSDictionary = [NSMutableDictionary dictionary];

    [GPSDictionary setValue:[NSNumber numberWithDouble:currentLatitude] forKey:(NSString*)kCGImagePropertyGPSLatitude];
    [GPSDictionary setValue:[NSNumber numberWithDouble:currentLongitude] forKey:(NSString*)kCGImagePropertyGPSLongitude];

    NSString* ref;
    if (currentLatitude <0.0)
        ref = @"S"; 
    else
        ref =@"N";  
    [GPSDictionary setValue:ref forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];

    if (currentLongitude <0.0)
        ref = @"W"; 
    else
        ref =@"E";  
    [GPSDictionary setValue:ref forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];

    [GPSDictionary setValue:[NSNumber numberWithFloat:location.altitude] forKey:(NSString*)kCGImagePropertyGPSAltitude];

    //For EXIF Dictionary
    NSMutableDictionary *EXIFDictionary = [[[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy]autorelease];
    if(!EXIFDictionary) 
        EXIFDictionary = [NSMutableDictionary dictionary];

    [EXIFDictionary setObject:[NSDate date] forKey:(NSString*)kCGImagePropertyExifDateTimeOriginal];
    [EXIFDictionary setObject:[NSDate date] forKey:(NSString*)kCGImagePropertyExifDateTimeDigitized];

    //add our modified EXIF data back into the image’s metadata
    [metadataAsMutable setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary];
    [metadataAsMutable setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];

    CFStringRef UTI = CGImageSourceGetType(source);

    NSMutableData *dest_data = [NSMutableData data];
    CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)dest_data, UTI, 1, NULL);

    if(!destination)
        dest_data = [[pngData mutableCopy] autorelease];
    else 
    {
        CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef) metadataAsMutable);
        BOOL success = CGImageDestinationFinalize(destination);
        if(!success)
            dest_data = [[pngData mutableCopy] autorelease];
    }

    if(destination)
        CFRelease(destination);

    CFRelease(source);

    return dest_data;
}

//FOR PHOTO LIBRARY IMAGE

-(NSMutableData *)getImagedataPhotoLibrary:(NSDictionary *)pImgDictionary andImage:(UIImage *)pImage
{
    NSData* data = UIImagePNGRepresentation(pImage);

    CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)data, NULL);
    NSMutableDictionary *metadataAsMutable = [[pImgDictionary mutableCopy]autorelease];

    CFStringRef UTI = CGImageSourceGetType(source);

    NSMutableData *dest_data = [NSMutableData data];

    //For Mutabledata
    CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)dest_data, UTI, 1, NULL);

    if(!destination)
        dest_data = [[data mutableCopy] autorelease];
    else 
    {
        CGImageDestinationAddImageFromSource(destination, source, 0, (CFDictionaryRef) metadataAsMutable);
        BOOL success = CGImageDestinationFinalize(destination);
        if(!success)
            dest_data = [[data mutableCopy] autorelease];
    }
    if(destination)
        CFRelease(destination);

    CFRelease(source);

    return dest_data;
}

我们将通过以下方式检索该数据

//FOR CAMERA IMAGE
NSData *originalImgData = [self getImageWithMetaData:imgOriginal];

//FOR PHOTO LIBRARY IMAGE
[self getImagedataPhotoLibrary:[[myasset defaultRepresentation] metadata] andImage:imgOriginal];

为了做到这一点,您需要导入AssetsLibrary.frameworkImageIO.framework


当前的纬度是从哪里来的?你是在调用UIImagePicker之前通过标准的CoreLocation调用设置它,还是直接从返回的图像中读取它? - Paul Cezanne
在调用UIImagePickerController之前先调用CoreLocation。 - Mehul Mistri
3
您实际上并未从图像中获取EXIF数据,而是创建了EXIF并将其添加到图像中。这并没有真正回答问题。 - ATOzTOA
这看起来不像是一个干净的解决方案。 - bneupaane
关于ATOzTOA在2012年的评论,是的,EXIF是手动创建的——这已经是几个版本的iOS所必需的工作流程,而且现在仍然如此(2018年,iOS 11.2)。这是为了隐私原因,以使具有相机/照片访问权限的应用程序不会自动获得位置数据,除非经过明确批准。 - brichins
显示剩余5条评论

10

我已经使用了这种方法从图像中获取exifdata字典,希望这对您也有用。

-(void)getExifDataFromImage:(UIImage *)currentImage
{

    NSData* pngData =  UIImageJPEGRepresentation(currentImage, 1.0);

    CGImageSourceRef mySourceRef = CGImageSourceCreateWithData((CFDataRef)pngData, NULL);

    //CGImageSourceRef mySourceRef = CGImageSourceCreateWithURL((__bridge CFURLRef)myURL, NULL);
    if (mySourceRef != NULL)
    {
        NSDictionary *myMetadata = (__bridge NSDictionary *)CGImageSourceCopyPropertiesAtIndex(mySourceRef,0,NULL);
        NSDictionary *exifDic = [myMetadata objectForKey:(NSString *)kCGImagePropertyExifDictionary];
        NSDictionary *tiffDic = [myMetadata objectForKey:(NSString *)kCGImagePropertyTIFFDictionary];
        NSLog(@"exifDic properties: %@", myMetadata); //all data
        float rawShutterSpeed = [[exifDic objectForKey:(NSString *)kCGImagePropertyExifExposureTime] floatValue];
        int decShutterSpeed = (1 / rawShutterSpeed);
        NSLog(@"Camera %@",[tiffDic objectForKey:(NSString *)kCGImagePropertyTIFFModel]);
        NSLog(@"Focal Length %@mm",[exifDic objectForKey:(NSString *)kCGImagePropertyExifFocalLength]);
        NSLog(@"Shutter Speed %@", [NSString stringWithFormat:@"1/%d", decShutterSpeed]);
        NSLog(@"Aperture f/%@",[exifDic objectForKey:(NSString *)kCGImagePropertyExifFNumber]);


        NSNumber *ExifISOSpeed  = [[exifDic objectForKey:(NSString*)kCGImagePropertyExifISOSpeedRatings] objectAtIndex:0];
        NSLog(@"ISO %ld",[ExifISOSpeed integerValue]);
        NSLog(@"Taken %@",[exifDic objectForKey:(NSString*)kCGImagePropertyExifDateTimeDigitized]);


    }

}

1

您需要使用ALAssetsLibrary来从图片中获取EXIF信息。只有当图片被保存到照片库时,才会添加EXIF信息。即使您使用ALAssetLibrary从库中获取图像资产,如果将其设置为UIImage,则会丢失所有EXIF信息。


-3

我尝试将iPad相机选取的GPS坐标插入图像元数据中,正如Mehul建议的那样。 它有效了,感谢你的帖子。

附言: 想要使用该代码的人,只需在函数-(NSMutableData *)getImageWithMetaData:(UIImage *)pImage {顶部替换两个地理位置即可。

double currentLatitude = [locationManager location].coordinate.latitude;
double currentLongitude = [locationManager location].coordinate.longitude;

...

假设您已经在代码中的某个地方初始化了locationManager,就像这样:

    locationManager = [[CLLocationManager alloc] init];
    [locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
    [locationManager setDelegate:self]; // Not necessary in this case
    [locationManager startUpdatingLocation]; // Not neccessary in this case

通过导入与相关框架一起的CoreLocation/CoreLocation.h和ImageIO/ImageIO.h头文件。

这不是回答问题,而是离题了。 - Juan Boero
离题了。他没有问如何获取位置,而是需要将位置作为UIImage的元数据放置。 - Onder OZCAN

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