在iOS iPhone中从相机返回的图像中读取GPS数据

36
我需要获取iOS设备相机拍摄的图像的GPS坐标。我不关心相机胶卷图片,只想获取UIImagePickerControllerSourceTypeCamera所拍摄的照片。我已经阅读了许多stackoverflow答案,例如从UIImage - UIImagePickerController获取Exif数据,但这些答案要么假设您使用AssetsLibrary框架(该框架似乎无法处理相机图像),要么使用CoreLocation从应用程序本身获取纬度/经度,而不是从图像获取。使用CoreLocation不是选项。这不会在按下快门按钮时给我坐标。(使用基于CoreLocation的解决方案,您需要在打开相机视图之前或之后记录坐标,当然,如果设备移动,坐标将错误。此方法应适用于静止设备。) 我只需支持iOS5,因此不需要支持旧设备。这也是商业产品,因此我不能使用http://code.google.com/p/iphone-exif/。那么,从iOS5返回的相机图像中读取GPS数据的选择是什么?目前我所能想到的就是将图像保存到相机胶卷中,然后使用AssetsLibrary,但这似乎很麻烦。谢谢!
这是我根据Caleb的答案编写的代码。
    UIImage *image =  [info objectForKey:UIImagePickerControllerOriginalImage];

    NSData *jpeg = UIImageJPEGRepresentation(image,1.0);
    CGImageSourceRef  source ;
    source = CGImageSourceCreateWithData((__bridge CFDataRef)jpeg, NULL);

    NSDictionary *metadataNew = (__bridge NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source,0,NULL);  

    NSLog(@"%@",metadataNew);

我的控制台显示:

    2012-04-26 14:15:37:137 ferret[2060:1799] {
        ColorModel = RGB;
        Depth = 8;
        Orientation = 6;
        PixelHeight = 1936;
        PixelWidth = 2592;
        "{Exif}" =     {
            ColorSpace = 1;
            PixelXDimension = 2592;
            PixelYDimension = 1936;
        };
        "{JFIF}" =     {
            DensityUnit = 0;
            JFIFVersion =         (
                1,
                1
            );
            XDensity = 1;
            YDensity = 1;
        };
        "{TIFF}" =     {
            Orientation = 6;
        };
    }

没有纬度/经度。


@SandyPatel,你找到正确的答案了吗?你是怎么解决的?我不想使用CoreLocation和这个框架http://code.google.com/p/iphone-exif/。难道只有先保存到相机胶卷中,然后再提取带有GPS数据的照片是唯一的解决方案吗? - jonypz
9个回答

17
问题在于自从iOS 4以来,UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];会去除地理位置信息。为了解决这个问题,你需要使用原始照片路径来访问完整的图像元数据,例如以下代码:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    NSURL *referenceURL = [info objectForKey:UIImagePickerControllerReferenceURL];
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    [library assetForURL:referenceURL resultBlock:^(ALAsset *asset) {
        ALAssetRepresentation *rep = [asset defaultRepresentation];
        NSDictionary *metadata = rep.metadata;
        NSLog(@"%@", metadata);

        CGImageRef iref = [rep fullScreenImage] ;

        if (iref) {
            self.imageView.image = [UIImage imageWithCGImage:iref];
        }
    } failureBlock:^(NSError *error) {
        // error handling
    }];

输出结果应该类似于:
{
    ColorModel = RGB;
    DPIHeight = 72;
    DPIWidth = 72;
    Depth = 8;
    Orientation = 6;
    PixelHeight = 1936;
    PixelWidth = 2592;
    "{Exif}" =     {
        ApertureValue = "2.970854";
        BrightnessValue = "1.115874";
        ColorSpace = 1;
        ComponentsConfiguration =         (
            0,
            0,
            0,
            1
        );
        DateTimeDigitized = "2012:07:14 21:55:05";
        DateTimeOriginal = "2012:07:14 21:55:05";
        ExifVersion =         (
            2,
            2,
            1
        );
        ExposureMode = 0;
        ExposureProgram = 2;
        ExposureTime = "0.06666667";
        FNumber = "2.8";
        Flash = 24;
        FlashPixVersion =         (
            1,
            0
        );
        FocalLength = "3.85";
        ISOSpeedRatings =         (
            200
        );
        MeteringMode = 5;
        PixelXDimension = 2592;
        PixelYDimension = 1936;
        SceneCaptureType = 0;
        SensingMethod = 2;
        Sharpness = 2;
        ShutterSpeedValue = "3.9112";
        SubjectArea =         (
            1295,
            967,
            699,
            696
        );
        WhiteBalance = 0;
    };
    "{GPS}" =     {
        Altitude = "1167.528";
        AltitudeRef = 0;
        ImgDirection = "278.8303";
        ImgDirectionRef = T;
        Latitude = "15.8235";
        LatitudeRef = S;
        Longitude = "47.99416666666666";
        LongitudeRef = W;
        TimeStamp = "00:55:04.59";
    };
    "{TIFF}" =     {
        DateTime = "2012:07:14 21:55:05";
        Make = Apple;
        Model = "iPhone 4";
        Orientation = 6;
        ResolutionUnit = 2;
        Software = "5.1.1";
        XResolution = 72;
        YResolution = 72;
        "_YCbCrPositioning" = 1;
    };
}

