【问题标题】:Reading the GPS data from the image returned by the camera in iOS iphone从iOS iphone中相机返回的图像中读取GPS数据
【发布时间】:2024-01-09 11:33:02
【问题描述】:

我需要获取使用 iOS 设备的相机拍摄的图像的 GPS 坐标。我不关心相机胶卷图像,只关心使用 UIImagePickerControllerSourceTypeCamera 拍摄的图像。

我已经阅读了许多 * 答案,例如 Get Exif data from UIImage - UIImagePickerController,它假设您正在使用 AssetsLibrary 框架,该框架似乎不适用于相机图像,或者使用 CoreLocaiton 从应用程序本身获取纬度/经度,而不是来自图像。

使用 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 也不想使用这个框架 code.google.com/p/iphone-exif 。它是保存到相机胶卷然后使用 gps 数据提取的唯一解决方案吗?

标签: ios geolocation gps camera


【解决方案1】:

斯威夫特回答:

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

【讨论】:

    【解决方案2】:

    我们对相机和UIImagePickerController 进行了很多工作,至少在iOS 5.1.1 之前(包括iOS 5.1.1),它不会在使用UIImagePickerController 拍摄的照片或视频的元数据中返回位置数据。

    相机应用是否启用定位服务无关紧要;这控制了相机应用程序对位置服务的使用,而不是UIImagePickerController 中的相机功能。

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

    【讨论】:

    • 这部分是正确的。 Apple 确实从 EXIF 中删除了 GPS 数据。但是可以打开图片的RAW数据,自己解析数据。
    • @GuilhermeTorresCastro 你有示例代码吗?
    【解决方案3】:

    正如Chris Markle 所指出的,Apple 确实从 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);
                }
         ];
    }
    

    【讨论】:

    • 我认为您正在从相机胶卷中读取数据,我需要从相机中读取数据。
    【解决方案4】:

    这是在 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);
            }];
    
        }
    
    }
    

    【讨论】:

    • 我认为您正在从相机胶卷中读取数据,我需要从相机中读取数据。
    • @PaulCezanne 如果是这种情况,您最好在图像捕获后使用 CLLocationManager 单独获取 GPS 坐标。
    • 是的,因此我接受了答案。除了我需要在图像捕获期间执行此操作,因此我将在后台任务中执行此操作,记录时间和位置,然后将最佳位置与图像捕获时间匹配。您需要这样做,因为该人可能会按下快门,然后在相机仍处于打开状态时移动到新位置以尝试欺骗系统。没什么大不了的,因为有一种解决方法,但它增加了复杂性。而且,自从我问这个问题已经 2 年多了,可悲的是,这个项目被搁置了。可惜,这是一个很好的项目,实际上仍然是!
    • @PaulCezanne 所以我想检索 GPS 坐标的最佳选择是保存到相机胶卷并稍后将其拉出,对吗?使用 CoreLocation 似乎很繁重。
    • 这取决于你。保存到相机胶卷意味着用户将在相机胶卷中看到它。
    【解决方案5】:

    问题是由于 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;
        };
    }
    

    【讨论】:

    • 我需要检查一下卡洛斯,看起来很有希望!
    • 很遗憾,它没有用。我猜你是从照片库中提取的,而不是直接从相机中提取的。 [info objectForKey:UIImagePickerControllerReferenceURL];对相机图像返回 nil。如果我从照片库中读取,我确实有一个 UIImagePickerControllerReferenceURL。
    • 确认info[UIImagePickerControllerReferenceURL]nil 用于使用UIImagePickerController 的相机拍摄的照片。
    • 这适用于从 iOS7 中的相机胶卷中挑选的图像。但是 GPS 信息可能为空,因为用户拍照时设备 GPS 信号可能并非一直可用。我没有使用 UIImagePickerController 拍照,所以我无法评论那部分......
    【解决方案6】:

    在您的 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.
    }
    

    【讨论】:

    • 我会检查一下,但我不抱希望。我想我已经看过了,不确定...
    • 只有在图像有元数据时才有效。如果图像没有 GPS 数据或 iPhone 相机在拍摄照片时设置为不保存 GPS 坐标,则可能不会。
    • 那里没有 GPS 信息,抱歉。没想到。
    • 我检查了设置/位置服务。相机设置为开启,所以我认为这不是问题,但还有其他地方可以检查吗?如果是这样,请留下答案,不能将赏金奖励给评论...
    • 我刚刚将手机与笔记本电脑同步,我的图像中确实包含 GPS 位置数据,所以我知道相机可以做到。
    【解决方案7】:

    一种可能性是在摄像头可见时让 CoreLocation 继续运行。将每个 CCLocation 连同样本的时间一起记录到一个数组中。当照片返回时,找到它的时间,然后匹配数组中最近的 CClocation。

    听起来很笨拙,但它会起作用。

    【讨论】:

    • kludgy,是的,但在我发布此内容后的几个月里,我真的没有机会进行更多工作,但我真的开始认为这是唯一的方法完全正确。
    • 这个问题已经 5 年了,我已经有 5 年没有真正研究过了。希望有。如果您找到方法,请在此处报告,我会尝试将您的答案设为可接受的答案。
    【解决方案8】:

    您发布的代码中没有使用来自相机的图像数据,而是生成了它的 JPEG 表示形式,这实际上会丢弃所有元数据。像 Caleb 建议的那样使用 image.CGImage

    还有:

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

    作者非常明确地指出,商业许可是可用的。

    【讨论】:

    • 1) 但是 image.CGImage 是一个 CGImageRef,而不是一个 CFDataRef。我都收到编译器警告,如果忽略它会导致“无法识别的选择器”崩溃。 2)我现在是一家没有资金的初创公司,一旦获得资金,我会很乐意看看商业图书馆!
    【解决方案9】:

    不能说我需要在我自己的东西中完全做到这一点,但从文档中可以清楚地看出,如果您使用 UIImagePickerController,您可以获取用户刚刚从 @ 获取的图像987654323@委托方法。使用密钥UIImagePickerControllerOriginalImage获取图片。

    获得图像后,您应该能够访问其属性,包括 EXIF 数据,如QA1654 Accessing image properties with ImageIO 中所述。要创建 CGImageSource,我会查看 CGImageSourceCreateWithData() 并使用从 UIImage 的 CGImage 方法获得的数据。获得图像源后,您可以通过CGImageSourceCopyProperties() 访问各种属性。

    【讨论】:

    • 遗憾的是,这并没有给出纬度/经度。我将编辑我的问题,以便您查看我的来源和结果...