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を作ったほうがいいでしょう.
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を特定します.
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はほぼ確実にサポートされるサイズではありますが..)
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に出てくると思います)
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なりで自分で間隔を制御します.
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 より大かどうか,を見ていますが,検出位置も合わせて見ることで,人がフレームインしてきたのかどうか,なども判定できると思います.
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
fun stopFaceDetection() {
captureSession?.stopRepeating()
}
Camera2 Close
fun close() {
cameraDevice?.close()
cameraDevice = null
}
まとめ
Camera2 APIはCamera1 APIに比べてAppが制御できる自由度が上がったかわりに抽象度が下がっているので,ちょっとした機能を作るには使いにくいかもしれません.(しかしCamera1 APIはDeprecated ...)
ただ,Cameraの画を解析させてMETA Dataを取る,という機能なら今回のFace Detectionのちょい変でほぼ対応できるうえ,Camera1 APIよりもかゆいところに手が届きやすいと思います.
---///