[TFLite] 5(2). 텐서플로 라이트 모델을 이용한 안드로이드 앱 개발 (입력 이미지 전처리)

3 minute read


입력 이미지 전처리

이전 포스팅에서 TFLite 모델을 로드하는 법에 대해 살펴보았습니다.

이번 포스팅에서는 모델에 입력할 이미지를 전처리하는 방법에 대해 알아봅니다.

초기 입력 이미지와 모델이 요구하는 입력 이미지는 크기와 포맷이 서로 다르기 때문에 다음과 같은 절차로 모델의 입력 형태에 맞추어 입력 이미지를 변환하는 작업이 필요합니다.

  • 모델의 입력 크기 확인
  • 입력 이미지 크기 변환
  • 입력 이미지 채널 변환
  • 입력 이미지 포맷 변환


모델의 입력 크기 확인


먼저 모델에서 입력 텐서를 통해 입력 데이터의 크기를 확인합니다.


1. 모델의 입력 크기 변수 선언

모델의 입력 크기는 계산 후 자주 참조되어 사용될 값이므로 Classifier의 멤버 변수(프로퍼티)로 선언합니다.

    /*전역변수 선언*/
    var context: Context = context
    var interpreter: Interpreter
    // 4. 입력 이미지 전처리 - 1. 모델의 입력 크기 확인
    // 변수 선언
    var modelInputWidth: Int = 0
    var modelInputHeight: Int = 0
    var modelInputChannel: Int = 0


2. 모델의 입출력 크기 계산 메서드 호출

init 초기화 블록에서 모델의 입출력 크기를 계산하는 메서드를 호출합니다.

    init{
        val model: ByteBuffer? = loadModelFile(MODEL_NAME)    
        model?.order(ByteOrder.nativeOrder())?:throw IOException()

        interpreter = Interpreter(model)

        // 4. 입력 이미지 전처리 - 1. 모델의 입력 크기 확인(cont.)
        // 모델의 입출력 크기 계산 메서드 호출
        initModelShape()

    }


3. 모델의 입출력 크기 계산 함수 정의

Interpreter의 getInputTensor( ) 메서드를 호출하여 입력 텐서를 하나 구하고, shape( ) 메서드를 호출하여 입력 텐서의 형상을 얻습니다.

shape( ) 메서드는 IntArray 타입의 정수형 배열을 반환하고 차례대로 채널 수, 가로 크기, 세로 크기 값이 저장되어 있습니다.

    // 4. 입력 이미지 전처리 - 1. 모델의 입력 크기 확인(cont.)
    // 모델의 입출력 크기 계산 메서드 정의
    private fun initModelShape(){
        // getInputTensor()로 입력 텐서 가져오기
        val inputTensor: Tensor = interpreter.getInputTensor(0)
        // shape()로 입력 텐서 형상 가져오고 프로퍼티에 저장
        val inputShape = inputTensor.shape()
        modelInputChannel = inputShape[0]
        modelInputWidth = inputShape[1]
        modelInputHeight = inputShape[2]
    }



2. 입력 이미지 크기 변환


이제 앞에서 얻은 손글씨 이미지의 크기를 모델에 맞게 변환해야 합니다.


4. 입력 이미지 크기 변환

가로세로 크기가 조정된 비트맵 객체를 반환하는 메서드를 정의합니다.

    // 4. 입력 이미지 전처리 - 2. 입력 이미지 크기 변환
    private fun resizeBitmap(bitmap: Bitmap): Bitmap{
        // 파라미터: 비트맵 인스턴스, 새로운 너비, 새로운 높이, 이미지 보간법
        // 이미지 보간법: 이미지를 늘릴 때(true로 설정, 양선형보간법)/이미지를 줄일 때(false로 설정, 최근접 보간법)
        return Bitmap.createScaledBitmap(bitmap, modelInputWidth, modelInputHeight, false)
    }

이미지 보간법

