LoginSignup
11
10

More than 5 years have passed since last update.

Camera2 APIを使ってFront CameraでFace Detectionさせるだけのかなりシンプルなサンプル

Posted at

TL;DR

Android Camera2 APIのサンプルって意外と少ないですね.(旧Camera API比(Camera1 API比))
今回,単純にFront CameraでBackgroundでFace Detectionさせる機能がほしかったので,
作るついでに必要な処理とか超ざっくりまとめました.

ざっくり概要とシーケンス

BackgroundでFace Detectionさせたいだけなので,スルー画の表示とかは無し.
Serviceとかからでも使いたいので,Activity依存なし,Context依存のみ.
実用上で必須になるMulti Product対応とかは書き始めるときりがないので言及までにします.

Camera2 Open

Face Detection Start

Face Detection Callback

Face Detection Stop

Camera2 Close

のシーケンスをひとつひとつざっくり実装します.

Android Manifest

何はともあれ
<uses-permission android:name="android.permission.CAMERA"/>
を追加

Camera2 Open

Callback Threadの準備

Camera2 APIはほとんどのAPIが非同期APIです.API Call後,Callbackを待つ設計になります.
また,Camera1 APIではCallback ThreadはCamera.open()を呼び出したThreadになる,という制約がありましたが,API 2ではCallbackを設定するAPIにはCallback Threadも設定できるようになっています.

Callback ThreadにUI Threadを設定することもできますが,Camera2のAPIによっては毎Frame Callbackされるものもあるため,UI Threadに負荷をかけないため,Camera2 API用に独自にBackground Threadを作ったほうがいいでしょう.

sample.kt
private val callbackHandler: Handler

init {
    // Callback thread.
    val callbackThread = HandlerThread("callback-worker")
    callbackThread.start()
    callbackHandler = Handler(callbackThread.looper)
}

使い終わったら
callbackHandler.looper.quitSafely()
とかで片付けます.

ちなみにandroid.os.HandlerはConstructorを呼び出したThreadのHandlerでInstance化されてしまうので,確実に非UI ThreadなHandlerにするために,HandlerThread等々を使ってThreadを作成しています.

CameraのOpen

Open時はFront Cameraを指定してOpenします.
Camera2 APIではCamera IDがStringになったため,Camera1 APIのときのようなID決め打ちは使えません.
(Front CameraのIDが "1" になっているProductがほとんどな気はしますが)
PlatformからサポートされているID一覧をとり,Facingを調べてFront CameraのIDを特定します.

sample.kt
private var cameraDevice: CameraDevice? = null

fun open(context: Context) {
    val cameraMng = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager

    // Detect front camera.
    val ids: Array<String> = cameraMng.cameraIdList
    var frontCamId: String = ""
    for (id in ids) {
        val camChars = cameraMng.getCameraCharacteristics(id)
        if (camChars.get(CameraCharacteristics.LENS_FACING)
                == CameraCharacteristics.LENS_FACING_FRONT) {
            // Front.
            frontCamId = id
            break
        }
    }

    if (frontCamId.isEmpty()) {
        // Error handle.
    }

    // Request open.
    cameraMng.openCamera(frontCamId, StateCallbackImpl(), callbackHandler)
}

private inner class StateCallbackImpl() : CameraDevice.StateCallback() {
    override fun onDisconnected(camera: CameraDevice?) {
        // Error handle.
    }

    override fun onError(camera: CameraDevice?, error: Int) {
        // Error handle.
    }

    override fun onOpened(camera: CameraDevice?) {
        cameraDevice = camera
    }
}

Face Detection Start

OpenしたCameraに対して,Preview(連続撮影,Streaming)開始 + Face Detection開始をリクエストするシーケンスです.
Callbackだらけで複雑なので各処理ごとに分けます.

Capture Session

Camera Platform → Appで画の送受信を行うStream(共有FrameBuffer,Surface)を構築します.
今回はCameraのスルー画をUIに出すわけではないので,写真撮影機能などに使われるImageReaderをStreamとして使用します.
(スルー画をUIに出す,等のUse CaseではSurfaceView(TextureView)が通常使われます)

Face Detection用途なので高解像度は必要ないため,サイズはVGA(640x480)を固定値で使っていますが,本来はFront Camera IDと同じようにCameraManager.getCameraCharacteristics()を使ってサポートされている値の中から動的に選ぶ必要があります.
(CTS(Compatibility Test Suite)内でVGAがサポートされていることを確認するTestがあったりするため,VGAはほぼ確実にサポートされるサイズではありますが..)

