はじめに
ケアプラン作成は、いまだにエクセル使用している施設ケアマネジャーです。
前回【Android】CameraX習作(ビットマップを取得する)ではカメラ映像からビットマップを取得できました。今回は、ImageAnalysis ユースケースからビットマップを取得しています。アナライザーにやってくるイメージを間引きながら、一定間隔でビットマップ画像を取得しています。
ボタンを押すとビットマップ画像を取得する間隔が切り替わります。取得間隔は10フレーム毎と1000ミリ秒毎に設定しています。
課題(前回と同じ)
一定間隔で、カメラ映像からビットマップ画像を取得したい。ただし、保存はしない。
まとめ
カメラでプレビューを表示しながら、イメージビューにビットマップを表示しています。
ビットマップを取得できたので、ObjectDetection で一人歩きを発見し、一人の時にImage segmentationで利用者であるか特定する、という順序で当初の目的(機械の力を借りて早期発見し、見守りにつなげ転倒を防ぎたい)を達成できそうです。
コード
build.gradle.kts(.app)
build.gradle.kts(.app)
android {
...
buildFeatures {
viewBinding = true
}
}
dependencies {
val camerax_version = "1.5.0-alpha03"
implementation("androidx.camera:camera-camera2:${camerax_version}")
implementation("androidx.camera:camera-lifecycle:${camerax_version}")
implementation("androidx.camera:camera-view:$camerax_version")
...
AndroidManifest.xml
AndroidManifest.xml
<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"/>
<application
...
activity_main.xml
activity_main.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:id="@+id/main"
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="300dp"
android:layout_marginTop="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/viewFinder"
tools:srcCompat="@tools:sample/avatars" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
MainActivity.kt
package yourpackagename
import android.Manifest
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Matrix
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
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.ImageAnalysis
import androidx.camera.core.ImageProxy
//
import yourpackagename.databinding.ActivityMainBinding
typealias TakeBitmapListener = (mode: String, bitmap: Bitmap) -> Unit
class MainActivity : AppCompatActivity() {
private lateinit var viewBinding: ActivityMainBinding
private lateinit var cameraExecutor: ExecutorService
private var everyFrame = true
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)
}
cameraExecutor = Executors.newSingleThreadExecutor()
viewBinding.button.setOnClickListener {
everyFrame = !everyFrame
startCamera()
}
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults:
IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)//
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(this,
"Permissions not granted by the user.",
Toast.LENGTH_SHORT).show()
finish()
}
}
}
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)
}
val imageAnalyzer = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
.build()
.also {
it.setAnalyzer(cameraExecutor, EveryTakeBitmapAnalyzer(everyFrame){ mode, bitmap ->
setBitmap(bitmap)
setModeButtonText(mode)
})
}
// Select front camera フロントカメラにしてあります
val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageAnalyzer)
} 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 FRAME_INTERVAL = 10//10frames
private const val MILLISECOND_INTERVAL = 1000//1000ms
private const val REQUEST_CODE_PERMISSIONS = 10
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
}
private fun setBitmap(bitmap: Bitmap?) {
bitmap ?: return
runOnUiThread {viewBinding.imageView.setImageBitmap(bitmap)}
}
private fun setModeButtonText(mode: String) {
runOnUiThread{
viewBinding.button.text = mode
}
}
private inner class EveryTakeBitmapAnalyzer(analyzerFlag: Boolean, private val listener: TakeBitmapListener) : ImageAnalysis.Analyzer {
//一定時間ごとにイメージを取得する場合(ここでは1000msごと)
var nowTime = System.currentTimeMillis()
var beforeSystemTime = 0L
//一定フレームごとにイメージを取得する場合(ここでは10フレームごと)
var frameCounter = 0
val analyzeFlag = analyzerFlag
override fun analyze(image: ImageProxy) {
if (analyzeFlag){
/* 指定したフレーム間隔でイメージを取得する場合 */
frameCounter += 1
if (frameCounter < FRAME_INTERVAL){
image.close()
}else {
frameCounter = 0
listener("every frame", imageProxyToBitmap(image))
image.close()
}
}else {
/* 指定した時間間隔でイメージを取得する場合 */
nowTime = System.currentTimeMillis()
if ((nowTime - beforeSystemTime) < MILLISECOND_INTERVAL) {
image.close()
} else {
beforeSystemTime = nowTime
listener("every millisecond", imageProxyToBitmap(image))
image.close()
}
}
}
private fun imageProxyToBitmap(image: ImageProxy?): Bitmap {
val planes: Array<ImageProxy.PlaneProxy> = image!!.planes
val buffer = planes[0].buffer
val bitmap = Bitmap.createBitmap(image.width, image.height, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(buffer)
val rotation = image.imageInfo.rotationDegrees
val rotateBitmap = rotateImage(bitmap, rotation)//上下を整える
val mirroredBitmap = mirrorImageBitmap(rotateBitmap)//鏡像にする
return mirroredBitmap
}
private fun rotateImage(bitmap: Bitmap, degree: Int): Bitmap {
val matrix = Matrix()
matrix.postRotate(degree.toFloat())
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}
private fun mirrorImageBitmap(bitmap: Bitmap) : Bitmap{
val matrix = Matrix()
matrix.preScale(-1f, 1f)
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}
}
}