졸업 프로젝트를 진행하면서 멀티모달 모델을 안드로이드 기기에서 돌려야 했습니다. 일단 제가 쓰는 모델은 vision 모델 하나와 nlp 모델 하나 이렇게 두 가지인데 vision모델을 사용하기 위해서는 이미지 전처리 과정을 거쳐야겠죠?
사실 파이썬에서는 numpy, torch 쓰면 코드 몇줄 찍찍에 뚝!딱! 끝나는 과정인데
"이 이미지 전처리 과정을 코틀린, 자바 언어를 사용하는 안드로이드 OS 도메인에서 어떻게 재현하지?"
가 이번 포스팅을 통해 해결해야 할 과제가 되겠습니다
먼저 이미지 전처리를 하려면 이미지를 불러와야겠죠?? "파이썬에서는" 코드 한줄 찎 쓰면 PIL로 이미지를 가져올 수 있지만 안드로이드에서는 조금 복잡한 과정을 거쳐야 합니다.
일단 BitmapFactor라는 유용한 클래스를 활용해서 이미지 파일을 비트맵으로 불러올 수 있는데 한번 해보겠습니다.
for (imgID in imgList){
val bitmap = BitmapFactory.decodeResource(this.resources, imgID)
bitmapList.add(bitmap)
}
imgList에는 이미지파일에 대한 location 정보가 담겨있는데 원래는 갤러리에서 이미지를 불러와서 구현해야 하지만 현재 데모 작성중이므로 Resources 에 직접 이미지 파일을 저장해서 불러오도록 하겠습니다.
BitmapFactor의 decodeResource 매서드를 통해 리소스이미지를 비트맵으로 불러올 수 있습니다.
갤러리에서 불러오려면 적절히 로직을 취해서
와 같은 매서드를 활용하시면 되겠습니다.
결과를 볼까요?
왼쪽이 파이썬으로 불러온 PIL 이미지 사이즈이고 오른쪽이 BitmapFactory를 활용해서 불러온 이미지 사이즈입니다.
이상하게 동일한 이미지를 불러왔는데 각각 사이즈가 다르게 출력이 되네요. 자세히 살펴보면 안드로이드에서 불러온 이미지가 파이썬PIL이미지보다 약 3배가량 큰 사이즈를 가지고 있는 걸을 발견할 수 있습니다.
이를 통해 우리가 간과하고 있는 사실을 하나 유추해볼 수 있습니다. 안드로이드 개발자라면 모두 알고있어야 하는 사실중 하나로, 안드로이드 휴대폰들은 기종마다 제각각인 화면 비율과 해상도를 가지고 있다는것!
따라서 다른 휴대폰으로 찍은 사진들은 모두 다른 해상도를 가질텐데(물론 화면 해상도와 전혀 관계없는 카메라 성능차이랍니다) 어느 휴대폰이든 항상 꽉찬 화면에 표시가 되게 되죠
그 이유는 BitmapFactory에서 비트맵파일로 디코딩할 때 기본적으로 화면 해상도를 고려하여 DP단위로 재구성하기 때문입니다.
일반적인 상황에선 문제가 없겠지만 이미지 전처리 과정에 이런 Rescaling 과정이 포함되어버리면 전처리 프로세스가 깨져 제대로된 결과를 얻을 수 없게 됩니다.
BitmapFactory가 자동으로 이미지를 Rescaling해서 변환시켜주는거라면 원본 해상도의 비트맵을 얻기 위한 방법도 BitmapFactory에서 찾을 수 있을거 같으니 공식문서를 한번 구경해봅시다
아까 사용했던 decodeResource 함수에 두 가지 버전이 존재하는군요!
기본적으로는 BitmapFactory.Options가 null이 들어가 기본값으로 적용되고 있었는데 이를 직접 커스텀 해줄 수 있는것 같습니다.
그럼 이 Options를 어떻게 잘 조작하면 Rescaling과정을 건너뛸 수 있지 않을까요?
다시 위 Options에 대한 공식문서로 들어가보면 아래와 같은 내용을 볼 수 있습니다.
일단 inDensity, targetDensity에 대한 내용도 위 문서에 있으니 읽어보면 inDensity는 이미지에 대한 해상도이고 targetDensity는 이미지가 그려질 기기 화면에 대한 해상도입니다. 따라서 위 inScaled 속성값이 기본적으로 true로 되어있기 때문에 이미지 크기가 화면 해상도인 targetDensity에 맞춰 재조정되어 비트맵으로 반환되는 것입니다.
따라서 inScaled 변수를 false로 주면 Rescaling 과정이 생략되겠죠?
val bmpFactoryOption = BitmapFactory.Options()
bmpFactoryOption.inScaled = false
for (imgID in imgList){
val bitmap = BitmapFactory.decodeResource(this.resources, imgID, bmpFactoryOption)
bitmapList.add(bitmap)
}
짜잔 이렇게 BitmapFactory.Options()를 직접 커스텀 해주면 아래와 같이 원본 사이즈의 이미지 비트맵이 반환되는것을 확인할 수 있습니다.
오예~! 이제 원본 이미지를 가져왔으니 지지고 볶아서 전처리 과정을 구현해주면 되겠죠? 이미지를 전처리 하는 과정은 다음 포스팅에서 다룰 예정입니다.