我需要检查一下这个,卡洛斯,看起来很有前途! - Paul Cezanne
很遗憾,它没有起作用。我猜你是从照片库中获取,而不是直接从相机中获取。[info objectForKey:UIImagePickerControllerReferenceURL]; 对于相机图像返回nil。如果我从照片库中读取,则会有UIImagePickerControllerReferenceURL。 - Paul Cezanne
1
确认使用UIImagePickerController相机拍摄的照片中,info[UIImagePickerControllerReferenceURL]nil - Matthew Quiros
1
这段代码在 iOS7 上针对从相机胶卷中选取的图片运行良好。但由于设备 GPS 信号可能不可用,因此 GPS 信息可能为空,特别是用户拍照时。我没有使用 UIImagePickerController 来拍照,所以对那部分不能发表评论... - Peng90

13

我们已经与相机和UIImagePickerController大量合作,并且至少在iOS 5.1.1及以下版本中,它不会返回拍摄使用UIImagePickerController的照片或视频的元数据中的位置数据。

无论相机应用程序是否启用了位置服务都没有关系;这控制的是相机应用程序对位置服务的使用,而不是UIImagePickerController内部的相机功能。

您的应用程序将需要使用CLLocation类获取位置信息,然后将其添加到从相机返回的图像或视频中。您的应用程序能否获得位置将取决于用户是否授权您的应用程序访问位置服务。请注意,用户随时可以通过设置>位置服务禁用您的应用程序(或设备整体)的位置服务。


这部分是正确的。苹果确实会从EXIF中剥离GPS数据。但是你可以打开图像的原始数据,并自己解析数据。 - Guilherme Torres Castro
@GuilhermeTorresCastro,你有如何实现的示例代码吗? - jonypz

5
你在发布的代码中没有使用相机的图像数据,你只是生成了一张JPEG格式的图像,这样会丢失所有元数据。按照Caleb的建议,应该使用 image.CGImage
此外:

这也是一个商业产品,所以我不能使用http://code.google.com/p/iphone-exif/

作者明确表示有商业许可证。

  1. 但是image.CGImage是CGImageRef,而不是CFDataRef。如果忽略编译器警告,则会导致“未识别的选择器”崩溃。
  2. 我现在是一家没有资金支持的初创公司,一旦获得资金支持,我很乐意考虑商业库!
- Paul Cezanne

4

一种可能的方法是在相机可见时保持CoreLocation运行。将每个CCLocation与样本时间记录到数组中。当照片返回时,找到其时间,然后从数组中匹配最接近的CClocation。

听起来有些笨拙,但它能够正常工作。


虽然有点笨拙,但自从我发布这个以来,几个月里我没有更多的机会去继续开发它,但我真的开始认为这是唯一确保它完全正确的方法。 - Paul Cezanne
这个问题已经五年了,而我也已经五年没有真正地去解决它。希望现在有人能够解决。如果你找到了方法,请回来并在这里报告,我会尝试将你的答案设为被采纳的答案。 - Paul Cezanne

3
我个人没有尝试过这种方法,但从文档中可以明确得知,如果您正在使用UIImagePickerController,则可以通过-imagePicker:didFinishPickingMediaWithInfo:委托方法获取用户刚拍摄的图像。使用UIImagePickerControllerOriginalImage关键字来获取图像。
一旦您获得了图像,就应该能够访问其属性,包括EXIF数据,如QA1654 Accessing image properties with ImageIO所述。要创建CGImageSource,我会查看CGImageSourceCreateWithData()并使用从UIImage的CGImage方法获取的数据。一旦您获得了图像源,就可以通过CGImageSourceCopyProperties()访问各种属性。

1
不幸的是,这并没有给出纬度/经度。我会编辑我的问题,这样你就可以看到我的来源和结果... - Paul Cezanne

