環境
Jetpack Compose
CameraX 1.4.0
Android SDK 35
概要
Androidで物体検知アプリを作成します。
完成するアプリのイメージです。
モデルはyolo v8を使用します。
事前準備編としてまずはJetpack Compose上でCameraXを実装します。
手順
プロジェクト新規作成→Empty Activityを選択しました。
(Empty Activityを選択するとJetpack Composeでプロジェクトが作成されます。)
プロジェクト名をobject_detection_sampleとしました。
Build configuration languageはデフォルトのKotlin DSLを選択しました。
エラーの指示どおりにSDKバージョンを35に変更します。
android {
namespace = "com.android.example.object_detection_sample"
// compileSdk = 34
compileSdk = 35
defaultConfig {
applicationId = "com.android.example.object_detection_sample"
minSdk = 30
// targetSdk = 34
targetSdk = 35
次にカメラを利用するためパッケージと権限設定を追加します。
dependencies {
// 追加
// camerax
val camerax_version = "1.4.0"
implementation("androidx.camera:camera-camera2:$camerax_version")
implementation("androidx.camera:camera-lifecycle:$camerax_version")
implementation("androidx.camera:camera-view:$camerax_version")
implementation("androidx.camera:camera-video:$camerax_version")
implementation("com.google.accompanist:accompanist-permissions:0.30.1")
// 以下略
<?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"
android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
<!--以下略-->
</manifest>
MainActivityから既存のGreeting関連のコードを削除し、カメラの権限確認画面を追加します。
※importは必要に応じて追加してください。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize()
){
CameraPreviewScreen()
}
}
}
}
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun CameraPreviewScreen() {
val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)
if (cameraPermissionState.status.isGranted) {
// プレビュー画面を表示
} else {
PermissionRequest(cameraPermissionState)
}
}
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun PermissionRequest(cameraPermissionState: PermissionState) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Button(onClick = {
cameraPermissionState.launchPermissionRequest()
}) {
Text("Grant Camera Permission")
}
}
}
}
カメラのプレビュー画面のコードを追加します。
CameraXはJetpack Composeに対応していないため実装の際はAndroidViewを使います。
// 中略
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun CameraPreviewScreen() {
val cameraPermissionState = rememberPermissionState(permission = android.Manifest.permission.CAMERA)
if (cameraPermissionState.status.isGranted) {
// プレビュー画面を表示
CameraPreview(modifier = Modifier.aspectRatio(3f/4f)) // 追加
} else {
PermissionRequest(cameraPermissionState)
}
}
// 中略
// 追加
@Composable
fun CameraPreview(modifier: Modifier = Modifier) {
val lifecycleOwner = LocalLifecycleOwner.current
val previewView = remember { PreviewView(this) }
val rotation = LocalConfiguration.current.orientation
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
val preview = androidx.camera.core.Preview.Builder()
.setTargetRotation(rotation)
.setResolutionSelector(
ResolutionSelector.Builder()
.setAspectRatioStrategy(
AspectRatioStrategy(
AspectRatio.RATIO_4_3,
AspectRatioStrategy.FALLBACK_RULE_AUTO
)
)
.build()
)
.build()
.also {
it.surfaceProvider = previewView.surfaceProvider
}
LaunchedEffect(Unit) {
val cameraProvider = getCameraProvider()
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
)
}
AndroidView({ previewView }, modifier = modifier)
}
private suspend fun Context.getCameraProvider(): ProcessCameraProvider =
suspendCoroutine { continuation ->
ProcessCameraProvider.getInstance(this).also { cameraProvider ->
cameraProvider.addListener({
continuation.resume(cameraProvider.get())
}, ContextCompat.getMainExecutor(this))
}
}
最終的なMainActivity.ktのコードは以下の通りになります。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize()
){
CameraPreviewScreen()
}
}
}
}
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun CameraPreviewScreen() {
val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)
if (cameraPermissionState.status.isGranted) {
CameraPreview(modifier = Modifier.aspectRatio(3f/4f))
} else {
PermissionRequest(cameraPermissionState)
}
}
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun PermissionRequest(cameraPermissionState: PermissionState) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Button(onClick = {
cameraPermissionState.launchPermissionRequest()
}) {
Text("Grant Camera Permission")
}
}
}
@Composable
fun CameraPreview(modifier: Modifier = Modifier) {
val lifecycleOwner = LocalLifecycleOwner.current
val previewView = remember { PreviewView(this) }
val rotation = LocalConfiguration.current.orientation
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
val preview = androidx.camera.core.Preview.Builder()
.setTargetRotation(rotation)
.setResolutionSelector(
ResolutionSelector.Builder()
.setAspectRatioStrategy(
AspectRatioStrategy(
AspectRatio.RATIO_4_3,
AspectRatioStrategy.FALLBACK_RULE_AUTO
)
)
.build()
)
.build()
.also {
it.surfaceProvider = previewView.surfaceProvider
}
LaunchedEffect(Unit) {
val cameraProvider = getCameraProvider()
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
)
}
AndroidView({ previewView }, modifier = modifier)
}
private suspend fun Context.getCameraProvider(): ProcessCameraProvider =
suspendCoroutine { continuation ->
ProcessCameraProvider.getInstance(this).also { cameraProvider ->
cameraProvider.addListener({
continuation.resume(cameraProvider.get())
}, ContextCompat.getMainExecutor(this))
}
}
}
お疲れさまでした。
物体検知実装編に続きます。
補足
実際の端末のウェブカメラを使いたい場合はエミュレーターの設定を変更しましょう。