web-dev-qa-db-ja.com

読み取りAndroid画像コールバックからのjpeg EXIFメタデータ

背景:メッセンジャープログラム用のカメラアプリを書いています。キャプチャしたイメージをいつでも永続ディスクに保存できません。カメラはすべての方向をサポートする必要があります。私の実装は、おなじみのSurfaceviewの例です。 Displayクラスを使用して向きを検出し、それに応じてカメラを回転させます。 takePicture jpegコールバックで、バイト[]からビットマップを作成して、私が抱えていたアスペクト比の問題を回避します。 カメラAPI:デバイス間の問題

問題の説明:一部のデバイスでは、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

私の質問には2つの部分があります。

  1. Byte []配列に完全なEXIF jpegヘッダーデータがそのまま含まれているのか、それともBitmapFactory.decode(...)/ BitmapFactory.compress(...)のパスが何らかの形で追加されているのか誰もが知っていますか?

  2. このEXIFデータがバイト配列に存在する場合、どのように方向情報を信頼できる方法で解析できますか?

編集10/12/18

以下のpcansの回答には、私の質問のパート2が含まれています。彼の答えの下のコメントで指摘したように、そのパーサーを使用する場合は、ソースをプロジェクトに組み込む必要があります。そのリンクされているSO投稿で言及された変更は、すでに行われ、ここに再投稿されています: https://github.com/strangecargo/metadata-extractor

注: metadata-extractor の新しいバージョンは、Androidを変更せずに直接動作し、Mavenを介して利用可能です。

ただし、パート1については、takePictureから取得したバイト配列で実行すると、パーサーから0タグが返されます。バイト配列に必要なデータがないのではないかと心配になっています。私はこれを引き続き調査しますが、さらなる洞察を歓迎します。

32
Andrew G

悪いニュース:

Android Apiでは、Streamからのみexifデータを読み取ることができません。Fileからのみです。
ExifInterfaceInputStreamを持つコンストラクターはありません。したがって、jpegコンテンツを自分で解析する必要があります。

良いニュース:

APIはpure Javaに存在します。これを使用できます。 https://drewnoakes.com/code/exif/
これは オープンソース で、Apache License 2の下で公開されており、 Mavenパッケージ として入手できます。

InputStreampublic ExifReader(Java.io.InputStream is)を持つコンストラクターがあります

次のようにInputStreamを使用して、byte[]によって支援されるByteArrayInputStreamを構築できます。

InputStream is = new ByteArrayInputStream(decodedBytes);
17
pcans

そこで、編集とpcansの提案を使用して、画像データを取得しましたが、期待したものではありませんでした。具体的には、すべてのデバイスが方向を示すわけではありません。このパスに従う場合は、

  • 私が指している「Android fixed」ExifReaderライブラリは、実際には編集済みの2.3.1であり、古いリリースです。 Webサイトとソースの新しい例は、最新の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 []を使用するコンストラクターがありますが、データ配列で直接使用しようとすると、返されたディレクトリのタグに到達するため、期待どおりに誤解していたに違いありません。

答えに関する限り、私は本当に追加しなかったので、私はpcansの答えを受け入れています。

4
Andrew G

Glideライブラリを使用している場合、InputStreamからExifの向きを取得できます。

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

URIがどこから来たかにあまり依存しないEXIFデータを読み取る方法が必要な場合は、 exifサポートライブラリ を使用してストリームから読み取ることができます。たとえば、これは画像の向きを取得する方法です。

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を実際のファイルパスに変換できないことです(一時ファイルパスを保持する以外)。

2
startoftext

content://タイプのUriがある場合、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では機能しませんが、簡単に調整できます。

0
natario

興味があるかもしれない人のために、2.3.1インターフェイスを使用して https://github.com/strangecargo/metadata-extractor を使用して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();
}
0
Nimrod Dayan