본문 바로가기
안드로이드 코틀린

[안드로이드][코틀린] 사람 이미지 배경 가리기 PorterDuff.Mode

by teamnova 2023. 12. 24.

 

오늘은 PorterDuff.Mode 와 ML kit 라이브러리를 활용하여 이미지 배경을 가리는 기능을 구현해보겠습니다.

자바 버전 >> https://stickode.tistory.com/949

 

[JAVA][Android] 사람 이미지 배경 가리기 PorterDuff.Mode

오늘은 PorterDuff.Mode 와 ML kit 라이브러리를 활용하여 이미지 배경을 가리는 기능을 구현해보겠습니다. PorterDuff.Mode 를 활용하여 이미지 합치는 기능을 먼저 보고 와주세요 https://stickode.tistory.com/94

stickode.tistory.com

 

해당 글을 보시기 전에 코틀린 버전으로 PorterDuff.Mode 를 활용하여 이미지 합치는 기능을 먼저 보고 와주세요 

https://stickode.tistory.com/1057 

 

ML Kit  이란?

ML Kit은 구글이 제공하는 모바일 애플리케이션을 위한 머신 러닝 라이브러리입니다. 이 라이브러리는 안드로이드와 iOS 플랫폼에서 사용할 수 있으며, 개발자들이 머신 러닝을 활용하여 자신들의 앱을 보다 지능적으로 만들 수 있게 도와줍니다.

ML Kit은 여러가지 머신 러닝 기능을 제공합니다. 주요 기능으로는 이미지와 텍스트 관련 작업을 수행하는 기능들이 포함되어 있습니다.

  1. 이미지 관련 작업:
    • 이미지 라벨링(Labeling): 사진에서 물체나 장면 등을 식별하여 라벨을 부여합니다.
    • 얼굴 감지(Face Detection): 사진이나 카메라 스트림에서 얼굴을 감지하고 특징을 분석합니다.
    • 텍스트 감지(Text Detection): 이미지에서 텍스트를 감지하고 읽어내는 기능입니다.
    • 이미지 세그멘테이션(Image Segmentation): 이미지를 객체와 배경으로 분할하여 객체를 식별합니다.
    • 바코드 스캐닝(Barcode Scanning): 바코드를 스캔하고 정보를 추출합니다.
    • 라벨 기반 물체 검출(Object Detection): 이미지에서 특정 물체들을 감지합니다.
  2. 텍스트 관련 작업:
    • 텍스트 번역(Text Translation): 다양한 언어로 텍스트를 번역합니다.
    • 언어 감지(Language Identification): 주어진 텍스트의 언어를 식별합니다.

ML Kit은 머신 러닝을 사용하더라도, 모델을 직접 훈련시킬 필요가 없이 간단한 API 호출을 통해 사용할 수 있습니다. 이는 개발자들에게 머신 러닝을 쉽게 적용할 수 있는 기회를 제공합니다.

또한 ML Kit은 오프라인에서도 동작하는 기능을 제공하며, 이는 사용자 경험을 향상시키는 데에 도움을 줍니다. ML Kit은 Firebase와도 통합되어 있어서 Firebase를 사용하는 애플리케이션에 머신 러닝을 쉽게 추가할 수 있습니다.

 

자세한 내용은 ML kit 홈페이지에서 확인해보세요

https://developers.google.com/ml-kit?hl=ko 

 

ML Kit  |  Google for Developers

모바일 개발자를 위한 Google의 기기별 머신러닝 키트입니다.

developers.google.com

 

MainActivity.kt

class MainActivity : AppCompatActivity() {
    // 필요한 뷰 및 비트맵 변수 선언
    private lateinit var imageView: ImageView
    private lateinit var imageView3: ImageView
    private lateinit var bitmap1: Bitmap
    private lateinit var button: Button
    private lateinit var segmenter: Segmenter

