为图像设置 exif 数据时出现问题
Posted
技术标签:
【中文标题】为图像设置 exif 数据时出现问题【英文标题】:Problem setting exif data for an image 【发布时间】:2011-07-04 18:19:15 【问题描述】:我在 ios 4.1 中使用新的 ImageIO 框架。我使用以下方法成功检索了 exif 元数据:
CFDictionaryRef metadataDict = CMGetAttachment(sampleBuffer, kCGImagePropertyExifDictionary , NULL);
读出来,它似乎是有效的。将图像保存出来是可行的,但图像中永远没有任何 exif 数据。
CGImageDestinationRef myImageDest = CGImageDestinationCreateWithURL((CFURLRef) docurl, kUTTypeJPEG, 1, NULL);
// Add the image to the destination using previously saved options.
CGImageDestinationAddImage(myImageDest, iref, NULL);
//add back exif
NSDictionary *props = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:.1], kCGImageDestinationLossyCompressionQuality,
metadataDict, kCGImagePropertyExifDictionary, //the exif metadata
nil];
//kCGImagePropertyExifAuxDictionary
CGImageDestinationSetProperties(myImageDest, (CFDictionaryRef) props);
// Finalize the image destination.
bool status = CGImageDestinationFinalize(myImageDest);
【问题讨论】:
保存到用户照片库时,4.1 中存在自定义 EXIF 数据的错误。保存到您的沙盒应该可以。 这个其实是保存到Documents目录下的,我是用4.2和4.3建的,同样的问题。很可能我在代码中做错了什么,但我还没有确定那是什么。 嗨,我有一个类似的问题,我快速浏览了一下。我无法解决你的问题,但我通过加载我知道设置了 kCGImageDestinationLossyCompressionQuality 的图像(或者在我的情况下为 GPS)并查看元数据字典来解决我的问题。这样,当我去保存它时,我可以确保我的结构具有相同的结构,因为如果 iOS 不喜欢该结构,它似乎不会保存 EXIF。另外,我注意到 kCGImageDestinationLossyCompressionQuality = "kCGImageDestinationLossyCompressionQuality"。看起来很奇怪? 【参考方案1】:以下博客文章是我在修改和保存 Exif 数据 Caffeinated Cocoa 时遇到问题时得到答案的地方。这适用于 iOS。
这是我编写 Exif 和 GPS 数据的测试代码。它几乎是上述博客中代码的复制和粘贴。我正在使用它来将 exif 数据写入捕获的图像。
NSData *jpeg = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer] ;
CGImageSourceRef source ;
source = CGImageSourceCreateWithData((CFDataRef)jpeg, NULL);
//get all the metadata in the image
NSDictionary *metadata = (NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source,0,NULL);
//make the metadata dictionary mutable so we can add properties to it
NSMutableDictionary *metadataAsMutable = [[metadata mutableCopy]autorelease];
[metadata release];
NSMutableDictionary *EXIFDictionary = [[[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy]autorelease];
NSMutableDictionary *GPSDictionary = [[[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyGPSDictionary]mutableCopy]autorelease];
if(!EXIFDictionary)
//if the image does not have an EXIF dictionary (not all images do), then create one for us to use
EXIFDictionary = [NSMutableDictionary dictionary];
if(!GPSDictionary)
GPSDictionary = [NSMutableDictionary dictionary];
//Setup GPS dict
[GPSDictionary setValue:[NSNumber numberWithFloat:_lat] forKey:(NSString*)kCGImagePropertyGPSLatitude];
[GPSDictionary setValue:[NSNumber numberWithFloat:_lon] forKey:(NSString*)kCGImagePropertyGPSLongitude];
[GPSDictionary setValue:lat_ref forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
[GPSDictionary setValue:lon_ref forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
[GPSDictionary setValue:[NSNumber numberWithFloat:_alt] forKey:(NSString*)kCGImagePropertyGPSAltitude];
[GPSDictionary setValue:[NSNumber numberWithShort:alt_ref] forKey:(NSString*)kCGImagePropertyGPSAltitudeRef];
[GPSDictionary setValue:[NSNumber numberWithFloat:_heading] forKey:(NSString*)kCGImagePropertyGPSImgDirection];
[GPSDictionary setValue:[NSString stringWithFormat:@"%c",_headingRef] forKey:(NSString*)kCGImagePropertyGPSImgDirectionRef];
[EXIFDictionary setValue:xml forKey:(NSString *)kCGImagePropertyExifUserComment];
//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); //this is the type of image (e.g., public.jpeg)
//this will be the data CGImageDestinationRef will write into
NSMutableData *dest_data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)dest_data,UTI,1,NULL);
if(!destination)
NSLog(@"***Could not create image destination ***");
//add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination,source,0, (CFDictionaryRef) metadataAsMutable);
//tell the destination to write the image data and metadata into our data object.
//It will return false if something goes wrong
BOOL success = NO;
success = CGImageDestinationFinalize(destination);
if(!success)
NSLog(@"***Could not create data from image destination ***");
//now we have the data ready to go, so do whatever you want with it
//here we just write it to disk at the same path we were passed
[dest_data writeToFile:file atomically:YES];
//cleanup
CFRelease(destination);
CFRelease(source);
【讨论】:
太棒了!谢谢史蒂夫。这是我一直在寻找的东西!这个周末试试! 嘿,史蒂夫,你找到不复制图像的方法了吗? 不,我没有使用 CC L. 的方法或第三方图像/exif 处理库。 您使用哪个第三方库? CC L 的回答很好,但我在当前项目中没有使用示例缓冲区,只是来自 UIImage 的常规 NSData。 该代码使用了一个未定义的名为 xml 的变量。它是什么数据类型?应该是什么格式?【参考方案2】:我尝试了史蒂夫的答案并且它有效,但我认为大图像的速度很慢,因为它会复制整个图像。
您可以使用 CMSetAttachments 直接在 CMSampleBuffer 上设置属性。只需在致电jpegStillImageNSDataRepresentation
之前执行此操作即可
CFDictionaryRef metaDict = CMCopyDictionaryOfAttachments(NULL, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
CFMutableDictionaryRef mutable = CFDictionaryCreateMutableCopy(NULL, 0, metaDict);
NSMutableDictionary * mutableGPS = [self getGPSDictionaryForLocation:self.myLocation];
CFDictionarySetValue(mutable, kCGImagePropertyGPSDictionary, mutableGPS);
// set the dictionary back to the buffer
CMSetAttachments(imageSampleBuffer, mutable, kCMAttachmentMode_ShouldPropagate);
getGPSDictionaryForLocation: 方法可以在这里找到:
Saving Geotag info with photo on iOS4.1
【讨论】:
感谢您的加入。我将不得不试一试。我从不喜欢复制图像。 如果您知道防止复制图像的方法,请更新我们,因为这对我来说是一个交易破坏者。如果您使用的是 AVFoundation,则此答案有效,但看不到如何将其与 UIImagePickerController 一起使用。 我无法想象为什么这个答案被低估了?!它就地更改 EXIF 信息,因此节省了大量内存和 CPU 时间。感谢您的洞察力! 这里一切正常,除了当我写出图像时,不会保存新字段,但更改的现有字段会保存。很奇怪:***.com/questions/28419536/…【参考方案3】:我创建了一个 MSMutableDictionary 类别来帮助将地理标记和其他元数据保存到图像中。在这里查看我的博文:
http://blog.codecropper.com/2011/05/adding-metadata-to-ios-images-the-easy-way/
【讨论】:
喜欢您的类别并计划使用它,但您应该提到,根据 Steve McFarlin 的帖子,一个 可以 实际上也可以使用它来将元数据写入文件以上。【参考方案4】:[由于没有解释的反对票而重新访问此答案。]
Apple 已更新其文章以解决此问题(技术问答 QA1622)。如果您使用的是旧版本的 Xcode,您可能仍然有文章说,或多或少,运气不好,如果不对图像数据进行低级解析,您将无法做到这一点。
更新版本在这里:
https://developer.apple.com/library/ios/#qa/qa1622/_index.html
我将那里的代码修改如下:
- (void) saveImage:(UIImage *)imageToSave withInfo:(NSDictionary *)info
// Get the image metadata (EXIF & TIFF)
NSMutableDictionary * imageMetadata = [[info objectForKey:UIImagePickerControllerMediaMetadata] mutableCopy];
// add (fake) GPS data
CLLocationCoordinate2D coordSF = CLLocationCoordinate2DMake(37.732711,-122.45224);
// arbitrary altitude and accuracy
double altitudeSF = 15.0;
double accuracyHorizontal = 1.0;
double accuracyVertical = 1.0;
NSDate * nowDate = [NSDate date];
// create CLLocation for image
CLLocation * loc = [[CLLocation alloc] initWithCoordinate:coordSF altitude:altitudeSF horizontalAccuracy:accuracyHorizontal verticalAccuracy:accuracyVertical timestamp:nowDate];
// this is in case we try to acquire actual location instead of faking it with the code right above
if ( loc )
[imageMetadata setObject:[self gpsDictionaryForLocation:loc] forKey:(NSString*)kCGImagePropertyGPSDictionary];
// Get the assets library
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// create a completion block for when we process the image
ALAssetsLibraryWriteImageCompletionBlock imageWriteCompletionBlock =
^(NSURL *newURL, NSError *error)
if (error)
NSLog( @"Error writing image with metadata to Photo Library: %@", error );
else
NSLog( @"Wrote image %@ with metadata %@ to Photo Library",newURL,imageMetadata);
;
// Save the new image to the Camera Roll, using the completion block defined just above
[library writeImageToSavedPhotosAlbum:[imageToSave CGImage]
metadata:imageMetadata
completionBlock:imageWriteCompletionBlock];
我是从
imagePickerController:didFinishPickingMediaWithInfo:
这是图像选择器的委托方法。 (这就是我放置逻辑的地方,看看是否有要保存的图像等)
为了完整起见,这里是获取 GPS 数据作为字典的辅助方法:
- (NSDictionary *) gpsDictionaryForLocation:(CLLocation *)location
CLLocationDegrees exifLatitude = location.coordinate.latitude;
CLLocationDegrees exifLongitude = location.coordinate.longitude;
NSString * latRef;
NSString * longRef;
if (exifLatitude < 0.0)
exifLatitude = exifLatitude * -1.0f;
latRef = @"S";
else
latRef = @"N";
if (exifLongitude < 0.0)
exifLongitude = exifLongitude * -1.0f;
longRef = @"W";
else
longRef = @"E";
NSMutableDictionary *locDict = [[NSMutableDictionary alloc] init];
// requires ImageIO
[locDict setObject:location.timestamp forKey:(NSString*)kCGImagePropertyGPSTimeStamp];
[locDict setObject:latRef forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];
[locDict setObject:[NSNumber numberWithFloat:exifLatitude] forKey:(NSString *)kCGImagePropertyGPSLatitude];
[locDict setObject:longRef forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];
[locDict setObject:[NSNumber numberWithFloat:exifLongitude] forKey:(NSString *)kCGImagePropertyGPSLongitude];
[locDict setObject:[NSNumber numberWithFloat:location.horizontalAccuracy] forKey:(NSString*)kCGImagePropertyGPSDOP];
[locDict setObject:[NSNumber numberWithFloat:location.altitude] forKey:(NSString*)kCGImagePropertyGPSAltitude];
return locDict;
另见Write UIImage along with metadata (EXIF, GPS, TIFF) in iPhone's Photo library
【讨论】:
【参考方案5】:一篇文章涉及为 EXIF 数据创建 GPS 元数据字典。这是一个 CLLocation 类别可以做到这一点:
https://gist.github.com/phildow/6043486
【讨论】:
【参考方案6】:因为我还在学习,所以我花了一些时间来拼凑史蒂夫和马蒂的答案,以将元数据添加到图像的数据并随后保存它。下面,我对他们的答案做了一个不使用 ImageIO 方法的 Swift 实现。
给定一个CMSampleBuffer样本缓冲区buffer
,一些CLLocationlocation
,并使用Morty的suggestion来使用CMSetAttachments
以避免重复图像,我们可以执行以下操作。扩展 CLLocation 的 gpsMetadata
方法可以在 here 找到(也是 Swift)。
if let location = location
// Get the existing metadata dictionary (if there is one)
var metaDict = CMCopyDictionaryOfAttachments(nil, buffer, kCMAttachmentMode_ShouldPropagate) as? Dictionary<String, Any> ?? [:]
// Append the GPS metadata to the existing metadata
metaDict[kCGImagePropertyGPSDictionary as String] = location.gpsMetadata()
// Save the new metadata back to the buffer without duplicating any data
CMSetAttachments(buffer, metaDict as CFDictionary, kCMAttachmentMode_ShouldPropagate)
// Get JPG image Data from the buffer
guard let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer) else
// There was a problem; handle it here
此时,您可以将图像数据写入文件或使用 Photos API 将图像保存到相机胶卷(对于 iOS 9+ 使用 PHAssetCreationRequest's addResource:with:data:options
,或者对于较旧的 iOS 版本,通过将 imageData 写入临时文件,然后调用PHAssetChangeRequest.creationRequestForAssetFromImage:atFileURL
)。请注意,iOS 9 不推荐使用 ALAssertLibrary。我在答案 here 中提供了更多实现细节。
【讨论】:
【参考方案7】:这样我设置了EXIF数据,如果需要你也可以压缩照片,这解决了我的问题:希望它有帮助
// Get your image.
NSURL *url = @"http://somewebsite.com/path/to/some/image.jpg";
UIImage *loImgPhoto = [NSData dataWithContentsOfURL:url];
// Get your metadata (includes the EXIF data).
CGImageSourceRef loImageOriginalSource = CGImageSourceCreateWithData(( CFDataRef) loDataFotoOriginal, NULL);
NSDictionary *loDicMetadata = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(loImageOriginalSource, 0, NULL);
// Set your compression quality (0.0 to 1.0).
NSMutableDictionary *loDicMutableMetadata = [loDicMetadata mutableCopy];
[loDicMutableMetadata setObject:@(lfCompressionQualityValue) forKey:(__bridge NSString *)kCGImageDestinationLossyCompressionQuality];
// Create an image destination.
NSMutableData *loNewImageDataWithExif = [NSMutableData data];
CGImageDestinationRef loImgDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)loNewImageDataWithExif, CGImageSourceGetType(loImageOriginalSource), 1, NULL);
// Add your image to the destination.
CGImageDestinationAddImage(loImgDestination, loImgPhoto.CGImage, (__bridge CFDictionaryRef) loDicMutableMetadata);
// Finalize the destination.
if (CGImageDestinationFinalize(loImgDestination))
NSLog(@"Successful image creation.");
// process the image rendering, adjustment data creation and finalize the asset edit.
//Upload photo with EXIF metadata
[self myUploadMethod:loNewImageDataWithExif];
else
NSLog(@"Error -> failed to finalize the image.");
CFRelease(loImageOriginalSource);
CFRelease(loImgDestination);
【讨论】:
【参考方案8】:SWIFT 5:
我花了一周的时间来确定范围,将所有部分收集到工作代码中。
这会将UIImage
保存为带有 GPS 元数据的 JPEG 临时文件:
let image:UIImage = mImageView.image! // your UIImage
// create filename
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy.MM.dd-HH.mm.ss"
let now = Date()
let date_time = dateFormatter.string(from: now)
let fileName:String = "your_image_"+date_time+".jpg" // name your file the way you want
let temporaryFolder:URL = FileManager.default.temporaryDirectory
let temporaryFileURL:URL = temporaryFolder.appendingPathComponent(fileName)
// save the image to chosen path
let jpeg = image.jpegData(compressionQuality: 0.85)! // set JPG quality here (1.0 is best)
let src = CGImageSourceCreateWithData(jpeg as CFData, nil)!
let uti = CGImageSourceGetType(src)!
let cfPath = CFURLCreateWithFileSystemPath(nil, temporaryFileURL.path as CFString, CFURLPathStyle.cfurlposixPathStyle, false)
let dest = CGImageDestinationCreateWithURL(cfPath!, uti, 1, nil)
// create GPS metadata from current location
let gpsMeta = gCurrentLocation?.exifMetadata() // gCurrentLocation is your CLLocation (exifMetadata is an extension)
let tiffProperties = [
kCGImagePropertyTIFFMake as String: "Camera vendor",
kCGImagePropertyTIFFModel as String: "Camera model"
// --(insert other properties here if required)--
] as CFDictionary
let properties = [
kCGImagePropertyTIFFDictionary as String: tiffProperties,
kCGImagePropertyGPSDictionary: gpsMeta as Any
// --(insert other dictionaries here if required)--
] as CFDictionary
CGImageDestinationAddImageFromSource(dest!, src, 0, properties)
if (CGImageDestinationFinalize(dest!))
print("Saved image with metadata!")
else
print("Error saving image with metadata")
这是 GPS 元数据扩展 (来自https://gist.github.com/chefren/8b50652d67c397a825619f83c8dba6d3):
import Foundation
import CoreLocation
extension CLLocation
func exifMetadata(heading:CLHeading? = nil) -> NSMutableDictionary
let GPSMetadata = NSMutableDictionary()
let altitudeRef = Int(self.altitude < 0.0 ? 1 : 0)
let latitudeRef = self.coordinate.latitude < 0.0 ? "S" : "N"
let longitudeRef = self.coordinate.longitude < 0.0 ? "W" : "E"
// GPS metadata
GPSMetadata[(kCGImagePropertyGPSLatitude as String)] = abs(self.coordinate.latitude)
GPSMetadata[(kCGImagePropertyGPSLongitude as String)] = abs(self.coordinate.longitude)
GPSMetadata[(kCGImagePropertyGPSLatitudeRef as String)] = latitudeRef
GPSMetadata[(kCGImagePropertyGPSLongitudeRef as String)] = longitudeRef
GPSMetadata[(kCGImagePropertyGPSAltitude as String)] = Int(abs(self.altitude))
GPSMetadata[(kCGImagePropertyGPSAltitudeRef as String)] = altitudeRef
GPSMetadata[(kCGImagePropertyGPSTimeStamp as String)] = self.timestamp.isoTime()
GPSMetadata[(kCGImagePropertyGPSDateStamp as String)] = self.timestamp.isoDate()
GPSMetadata[(kCGImagePropertyGPSVersion as String)] = "2.2.0.0"
if let heading = heading
GPSMetadata[(kCGImagePropertyGPSImgDirection as String)] = heading.trueHeading
GPSMetadata[(kCGImagePropertyGPSImgDirectionRef as String)] = "T"
return GPSMetadata
extension Date
func isoDate() -> String
let f = DateFormatter()
f.timeZone = TimeZone(abbreviation: "UTC")
f.dateFormat = "yyyy:MM:dd"
return f.string(from: self)
func isoTime() -> String
let f = DateFormatter()
f.timeZone = TimeZone(abbreviation: "UTC")
f.dateFormat = "HH:mm:ss.SSSSSS"
return f.string(from: self)
就是这样!
您现在可以使用activityViewController
将临时图像(使用temporaryFileURL
)保存到相册,或将其保存为文件,或将其共享到其他应用程序,或任何您想要的。
【讨论】:
@lenooh....如何将内容创建者属性添加到现有元数据。即内容创建者:*** Asma Godil:将其添加到上面写着--(insert other properties here if required)--
的位置。您可以命令单击kCGImagePropertyTIFFModel
以查看所有其他选项...【参考方案9】:
将 Steve McFarlin 的 answer 翻译成 Swift 并将其封装在一个类中。
class GeoTagImage
/// Writes GPS data into the meta data.
/// - Parameters:
/// - data: Coordinate meta data will be written to the copy of this data.
/// - coordinate: Cooordinates to write to meta data.
static func mark(_ data: Data, with coordinate: Coordinate) -> Data
var source: CGImageSource? = nil
source = CGImageSourceCreateWithData((data as CFData?)!, nil)
// Get all the metadata in the image
let metadata = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil) as? [AnyHashable: Any]
// Make the metadata dictionary mutable so we can add properties to it
var metadataAsMutable = metadata
var EXIFDictionary = (metadataAsMutable?[(kCGImagePropertyExifDictionary as String)]) as? [AnyHashable: Any]
var GPSDictionary = (metadataAsMutable?[(kCGImagePropertyGPSDictionary as String)]) as? [AnyHashable: Any]
if !(EXIFDictionary != nil)
// If the image does not have an EXIF dictionary (not all images do), then create one.
EXIFDictionary = [:]
if !(GPSDictionary != nil)
GPSDictionary = [:]
// add coordinates in the GPS Dictionary
GPSDictionary![(kCGImagePropertyGPSLatitude as String)] = coordinate.latitude
GPSDictionary![(kCGImagePropertyGPSLongitude as String)] = coordinate.longitude
EXIFDictionary![(kCGImagePropertyExifUserComment as String)] = "Raw Image"
// Add our modified EXIF data back into the image’s metadata
metadataAsMutable!.updateValue(GPSDictionary!, forKey: kCGImagePropertyGPSDictionary)
metadataAsMutable!.updateValue(EXIFDictionary!, forKey: kCGImagePropertyExifDictionary)
// This is the type of image (e.g., public.jpeg)
let UTI: CFString = CGImageSourceGetType(source!)!
// This will be the data CGImageDestinationRef will write into
let dest_data = NSMutableData()
let destination: CGImageDestination = CGImageDestinationCreateWithData(dest_data as CFMutableData, UTI, 1, nil)!
// Add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination, source!, 0, (metadataAsMutable as CFDictionary?))
// Tells the destination to write the image data and metadata into our data object.
// It will return false if something goes wrong
_ = CGImageDestinationFinalize(destination)
return (dest_data as Data)
/// Prints the Meta Data from the Data.
/// - Parameter data: Meta data will be printed of this object.
static func logMetaData(from data: Data)
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil)
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
if let dict = imageProperties as? [String: Any]
print(dict)
【讨论】:
谢谢。Coordinate
是在哪里定义的?
只是CLLocationCoordinate以上是关于为图像设置 exif 数据时出现问题的主要内容,如果未能解决你的问题,请参考以下文章
在图像上设置 Exif 数据总是在 Android 7 上返回错误