Camera2를 이용해서 수동 초점 촬영 방식
하단의 그림처럼 prview를 볼 수 있는 preview화면과 하단의 3개의 수동 초점 촬영 버튼을 통해 촬영할 수 있는 안드로이드 앱을 제작할 것이다.
- 3f : 초점거리를 3f로 하고 촬영
- 5f : 초점거리를 5f로 하고 촬영
- 8f : 초점거리를 8f로 하고 촬영
모든 촬영이 끝나면 다시 자동 초점 모드로 변환된다.

버튼 클릭 후, 수동 초점 촬영 함수 호출
수동 촬영을 하는 함수는 setFocusDistance(value: ArrayDeque<Float>) 이다.
수동 초점 촬영을 하고 싶은 초점거리 값을 ArrayDeque에 담아서 인자로 전달하면 ArrayDeque에 담긴 모든 초점 거리 값에 맞춰진 각각의 사진을 촬영해준다.
(즉, ArrayDeque.size 만큼의 사진이 제작)
그래서 다음 코드처럼 해당 버튼에서 원하는 초점 거리를 ArrayDeque에 삽입하고, setFocusDistance에 인자로 담아 호출하면 된다.
binding.focus3.setOnClickListener {
val queue = ArrayDeque<Float>()
queue.add(3f)
setFocusDistance(queue)
}
binding.focus5.setOnClickListener {
val queue = ArrayDeque<Float>()
queue.add(5f)
setFocusDistance(queue)
}
binding.focus8.setOnClickListener {
val queue = ArrayDeque<Float>()
queue.add(8f)
setFocusDistance(queue)
}
setFocusDistance(value: ArrayDeque<Float>) 함수 코드 설명
setFocusDistance( .. ) 함수는 다음 흐름대로 실행된다.
- 수동 초점 모드로 카메라 설정
- CameraCaptureSession.CaptureCallback() 제작
- 수동초점 모드 설정 값 실행 및 프리뷰 출력
수동 초점 모드로 카메라 설정
카메라를 수동 초점 모드로 변환하고자 한다면 다음 설정을 해주면 된다.
프리뷰화면 출력을 도와주는 CaptureRequest.Builder(previewRequestBuilder) 하단의 표와 코드처럼 설정해줘야 한다.
변경해야 하는 설정 변수 | 이전 설정 값 | 변경 설정 값 |
CaptureRequest .CONTROL_AF_MODE | CaptureRequest .CONTROL_AF_MODE_CONTINUOUS_PICTURE | CaptureRequest .CONTROL_AF_MODE_OFF |
CaptureRequest .LENS_FOCUS_DISTANCE | x | 원하는 초점 거리 값 (ex. 3f) |
이 변수(previewRequestBuilder)는 전역 변수이므로 다시 이전 설정 값으로 변경해줘야 한다
이는setAutoFocus( )에서 실행해 준다.
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF)
previewRequestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, focusDistanceValue)
이제 사용하는 카메라는 자동 초점 모드를 끄고 수동 초점 모드로 설정되었고 (CONTROL_AF_MODE_OFF),
카메라 렌즈의 초점거리 설정은 원하는 초점 거리 값 (focusDistanceValue)로 설정되었다.
CameraCaptureSession.CaptureCallback() 제작
이후에 사용될 콜백 메소드 captureCallback 제작한다.
val captureCallback = object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult,
) {
// 렌즈 현재 상태 알아낼 수 있음
val lensState = result.get(CaptureResult.LENS_STATE)
if (lensState != null) {
// 렌즈가 정지된 상태입니다. 초점이 안정되어 있을 가능성이 높습니다.
if (lensState == CaptureResult.LENS_STATE_STATIONARY) {
var distanceValue = result.get(CaptureResult.LENS_FOCUS_DISTANCE)
// 내가 지정한 바운더리 안에 있는지 확인
if (distanceValue != null && distanceValue > value.peek() - 0.1f
&& distanceValue < value.peek() + 0.1f
) {
Log.d(
"렌즈 초점 거리",
"렌즈 초점거리 ${result.get(CaptureResult.LENS_FOCUS_DISTANCE)}"
)
captureStillPicture(value)
}
// 렌즈가 이동 중입니다. 초점이 아직 맞춰지지 않았을 가능성이 있습니다.
} else if (lensState == CaptureResult.LENS_STATE_MOVING) {
}
}
}
}
작업이 완료되고 부르는 onCaptureCompleted( ... ) 함수에서 초점 설정이 완료되었는지,설정된 초점 값과 동일한지 판단한 후 실제 캡처를 도와주는 CaptureStillPicture 함수에 (자신이 인자로 받은)ArrayDeque를 인자로 전달하며 호출한다.
수동초점 모드 설정 값 실행 및 프리뷰 출력
previewRequestBuilder.set( ... ) 을 통해 설정을 했다면 이제 Build를 시켜 Request(previewRequest)를 설정하고, 설정된 요청값을 프리뷰에 계속 띄우기 위해 setRepeatingRequest( ... ) 를 실행시킨다.
previewRequest = previewRequestBuilder.build()
captureSession?.setRepeatingRequest(
previewRequest, captureCallback, backgroundHandler
)
setRepeatingRequest( ... ) 작업이 완료되면, setRepeatingRequest를 호출할 때 인자로 준 captureCallback을 호출한다.
CaptureStillPicture(value: ArrayDeque<Float>?) 함수 코드 설명
CaptureStillPicture( ... ) 함수는 다음 흐름대로 실행된다.
- captureBuilder 설정 및 타겟 지정 ( + 회전 설정 )
- CaptureRequest 카메라 설정
- CameraCaptureSession.CaptureCallback() 제작
- 촬영Queue에 남아있는 Capture와 촬영대기Queue에 남아있는 Capture를 정리
- value 값에 따른 MutableList<CaptureRequest>을 제작
- CaptureBurst 실행
해당 코드에 자세한 설명을 알고 싶으면 다음 포스트 참고하길 바라며, 해당 포스트에서는 수동 초점 설정 부분에 대해서만 설명하겠다.
[Camera2] Camera2로 사진 촬영(Capture)
Camera2 프리뷰띄우기 및 캡처 샘플 코드 - GitHub GitHub - googlearchive/android-Camera2Basic: Migrated: Migrated:. Contribute to googlearchive/android-Camera2Basic development by creating an account on GitHub. github.com Camera2 흐름도 - 사
kyumq.tistory.com
CaptureRequest 카메라 설정
value가 null일 경우는 자동초점 사진 촬영을 의미하는 것이며, 이게 아닐 경우에는 value에 초점거리 값들을 담아서 전달받는다.
그래서 해당 값을 추출하여 자동초점 촬영모드를 끄고 Distance값으로 초점거리를 설정한다.
변경해야 하는 설정 변수 | value == null (자동 초점 촬영) 설정 값 | value != null (수동 초점 촬영) 설정 값 |
CaptureRequest .CONTROL_AF_MODE | CaptureRequest .CONTROL_AF_MODE_CONTINUOUS_PICTURE | CaptureRequest .CONTROL_AF_MODE_OFF |
CaptureRequest .LENS_FOCUS_DISTANCE | x | 원하는 초점 거리 값 (ex. 3f) |
이 변수(captureBuilder)는 지역 변수이므로 다시 이전 설정 값으로 변경해 줄 필요가 없다.
if(value != null) {
var focusDistanceValue = value.peek()
if (focusDistanceValue > minimumFocusDistance) {
focusDistanceValue = minimumFocusDistance
}
set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF)
set(CaptureRequest.LENS_FOCUS_DISTANCE, focusDistanceValue)
}
이제 사용하는 카메라는 자동 초점 모드를 끄고 수동 초점 모드로 설정되었고 (CONTROL_AF_MODE_OFF),
카메라 렌즈의 초점거리 설정은 원하는 초점 거리 값 (focusDistanceValue)로 설정되었다.
CameraCaptureSession.CaptureCallback() 제작
- value가 존재하는지 확인한다.
- value가 존재할 경우 : 수동 초점 촬영
→ 해당 촬영한 초점거리를 제외 후, 남은 초점거리 존재하는지 확인- 남은 초점거리 존재 O : 다시 촬영 ( setFocusDistance(value) )
- 남은 초점거리 존재 X : 자동 초점 모드로 변환 (setAutoFocus( ) )
- value가 존재하지 않은 경우 : 자동 초점 촬영
→ 자동 초점 트리거 풀기 ( unlockFocus( ) )
- value가 존재할 경우 : 수동 초점 촬영
val captureCallback = object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult,
) {
Toast.makeText( requireContext(),
"렌즈 초점 결과: " + result.get(CaptureResult.LENS_FOCUS_DISTANCE).toString(),
Toast.LENGTH_SHORT
).show()
// 수동 초점 모드 촬영일 경우 : 자동 초점 모드로 다시 변경
if (value != null) {
setAutoFocus()
}
// 자동 초점 모드 촬영일 경우 : 트리거 잠금 해제
else {
unlockFocus()
}
}
}
value 값에 따른 MutableList<CaptureRequest>을 제작
여러장 촬영일 경우, 시간을 빠르게 하기 위해 captureBurst 함수를 사용하고자 했으므로 MutableList<CaptureRequest>로 제작.
- value == null (자동 초점)
: 전역변수 PICTURE_SIZE만큼 captureRequest.build()해서 추가 - value != null (수동 초점)
: value.size만큼 서 value에 들어있는 값으로 Distance설정하고 captureRequest.build()해서 추가
val captureRequestList = mutableListOf<CaptureRequest>()
if(value == null) {
for (i in 0 until PICTURE_SIZE) {
captureBuilder?.let { captureRequestList.add(it.build()) }
}
}
else {
while (value.isNotEmpty()) {
captureBuilder?.set(CaptureRequest.LENS_FOCUS_DISTANCE, value.poll())
captureBuilder?.let { captureRequestList.add(it.build()) }
}
}
setAutoFocus( )
이는 다시 자동 초점 모드로 프리뷰를 변환시키는 함수이다.
private fun setAutoFocus() {
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
previewRequest = previewRequestBuilder.build()
captureSession?.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler)
}
수동 초점 모드 연속 촬영
이 코드를 통해 수동촬영을 여러 번 연속 촬영할 수 도 있다.
처음 버튼을 누르고 Queue를 만들어서 함수를 호출할 때, Queue안에 원하는 초점거리를 여러 개 넣으면 간단하게 해결된다.
binding.burstFocus.setOnClickListener {
val queue = ArrayDeque<Float>()
queue.add(3f)
queue.add(5f)
queue.add(8f)
setFocusDistance(queue)
}
이렇게 하면 3f, 5f, 8f에 초점이 잡힌 3개의 사진을 한 번의 클릭으로 제작할 수 있다.
binding.bustPictureBtn.setOnClickListener {
val distanceUnit = minimumFocusDistance / 10f
val queue = ArrayDeque<Float>()
for(i in 1 .. 10) {
queue.add(distanceUnit*i);
}
setFocusDistance(queue)
}
또한 이렇게 하면 0부터 초점거리 최댓값까지 10개의 구간을 나누어 각 구간마다 맞춰진 10개의 초점 사진을 한 번의 클릭으로 제작할 수 있다.
binding.burstPicture.setOnClickListener {
PICTURE_SIZE = 3
lockFocus()
}
간단하게 연속 사진 촬영을 하고 싶으면, 전역변수 값을 바꾼 뒤 lockFocus()를 실행하면 3장의 사진을 한 번의 클릭으로 제작할 수 있다.
전체 코드
/**
* 실제 사진 촬영을 하는 함수 (이미지 캡처)
*/
private fun captureStillPicture(value: ArrayDeque<Float>?) {
Log.d("렌즈 초점 결과", "captureStillPicture")
try {
if (activity == null || cameraDevice == null) return
val rotation = activity?.windowManager?.defaultDisplay?.rotation
// This is the CaptureRequest.Builder that we use to take a picture.
val captureBuilder = cameraDevice?.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE
)?.apply {
addTarget(imageReader?.surface!!)
set(
CaptureRequest.JPEG_ORIENTATION,
(ORIENTATIONS.get(rotation!!) + sensorOrientation + 270) % 360
)
// Use the same AE and AF modes as the preview.
// 수동 초점 사진
if(value != null) {
set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF)
}
// 자동 초점 사진
else {
set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
}
}
val captureCallback = object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult,
) {
val distanceResult = result.get(CaptureResult.LENS_FOCUS_DISTANCE)
Toast.makeText(requireContext(), "렌즈 초점 결과: " +distanceResult.toString(), Toast.LENGTH_SHORT).show()
Log.d("렌즈 초점 결과", distanceResult.toString())
if(value != null && distanceResult == 10f) {
setAutoFocus()
mediaPlayer.start()
}
else {
unlockFocus()
mediaPlayer.start()
}
}
}
captureSession?.apply {
stopRepeating()
abortCaptures()
val captureRequestList = mutableListOf<CaptureRequest>()
if(value == null) {
for (i in 0 until PICTURE_SIZE) {
captureBuilder?.let { captureRequestList.add(it.build()) }
}
}
else {
while (value.isNotEmpty()) {
captureBuilder?.set(CaptureRequest.LENS_FOCUS_DISTANCE, value.poll())
captureBuilder?.let { captureRequestList.add(it.build()) }
}
}
// Capture the burst of images
captureBurst(captureRequestList, captureCallback, null)
}
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
}
}
/**
* 프리뷰 초점 수동으로 변경하고 초점 설정해서 촬영
*/
private fun setAutoFocus() {
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
previewRequest = previewRequestBuilder.build()
state = STATE_PREVIEW
captureSession?.setRepeatingRequest(
previewRequest, captureCallback, backgroundHandler
)
}
/**
* 프리뷰 초점 수동으로 변경하고 초점 설정해서 촬영
*/
private fun setFocusDistance(value: ArrayDeque<Float>) {
state = STATE_PICTURE_TAKEN
var focusDistanceValue = value.peek()
previewRequestBuilder.set(
CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF
)
previewRequestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, focusDistanceValue)
val captureCallback = object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(
session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult,
) {
// 렌즈 현재 상태 알아낼 수 있음
val lensState = result.get(CaptureResult.LENS_STATE)
if (lensState != null) {
// 렌즈가 정지된 상태입니다. 초점이 안정되어 있을 가능성이 높습니다.
if (lensState == CaptureResult.LENS_STATE_STATIONARY) {
var distanceValue = result.get(CaptureResult.LENS_FOCUS_DISTANCE)
// 내가 지정한 바운더리 안에 있는지 확인
if (distanceValue != null && distanceValue > value.peek() - 0.1f
&& distanceValue < value.peek() + 0.1f
) {
Log.d(
"렌즈 초점 거리",
"렌즈 초점거리 ${result.get(CaptureResult.LENS_FOCUS_DISTANCE)}"
)
captureStillPicture(value)
}
// 렌즈가 이동 중입니다. 초점이 아직 맞춰지지 않았을 가능성이 있습니다.
} else if (lensState == CaptureResult.LENS_STATE_MOVING) {
}
}
}
}
previewRequest = previewRequestBuilder.build()
captureSession?.setRepeatingRequest(
previewRequest,
captureCallback, backgroundHandler
)
}
'OnePIC (Android App) > 카메라' 카테고리의 다른 글
[Camera2] Tensorflow Lite를 이용한 객체 분석 프리뷰 및 객체별 촬영 (1) | 2023.08.09 |
---|---|
[Camera2] Camera2 Preview 터치된 곳으로 초점 변경 (0) | 2023.08.07 |
[Camera2] 카메라 장치에 관한 설정 변수 정리 (0) | 2023.08.01 |
[Camera2] Camera2로 사진 촬영(Capture) (0) | 2023.07.31 |
[Camera2] Camera2로 Preview 띄우기 (0) | 2023.07.30 |