このまま出来る!AndroidStudioのCameraXを使って、カメラを使ったアプリを作成

AndroidStudioではカメラを使ったアプリを作成するための仕組みが多く準備されています。2026年時点では、CameraXとCamera2というライブラリが準備されており、CameraXは初心者向けで簡単にカメラ機能が実装でき、Camera2は中、上級者向けで複雑な機能を実装する事ができます。本記事では初心者向けのCameraXを使ってカメラ機能を実装する方法を説明し、このまま実行すれば、ひとまず自作のプログラムでカメラが動くところまで説明しようと思います。

AndroidStudioのインストール

まずは、AndroidStudioをインストールしなければいけません。インストールの方法は下記の記事で詳しく説明していますので、ご覧ください。

そのままできる!Yoloを使った物体認識。Androidアプリで実行 その1:Android Studioインストール

今回説明する環境は、

・Windows11

・Android Studio 2025.1.1.14

となります。

プロジェクト作成

AndroidStudioを開き、File->New->New Projectを選択

Empty View Activityを選択 -> Next

Name:好きな名前に

Save location:プロジェクトを保存するパス

Language:Kotlin(今回の記事ではKotlinのソースを掲載します)

Minimun SDK:API21を選択(CameraXを利用するための最小要件に変更)

Finishを押すと、プロジェクトが生成されます。

プログラム作成

それではプログラムを作成していきます。これから記載するコードをそのままコピーをして活用すれば動作するように記載していきますので、コードはご活用ください。

①build.gradle.ktsを書き換える

プロジェクトを起動すると、上記ファイルにコードが書かれていますが、全部消して、下に記載したコードをコピーして貼り付けてください。

↓はじめに書かれているコード

↓このコードをコピーして上のファイルに上書き

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "com.example.cameraxapp"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.cameraxapp"
        minSdk = 21
        targetSdk = 33
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
    buildFeatures {
        viewBinding = true
        dataBinding = true
    }
}

dependencies {
    implementation ("androidx.camera:camera-core:1.1.0-beta01")
    implementation ("androidx.camera:camera-camera2:1.1.0-beta01")
    implementation ("androidx.camera:camera-lifecycle:1.1.0-beta01")
    implementation ("androidx.camera:camera-video:1.1.0-beta01")
    implementation ("androidx.camera:camera-view:1.1.0-beta01" )
    implementation ("androidx.camera:camera-extensions:1.1.0-beta01")
    implementation("androidx.core:core-ktx:1.9.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.10.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

②res/layout/activity_main.xmlのactivity_mainを書き換える。

↓のようにactivity_main.xmlをダブルクリックすると、白い画面が見えるとおもいますので、そこで「右クリック」しGotoXMLを選択

すると、XMLファイルが開けます。

上の赤枠部分を下記のコードで上書き

<?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">

    <androidx.camera.view.PreviewView
        android:id="@+id/viewFinder"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:id="@+id/image_capture_button"
        android:layout_width="110dp"
        android:layout_height="60dp"
        android:layout_marginBottom="20dp"
        android:layout_marginEnd="20dp"
        android:elevation="2dp"
        android:text="@string/take_photo"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintEnd_toStartOf="@id/vertical_centerline" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/vertical_centerline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent=".70" />

</androidx.constraintlayout.widget.ConstraintLayout>

そうすると、先ほど「Hello World」が出ていた画面がこのように変わると思います。

③strong.xmlファイルを書き換え、アプリ名とボタンの名前を追加します。↓のようにコードが書かれているので、

その下に記載したコードに書き換えてください。

<resources>
    <string name="app_name">CameraXApp</string>
    <string name="take_photo">Take Photo</string>
</resources>

④AndroidManifest.xmlを書き換える

上のコードを↓に書いたコードに置き換えます。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-feature android:name="android.hardware.camera.any" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.CameraXApp"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

⑤MainActivity.ktを書き換えます。

置き換えるコードは下記。

package com.example.cameraxapp

import android.Manifest
import android.content.ContentValues
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.ImageCapture
import androidx.camera.video.Recorder
import androidx.camera.video.Recording
import androidx.camera.video.VideoCapture
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.cameraxapp.databinding.ActivityMainBinding
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import android.widget.Toast
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.core.Preview
import androidx.camera.core.CameraSelector
import android.util.Log
import androidx.camera.core.ImageCaptureException
import java.text.SimpleDateFormat
import java.util.Locale

typealias LumaListener = (luma: Double) -> Unit

class MainActivity : AppCompatActivity() {
    private lateinit var viewBinding: ActivityMainBinding

    private var imageCapture: ImageCapture? = null

    private var videoCapture: VideoCapture<Recorder>? = null
    private var recording: Recording? = null

    private lateinit var cameraExecutor: ExecutorService

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(viewBinding.root)

        // Request camera permissions
        if (allPermissionsGranted()) {
            startCamera()
        } else {
            ActivityCompat.requestPermissions(
                this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
        }

        // Set up the listeners for take photo and video capture buttons
        viewBinding.imageCaptureButton.setOnClickListener { takePhoto() }

        cameraExecutor = Executors.newSingleThreadExecutor()
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                startCamera()
            } else {
                Toast.makeText(
                    this,
                    "Permissions not granted by the user.",
                    Toast.LENGTH_SHORT
                ).show()
                finish()
            }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }

    private fun takePhoto() {
        // Get a stable reference of the modifiable image capture use case
        val imageCapture = imageCapture ?: return

        // Create time stamped name and MediaStore entry.
        val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
            .format(System.currentTimeMillis())
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, name)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
            if(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
                put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
            }
        }

        // Create output options object which contains file + metadata
        val outputOptions = ImageCapture.OutputFileOptions
            .Builder(contentResolver,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                contentValues)
            .build()

        // Set up image capture listener, which is triggered after photo has
        // been taken
        imageCapture.takePicture(
            outputOptions,
            ContextCompat.getMainExecutor(this),
            object : ImageCapture.OnImageSavedCallback {
                override fun onError(exc: ImageCaptureException) {
                    Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
                }

                override fun
                        onImageSaved(output: ImageCapture.OutputFileResults){
                    val msg = "撮影成功"
                    Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
                    Log.d(TAG, msg)
                }
            }
        )
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // Preview
            val preview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(viewBinding.viewFinder.surfaceProvider)
                }

            imageCapture = ImageCapture.Builder()
                .build()

            // Select back camera as a default
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }


    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(
            baseContext, it) == PackageManager.PERMISSION_GRANTED
    }

    override fun onDestroy() {
        super.onDestroy()
        cameraExecutor.shutdown()
    }

    companion object {
        private const val TAG = "CameraXApp"
        private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS =
            mutableListOf (
                Manifest.permission.CAMERA,
                Manifest.permission.RECORD_AUDIO
            ).apply {
                if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
                    add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                }
            }.toTypedArray()
    }
}

