본문 바로가기

OnePIC (Android App)/카메라

[Camera2] Camera2 Preview 터치된 곳으로 초점 변경

반응형
개발하고자 하는 기능

 
Preview에서 초점을 원하는 부분을 터치하면 해당 부분으로 초점이 변경되는 기능 구현하고자 한다.
 


필요한 정보

 

  • 이미지 센서 정보
    • width
    • height
  • preview 정보
    • width
    • height
  • preview에서 실제 터치된 부분 정보
  • 이미지 센서에서의 터치된 부분 정보 : 위 정보들을 통해 구해야 함

 
preview에 보이는 화면과 실제 이미지 센서에 잡혀진 화면은 회전정보로 인해 좀 다르다.
그러므로 실제 초점을 변경하기 위해서는 preview에 터치된 부분 정보를 통해 이미지 센서에서의 터치된 부분 정보를 알아내야한다.
 


이미지 센서와 Preview의 차이

 

위 그림과 같이 Preview에서 보이는 것과 ImageSensor가 인지한 회전 정보가 다르다
 
대부분의 휴대폰 및 태블릿에서 장치는 전면 카메라의 경우 270도, 후면 카메라의 경우 90도(장치 후면에서 본 시점)의 센서 방향을 보고한다. 즉 이에 맞춰서 회전 시키면 Preview와 동일한 화면을 보여주는 것이다.
+ 전면 카메라의 경우 회전은 시계 반대 방향, 후면 카메라의 경우 시계 방향으로 회전해야한다.
 

카메라 이미지 센서가 "카메라의 긴 치수가 화면의 긴 치수와 정렬되도록 방향을 맞춰야 합니다"라고 지정한다. 즉, ImageSensor를 Preview에 맞춰서 회전 시킨 값과 비교해야 한다.

 
더 자세한 정보는 다음 Developers를 확인하길 바란다.
 

Camera preview  |  Android Developers

Camera preview Stay organized with collections Save and categorize content based on your preferences. Note: This page refers to the Camera2 package. Unless your app requires specific, low-level features from Camera2, we recommend using CameraX. Both Camera

developer.android.com


필요한 정보 알아내는 코드

이미지 센서 정보 

 

// CameraManager 정보 얻어내기
val manager = activity?.getSystemService(Context.CAMERA_SERVICE) as CameraManager

// 현재 사용중인 CameraId를 통해 현재 카메라 정보 알아내기
val characteristics = manager.getCameraCharacteristics(cameraId)

// 현재 카메라의 이미지센서(카메라 센서)의 활성 배열 사이즈를 알아낸다
val sensorArraySize: Rect? =
	characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)

 
CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE
: 카메라 센서에서 사진을 찍을 때 실제로 빛을 받는 영역(카메라의 렌즈나 다른 요소로 인해 생기는 왜곡을 보정한 후의 영역)을 의미한다.

이미지의 전체 픽셀 배열을 기준으로 정의되며, 가장 왼쪽 위의 픽셀이 (0,0)이다.
전체 픽셀 배열의 크기는 android.sensor.info.pixelArraySize로 주어집니다.

 
쉽게 말해, 다음 그림과 같이 카메라의 이미지 센서 크기를 왼쪽 상단(0,0) 기준으로 알 수 있다. 

 

Preview 정보 | preview에서 실제 터치된 부분 정보

 
이는 TouchEventListener를 통해 알 수 있다.
 

  • MotionEvent : preview에서 실제 터치된 부분 정보
  • View : Preview 정보

 


이미지 센서에서의 터치된 부분 계산

 
위에 말했듯이 이미지 센서와 preview는 다음과 같이 회전 정보가 다르다. 즉 이를 고려해서 이미지센서에서 터치된 좌표와 동일한 부분을 알아내야한다.
 
다음과 같은 과정을 거쳐 이미지 센서의 터치된 부분을 알아냈다.
 

 
1. 이미지 센서 이미지를 Preview와 동일하게 보이도록 회전시킨다. ( 시계방향 90도 )

  • 이미지 센서와 Preview를 동일하게 회전시키면 x,y 방향이 반대가 됨을 알게 되었다.

 
2. Preview에서 터치 좌표를 그대로 ImageSensor에 x,y 만 반대로 해서 적용해본다.
 ex) Preview 터치 좌표 (100,50) → ImageSensor 터치 좌표 (50,100)

  • 이미지 센서에서 x좌표는 잘 설정되었지만, y좌표 설정은 달랐다.
    : Preview에서는 오른쪽에서 100이지만, ImageSensor에서는 왼쪽에서 100이라 다른 곳을 가르키게 된다.

 
