[TFLite] 6(2). 프레임 워크를 활용한 이미지 분류 앱 개발 (카메라와 갤러리 이미지 사용하기)
카메라와 갤러리 이미지 사용하기
이 장에서는 텐서플로 라이트 서포트 라이브러리를 이용하여 카메라와 갤러리의 이미지를 사용해 이미지 분류를 하는 앱을 개발해봅니다.
이전 포스팅들 중 [Android] 6(3). BaseActivity 설계하기, [Android] 9. 카메라와 갤러리, [TFLite] 6(1). 프레임워크를 활용한 이미지 분류 앱 개발 (텐서플로 라이트 서포트 라이브러리) 에서의 코드들을 조합하여 하나의 완성된 기능을 갖는 앱을 개발합니다.
위 3개의 포스팅은 아래 주소에서 보실 수 있습니다.
- [Android] 6(3). BaseActivity 설계하기
- [Android] 9. 카메라와 갤러리
- [TFLite] 6(1). 프레임워크를 활용한 이미지 분류 앱 개발 (텐서플로 라이트 서포트 라이브러리)
아키텍처
사전 작업
레이아웃을 만들기 전에 사전 작업을 합니다.
- AndroidManifest.xml 파일에 권한 명세
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="kr.co.hanbit.imageclassifier">
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera2"/>
<application
...
</application>
</manifest>
- 프로젝트의 build.gradle 파일에 텐서플로 라이트/텐서플로 라이트 서포트 라이브러리 의존성 추가
dependencies {
...
// 1. 텐서플로 라이트와 텐서플로 라이트 서포트 라이브러리 의존성 추가
implementation 'org.tensorflow:tensorflow-lite:2.4.0'
implementation 'org.tensorflow:tensorflow-lite-support:0.1.0'
...
}
- BaseActivity.kt 파일을 프로젝트에 포함
‘BaseActivity 설계하기’ 포스팅에서 구현한 BaseActivity.kt 클래스는 권한을 요청과 처리에 사용되는 전용 클래스입니다.
레이아웃
레이아웃은 ‘카메라와 갤러리’ 포스팅에서 사용한 레이아웃에 추론 결과를 출력할 텍스트뷰 하나를 추가합니다.
PhotoActivity.kt로 생성합니다.
- 이미지뷰: id = imageView
- 버튼1: id = btnCamera, text = CAMERA
- 버튼2: id = btnGallery, text = GALLERY
- 텍스트뷰: id = textView, text = RESULT
UI 로직 구현
UI 로직도 ‘카메라와 갤러리’ 포스팅에서 사용한 로직을 사용합니다.
다만 수정해야 하는 부분을 살펴보겠습니다.
- openGallery 메서드 수정
private fun openGallery(){
// 권장 코드
val intent = Intent(Intent.ACTION_GET_CONTENT).setType("image/*")
// val intent = Intent(Intent.ACTION_PICK)
// intent.type = MediaStore.Images.Media.CONTENT_TYPE
startActivityForResult(intent, REQ_GALLERY)
}
- onActivityResult 메서드 수정
모델의 추론 메서드를 호출할 때는 Bitmap 인스턴스를 파라미터로 넘겨야 합니다. 따라서 카메라와 갤러리의 경우 모두 Uri를 Bitmap으로 바꿔줍니다.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode == RESULT_OK)
when(requestCode){
// uri를 bitmap으로 변환
REQ_CAMERA -> {
photoUri?.let{ uri ->
val capturedImage = loadBitmap(uri)
photoUri = null
// 비트맵을 모델에 전달하여 추론
val result = callClassifier(capturedImage)
binding.imageView.setImageBitmap(capturedImage)
binding.textView.text = result
}
}
REQ_GALLERY -> {
val selectedImageUri: Uri = data?.data as Uri
val selectedImage = loadBitmap(selectedImageUri)
// 비트맵을 모델에 전달하여 추론
val result = callClassifier(selectedImage)
binding.imageView.setImageBitmap(selectedImage)
binding.textView.text = result
}
}
else{
Toast.makeText(baseContext,
"Result Canceled!",
Toast.LENGTH_LONG).show()
}
}
- callClassifier 메서드 추가
모델의 추론 메서드를 호출하여 결괏값을 받고 문자열로 리턴하는 메서드입니다.
private fun callClassifier(bitmap: Bitmap?): String{
val output: Pair<String, Float> = classifier.classify(bitmap)
val resultStr = String.format(Locale.ENGLISH,
"class : %s Prob : %.2f%%",
output.first, output.second * 100)
return resultStr
}
- onDestroy 메서드 오버라이드
액티비티 종료 시 모델에게 할당된 자원을 해제하도록 합니다.
override fun onDestroy() {
super.onDestroy()
classifier.finish()
}
이미지 포맷 변환
분류기 모델은 ‘프레임워크를 활용한 이미지 분류 앱 개발 (텐서플로 라이트 서포트 라이브러리)’ 포스팅에서 구현했던 ClassifierWithModel.kt 파일을 사용합니다.
이 소스파일도 약간의 수정이 필요합니다.
- loadImage 메서드 수정하기
파라미터로 전달된 Bitmap 인스턴스를 TensorImage에 저장할 때 Bitmap의 데이터 포맷을 ARGB_8888로 맞춰줘야 합니다.
따라서 이 코드를 loadImage 메서드에 추가합니다.
private fun loadImage(bitmap: Bitmap?): TensorImage{
// TensorImage에 이미지 데이터 저장
// 7-2. 추가 - 데이터 포맷 변환: bitmap의 데이터 포맷이 ARGB_8888이 아닌 경우 변환
if (bitmap != null) {
if(bitmap.config != Bitmap.Config.ARGB_8888)
inputImage.load(convertBitmap2ARGB8888(bitmap))
else
inputImage.load(bitmap)
}
// inputImage?.load(bitmap)
val imageProcessor =
ImageProcessor.Builder()
.add(ResizeOp(modelInputWidth,modelInputHeight,
ResizeOp.ResizeMethod.NEAREST_NEIGHBOR))
.add(NormalizeOp(0.0f, 255.0f))
.build()
// 이미지를 전처리하여 TensorImage 형태로 반환
return imageProcessor.process(inputImage)
}
- convertBitmap2ARGB8888 메서드 구현하기
loadImage에서 호출할 Bitmap의 포맷을 ARGB_8888로 변환해주는 메서드를 구현합니다.
// 7-1. 추가 - 데이터 포맷 변환: loadImage 메서드의 bitmap 변수가 ARGB_8888이 아닌 경우 변환
private fun convertBitmap2ARGB8888(bitmap: Bitmap): Bitmap{
return bitmap.copy(Bitmap.Config.ARGB_8888, true)
}
결과 확인
이것으로 끝입니다!
앞선 3개의 포스팅에서 살펴본 개념과 코드를 통해 간단한 이미지 분류기 앱을 구현할 수 있습니다.
결과 화면은 다음과 같습니다.
카메라를 통해 촬영하거나 갤러리에서 선택된 이미지를 통해 이미지 분류가 되는 것을 볼 수 있습니다.
본 포스팅에 대한 모든 코드는 깃허브 저장소 에서 확인할 수 있습니다.
정리
권한, 카메라와 갤러리, tflite 모델 사용이라는 개념들을 종합하여 하나의 이미지 분류 앱을 구현해보았습니다.
이처럼 알고있는 개념들을 종합하거나 새로운 지식을 추가하여 멋진 앱을 만들 수 있을 것입니다.
Leave a comment