-
CameraX api 3편 : takePicture 메소드회사 생활/여권 NFC (CameraX + OCR + NFC) 2023. 11. 13. 14:30
1편에서는 CameraX api의 전반적인 설명과 2편에서는 codelab에서 학습할 수 있는 기본 코드를 살짝 학습해봤다.
이번 CameraX 3편에서는 takePhoto만 imageCapture 객체의 takePicture메소드만 다시 다뤄 보려고 한다.
2편 코드에서는 takePhoto메소드를 이용해 사진을 찍으면 자동적으로 저장이 되었다.
하지만 이미지를 찍으면 저장이 되는걸 원하지 않을 경우도 있을 것이다.
그렇다면 어떻게 해야 이미지를 바로 저장안하고 이미지 파일만 가져올 수 있을까?
>>> 방법은 다른 takePicture메소드를 사용하는 것이다.
takePicture 메소드를 눌러서 타고 들어가면 2개의 메소드가 나온다.
- 3개의 parameters를 갖는 takePicture
- 2개의 parameters를 갖는 takePicture
(a) 3개의 메소드를 parameter로 갖는 takePicture (이미지 바로 저장 O)
private fun takePhoto() { // Get a stable reference of the modifiable image capture use case val imageCapture = imageCapture ?: return // Create time stamped name and MediaStore entry. 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") } } // Create output options object which contains file + metadata val outputOptions = ImageCapture.OutputFileOptions .Builder( contentResolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues ) .build() // Set up image capture listener, which is triggered after photo has been taken 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) } } ) }이미지 Capture가 정상적으로 성공한다면 onImageSaved 메소드 안에 있는 부분이 수행되고
이미지 Capture가 정상적으로 실패한다면 onError 메소드 안에 있는 부분이 수행된다.
이미지가 저장하는건 알겠지만 조금더 무슨 내용인지 타고 들어가서 살펴보면 아래와 같다.
더보기/** * Captures a new still image and saves to a file along with application specified metadata. * * <p> The callback will be called only once for every invocation of this method. * * <p> If the {@link ImageCapture} is in a {@link UseCaseGroup} where {@link ViewPort} is * set, or {@link #setCropAspectRatio} is used, the image may be cropped before saving to * disk which causes an additional latency. * * Params: outputFileOptions – Options to store the newly captured image. * executor – The executor in which the callback methods will be run. * imageSavedCallback – Callback to be called for the newly captured image. */ public void takePicture( final @NonNull OutputFileOptions outputFileOptions, final @NonNull Executor executor, final @NonNull OnImageSavedCallback imageSavedCallback) { if (Looper.getMainLooper() != Looper.myLooper()) { CameraXExecutors.mainThreadExecutor().execute( () -> takePicture(outputFileOptions, executor, imageSavedCallback)); return; }Captures a new still image and saves to a file along with application specified metadata.
>>> 이미지를 캡처하고 앱에서 지정한 메타데이터와 함께 파일로 저장합니다.
The callback will be called only once for every invocation of this method>>> 이 메서드를 호출할 때마다 콜백이 한 번만 호출됩니다.
If the ImageCapture is in a UseCaseGroup where ViewPort is set, or setCropAspectRatio is used, the image may be cropped before saving to disk which causes an additional latency.
>>> 만약 ImageCapture가 ViewPort가 설정된 UseCaseGroup에 속하거나 setCropAspectRatio가 사용된 경우, 이미지는 디스크에 저장되기 전에 잘릴 수 있으며, 이로 인해 추가 지연이 발생할 수 있습니다."
추후 더 알아보겠지만, Preview, ImageAnalysis,ImageCapture, VideoCapture의 비율을 지정하면 원래 이미지에서 16:9, 4:3 같은 비율을 비율에 맞춰서 이미지가 잘린다는 얘기 같다.
비율을 지정하는 방법은
- (1) ViewPort라는 것을 사용해 UseCase를 상속한 4가지를 한번에 가능하고
- (2) setTargetAspectRatio를 사용해서 4가지 따로따로도 가능하다
뭐 이렇단다.........---------------------------------------------------------------------------------------------------------------
저장된다는건 알겠고, 저장이 성공하면 받는 parameter 부분을 보자.
override fun onImageSaved(output: ImageCapture.OutputFileResults)ImageCapture.OutputFileResult 는 Uri를 가지고 있어서 이것을 통해 이미지를 저장하고 저장한 주소를 불러올 수 있다.
Log.d(TAG, "ImageCapture url : " + output.savedUri)OutputFileResults 클래스
더보기public static class OutputFileResults { @Nullable private final Uri mSavedUri; /** * */ @RestrictTo(Scope.LIBRARY_GROUP) public OutputFileResults(@Nullable Uri savedUri) { mSavedUri = savedUri; } /** * Returns the {@link Uri} of the saved file. * * <p> Returns null if the {@link OutputFileOptions} is constructed with * {@link androidx.camera.core.ImageCapture.OutputFileOptions.Builder * #Builder(OutputStream)}. */ @Nullable public Uri getSavedUri() { return mSavedUri; } }하지만 상황에 따라 이미지를 바로 저장하는게 아니라 이미지를 확인하고 저장해야 할 상황도 있을 것이다.
그런 상황에서는 2개의 parameter를 가지고 있는 takePicture 사용해야 한다.
이렇게만 봐서는 잘 모르겠어서 takePicture가 어떻게 되어있는지 들어가 보면 아래와 같이 나온다.
---------------------------------------------------------------------------------------------------------------
(b) 2개의 메소드를 parameter로 갖는 takePicture 메소드다. (이미지 저장 X)
private fun takeAndProcessImage() { val imageCapture = imageCapture val newExecutor: Executor = Executors.newSingleThreadExecutor() // 이미지 캡처 콜백 등록 imageCapture.takePicture( newExecutor, object : OnImageCapturedCallback() { override fun onCaptureSuccess(image: ImageProxy) { // 이미지 데이터 추출 및 처리 processImage(image) // 이미지 닫기 image.close() } override fun onError(exception: ImageCaptureException) { // 캡처 에러 처리 } }) }이것도 코드를 타고 들어가면 아래와 같이 정의 되어있다.
더보기/** * Captures a new still image for in memory access. * * <p>The callback will be called only once for every invocation of this method. The listener * is responsible for calling {@link Image#close()} on the returned image. * * @param executor The executor in which the callback methods will be run. * @param callback Callback to be invoked for the newly captured image */ public void takePicture(@NonNull Executor executor, final @NonNull OnImageCapturedCallback callback) { if (Looper.getMainLooper() != Looper.myLooper()) { CameraXExecutors.mainThreadExecutor().execute(() -> takePicture(executor, callback)); return; } // The captured image will be directly provided to the app via the OnImageCapturedCallback // callback. It won't be uncompressed and compressed again after the image is captured. // The JPEG quality setting will be directly provided to the HAL to compress the output // JPEG image. sendImageCaptureRequest(executor, callback, getJpegQualityInternal()); }Captures a new still image for in memory access.
>>> 이 메서드는 메모리 내에서 새로운 정지 이미지를 캡처합니다.
The listener * is responsible for calling {@link Image#close()} on the returned image.
>>> 새로 캡처된 이미지에 대한 처리가 끝난 후에는 Image.close()를 호출하여 이미지를 닫아야 합니다.
이미지를 다 활용한다면 Image.close()로 종료해줘야 한다.
이미지 capture가 성공적으로 된다면 onCaptureSuccess는 ImageProxy를 돌려 받는다.
ImageProxy는 CameraX에서 이미지 데이터를 캡처하거나 스트리밍할 때 사용되는 객체입니다. 카메라가 이미지를 캡처하면 이 이미지 데이터는 ImageProxy 형식으로 전달됩니다. ImageProxy는 이미지 데이터와 해당 메타데이터(예: 이미지 해상도, 회전 등)를 포함합니다.
ImageProxy는 toBitmap이라는 메소드가 있어서 bitmap으로 바꿔서 활용하고 싶다면 아래와 같이 활용할 수 있다.
image.toBitmap()(a)와 (b) 방법의 차이점을 보자면 :
- OutputFileOptions이 없어짐
- Callback 함수가 달라짐 : OnImageSavedCallback -> OnImageCapturedCallback
OnImageSavedCallback가 가지고 있던 onImageSaved, onError 메소드 대신
OnImageCapturedCallback의 onCaptureSuccess, onError 메소드를 이용하게 된다.
마지막으로 이미지 활용하는 작업이 다 끝난다면 image.close()를 불러와야한다. 왜냐하면,
- 메모리 누수를 방지하기 위해서
- 카메라 자원은 한 번에 하나의 작업만 사용할 수 있으며, 다음 작업을 수행하기 전에 이전 작업을 정리해야 한다.
Github 저장소 :
실행화면 스크린샷 :
Preview 이미지 Result 이미지 

'회사 생활 > 여권 NFC (CameraX + OCR + NFC)' 카테고리의 다른 글
CameraX api 4-2편 : Analysis + OCR + TextGraphic (1) 2023.11.18 CameraX api 4편 : Anlysis와 text 분석 라이브러리(OCR) (0) 2023.11.14 CameraX api 2 : CameraX 기본 코드 분석 (0) 2023.11.10 CameraX api 1 : 기초 (0) 2023.11.05 Android Camera 사용하기 (0) 2023.07.29