3. ImageSensor에서 y좌표 설정을 다음과 같이 설정하였다. ( ImageSensor.height - Preview.x )

  • 왼쪽에서 100이 아니라, 오른쪽에서 100 이동한 위치여야 하므로, 전체 height 크기에서 100을 뺀 곳을 가르키게하니 Preview와 동일한 위치를 가르킨다.

 
 
추가적으로 Preview와 ImageSensor의 크기에서도 차이점이 발생하여, 이를 재정비 하기 위해 둘의 비율비을 구한 후, 곱해주었다.
 

    // halfTouchWidth,halfTouchHeight : 초점을 주고 싶은 반경 설정

	val newX = max((y * (sensorArraySize!!.width() / textureView.height) - halfTouchWidth).toInt(), 0)
        val newY = max(((textureView.width - x) * (sensorArraySize.height() / textureView.width) - halfTouchHeight).toInt(),0)
           
        val focusAreaTouch = MeteringRectangle(
            newX, newY,
            halfTouchWidth * 2,
            halfTouchHeight * 2,
            MeteringRectangle.METERING_WEIGHT_MAX - 1
        )

 

halfTouchWidth와 halfTouchHeight는 초점을 주고 싶은 영역의 절반 크기이다. 이 크기의 2배가 초점이 잡힐 영역이다.

해당 위치로 초점 변경 코드

 
이제 필요한 정보를 다 구했으니, 해당 위치로 초점을 변경해보겠다. 초점 변경은 다음 과정을 따른다.
 
1. 모든 캡처 정보를 삭제한다.

captureSession?.stopRepeating()

 
2. 초점 변경을 하기 위해 AF 모드를 끈다.

previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL)
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF)
captureSession?.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler)

 
3. 위에 설정한 위치로 초점 및 노출 정도를 변경한다.

// 터치 위치로 초점 변경
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, arrayOf(focusAreaTouch))
// 터치 위치에 맞춰서 노출 정도 변경
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, arrayOf(focusAreaTouch))

 
 
4. AF 모드를 다시 설정한다.

previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO)
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CameraMetadata.CONTROL_AF_TRIGGER_START)
captureSession?.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);

 


전체 코드
    /**
     * 해당 위치에 초점을 맞춰주는 함수
     */
    fun setTouchPointDistanceChange(x: Float, y: Float, halfTouchWidth: Int, halfTouchHeight: Int) {
        Log.d("detectionResult", "3. setTouchPointDistanceChange")

        val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        // 카메라 정보 알아내기
        val characteristics = manager.getCameraCharacteristics(cameraId)

        val sensorArraySize: Rect? =
            characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)

        val newX = max((y * (sensorArraySize!!.width() / textureView.height) - halfTouchWidth).toInt(), 0)
        val newY = max(((textureView.width - x) * (sensorArraySize.height() / textureView.width) - halfTouchHeight).toInt(),0)
           
        val focusAreaTouch = MeteringRectangle(
            newX, newY,
            halfTouchWidth * 2,
            halfTouchHeight * 2,
            MeteringRectangle.METERING_WEIGHT_MAX - 1
        )

        val captureCallback = object : CameraCaptureSession.CaptureCallback() {
            override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest,result: TotalCaptureResult) {
                // 실제 초점 설정을 하고나서 설정
                if (request.tag == "FOCUS_TAG") {
                    previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, null)
                    captureSession?.setRepeatingRequest(previewRequestBuilder.build(), null, null)
                }
            }
        }

        // 모든 캡처 정보를 삭제
//        captureSession?.stopRepeating()

        // 초점 변경을 위한 AF 모드 off
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL)
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF)
        captureSession?.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler)

        // 터치 위치로 초점 변경
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, arrayOf(focusAreaTouch))
        
        // 터치 위치에 맞춰서 노출 정도 변경
        previewRequestBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, arrayOf(focusAreaTouch))

        // AF 모드 다시 설정 - 안하면 초점 변경 안됨
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO)
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START)
        previewRequestBuilder.setTag("FOCUS_TAG"); //we'll capture this later for resuming the preview

        //then we ask for a single request (not repeating!)
        captureSession?.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler)

    }

 

반응형