sample.kt
private var previewStream: ImageReader? = null

fun startFaceDetection() {
    // Stream.
    val stream = ImageReader.newInstance(
            640,
            480,
            ImageFormat.PRIVATE,
            2)
    stream.setOnImageAvailableListener(
            ImageAvailableCallback(),
            callbackHandler)

    val streams: List<Surface?> = listOf(stream.surface)

    cameraDevice?.createCaptureSession(
            streams,
            CaptureSessionCallback(),
            callbackHandler)
}

Image Available Callback

Streamに設定する撮影される画のFrameBufferに関するCallback.
Face Detection用途なら撮影画は必要ないので全部捨てでOKなのですが,ImageReaderの場合,各FrameBufferを開放する責務はAppが持つので,Callbackされるたびに最新のFrameBufferをclose()して開放しています.

(これをしないと,空きFrameBufferが無い,というようなErrorがlogcatに出てくると思います)

sample.kt
private inner class ImageAvailableCallback : ImageReader.OnImageAvailableListener {
    override fun onImageAvailable(reader: ImageReader?) {
        val image = reader?.acquireLatestImage()
        image?.close()
    }
}

Capture Session Callback

CameraのPlatformに対して,どういうStreamingをさせるか,のCapture Requestを作成します.
・AF Mode = Continuous Picture
 → 常にPintを合わせ続けるように
・Face Detection Mode = Simple
 → 顔位置の検出を有効に
・Control Mode = Auto
 → その他もろもろの設定をPlatformに良い感じに自動制御に

Face Detection ModeにFullというのもありますが,サポートされる端末が限られます.Face DetectionのみならSimpleでも問題ないので,よりサポート端末の多いSimpleを選んでいます.

通常,1 Request = 1 Result ですが,setRepeatingRequest()にすることで,止めるまで自動で再Requestし続けるようにしています.
Face Detectionの間隔を多く取りたい場合はここをCameraCaptureSession.capture()に変えて,HandlerなりTimerなりで自分で間隔を制御します.

sample.kt
private var captureSession: CameraCaptureSession? = null

private inner class CaptureSessionCallback : CameraCaptureSession.StateCallback() {
    override fun onConfigureFailed(session: CameraCaptureSession?) {
        // Error handle.
    }

    override fun onConfigured(session: CameraCaptureSession?) {
        captureSession = session

        // Request.
        val builder: CaptureRequest.Builder
                = device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
        builder.addTarget(previewStream?.surface)
        builder.set(
                CaptureRequest.CONTROL_MODE,
                CaptureRequest.CONTROL_MODE_AUTO)
        builder.set(
                CaptureRequest.CONTROL_AF_MODE,
                CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
        builder.set(
                CaptureRequest.STATISTICS_FACE_DETECT_MODE,
                CaptureRequest.STATISTICS_FACE_DETECT_MODE_SIMPLE)

        val request = builder.build()

        session?.setRepeatingRequest(
                request,
                CaptureCallback(),
                callbackHandler)
    }
}

Capture Callback (Face Detection)

撮影画がCallbackされるImage Available Callbackと違ってこちらはCapture Requestに対するCapture ResultのCallbackです.
Face Detection結果など,画に対するMETA DataがCapture Resultに入っています.

ここでは単純に検出された顔の数が 0 より大かどうか,を見ていますが,検出位置も合わせて見ることで,人がフレームインしてきたのかどうか,なども判定できると思います.

sample.kt
private inner class CaptureCallback : CameraCaptureSession.CaptureCallback() {
    override fun onCaptureCompleted(
            session: CameraCaptureSession?,
            request: CaptureRequest?,
            result: TotalCaptureResult?) {
        super.onCaptureCompleted(session, request, result)

        val faces = result?.get(CaptureResult.STATISTICS_FACES)

        if (faces != null) {
            if (faces.size > 0) {

                // FACE DETECTED !

            }
        }
    }
}

Face Detection Stop

sample.kt
fun stopFaceDetection() {
    captureSession?.stopRepeating()
}

Camera2 Close

sample.kt
fun close() {
    cameraDevice?.close()
    cameraDevice = null
}

まとめ

Camera2 APIはCamera1 APIに比べてAppが制御できる自由度が上がったかわりに抽象度が下がっているので,ちょっとした機能を作るには使いにくいかもしれません.(しかしCamera1 APIはDeprecated ...)

ただ,Cameraの画を解析させてMETA Dataを取る,という機能なら今回のFace Detectionのちょい変でほぼ対応できるうえ,Camera1 APIよりもかゆいところに手が届きやすいと思います.

---///

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10