Android Jetpack - CameraX

강보훈·2021년 11월 28일
1

Android Jetpack

목록 보기
2/3

아키텍처

  • 기존 Camera2 API의 기능을 쉽게 활용할 수 있게끔 한 Jetpack의 부가기능

  • API 모델

    1. 원하는 사용 사례와 구성 옵션 지정
    2. 리스터 등록해서 출력 데이터로 할 일 지정
    3. 사용 사례를 Android 아키텍처 수명 주기에 결합하여 카메라 사용 시기 및 데이터 생성 시기와 같은 의도된 흐름 지정
  • 수명주기

    • 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는 단독으로 작동하지 않는다.

장점

실제로 프로젝트를 하면서 가장 크게 와닿은 장점은 실시간으로 이미지 프로세싱이 가능하다는 점이었다. 다른 작업없이 실시간으로 이미지 처리를 해서 서버로 전송이 가능하다보니 굉장히 간편했다. 아래는 공식 홈페이지에 나온 장점이다.

  • 호환성
    Andoird 5.0(API 21) 이상을 실행하는 기기이면 지원가능하다
  • 사용 편의성
    어떤 기기를 사용하던 동일한 코드를 사용할 수 있다.
    1. 미리보기 -> 화면의 이미지를 가져온다
    2. 이미지 분석 -> MLKit사용할 때와 같이 알고리즘에 사용할 수 있도록 버퍼에 접근이 쉬움
    3. 이미지 캡처 -> 고화질 이미지 저장
    4. 동영상 캡처 -> 동영상 및 오디오 캡처
  • 기기 간 일관성
    여러 앱에 걸쳐 카메라 제어하는 것은 어려운 일이지만 CameraX는 기본적인 동작을 자동으로 해결해준다.
  • 확장성
    단 두 줄의 코드로 기기에 기본 설치되어 있는 네이티브 카메라 앱과 동일한 기능을 이용할 수 있게 해주는 확장 프로그램이라는 선택적 부가기능이 있다.

요구사항

  1. Android API 수준 21
  2. Android 아키텍처 구성요소 1.1.1

사용방법

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를 통해서 만들 수 있고 이미지 출력 타입과 이미지 흐름제어(백그라운드 실행자, 이미지 큐 깊이, 백 프레셔 전략)을 설정 가능하다.
    해상도나 가로세로 비율을 지정할 수 있으나 둘 중 하나만 설정해야한다.
    여기서 실제로 데이터가 넘어오는 ImageProxyMedia.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))
  • 추가로 알아두면 좋은 기능

    • 줌이나 플래시 등 조금 더 쉽게 카메라를 제어하고 싶으면 CameraController를 사용할 수 있다.
       val camera = provider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview, imageAnalysis)
               
        camera.cameraControl //Do SomeThing with camera controller
profile
신입 안드로이드 개발자입니다!

0개의 댓글