본문 바로가기

OnePIC (Android App)/카메라

[Camera2] Tensorflow Lite를 이용한 객체 분석 프리뷰 및 객체별 촬영

반응형

Tensorflow Lite를 이용해 객체 분석 및 프리뷰 띄우기 및 객체 촬영 흐름도


TensorFlow Lite를 이용해 객체 분석

다음 코드들은 Camera2와 연동하기 전에 TensorFlow Lite를 Android에서 사용하기 위해 필요한 설정들과 이에 대한 코드이다.
 

build.gradle (:app) 설정
dependencies {
    // TFLite
    implementation 'org.tensorflow:tensorflow-lite-task-vision:0.3.1'
}

 
Tensorflow Lite 사용을 위한 종속성 추가
 

모델 ( .tflite ) 을 저장하는 assets 폴더 생성

 
/[안드로이드 프로젝트]/app/src/main 위치에 assets 폴더 생성
해당 assets 폴더에 사용하고자 하는 모델 ( .tflite ) 파일 추가
 
본인이 사용한 .tflite 파일

 

TensorFlow Hub

tfhub.dev

 

setDetecter( ) - 객체 분석 디렉터 설정

 
객체 분석을 도와줄 디렉터 설정하는 함수 
 
아래 코드는 정확도 높은 랜드마크 감지 및 얼굴 분류되게 설정하고, 사용할 모델을 설정한다.

private fun setDetecter() {
        // High-accuracy landmark detection and face classification
        val highAccuracyOpts = FaceDetectorOptions.Builder()
            .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
            .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
            .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
            .enableTracking()
            .build()

        faceDetector = FaceDetection.getClient(highAccuracyOpts)

        // Initialize the detector object
        val options = ObjectDetector.ObjectDetectorOptions.builder()
            .setMaxResults(5)          // 최대 결과 (모델에서 감지해야 하는 최대 객체 수)
            .setScoreThreshold(0.5f)    // 점수 임계값 (감지된 객체를 반환하는 객체 감지기의 신뢰도)
            .build()
            
        customObjectDetector = ObjectDetector.createFromFileAndOptions(
            context,
            "lite-model_efficientdet_lite0_detection_metadata_1.tflite",
            options
        )
    }

 
 

runObjectDetection(bitmap: Bitmap) - bitmap 사진을 객체 분석하기

 
이미지를 받아 분석하여 분석 결과가 표시된 이미지 반환하는 함수
 

 	/**
     * runObjectDetection(bitmap: Bitmap)
     *      TFLite Object Detection function
     *      사진 속 객체를 감지하고, 감지된 객체에 boundingBox를 표시해 반환한다.
     */
    fun runObjectDetection(bitmap: Bitmap): Bitmap {
    
    	// 카메라와 연결했을 때 사용되는 변수 : 카메라가 지금 현재 result를 가져갔는지 확인
        if(!isGetDetectionResult) {
            // Object Detection
            detectionResult = getObjectDetection(bitmap)
        }

        // ObjectDetection 결과(bindingBox) 그리기
        val objectDetectionResult =
            drawDetectionResult(bitmap, detectionResult)

        return objectDetectionResult!!
    }

 

getObjectDetection(bitmap: Bitmap) - bitmap 사진 분석해 분석 결과 반환

 
이미지를 분석하여 분석 결과를 반환하는 함수
 

  1. Tensorflow Image 객체 생성
  2. Image 객체로 변환한 이미지를 디텍터로 공급
  3. 분석 결과를 List<DetectionResult> 형태로 제작하여 반환

 

   /**
     * getObjectDetection(bitmap: Bitmap):
     *         ObjectDetection 결과(bindingBox)를 반환한다.
     */
    private fun getObjectDetection(bitmap: Bitmap): List<DetectionResult> {
        // Step 1: Create TFLite's TensorImage object
        val image = TensorImage.fromBitmap(bitmap)

        // Step 2: Feed given image to the detector
        val results = customObjectDetector.detect(image)

        // Step 3: Parse the detection result and show it
        val resultToDisplay = results.map {
            // Get the top-1 category and craft the display text
            val category = it.categories.first()
            val text = "${category.label}"

            // Create a data object to display the detection result
            DetectionResult(it.boundingBox, text)
        }
        return resultToDisplay
    }
    
    data class DetectionResult(val boundingBox: RectF, val text: String)

 