    // 액티비티 생성 시 호출되는 메서드
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 레이아웃 설정
        setContentView(R.layout.activity_main)
        // 뷰와 버튼 초기화
        imageView = findViewById(R.id.imageView)
        imageView3 = findViewById(R.id.imageView3)
        button = findViewById(R.id.button)
        segmenterCreate()
        // 첫 번째 이미지뷰에 비트맵 설정
        bitmap1 = BitmapFactory.decodeResource(applicationContext.resources, R.drawable.img_3)
        imageView.setImageBitmap(bitmap1)
        // 버튼에 클릭 리스너 등록
        button.setOnClickListener {
            val drawable1 = imageView.drawable as BitmapDrawable
            val dst = drawable1.bitmap
            val mlImage = InputImage.fromBitmap(dst, 0)
            segmenter.process(mlImage)
                .addOnSuccessListener { segmentationMask ->
                    // Segmentation 성공 시 호출되는 메서드

                    // SegmentationMask에서 ByteBuffer, 가로 및 세로 크기를 가져옵니다.
                    val mask = segmentationMask.buffer
                    val maskWidth = segmentationMask.width
                    val maskHeight = segmentationMask.height

                    // ByteBuffer에서 색상 배열을 가져옵니다.
                    val arr = maskColorsFromByteBuffer(mask, maskWidth, maskHeight)

                    // 새로운 비트맵 객체를 생성합니다.
                    val segmentedBitmap = Bitmap.createBitmap(dst.width, dst.height, Bitmap.Config.ARGB_8888)
                    val maskBitmap = Bitmap.createBitmap(arr, maskWidth, maskHeight, Bitmap.Config.ARGB_8888)

                    // Canvas를 사용하여 이미지를 그립니다.
                    val canvas = Canvas(segmentedBitmap)
                    val paint = Paint()
                    canvas.drawBitmap(dst, 0f, 0f, paint)

                    // Xfermode를 설정하여 마스크를 적용합니다.
                    paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)

                    // 비트맵 크기를 조정하고 이동시켜서 블렌딩합니다.
                    val matrixz = Matrix()
                    matrixz.setScale(dst.width.toFloat() / maskBitmap.width, dst.height.toFloat() / maskBitmap.height)
                    matrixz.postTranslate(0f, 0f)
                    canvas.drawBitmap(maskBitmap, matrixz, paint)

                    // 결과 이미지를 imageView3에 설정합니다.
                    imageView3.setImageBitmap(segmentedBitmap)
                }
        }
    }

    // ByteBuffer에서 mask 색상 배열 추출
    @ColorInt
    private fun maskColorsFromByteBuffer(byteBuffer: ByteBuffer, maskWidth: Int, maskHeight: Int): IntArray {
        val colors = IntArray(maskWidth * maskHeight)
        for (i in 0 until maskWidth * maskHeight) {
            val backgroundLikelihood = 1 - byteBuffer.getFloat()
            if (backgroundLikelihood > 0.9) {
                colors[i] = Color.rgb(51, 112, 69) // 높은 확률의 배경일 경우 녹색으로 설정
            } else if (backgroundLikelihood > 0.2) {
                colors[i] = Color.rgb(51, 112, 69) // 일반적인 배경일 경우 녹색으로 설정
            }
        }
        return colors
    }

    // Segmentation 모델 초기화
    private fun segmenterCreate() {
        try {
            val options = SelfieSegmenterOptions.Builder()
                .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE)
                .enableRawSizeMask()
                .build()
            segmenter = Segmentation.getClient(options)
        } catch (e: MlKitException) {
            // Handle the exception appropriately
            e.printStackTrace()
        }
    }
}

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <ImageView
        android:id="@+id/imageView"
        android:layout_width="200dp"
        android:layout_height="200dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />

    <ImageView
        android:id="@+id/imageView3"
        android:layout_width="300dp"
        android:layout_height="300dp"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView"
        tools:srcCompat="@tools:sample/avatars" />


    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>