Androidでカメラを作る ~ その1(プロローグ)

2021年7月3日

私はカメラ初心者ですが、結構カメラ好きです。そのため、サブサイトの「kunimiyasoftアイテムセンター」で、いろいろなカメラを紹介しています。

しかし、近年はスマートフォンカメラが高性能化した関係で、カメラが売れなくなりました。そこで危機的な状況に置かれたカメラメーカーは、最後の砦としてミラーレス一眼カメラに最先端の技術を投入して対抗しています。

その結果、新しく出てくるカメラは、数十万円を出さないと買えないような状況になっています。そろそろカメラの買い替えを考えている私ですが、これほど高額なカメラの購入は気が引けます。

そこで、欲しい機能を持つカメラが安くなるまで待つことにしました。その間、スマートフォンカメラアプリを作ってみようかと考えました。今までも「ARJ-GPS」でカメラ機能を使っていたのですが、今回は最初から撮影用として作り直します。最小限のカメラ実装になりますが、スマートフォンカメラで一番気に入らない部分を改良したものを作ります。

なお、Androidにおけるカメラ機能は非常に複雑で、すべてのコードを載せるのは難しいです。そのため、抜粋形式になることをご了承ください。

1.Androidのカメラサンプル

Androidカメラアプリを作るには、「Camera2」というライブラリを使います。サンプルソースが提供されています。

Androidカメラ機能が、恐ろしく複雑なのが分かります。

それでは、私がプログラムする上で気になった点を述べていきたいと思います。(以下は、随時更新する予定です)

2.カメラIDを取得する

スマートフォンには、外側と内側にカメラが付いています。また、外側に関しては、複数のカメラが付くのが普通になりました。

そうなると、撮影時には、カメラを指定する必要があります。使用できるカメラは、CameraManagerの「getCameraIdList」関数で取得ができます。今回はメインカメラのみで撮影するので、CameraCharacteristicsで「LENS_FACING_BACK」であるかを調べます。

        for (String cameraId2 : cameraManager.getCameraIdList()) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId2);
            if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) {
                cameraId =  cameraId2;
            }
        }

カメラIDは文字列であることに注意してください。上記のロジックでメインカメラを取得していますが、カメラID「0」がメインカメラであるようです。

3.写真とプレビューの関係

スマートフォンで写真を撮る場合、撮影範囲を確認するためのプレビューモニターに表示します。しかし、モニターの縦横比とセンサー(撮像素子)の縦横比は違います。また、解像度も違うので、プレビューセンサー縦横比に合わせて映像を補正するのが理想です。

List<Size> outputSizes = Arrays.asList(map.getOutputSizes(SurfaceTexture.class));
Size imageDimension = outputSizes.get(0) ;

プレビューのサイズを取得できます。配列の0番目最大解像度です。

私のスマートフォンだと、センサーと同じ解像度(4000×3000)で取得できます。ところが、センサーと同じサイズでプレビューサイズ(デフォルト)を指定すると

SurfaceTexture texture = mTextureView.getSurfaceTexture();
texture.setDefaultBufferSize(imageDimension.getWidth(), imageDimension.getHeight());

例外処理が発生して、プログラムが停止してしまいます。プレビューサイズが大きすぎるのです。

結局、 私の場合は画面解像度を考慮して横幅最大1000として指定するようにしました。これじゃ、取得した解像度の意味がありませんね。

mCaptureSession.setRepeatingRequest(mCaptureRequestBuilder.build(), null, null); 

数々の非同期処理を経て、上記の命令を出してようやくプレビューが表示されます。

4.撮影した写真を保存する

Androidで写真を撮った後ですが、保存する場所に困りますWindowsであれば、ストレージ指定のディレクトリを指定すれば良いですが、Androidはフォルダ構成が見えません。それに権限やOSのバージョンも絡んできて、超難解になっています。

保存する場所を決める

撮影した写真は、これまでgetExternalStorageDirectoryで指定された場所に保存していました。今回も撮影した写真を同様の手順で保存しようとしたのですが、「権限がない」が出て保存できません。コードにミスが見当たらず、どうしてしまったのだろうと思っていたら、 getExternalStorageDirectoryAndroid10から使えなくなっていました。

調べると、3つの代替え手段があるそうです。以下のサイト様を参考にさせて頂きました。

このような代替手段がありますが、私は簡単な「getExternalFilesDir」を使うことにしました。これはアンインストールすると写真も消える欠点がありますが、その前にコピーしておくようにします。

画像を確認する

Androidには、Windowsのようなエクスプローラーがありません。そのため、上記の方法で写真を保存しても、確認できない事態になります。そこで、Android Studioには、「Device Explorer」があります。

そして、 getExternalFilesDir で取得したディレクトリをアクセスするわけですが、デバッガで取得したディレクトリDevice Explorerで見ようとしても、権限なしで弾かれます。実は、Device Explorerの違うディレクトリに画像が保存されています。

私の例では、「sdcard/android/data/アプリのフォルダ/files/pictures/」の下に保存されていました。何でこんなことになるのか分かりません。これは、環境によっても変わるかもしれません。

ギャラリーなどで確認できるようにする

カメラを使う時にAndroid Studioを使うわけには行かないので、写真ギャラリーなどから見れるようにしなければなりません。そのためには、Androidに画像を登録する必要があります。

    private void registAndroidDB(String path, String filename) {
        ContentValues values = new ContentValues();
        ContentResolver contentResolver = this.getContentResolver();

        values.put(MediaStore.Images.Media.TITLE, filename);
        values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
        values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        values.put(MediaStore.Images.Media.DATA, path + "/" + filename);

        contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}

上のロジックで、Androidに画像を登録しました。

しかし、私の使っているスマートフォンでは登録できないです。エミュレーターで選んだ機種ではうまくいってるのですが、機種による、またはAndroid10からの仕様により、うまくいかないようです。この件は、別投稿で対応したいと思います。(今回紹介したやり方は使わなくなります)

ちょっと、カメラ作成の心が折れました

5.どんなカメラを作るのか

Android端末でないのはご愛敬

今回、カメラを作成しようと思ったのは、スマートフォンカメラシャッター問題のためです。私のような高齢になると、画面をタッチしてシャッターを切るのが難しいのです。

シャッターを押したのに反応しなかったり、押すつもりがないのに押されてしまったりで、いつも思ったタイミングで写真を撮れません。これは老眼が一番の理由ですが、カメラシャッターのように物理的であれば問題が起こりません。

確かに、音量ボタンシャッターに割り当てられている場合も多いのですが、音量ボタンの位置が押しやすい場所ではないです。私の場合だと、左下にあって、押しにくいです。また、カメラにおいて、シャッターの押し具合も大事な要素ですが、スマートフォン物理ボタン硬くて押すとブレやすいです。

そこで、作成してみようと思うのは、物理的なシャッターを用意したカメラアプリケーションです。bluetoothを利用した製品も売っていますが、こちらはM5StickCのボタンをシャッターとする予定です。

次回で、そのサンプルを公開したいと思います。果たしてうまくいくでしょうか?

※投降後、カメラアプリを作らなくても、既存のカメラアプリでM5StickCからシャッターを切る方法を見つけました。でも、せっかくですからカメラアプリを作っていきたいと思います。