【问题标题】:reading android jpeg EXIF metadata from picture callback从图片回调中读取 android jpeg EXIF 元数据
【发布时间】:2012-10-17 22:02:28
【问题描述】:

背景:我正在为信使程序编写相机应用程序。我无法随时将捕获的图像保存到永久磁盘。相机必须支持所有方向。我的实现是熟悉的 Surfaceview 示例。我使用 Display 类来检测方向并相应地旋转相机。在 takePicture jpeg 回调中,我从 byte[] 构造了一个位图,以解决我遇到的一些纵横比问题:Camera API: Cross device issues

问题描述:在某些设备上,在 ROTATION_270(设备顺时针旋转 90 度)拍摄的构造位图颠倒了。到目前为止,它似乎是三星。我只能假设相机可能是以其他方式焊接的,或者有什么影响,但这既不存在也不存在。虽然我可以检查位图是否横向,但我无法从逻辑上检查它是否按尺寸倒置,因此我需要访问 EXIF 数据。

Android 为这个http://developer.android.com/reference/android/media/ExifInterface.html 提供了一个解析器,但不幸的是它有一个接受文件的构造函数......我没有也不想这样做。直观地说,我可以为字节数组编写一个构造函数,但考虑到他们调用本机代码http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2.1_r1/android/media/ExifInterface.java

,这似乎真的很痛苦

我的问题分为两部分:

  1. 有谁知道 byte[] 数组是否包含完整的 EXIF jpeg 标头 数据原样或通过 BitmapFactory.decode(...) / 的路径 BitmapFactory.compress(...) 以某种方式添加?

  2. 如果此 EXIF 数据存在于字节数组中,我该如何解析 以可靠的方式提供方向信息?

2012 年 10 月 18 日编辑

pcans 下面的回答涉及我问题的第 2 部分。正如我在他的答案下方的 cmets 中指出的那样,如果您想使用该解析器,您必须将源代码合并到您的项目中。该链接 SO 帖子中提到的更改已在此处进行并重新发布:https://github.com/strangecargo/metadata-extractor

注意metadata-extractor 的较新版本无需修改即可直接在 Android 上运行,并可通过 Maven 获得。

但是,对于第 1 部分,当我使用从 takePicture 获得的字节数组运行它时,我从解析器返回 0 个标签。我开始担心字节数组没有我需要的数据。我将继续对此进行研究,但欢迎任何进一步的见解。

【问题讨论】:

  • 我已经为 Android 提供了一个 Java 元数据库,它可以为 JPEG、TIFF、PNG、GIF 等图像提取和插入元数据(Exif、XMP、IPTC、ICC_Profile、Photoshop IRB 等)。检查它here
  • 关于reading and writing EXIF到android中的图像文件的文章
  • 元数据提取器链接需要一个 .com:drewnoakes.com/code/exif

标签: java android jpeg exif


【解决方案1】:

使用Drew Noakesmetadata extraction library in Java的2.9.1版从图像byte[](对Camera.takePicture()有用)读取元数据/EXIF:

try
{
    // Extract metadata.
    Metadata metadata = ImageMetadataReader.readMetadata(new BufferedInputStream(new ByteArrayInputStream(imageData)), imageData.length);

    // Log each directory.
    for(Directory directory : metadata.getDirectories())
    {
        Log.d("LOG", "Directory: " + directory.getName());

        // Log all errors.
        for(String error : directory.getErrors())
        {
            Log.d("LOG", "> error: " + error);
        }

        // Log all tags.
        for(Tag tag : directory.getTags())
        {
            Log.d("LOG", "> tag: " + tag.getTagName() + " = " + tag.getDescription());
        }
    }
}
catch(Exception e)
{
    // TODO: handle exception
}

要读取图像的 EXIF 方向(不是缩略图的方向):

try
{
    // Get the EXIF orientation.
    final ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
    if(exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION))
    {
        final int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);

        /* Work on exifOrientation */
    }
    else
    {
        /* Not found */
    }
}
catch(Exception e)
{
    // TODO: handle exception
}

方向是从 1 到 8。请参阅 here、here、here 或 here。


根据 EXIF 方向转换位图:

try
{
    final Matrix bitmapMatrix = new Matrix();
    switch(exifOrientation)
    {
        case 1:                                                                                     break;  // top left
        case 2:                                                 bitmapMatrix.postScale(-1, 1);      break;  // top right
        case 3:         bitmapMatrix.postRotate(180);                                               break;  // bottom right
        case 4:         bitmapMatrix.postRotate(180);           bitmapMatrix.postScale(-1, 1);      break;  // bottom left
        case 5:         bitmapMatrix.postRotate(90);            bitmapMatrix.postScale(-1, 1);      break;  // left top
        case 6:         bitmapMatrix.postRotate(90);                                                break;  // right top
        case 7:         bitmapMatrix.postRotate(270);           bitmapMatrix.postScale(-1, 1);      break;  // right bottom
        case 8:         bitmapMatrix.postRotate(270);                                               break;  // left bottom
        default:                                                                                    break;  // Unknown
    }

    // Create new bitmap.
    final Bitmap transformedBitmap = Bitmap.createBitmap(imageBitmap, 0, 0, imageBitmap.getWidth(), imageBitmap.getHeight(), bitmapMatrix, false);
}
catch(Exception e)
{
    // TODO: handle exception
}

【讨论】:

【解决方案2】:

坏消息:

遗憾的是,Android Api 不允许您从 Stream 读取 exif 数据,只能从 File 读取。
ExifInterface 没有带有 InputStream 的构造函数。 所以必须自己解析jpeg内容。

好消息:

为此,API 存在于纯 Java 中。你可以使用这个:https://drewnoakes.com/code/exif/
它是 Open Source,根据 Apache 许可证 2 发布,并以 Maven package 的形式提供。

有一个带有InputStream 的构造函数: public ExifReader(java.io.InputStream is)

您可以像这样使用ByteArrayInputStream 构建由byte[] 支持的InputStream:

InputStream is = new ByteArrayInputStream(decodedBytes);

【讨论】:

  • 太棒了。我在 Apache 下找不到一个。现在我只希望字节数组包含我想要的信息!我现在就试一试
  • 也许是个愚蠢的问题,但有什么理由不能在 Android 上运行吗?即使我已经完成了将 jar 放入 libs 文件夹并将其添加到构建路径的常规过程,我仍然无法检测到该库...
  • @AndrewG 该库直接在 Android 上运行。有一些不兼容的旧版本,但那是不久前的事了(正如你的问题——我为其他浏览这些部分的人提到了这一点)。
  • Android API 24 ExifInterface 添加了对 InputStream 的支持。来源:developer.android.com/reference/android/media/…
  • 图书馆链接已失效
【解决方案3】:

AndroidX ExifInterface 支持从输入流中读取 EXIF 信息:

implementation "androidx.exifinterface:exifinterface:1.1.0"

然后您可以像这样将输入流传递给构造函数:

val exif = ExifInterface(inputStream)
val orientation =
        exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)

【讨论】:

  • 这是 2020 年的正确答案。不再需要外部库。只是你的 Java 在某些方面看起来像 javascript ...
  • 是的,这是有效的解决方案! val 输入流:输入流? = contentResolver.openInputStream(uri) inputStream?.run { exif = ExifInterface(inputStream) }
【解决方案4】:

因此,使用我的编辑和 pcans 建议,我得到了图像数据,但这不是我所期望的。具体来说,并非所有设备都会给出方向。如果您遵循此路径,请注意

  • 我指向的“Android 固定”ExifReader 库实际上是 编辑了 2.3.1,这是一些旧版本。新的例子 网站和源代码属于最新的 2.6.x,他在那里 显着改变了 API。使用2.3.1界面,可以 通过执行以下操作从 byte[] 转储所有 EXIF 数据:

            Metadata header;    
            try {
                ByteArrayInputStream bais= new ByteArrayInputStream(data);
                ExifReader reader = new ExifReader(bais);
                header = reader.extract();
                Iterator<Directory> iter = header.getDirectoryIterator();
                while(iter.hasNext()){
                   Directory d = iter.next();
                   Iterator<Tag> iterTag = d.getTagIterator();
                   while(iterTag.hasNext()){
                      Tag t = iterTag.next();
                      Log.e("DEBUG", "TAG: " + t.getTagName() + " : " + t.getDescription());
                   }
                }
            } catch (JpegProcessingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (MetadataException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    

如果你想要数字标签值,只需替换

t.getDescription()

与

d.getInt(t.getTagType())
  • 虽然 ExifReader 有一个使用 byte[] 的构造函数,但我必须有 误解了它的期望,因为如果我尝试将它与 直接data数组,我就到了返回目录下的Tags。

就答案而言,我真的没有添加太多,所以我接受 pcans 的回答。

【讨论】:

    【解决方案5】:

    如果您想要一种读取 EXIF 数据的方法,它不会依赖于您的 URI 来自您的位置,您可以使用 exif support library 并从流中读取它。例如,这就是我获取图像方向的方式。

    build.gradle

    dependencies {
    ...    
    compile "com.android.support:exifinterface:25.0.1"
    ...
    }
    

    示例代码:

    import android.support.media.ExifInterface;
    ...
    try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
          ExifInterface exif = new ExifInterface(inputStream);
          int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
        } catch (IOException e) {
          e.printStackTrace();
        }
    

    一旦我们开始针对 api 25(也许 24+ 也有问题)但仍然支持回到 api 19,我必须这样做的原因,在 android 7 上,如果我传入一个 URI,我们的应用程序会崩溃只是引用一个文件。因此,我必须创建一个 URI 来传递给这样的相机意图。

    FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileprovider", tempFile);
    

    问题在于该文件无法将 URI 转换为真实的文件路径(除了保留临时文件路径)。

    【讨论】:

      【解决方案6】:

      如果您使用 Glide 库,您可以从 InputStream 获取 Exif 方向:

      InputStream is=getActivity().getContentResolver().openInputStream(originalUri);
      int orientation=new ImageHeaderParser(is).getOrientation();
      

      【讨论】:

      • 当通过 mCamera.takePicture(null, null, mPictureCallback) 捕获图像时,此方法不适用于 Camera.PictureCallback.onPictureTaken 返回的字节数组。在这种情况下,方向为 -1 表示失败。
      【解决方案7】:

      对于任何可能感兴趣的人,这里是如何使用来自https://github.com/strangecargo/metadata-extractor 的 2.3.1 接口获取 Orientation 标签

      Metadata header;
      try {
          ByteArrayInputStream bais= new ByteArrayInputStream(data);
          ExifReader reader = new ExifReader(bais);
          header = reader.extract();
          Directory dir = header.getDirectory(ExifDirectory.class);
          if (dir.containsTag(ExifDirectory.TAG_ORIENTATION)) {
              Log.v(TAG, "tag_orientation exists: " + dir.getInt(ExifDirectory.TAG_ORIENTATION));
          }
          else {
              Log.v(TAG, "tag_orietation doesn't exist");
          }
      
      
      } catch (JpegProcessingException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
      } catch (MetadataException e) {
          e.printStackTrace();
      }
      

      【讨论】:

        【解决方案8】:

        如果你有Uri的content://类型,android通过ContentResolver提供API,不需要使用外部库:

        public static int getExifAngle(Context context, Uri uri) {
            int angle = 0;
            Cursor c = context.getContentResolver().query(uri,
                    new String[] { MediaStore.Images.ImageColumns.ORIENTATION },
                    null,
                    null,
                    null);
        
            if (c != null && c.moveToFirst()) {
                int col = c.getColumnIndex( MediaStore.Images.ImageColumns.ORIENTATION );
                angle = c.getInt(col);
                c.close();
            }
            return angle;
        }
        

        您还可以读取在MediaStore.Images.ImageColumns 中找到的任何其他值,例如纬度和经度。

        这目前不适用于 file:/// Uris,但可以轻松调整。

        【讨论】:

        • 至少在某些设备上不起作用。为我返回col = -1。
        • @AleksN 这仅适用于将带有正确标签的图片添加到 MediaStore 的情况。此外,它必须是内容 uri 而不是文件 uri。不管怎样,我已经改变了主意,我建议使用 Exif 阅读器,不要管内容解析器查询。这适用于文件和内容 uri。
        猜你喜欢
        • 1970-01-01
        • 2014-09-06
        • 1970-01-01
        • 2017-08-29
        • 2015-07-02
        • 1970-01-01
        • 2017-07-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多