1
正如Chris Markle所指出的那样,苹果会从EXIF中剥离GPS数据。但你可以打开图像的RAW数据,并解析数据自己或使用第三方库进行解析,例如example
以下是示例代码:
- (void) imagePickerController: (UIImagePickerController *) picker
 didFinishPickingMediaWithInfo: (NSDictionary *) info {

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

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

                 if (length != 0)  {

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

                     EXFJpeg* jpegScanner = [[EXFJpeg alloc] init];
                     [jpegScanner scanImageData: adata];
                     EXFMetaData* exifData = jpegScanner.exifMetaData;

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

                     self.locationLabel.text = [NSString stringWithFormat:@"Local: %@ - %@",latitudeValue,longitudeValue];
                     self.dateLavel.text = [NSString stringWithFormat:@"Data: %@", datetime];

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

我认为你正在从相机胶卷中读取,我需要从相机中读取数据。 - Paul Cezanne

0

Swift 答案:

import AssetsLibrary
import CoreLocation


// MARK: - UIImagePickerControllerDelegate
extension ViewController: UIImagePickerControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        defer {
            dismiss(animated: true, completion: nil)
        }
        guard picker.sourceType == .photoLibrary else {
            return
        }
        guard let url = info[UIImagePickerControllerReferenceURL] as? URL else {
            return
        }

        let library = ALAssetsLibrary()
        library.asset(for: url, resultBlock: { (asset) in
            guard let coordinate = asset?.value(forProperty: ALAssetPropertyLocation) as? CLLocation else {
                return
            }
            print("\(coordinate)")

            // Getting human-readable address.
            let geocoder = CLGeocoder()
            geocoder.reverseGeocodeLocation(coordinate, completionHandler: { (placemarks, error) in
                guard let placemark = placemarks?.first else {
                    return
                }
                print("\(placemark.addressDictionary)")
            })
        }, failureBlock: { (error: Error?) in
            print("Unable to read metadata: \(error)")
        })
    }
}

0

这在iOS 8上经过测试并且适用于视频,因此只需进行一些微调即可同样适用于照片。

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

    NSURL *videoUrl = (NSURL *)[info objectForKey:UIImagePickerControllerMediaURL];
    NSString *moviePath = [videoUrl path];

    if ( UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(moviePath) ) {

        ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init];

        [assetLibrary assetForURL:[info objectForKey:UIImagePickerControllerReferenceURL] resultBlock:^(ALAsset *asset) {

            CLLocation *location = [asset valueForProperty:ALAssetPropertyLocation];
            NSLog(@"Location Meta: %@", location);

        } failureBlock:^(NSError *error) {
            NSLog(@"Video Date Error: %@", error);
        }];

    }

}

我认为你正在从相机胶卷中读取,我需要从相机中读取数据。 - Paul Cezanne
如果是这样的话,拍摄图像后最好使用CLLocationManager单独获取GPS坐标。 - Nur Iman Izam
是的,这就是我被接受的答案。除了我需要在图像捕获期间完成它,所以我将在后台任务上记录时间和位置,然后将最佳位置与图像捕获时间匹配。你需要这样做,因为人可能会按下快门,然后在相机仍然打开的情况下移动到新位置,尝试欺骗系统。虽然有解决方法,但它增加了复杂性。而且,自从我提出这个问题已经过去了两年多,不幸的是,那个项目已经搁置了。很遗憾,那是一个好项目,实际上仍然是! - Paul Cezanne
@PaulCezanne,我猜检索GPS坐标的最佳选项是将其保存到相机胶卷中,然后稍后再提取,对吗?使用CoreLocation似乎太重了。 - jonypz
由你决定。将其保存到相机胶卷中意味着用户将在相机胶卷中看到它。 - Paul Cezanne

-1
在你的UIImagePickerController代理中,执行以下操作:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
  NSDictionary *metadata = [info valueForKey:UIImagePickerControllerMediaMetadata];

  // metadata now contains all the image metadata.  Extract GPS data from here.
}

我会去看一下,但我不抱太大希望。我想我已经看过了,但不确定... - Paul Cezanne
1
只有当图像具有元数据时,它才能正常工作。如果图像没有GPS数据或者iPhone相机在拍摄照片时设置为不保存GPS坐标,则可能无法正常工作。 - rekle
1
很抱歉,那里没有GPS信息。我就知道不会有的。 - Paul Cezanne
我检查了设置/位置服务。相机已经打开,所以我不认为那是问题所在,但是还有其他地方需要检查吗?如果有,请留下答案,不能将赏金授予评论... - Paul Cezanne
我刚刚将手机与笔记本电脑同步,我的图片中确实有 GPS 定位数据,所以我知道相机具备此功能。 - Paul Cezanne
如何将GPS添加到从相机拍摄的图像中,你能帮我吗? - dineshprasanna

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