기존 Camera2 API의 기능을 쉽게 활용할 수 있게끔 한 Jetpack의 부가기능
API 모델
수명주기
CameraX는 카메라를 여는 시점, 캡처 세션을 생성할 시점, 중지 및 종료 시점을 결정하기 위해 수명 주기를 따르고 사용 사례 API에서는 진행 상황을 모니터링할 메서드 호출과 콜백을 제공한다.
표준 Android LifeCycle에 따르지 않고 명시적으로 CameraX의 수명주기를 관리할 수 있다. 더 자세한 내용은 조금 더 공부한 뒤에 추가하겠다. 참고
class CustomLifecycle : LifecycleOwner {
private val lifecycleRegistry: LifecycleRegistry
init {
lifecycleRegistry = LifecycleRegistry(this);
lifecycleRegistry.markState(Lifecycle.State.CREATED)
}
...
fun doOnResume() {
lifecycleRegistry.markState(State.RESUMED)
}
...
override fun getLifecycle(): Lifecycle {
return lifecycleRegistry
}
}
동시 사용 사례
사용 사례를 동시에 실행이 가능하다. LifeCycle에 맞춰 순차적으로 결합도 가능하지만 CameraProcessProvider.bindToLifeCycle()
로 한번에 모두 결합하는 것이 권장된다.
아래의 코드는 미리보기와 사진을 촬용하는 두 개의 사용사례를 사용하는 간단한 방법이다.
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val provider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
val imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
.build()
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
val camera = provider.bindToLifecycle( this as LifecycleOwner, cameraSelector, preview, imageCapture)
preview.setSurfaceProvider(binding.preView.surfaceProvider)
}, ContextCompat.getMainExecutor(this))
이 둘만이 아니라 다양한 구성이 가능하다. 사진출처
다만 확장 프로그램을 사용할 경우 ImageCapture, Preview만 보장된다. 그리고 Preview, ImageAnalysis는 단독으로 사용이 가능하지만 ImageCapture는 단독으로 작동하지 않는다.
실제로 프로젝트를 하면서 가장 크게 와닿은 장점은 실시간으로 이미지 프로세싱이 가능하다는 점이었다. 다른 작업없이 실시간으로 이미지 처리를 해서 서버로 전송이 가능하다보니 굉장히 간편했다. 아래는 공식 홈페이지에 나온 장점이다.
app 단 build.gradle 에 종속성 추가 버전별 사항
def camerax_version = "1.0.2"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:1.0.0-alpha31"
extension의 경우 아직 사용해보지 않아서 따로 추가하지 않았다.
Preview
단순히 카메라 영상을 보여준다.
//권한 요구
requestPermissions(arrayOf(Manifest.permission.CAMERA), 1)
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val provider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK) //전면, 후면카메라 결정
.build()
val camera = provider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview)
preview.setSurfaceProvider(binding.preView.surfaceProvider)
}, ContextCompat.getMainExecutor(this))
Preview + ImageCapture
카메라 영상을 화면에 보여주고 버튼을 누른 경우 사진 저장
//권한 요구
requestPermissions(arrayOf(Manifest.permission.CAMERA), 1)
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val provider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
.build()
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK) //전면, 후면카메라 결정
.build()
val camera = provider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview, imageCapture)
preview.setSurfaceProvider(binding.preView.surfaceProvider)
}, ContextCompat.getMainExecutor(this))
val file = File("$filesDir/test/test.jpg")
if (!file.exists()) {
file.mkdirs()
}
val outputOption = ImageCapture.OutputFileOptions.Builder(file).build()
binding.captureButton.setOnClickListener { imageCapture!!.takePicture(outputOption, ContextCompat.getMainExecutor(this), object : OnImageSavedCallback{
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(file)
val msg = "Photo capture succeeded: $savedUri"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d("save url", msg)
}
override fun onError(exception: ImageCaptureException) {
Log.e("Error Take", exception.localizedMessage!!)
}
}) }
Preview + ImageAnalysis
ImageAnalysis는 ImageAnalysis.Builder를 통해서 만들 수 있고 이미지 출력 타입과 이미지 흐름제어(백그라운드 실행자, 이미지 큐 깊이, 백 프레셔 전략)을 설정 가능하다.
해상도나 가로세로 비율을 지정할 수 있으나 둘 중 하나만 설정해야한다.
여기서 실제로 데이터가 넘어오는 ImageProxy
는 Media.Image
의 랩퍼 클래스다.
실제 분석하는 부분은 주엊니 프레임 속도 내 시간(30fps면 32ms미만)내에서 처리를 마치는 것을 권장한다. 만약 처리속도가 부족하다면 프레임 드롭 매커니즘을 사용하는 것이 권장 사항이다.
하나 중요한 점을 꼽자면 넘어온 데이터를 사용을 마친 뒤 반드시 ImageProxy.close()
함수를 실행시켜줘야만 한다.
//권한 요구
requestPermissions(arrayOf(Manifest.permission.CAMERA), 1)
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
val executor = Executors.newSingleThreadExecutor()
cameraProviderFuture.addListener({
val provider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
val imageAnalysis = ImageAnalysis.Builder()
.build()
.also {
it.setAnalyzer(executor, { image: ImageProxy ->
//Do SomeThing
image.close()
})
}
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK) //전면, 후면카메라 결정
.build()
val camera = provider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview, imageAnalysis)
preview.setSurfaceProvider(binding.preView.surfaceProvider)
}, ContextCompat.getMainExecutor(this))
추가로 알아두면 좋은 기능
val camera = provider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview, imageAnalysis)
camera.cameraControl //Do SomeThing with camera controller