イベント好きエンジニア鈴木さんのブログ

イベント駆動ものづくりPodcastのAKIあきラジオをやってる人のブログです。

【Java】Camera XをPreview(TextureView)なしでAnalyzeだけ動かす

ちょっと知り合いから相談されて、突然Androidのカメラを利用したAndroidアプリを触ることになりました。 そのソフトを作るために必要だったのが、プレビューを出さずに画像の解析を行う、ということでした。 Githubやブログに落ちているサンプル探していろいろ動かしてみましたが、どうしてもビューがないと動かないので困っていました。 そこで紹介されたのがCamera Xという機能です。

developer.android.com

これは最近追加された機能らしく、既存と同様の機能に加えて画像の解析もできるようです。 わかりやすいチュートリアルもありますが、新しい機能であるためKotlinで書かれたものになっています。

codelabs.developers.google.com

しかしながら、カメラを使って実装うする機能がソースコードJavaなのでどうしたものかと困ってしまいました。 ちょっと考えた結果、KotlinのソースをJavaに書き換えることにしました。

結果としてうまくいったのでそのソースコードを載せておきます。 一応おことわりとして、Androidは普段あまり開発していないので、なにか間違いがあるかもしれません。

プロジェクト設定

まず、AndroidManifest.xmlにカメラのパーミッションをいれることと、Camera Xを使うために必要なbuild.gradleの設定はしておいてください。

<uses-permission android:name="android.permission.CAMERA" />

build.gradle(Module: app)はandroidの中に、以下を追加。

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

dependenciesに以下を追加します。

    def camerax_version = '1.0.0-alpha06'
    implementation "androidx.camera:camera-core:${camerax_version}"
    implementation "androidx.camera:camera-camera2:${camerax_version}"

dependenciesはalphaとなっているので時期によっては表記が変わるかもしれません。

なくてもいいはずだけど、コメントアウト解除したら使うのでActivityも載せておきます。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextureView
        android:id="@+id/view_finder"
        android:layout_width="640px"
        android:layout_height="640px"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

ビューなしでカメラの画像を解析する

そのうえで以下がメインのソース。本来Texture Viewが必要なところはコメントアウトしています。

package com.suzuki.camerax_appjava;

import androidx.appcompat.app.AppCompatActivity;

import android.Manifest;
import android.os.Bundle;
import android.content.pm.PackageManager;
import android.util.Log;
import android.util.Size;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.view.TextureView;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import androidx.camera.core.*;
import androidx.camera.core.ImageAnalysis;

public class MainActivity extends AppCompatActivity {

    private static final int REQUEST_CODE_PERMISSIONS = 10;
    private static final String[] REQUIRED_PERMISSIONS = {Manifest.permission.CAMERA};
    private ExecutorService executor = Executors.newSingleThreadExecutor();

//    private TextureView viewFinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//        viewFinder =  findViewById(R.id.view_finder);

        if (allPermissionsGranted()) {
            startCamera();
//            viewFinder.post(() -> {
//                startCamera();
//            });
        } else {
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
        }
    }

    private void startCamera() {
        ImageAnalysisConfig config =
                new ImageAnalysisConfig.Builder()
                        .setTargetResolution(new Size(1280, 720))
                        .setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
                        .build();

        ImageAnalysis imageAnalysis = new ImageAnalysis(config);

        imageAnalysis.setAnalyzer(executor, imageAnalyzer);
        CameraX.bindToLifecycle( this, imageAnalysis);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                startCamera();
//                viewFinder.post( () -> {
//                    startCamera();
//                });
            } else {
                Toast.makeText(this,
                        "Permissions not granted by the user.",
                        Toast.LENGTH_SHORT).show();
                finish();
            }
        }
    }

    private boolean allPermissionsGranted() {
        for(String permission : REQUIRED_PERMISSIONS){
            if(ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED){
                return false;
            }
        }
        return true;
    }

    private ImageAnalysis.Analyzer imageAnalyzer = new ImageAnalysis.Analyzer() {
        private long lastAnalyzedTimestamp = 0L;
        @Override
        public void analyze(ImageProxy image, int rotationDegrees) {
            long currentTimestamp = System.currentTimeMillis();
            // Calculate the average luma no more often than every second
            if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.SECONDS.toMillis(1)) {
                // ここに画像の解析処理を書く[f:id:suzu_hack:20200320205423p:plain]

                // Log the new luma value
                Log.d("CameraXApp", "Analyzed");
                // Update timestamp of last analyzed frame
                lastAnalyzedTimestamp = currentTimestamp;
            }
        }
    };
}

うまく動けばlogcat画面にAnalyzedという文字がでます。

f:id:suzu_hack:20200320205423p:plain

画面は真っ白なので、一見動いたようには見えないのでご注意を。