ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • CameraX api 2 : CameraX 기본 코드 분석
    회사 생활/여권 NFC (CameraX + OCR + NFC) 2023. 11. 10. 18:19

    모든 코드 보기 :

     

    CameraX api를 최신 버전 (1.4.0-alpha02) 으로 추가했다. 설명은 코드내에 간단히 적혀있다. 

    공식 문서에 들어가면 버전별로 여러가지 업데이트 사항이 적혀있다. (버전 확인 : 링크

    val cameraxVersion = "1.4.0-alpha02"

     

    MainActivity에서 간단히 필요한 부분만 살펴보자 !!!

    1. startCamera 메소드에서 카메라 활용에 필요한 세팅을 하고 있다.

     

    a) 먼저 ProcessCameraProvider 는 카메라의 생명 주기를 생명 주기 소유자(Activity)와 바인딩하는 데 사용한다. CameraX가 생명 주기를 인식하므로 카메라를 열고 닫는 작업이 필요하지 않게 된다.

     

     

    b) startCamera 메소드에서  Preview, ImageAnalysis, ImageCapture, VideoCapture 이렇게 4가지가 세팅되어있다.

     

    • Preview (미리보기): Preview Use Case는 카메라 뷰(카메라 미리보기 화면)를 제공하는 데 사용됩니다. 
    • ImageAnalysis (이미지 분석): ImageAnalysis Use Case는 카메라에서 가져온 프레임(이미지) 데이터를 분석하는 데 사용됩니다.
    • ImageCapture (이미지 캡처): ImageCapture Use Case는 카메라로 사진을 캡처하고 저장하는 데 사용됩니다.
    • VideoCapture (동영상 캡처): VideoCapture Use Case는 카메라로 동영상을 촬영하는 데 사용됩니다.

     

    위에 4가지 UseCase는 비율 같은 것들을 추가로 세팅할 수 있다. 이 부분은 좀 나중에 자세히 알아보겠다.

     

     

    c) CameraSelector로 전면 후면 카메라를 세팅한다. 

    val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
    

     

     

    d) 마지막으로 Preview, ImageAnalysis,ImageCapture, VideoCapture 만든 것을 생명주기를 관리하는ProcessCameraProvider 와 연결하는 부분이다. 

    cameraProvider.bindToLifecycle(
        this, cameraSelector, preview, imageAnalyzer, imageCapture
    )

     

    bindToLifecycle에 들어가는 UseCase (Preview, ImageAnalysis,ImageCapture, VideoCapture) 는 3가지만 바인딩 할수 있다. 그래서 아래와 같이 필요할 때 조합에 맞춰서 넣어야 한다. 

     

    실시간 이미지 분석 : Preview, ImageAnalysis

    사진 찍을때 : Preview, ImageCapture

    비디오 촬영할때 : Preview, VideoCapture 

    Currently up to 3 use cases may be bound to a {@link Lifecycle} at any time. Exceeding
    * capability of target camera device will throw an IllegalArgumentException.

     

     

    ImageAnalysis.Analyzer를 만드는 3가지 방법 (이거는 제가참고하려고 만들었습니다.)

    더보기

    (1) ImageAnalysis.Analyzer 를 상속한 class

    public class YourAnalyzer implements ImageAnalysis.Analyzer {
    
        @Override
        public void analyze(ImageProxy image) {
            // 이미지 데이터를 추출하여 활용하는 작업을 수행합니다.
            A(image);
            
            // 이미지 프록시를 닫습니다.
            image.close();
        }
    
        private void A(ImageProxy image) {
            // 이미지 데이터 추출
            ByteBuffer buffer = image.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            
            // 여기에서 이미지 데이터(bytes)를 활용하는 작업을 수행합니다.
            // 예를 들어, 바이트 배열을 Bitmap으로 변환하거나 분석 작업을 수행할 수 있습니다.
            // 필요에 따라 추가 작업을 수행하세요.
        }
    }

     

     

    (2) 익명 내부 클래스 (Anonymous Inner Class) 사용

    public class MainActivity {
    
        private ImageAnalysis.Analyzer analyzer = new ImageAnalysis.Analyzer() {
            @Override
            public void analyze(ImageProxy image) {
                // 이미지 데이터를 추출하여 활용하는 작업을 수행합니다.
                A(image);
    
                // 이미지 프록시를 닫습니다.
                image.close();
            }
    
            private void A(ImageProxy image) {
                // 이미지 데이터 추출 및 처리 작업을 수행합니다.
            }
        };
    
        // 나머지 코드...
    }

     

    (3) 람다식 사용

    public class MainActivity {
    
        private ImageAnalysis.Analyzer analyzer = image -> {
            // 이미지 데이터를 추출하여 활용하는 작업을 수행합니다.
            A(image);
    
            // 이미지 프록시를 닫습니다.
            image.close();
        };
    
    	private A(ImageProxy image){
        	... 생략 ...
        }
        // 나머지 코드...
    }

     

    모든 부분에 공통적으로 들어가 있는 것이 ImageProxy image이다. 이미지 관련 일을 처리 할때 이부분을 건드려야하는데 이걸 바로 활용할 수는 없고 이것을 Bitmap이나 ByteArray 형식으로 변환해서 활용해줘야 하는데 자세한 내용은 다음번에 학습하기로 한다.

     

     

     

    2. takePhoto 메소드는 사진찍고 저장하는 기능을 수행하고 있다.

            val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US)
                .format(System.currentTimeMillis())
            val contentValues = ContentValues().apply {
                put(MediaStore.MediaColumns.DISPLAY_NAME, name)
                put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
                if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
                    put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
                }
            }

     

     

    ContentValues는 처음 본다. ContentValues ContentResolver가 처리 할수 있는  값 집합을 저장하는데 사용됩니다.

    ContentValues는 ContentResolver가 사용하는 데이터 운송 수단이라고 생각하시면 좋을 것 같습니다.

    ContentValues에 순서대로, 사진이름, 사진 타입(jpeg), 사진이 저장될 저장될 이미지의 상대적인 저장 경로(Relative Path)를 넣어준다.

     

     

    이 코드는 ImageCapture를 사용하여 이미지를 캡처할 때 저장 옵션을 구성하는 부분이다.

    val outputOptions = ImageCapture.OutputFileOptions
        .Builder(
            contentResolver,
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            contentValues
        )
        .build()

     

     

    1. getContentResolver(): 컨텐트 리졸버(Content Resolver) 객체를 제공합니다. 컨텐트 리졸버는 애플리케이션과 안드로이드 시스템 간의 데이터 공유와 통신을 담당합니다.

     

    2. MediaStore.Images.Media.EXTERNAL_CONTENT_URI: 이미지 파일을 저장할 위치를 나타내는 URI입니다. EXTERNAL_CONTENT_URI는 외부 저장소에 이미지 파일을 저장하는 데 사용됩니다.

     

    3. contentValues: 이미지 파일의 메타데이터와 저장 위치를 설정하는 ContentValues 객체입니다. 윗부분에서 지정한 파일 이름, MIME 타입, 저장 경로 등을 설정합니다.

     

     

    마지막으로 사진찍는 일을 수행하는 takePicture 부분이다.

            imageCapture.takePicture(
                outputOptions,
                ContextCompat.getMainExecutor(this),
                object : ImageCapture.OnImageSavedCallback {
                    override fun onError(exc: ImageCaptureException) {
                        Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
                    }
    
                    override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                        val msg = "Photo capture succeeded: ${output.savedUri}"
                        Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
                        Log.d(TAG, msg)
                        Log.d(TAG, "ImageCapture url : " + output.savedUri)
    
                    }
                }
            )

     

    앞서 지정했던 옵션들을 설정해주고 CallBack method를 활용해 이미지 저장이 잘 되었는지 알아볼수 있다. 

     

    여기서 불편한 점은 이미지를 바로 저장한다는 점이다. 이부분도 바로 저장되지 않고 원하는 방법으로 가공하고 저장을 하던 저장을 안하던 하는 방법이 있다. 그것은 다음편에서 알아보기로 한다. 

     

    이미지를 저장하고 로그를 찍어보니 이런식으로 나온다. content://media/external/images/media/168

    그리고 preview를 켜놓고 있는 상태여서 아래와 같이 luminosity를 ImageAnalysis가 측정중이다.

    2023-11-10 17:19:12.970 23264-24301 CameraXApp              com.example.camerax_1                D  Average luminosity: 134.9921484375
    2023-11-10 17:19:13.008 23264-24301 CameraXApp              com.example.camerax_1                D  Average luminosity: 135.17710611979166
    2023-11-10 17:19:13.037 23264-24301 CameraXApp              com.example.camerax_1                D  Average luminosity: 134.973271484375
    2023-11-10 17:19:13.086 23264-24301 CameraXApp              com.example.camerax_1                D  Average luminosity: 135.16990885416666
    2023-11-10 17:19:13.134 23264-24301 CameraXApp              com.example.camerax_1                D  Average luminosity: 135.173642578125
    2023-11-10 17:19:13.168 23264-24301 CameraXApp              com.example.camerax_1                D  Average luminosity: 135.164716796875

     

    Github 저장소 :

     

    이번 내용은 codelab 부분을 참고하였습니다.

Designed by Tistory.