이미지 보간법은 이미지를 기하학적으로 변환(축소/확대/회전 등)할 때, 정보를 갖지 못하는 픽셀(홀)이 생길 수 있어 홀 주변의 알고 있는 값들을 이용하여 그 값을 찾는 알고리즘입니다.

이미지 보간법에는 여러 알고리즘이 있지만, 크게 최근접 보간법, 양선형 보간법, 바이큐빅 보간법이 있습니다.

  • 최근접 보간법(Nearest Neigbor interpolation): 가장 가까운 화소값을 사용
  • 양선형 보간법(Bilinear interpolation): 인접한 4개 화소의 화소값과 거리비를 사용하여 결정
  • 바이큐빅 보간법(Bicubic interpolation): 인접한 16개 화소의 화소값과 거리에 따른 가중치의 곱을 사용하여 결정

일반적으로 이미지의 품질이 중요할 때는 양선형 보간법 또는 바이큐빅 보간법을, 성능(속도)이 중요할 때는 최근접 보간법을 사용합니다.

이미지를 축소하는 경우에는 계단 현상이라는 제약으로부터 비교적 자유롭기 때문에 최근접 보간법을 선택하는 것이 좋습니다.


3. 입력 이미지 채널과 포맷 변환


5. 입력 이미지 채널과 포맷 변환

이제 남은 프로세스는 입력 이미지의 채널 변환과 ByteBuffer로의 포맷 변환입니다. 우리가 사용하는 모델이 받을 수 있는 입력 데이터는 1채널 데이터이므로 ARGB 채널의 이미지를 GrayScale로 변환하고, DrawView 위젯에서 Bitmap 포맷으로 얻은 이미지를 ByteBuffer 포맷으로 변환합니다.

이 두가지를 한꺼번에 변환할 수 있으므로 다음과 같이 하나의 메서드 안에서 처리하도록 구현합니다.

    // 4. 입력 이미지 전처리 - 3, 4. 입력 이미지 채널과 포맷 변환 (하나의 메서드에서 처리)
    // 변환된 ByteBuffer를 반환
    private fun convertBitmapToGrayByteBuffer(bitmap: Bitmap): ByteBuffer{
        // 바이트 크기만큼 ByteBuffer 메모리를 할당
        val byteBuffer: ByteBuffer = ByteBuffer.allocateDirect(bitmap.byteCount)
        // 모델과 동일한 바이트 순서로 설정
        byteBuffer.order(ByteOrder.nativeOrder())

        // 비트맵의 픽셀 값 가져오기
        // 파라미터: 저장할 배열, offset, stride, x, y, width, height
        val pixels = IntArray(bitmap.width * bitmap.height)
        bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)

        // grayscale로 변환
        for(pixel in pixels){
            // 예시: Pixel(int, ARGB): 11111111 00001111 11110000 01010101 (4바이트, 32비트)
            val r = pixel shr 16 and 0xFF
            val g = pixel shr 8 and 0xFF
            val b = pixel and 0XFF

            // 픽셀의 평균값을 구하고 0~1 사이의 값으로 정규화(***모델을 훈련시킬 때와 동일하게 정규화***)
            val avgPixelValue = (r + g + b) / 3.0f
            val normalizedPixelValue = avgPixelValue / 255.0f

            // 반환할 ByteBuffer에 정규화된 픽셀값을 추가
            byteBuffer.putFloat(normalizedPixelValue)
        }

        return byteBuffer
    }



정리


이상 TFLite 모델 로드 이후 수행해야 하는 입력 이미지 전처리 과정에 대해 살펴보았습니다.

입력 이미지 전처리 과정은 다음과 같습니다.

  • 모델의 입력 크기 확인
  • 입력 이미지 크기 변환
  • 입력 이미지 채널과 포맷 변환

다음 포스팅에서는 마지막으로 모델의 추론 및 결과 해석 과정에 대해 살펴보도록 하겠습니다.

Categories:

Updated:

Leave a comment