⑥build&syncしてみましょう。

⑦上記成功したら、runを実行

こんな感じの画面が出たら成功です。

もしエミュレータが起動せずにエラーがでたら、デバイスマネージャからデバイスを選択します

下記の記事にも記載しているので、ご参考までに。

そのままできる!Yoloを使った物体認識。Androidアプリで実行 その3:tfliteをAndroidStudioへ実装

まずデバイスマネージャ

Create Virtual Device

デバイスは適当に選択

Finish

先ほど選択したデバイスへ切り替え

実行

こんな感じで少し考えます。

その後こんな感じで、実行されると思います。

実機確認(端末の準備)

それでは、実際にAndroidのスマホ、タブレットを接続して動作確認していきましょう。

お手持ちのスマホ、タブレットを開発者モードに設定します。次の手順です。

下記の記事にも記載しているので、ご参考までに。

そのままできる!Yoloを使った物体認識。Androidアプリで実行 その3:tfliteをAndroidStudioへ実装

設定→システム→ビルド番号のところを何度もタップ。そうすると、「デベロッパーモードになりました。」と出てくると思います。

②開発者向けオプションがONになっている事を確認します。

③USBでデバッグする際は、USBデバッグを有効化

実機確認

それではいよいよ確認です。USBでPCとスマホ/タブレットを接続

すると、端末にはこのこのように接続を許可するかどうか聞いてくるので、許可を選択。

AndroidStudioには、接続したデバイスが見えると思いますので、選択して、スタート

すると、実機にソフトがインストールされて動きますので、ご確認ください。

真ん中の「Take photo」ボタンを押すと、下記のパスへ写真が保存されます。

Lenovo Tab M8\SDカード\Pictures\CameraX-Image

AndroidStudio関連は他にも記事がありますので、ご参考にしてください。

そのままできる!Yoloを使った物体認識。Androidアプリで実行 その1:Android Studioインストール

そのままできる!Yoloを使った物体認識。Androidアプリで実行 その2:Yoloからtfliteへの変換

そのままできる!Yoloを使った物体認識。Androidアプリで実行 その3:tfliteをAndroidStudioへ実装

コメント

タイトルとURLをコピーしました