Petnow LogoPetnow
Android SDKUI 모듈

PetnowCameraHelper 사용법

PetnowCameraHelper를 사용하여 완전 커스텀 카메라 UI를 구현하는 방법.


이 가이드를 시작하기 전에 UI 모듈 개요를 읽어보세요.

개요

PetnowCameraHelperPetnowCameraFragmentUI 독립적인 대안입니다. Fragment 상속 없이 카메라 탐지 로직만 제공하여, 개발자가 완전히 자유로운 UI를 구현할 수 있습니다.

PetnowCameraFragment vs PetnowCameraHelper

기능PetnowCameraFragmentPetnowCameraHelper
카메라 프리뷰내장직접 구현
탐지 오버레이내장 (Lottie)직접 구현
카메라 권한자동 요청직접 처리
라이프사이클 관리자동직접 관리
Compose 호환제한적완벽 호환
React Native 호환불가가능
UI 자유도오버레이 레이아웃100% 커스텀

적합한 사용 사례

  • Jetpack Compose 기반 앱
  • React Native 또는 Flutter 브릿지
  • Fragment 상속이 어려운 구조
  • 완전히 커스텀한 카메라 UI가 필요한 경우

아키텍처

PetnowCameraHelper는 카메라 탐지의 전체 라이프사이클을 관리하되, UI 렌더링에는 관여하지 않습니다. 상태 변화는 StateFlow로, 이벤트는 PetnowCameraDetectionListener 콜백으로 전달됩니다.


기본 통합

Step 1: Helper 생성

import io.petnow.ui.PetnowCameraHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers

class CameraScreen(private val context: Context) {
    private val scope = CoroutineScope(Dispatchers.Main)
    private val cameraHelper = PetnowCameraHelper(context, scope)
}

Step 2: 초기화

// PetnowUiClient.initialize()가 선행되어야 합니다
suspend fun setup() {
    cameraHelper.initialize()
    cameraHelper.initializeDetector()
    cameraHelper.initializeSpecies(isDog = true)  // true: 강아지, false: 고양이
}

Step 3: 리스너 등록

import io.petnow.callback.PetnowCameraDetectionListener
import io.petnow.ui.DetectionCaptureResult
import io.petnow.ui.status.PetnowDetectionStatus

cameraHelper.setDetectionListener(object : PetnowCameraDetectionListener {
    override fun onDetectionStatus(primaryDetectionStatus: PetnowDetectionStatus) {
        // 탐지 상태 업데이트 (3초 디바운스 적용됨)
        when (primaryDetectionStatus) {
            PetnowDetectionStatus.Detected -> showMessage("탐지 중...")
            PetnowDetectionStatus.NoObject -> showMessage("반려동물을 화면에 비춰주세요")
            PetnowDetectionStatus.TooFarAway -> showMessage("좀 더 가까이 와주세요")
            PetnowDetectionStatus.TooClose -> showMessage("좀 더 멀리 떨어져주세요")
            else -> showMessage(primaryDetectionStatus.name)
        }
    }

    override fun onDetectionProgress(progress: Int) {
        // 진행률 업데이트 (0-100)
        updateProgressBar(progress)
    }

    override fun onDetectionFinished(result: DetectionCaptureResult) {
        when (result) {
            is DetectionCaptureResult.Success -> {
                // result.noseImageFiles: 코무늬 이미지 파일
                // result.faceImageFiles: 얼굴 이미지 파일
                handleSuccess(result)
            }
            is DetectionCaptureResult.Fail -> {
                handleFailure()
            }
        }
    }
})

Step 4: 카메라 열기 및 탐지 시작

import java.util.UUID

suspend fun startCapture(surface: Surface, captureSessionId: UUID) {
    // Configuration 설정 (선택)
    val config = DetectionConfiguration(
        species = PetSpecies.DOG,
        purpose = DetectionPurpose.PET_PROFILE_REGISTRATION,
        enableFakeDetection = false
    )
    cameraHelper.setDetectionConfiguration(config)

    // 카메라 열기
    cameraHelper.openCamera(surface, captureSessionId)

    // 탐지 세션 시작
    cameraHelper.startDetectionSession()
}

Step 5: 상태 관찰 (StateFlow)

콜백 방식 외에도, StateFlow로 상태를 관찰할 수 있습니다. Compose에서 특히 유용합니다.

// StateFlow 관찰
scope.launch {
    cameraHelper.state.collect { state ->
        // state.progressPercent: 진행률 (0-100)
        // state.detectionStatusList: 현재 프레임의 탐지 상태 목록
        // state.isDetectionFinished: 탐지 완료 여부
        // state.isCameraOpened: 카메라 열림 상태
        // state.isDetectionRunning: 탐지 진행 중 여부
        // state.isTemporaryPause: 임시 중지 상태
        // state.currentCameraId: 현재 카메라 ID (전면/후면)
    }
}

Step 6: 리소스 해제

fun cleanup() {
    cameraHelper.closeCamera()
    cameraHelper.release()
}

탐지 세션 제어

자동 캡처 (기본)

cameraHelper.startDetectionSession()

재촬영

cameraHelper.startDetectionSession()

Detection 일시정지 / 재개

탐지를 일시적으로 중단하고 재개할 수 있습니다. 오버레이 UI(예: 팁 시트)를 표시하는 동안 유용합니다.

// 탐지 일시정지 (진행률 유지)
cameraHelper.pauseDetection()

// 탐지 재개 (일시정지 시점부터 계속)
cameraHelper.resumeDetection()