drawDetectionResult(bitmap: Bitmap, detectionResults: List<DetectionResult> 
- 분석 결과를 바탕으로 이미지에 표시

 
객체 분석 결과(detectionResults)를 이용해 이미지 위에 분석 결과를 표시한 후 표시된 이미지를 반환하는 함수
 

    /**
     * drawDetectionResult(bitmap: Bitmap, detectionResults: List<DetectionResult>
     *      객체 분석 된 boundingBox에 맞춰 이미지 위에 표시한 후 표시된 이미지를 반환한다.
     */
    private fun drawDetectionResult(
        bitmap: Bitmap,
        detectionResults: List<DetectionResult>
    ): Bitmap? {
        val outputBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
        val canvas = Canvas(outputBitmap)
        val pen = Paint()
        pen.textAlign = Paint.Align.LEFT

        detectionResults.forEach {
            // draw bounding box
            pen.color = Color.parseColor("#B8C5BB")
            pen.strokeWidth = 10F
            pen.style = Paint.Style.STROKE
            val box = it.boundingBox
            canvas.drawRoundRect(box, 10F, 10F, pen)
        }
        return outputBitmap
    }

 

getDetectionResult( ) - 현재 객체 분석을 정지시키고, 분석 결과 중 하나(한 객체 정보)를 반환하는 함수

1. isGetDetectionResult = true로 설정함으로써, 현재 객체 분석을 정지시킨다.
2. index 변수를 가지고 해당 index에 분석 결과를 선택한다.
 

만약 index가 분석결과 사이즈보다 작을 경우, resetDetectionResult( ) 함수를 호출해 다시 객체 분석을 시작한다.

 

    fun getDetectionResult() : DetectionResult? {

        if(detectionResult.size > detectionResultIndex) {
            isGetDetectionResult = true
            return detectionResult[detectionResultIndex++]
        }
        else {
            resetDetectionResult()
            return null
        }
    }

    fun resetDetectionResult() {
        isGetDetectionResult = false
        detectionResultIndex = 0
    }

 
 

추가적인 함수
    fun getIsDetectionStop() : Boolean {
        return isGetDetectionResult
    }

    fun getIsDetectionSize() : Int {
        return detectionResult.size
    }

Camera2에서 실시간으로 객체 분석

 

Object Detection 함수를 지금 할 것인지 안할 것인지 Switch로 판단

 
Switch가 true일 때는 Object Detection 실행, false일 때는 Object Detection 을 실행하지 않는다.
 
isDetectionChecked 변수 - 현재 Object Detection을 할지 안할지에 대한 변수

        binding.detectionBtn.setOnCheckedChangeListener { _, isChecked ->
            isDetectionChecked = isChecked
            if(!isChecked) {
                binding.imageView.setImageBitmap(null)
            }
        }

 

TextureView.SurfaceTextureListener의 onSurfaceTextureUpdated(texture: SurfaceTexture) 함수

 
TextureView.SurfaceTextureListener의 onSurfaceTextureUpdated(texture: SurfaceTexture) 함수를 이용하여 Surface의 변화가 생길 때마다 Object Detection을 다시 실행하여 Surface위에 ImageView에 띄운다.
 

TextureView.SurfaceTextureListener: TextureView에 등록된 Listener
onSurfaceTextureUpdated(texture: SurfaceTexture) : surfaceTexture가 업데이트 될 때 호출되는 함수
        // surfaceTexture가 업데이트 됨
        override fun onSurfaceTextureUpdated(texture: SurfaceTexture) {

            if (isDetectionChecked) {
                // 객체 인식 코드 호출
                val newBitmap = binding.texture.bitmap?.let {
                    objectDetectionModule.runObjectDetection(it)
                }
                binding.imageView.setImageBitmap(newBitmap)
            }
        }

 

focusDetectionPictures( ) : 객체 분석 결과를 얻어와 해당 객체에 초점 맞추기

 
객체 분석 결과를 얻어와 해당 객체에 초점을 맞추는 함수
 
1. objectDetectionModule.getDetectionResult()
     : 객체 분석 결과를 하나 얻어온다
2. 분석 결과로 얻어진 boundingBox를 가지고, 객체 중앙 값과 절반너비, 절반높이를 알아낸다
3. setTouchPointDistanceChange( ... )
     : 위에 알아낸 값들을 토대로 해당 객체에 초점을 맞춘다. ( 해당 함수는 다음 포스터에서 확인 )
 

 

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

kyumq.tistory.com

 

setTouchPointDistanceChange에서 CameraCaptureSession.CaptureCallback() 수정

 
CameraCaptureSession.CaptureCallback() 수정을 통해 초점이 완료되면 사진 촬영하게 설정
 

    val captureCallback = object : CameraCaptureSession.CaptureCallback() {
        override fun onCaptureCompleted(
            session: CameraCaptureSession,
            request: CaptureRequest,
            result: TotalCaptureResult,
        ) {
            // 현재 객체 감지 촬영일 경우
            if(objectDetectionModule.getIsDetectionStop()) {
                previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, null)
                captureSession?.setRepeatingRequest(previewRequestBuilder.build(), null, null)

                // 사진 촬영을 위한 포커스 잠금
                lockFocus()
            }
            else {
                previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, null)
                captureSession?.setRepeatingRequest(previewRequestBuilder.build(), null, null)
            }
        }
    }

 

captureStillPicture에서 CameraCaptureSession.CaptureCallback() 수정

 
CameraCaptureSession.CaptureCallback() 수정을 통해 촬영이 완료된 후, 현재 객체 초점 촬영일 경우 다음 객체 초점 촬영 시작
 

            val captureCallback = object : CameraCaptureSession.CaptureCallback() {
                override fun onCaptureCompleted(
                    session: CameraCaptureSession,
                    request: CaptureRequest,
                    result: TotalCaptureResult,
                ) {

                    val distanceResult = result.get(CaptureResult.LENS_FOCUS_DISTANCE)
                    
                    Log.d("렌즈 초점 결과", distanceResult.toString())
                    
                    // 수동 초점
                    if(value != null && distanceResult == 10f) {
                        setAutoFocus()
                    }
                    // 자동 초점
                    else {
                        unlockFocus()
                        // 객체 촬영일 경우, 다음 객체 촬영 시작
                        if(isDetectionChecked) {
                            focusDetectionPictures()
                        }
                    }
                }
            }

 


\

반응형