pauseDetection()은 진행 상태를 유지하면서 일시정지합니다. resumeDetection()은 일시정지 시점부터 이어서 재개합니다. 재촬영(진행률 리셋)은 startDetectionSession()을 사용하세요.


임시 정지 (Temporary Pause)

갤러리 피커 같은 외부 UI를 사용할 때, 카메라를 닫지 않아야 하는 경우가 있습니다.

// 갤러리 열기 전
cameraHelper.setTemporaryPause(true)

// 갤러리에서 돌아온 후
cameraHelper.setTemporaryPause(false)

라이프사이클 관리 코드에서 이 상태를 확인합니다:

override fun onPause() {
    super.onPause()
    // 임시 정지 상태가 아닐 때만 카메라를 닫음
    if (!cameraHelper.state.value.isTemporaryPause) {
        cameraHelper.closeCamera()
    }
}

카메라 전환

val result = cameraHelper.switchCamera()
result.onSuccess {
    // 전면 ↔ 후면 전환 성공
}
result.onFailure { e ->
    // 전면 카메라가 없는 기기 등
}

브래킷팅 모드

노출 보정을 자동 조정하는 브래킷팅 모드를 설정합니다.

// 탐지 세션 시작 전에 설정해야 합니다
cameraHelper.setBracketingMode(enabled = true)

// 현재 상태 확인
val isEnabled = cameraHelper.isBracketingModeEnabled()

브래킷팅 모드는 반드시 startDetectionSession() 호출 전에 설정해야 합니다. 세션 시작 후 변경하면 다음 세션부터 적용됩니다.


사운드 재생

import io.petnow.ui.sound.SoundType

// 사운드 재생
val streamId = cameraHelper.playSound(SoundType.CAPTURE)

// 사운드 중지
cameraHelper.stopSound(streamId)

Jetpack Compose 예제

@Composable
fun PetnowCameraScreen(captureSessionId: UUID) {
    val context = LocalContext.current
    val scope = rememberCoroutineScope()
    
    val cameraHelper = remember {
        PetnowCameraHelper(context, scope)
    }
    
    val cameraState by cameraHelper.state.collectAsState()

    DisposableEffect(Unit) {
        scope.launch {
            cameraHelper.initialize()
            cameraHelper.initializeDetector()
            cameraHelper.initializeSpecies(isDog = true)
        }
        
        onDispose {
            cameraHelper.closeCamera()
            cameraHelper.release()
        }
    }

    // 리스너 설정
    LaunchedEffect(Unit) {
        cameraHelper.setDetectionListener(object : PetnowCameraDetectionListener {
            override fun onDetectionStatus(status: PetnowDetectionStatus) { /* ... */ }
            override fun onDetectionProgress(progress: Int) { /* ... */ }
            override fun onDetectionFinished(result: DetectionCaptureResult) { /* ... */ }
        })
    }

    Column(modifier = Modifier.fillMaxSize()) {
        // Camera preview (AndroidView로 SurfaceView 감싸기)
        AndroidView(
            factory = { ctx ->
                SurfaceView(ctx).apply {
                    holder.addCallback(object : SurfaceHolder.Callback {
                        override fun surfaceCreated(holder: SurfaceHolder) {
                            scope.launch {
                                cameraHelper.openCamera(holder.surface, captureSessionId)
                                cameraHelper.startDetectionSession()
                            }
                        }
                        override fun surfaceChanged(h: SurfaceHolder, f: Int, w: Int, ht: Int) {}
                        override fun surfaceDestroyed(holder: SurfaceHolder) {
                            cameraHelper.closeCamera()
                        }
                    })
                }
            },
            modifier = Modifier.weight(1f)
        )

        // 진행률 바
        LinearProgressIndicator(
            progress = { cameraState.progressPercent / 100f },
            modifier = Modifier.fillMaxWidth()
        )

        // 탐지 상태 텍스트
        Text(
            text = cameraState.detectionStatusList.firstOrNull()?.name ?: "",
            modifier = Modifier.padding(16.dp),
            textAlign = TextAlign.Center
        )
    }
}

False-Negative 수집

False-negative 이미지를 수집하여 탐지 품질을 개선할 수 있습니다.

import io.petnow.callback.PetnowCameraFalseNegativeListener
import io.petnow.ui.model.FalseNegativeUiInfo

// False-negative 수집 활성화
cameraHelper.setFalseNegativeCollect(enable = true, intervalMillis = 1000L)

// 리스너 설정
cameraHelper.setFalseNegativeListener(object : PetnowCameraFalseNegativeListener {
    override fun onDetectionFalseNegative(falseNegativeInfo: FalseNegativeUiInfo) {
        // False-negative 데이터 처리
    }
})

API 요약

메서드설명
initialize()모니터링 서비스 초기화
initializeDetector()ObjectDetector 초기화
initializeSpecies(isDog)강아지/고양이 모드 설정
openCamera(surface, captureSessionId)카메라 열기
closeCamera()카메라 닫기
startDetectionSession()자동 탐지 세션 시작
resumeDetection()일시정지된 탐지 재개
pauseDetection()탐지 일시정지 (진행률 유지)
setTemporaryPause(temporary)임시 정지 상태 설정
switchCamera()전면/후면 카메라 전환
setBracketingMode(enabled)브래킷팅 모드 설정
setDetectionConfiguration(config)탐지 설정 적용
setDetectionListener(listener)탐지 리스너 등록
setFalseNegativeCollect(enable, intervalMillis)False-negative 수집
playSound(soundType) / stopSound(id)사운드 재생/중지
release()리소스 해제

다음 단계

On this page