여러분의 참가자분들이 별도의 결제 수단(예: 신용카드) 등록 없이 Google Cloud Platform을 무료로 사용할 수 있도록, 아래 링크를 제공합니다.
Google 계정으로 회원가입하고 Free Tier를 활성화하는 방법은 [별도 안내 자료(Deck)](첨부 예정)에서 확인할 수 있습니다.

캠페인 크래딧 받으러 가기 캠페인 등록 방법 가이드

중요:

Last Updated: 2025-04-26

1.1 MediaPipe 소개

MediaPipe 솔루션을 사용하면 머신러닝(ML) 솔루션을 앱에 적용할 수 있습니다. 이 솔루션으로 제공되는 프레임워크를 통해 사용자에게 즉각적이고, 매력적이고, 유용한 출력을 제공하는 사전 빌드된 처리 파이프라인을 구성할 수 있습니다. MediaPipe Model Maker를 사용해서 이러한 솔루션을 맞춤설정하여 기본 모델을 업데이트할 수도 있습니다.

주요 특징

다음 라이브러리와 리소스는 각 MediaPipe 솔루션의 핵심 기능을 제공합니다.

다음 도구를 사용하여 솔루션을 맞춤설정하고 평가할 수 있습니다.

1.2 MediaPipe Studio

MediaPipe Studio를 이용하면 Android 디바이스 없이도, 브라우저에서 바로 다양한 솔루션(예: 얼굴 스타일 변환, 객체 감지, 손 랜드마크 감지 등)을 테스트할 수 있습니다.

➔ MediaPipe Studio로 이동해서 데모 체험하기: https://mediapipe-studio.webapps.google.com/home

1.3 학습할 내용

이 Codelab에서는 비어 있는 프로젝트로 시작하여 MediaPipe 태스크를 사용하여 Android 기기에서 로컬로 실행되는 "얼굴 스타일 지정 (Face Stylizer)" 솔루션을 직접 적용하는 방법을 배워봅니다.

1.4 필요한 항목

1.5 안내: Android SDK 기본 구현 범위 제외

이 코드랩은 MediaPipe Face Stylizer 기능을 Android 앱에 통합하는 실습을 중심으로 구성되어 있습니다.

참여 대상: Android Studio와 Kotlin/Compose 기본 사용법에 익숙한 개발자

만약 Android SDK나 Compose 기초가 필요하다면, 다음 공식 자료를 참고하세요:

MediaPipe Face Stylizer 작업을 사용하면 이미지의 얼굴에 얼굴 스타일 지정을 적용할 수 있습니다. 이 태스크를 사용하여 다양한 스타일의 가상 아바타를 만들 수 있습니다.

사용해보기

2.1 모델

이 작업에서는 얼굴 생성기와 얼굴 인코더로 구성된 BlazeFaceStylizer 모델을 사용합니다. StyleGAN 모델 제품군의 경량 구현인 BlazeStyleGAN 얼굴 생성기는 지정된 스타일에 따라 얼굴을 생성하고 수정합니다. MobileNet V2 백본을 사용하는 얼굴 인코더는 입력 이미지를 얼굴 생성기에서 생성한 얼굴에 매핑합니다. 얼굴 스타일라이저를 사용하려면 얼굴 스타일 지정 모델을 다운로드하여 프로젝트 디렉터리에 저장해야 합니다. 각 모델은 입력 이미지 내 얼굴에 특정 스타일을 적용하도록 학습되었습니다.

색상 스케치

이 모델은 얼굴을 색연필 획과 브러시 획으로 스케치를 모방한 이미지로 변환합니다. 이 모델을 학습하는 데 사용되는 스타일은 아래와 같습니다.

컬러 스케치 출력

모델 이름

입력 셰이프

양자화 유형

버전

컬러 스케치

256 x 256 x 3

Float32

최신

컬러 잉크

이 모델은 얼굴을 수채화 그림을 모방한 이미지로 변환합니다. 이 모델을 학습하는 데 사용되는 스타일은 아래와 같습니다.

컬러 잉크 출력

모델 이름

입력 셰이프

양자화 유형

버전

컬러 잉크

256 x 256 x 3

Float32

최신

유화

이 모델은 얼굴을 유화를 모방한 이미지로 변환합니다. 이 모델을 학습하는 데 사용되는 스타일은 아래와 같습니다.

유화 출력

모델 이름

입력 셰이프

양자화 유형

버전

유화

256 x 256 x 3

Float32

최신

2.2 Face Stylizer 동작 흐름 요약

  1. 이미지 입력 : 갤러리 선택
  2. 얼굴 검출 및 스타일 변환 : Mediapipe Tasks API를 통해 얼굴을 인식하고 스타일을 적용
  3. 스타일링된 이미지 출력 : 변환 결과를 화면으로 실시간 표시

2.3 참고: 공식 샘플 코드

Face Stylizer는 Mediapipe 공식 GitHub에서도 Android 샘플 프로젝트를 제공합니다.
실습 중 참고하거나 추가 학습을 원할 경우, 다음 링크를 활용할 수 있습니다.

👉공식 Mediapipe Face Stylizer Android 샘플 코드

3.1 Android Studio 준비

이 코드랩에서는 Android Studio를 사용하여 Compose 기반 Android 프로젝트를 생성합니다.

필수 개발 환경

3.2 새 Android 프로젝트 만들기

  1. Android Studio를 열고 New Project를 선택합니다.
  2. 템플릿으로 Empty Compose Activity를 선택합니다.
  3. 프로젝트 정보를 다음과 같이 설정합니다
  4. Finish를 눌러 프로젝트를 생성합니다.

항목

설정 값

Name

FaceStylizerApp

Package Name

원하는 이름 사용 (예: com.example.facestylizer)

Language

Kotlin

Minimum SDK

API 24 이상

3.3 Gradle 설정

build.gradle (app)

dependencies {
    // MediaPipe Tasks Android 라이브러리 추가
    implementation 'com.google.mediapipe:tasks-vision:0.10.14' 
    // // 이미지 표시
    implementation "io.coil-kt:coil-compose:2.7.0"
}

Face Stylizer를 적용하려면 먼저 사용자의 얼굴 이미지를 준비해야 합니다.

이 단계에서는 갤러리에서 이미지를 선택하는 기능을 구현합니다.

4.1 갤러리 열기 및 사진 선택

GalleryPicker.kt 클래스를 새로 생성해 줍니다.

GalleryPicker.kt

import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.activity.result.PickVisualMediaRequest

@Composable
fun GalleryPicker(
   onImageSelected: (Uri) -> Unit
) {
   val launcher = rememberLauncherForActivityResult(
       contract = ActivityResultContracts.PickVisualMedia()
   ) { uri: Uri? ->
       uri?.let { onImageSelected(it) }
   }

   Box(
       modifier = Modifier.fillMaxWidth(),
       contentAlignment = Alignment.TopCenter
   ) {
       Button(onClick = {
           launcher.launch(
               PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
           )
       }) {
           Text(text = "갤러리에서 이미지 선택")
       }
   }
}

이 코드랩에서는 ActivityResultContracts.PickVisualMedia() API를 사용하여 사진 선택기를 호출합니다. 사용자는 시스템 Photo Picker 화면에서 직접 사진을 선택하거나 취소할 수 있습니다. 사진 선택 도구 코드에 대해서 자세 알고 싶다면 다음 링크를 참조하세요.

GalleryPicker 를 사용해 이미지를 표시해줄 MainScreen 을 만들어 줍니다.

MainScreen.kt

import android.net.Uri
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage


@Composable
fun MainScreen(
   modifier: Modifier = Modifier
) {
   var selectedImageUri by remember { mutableStateOf<Uri?>(null) }

   Column(
       modifier = modifier
           .fillMaxSize(),
       verticalArrangement = Arrangement.spacedBy(16.dp)
   ) {
       // 갤러리 열기 버튼
       GalleryPicker(
           onImageSelected = { uri ->
               selectedImageUri = uri
           }
       )

       // 선택한 이미지 표시
       selectedImageUri?.let { uri ->
           AsyncImage(
               model = uri,
               contentDescription = "Selected Image",
               modifier = Modifier
                   .fillMaxWidth()
                   .aspectRatio(1f)
           )
       }
   }
}

4.2 중간 실행 결과

앱을 실행해서 사진이 잘 선택 되는 지 확인합니다.

이제 갤러리에서 선택한 이미지를 Mediapipe Face Stylizer를 통해 변환하는 기능을 구현합니다. 이 단계에서는 Mediapipe Tasks SDK를 이용해 Face Stylizer를 초기화하고, 선택한 이미지를 스타일링한 결과를 Compose 화면에 표시합니다.

5.1 Mediapipe Tasks 라이브러리 준비 확인

이전 3.3 Gradle 설정에서 다음 의존성을 추가했는지 다시 확인합니다

build.gradle

dependencies {
    implementation 'com.google.mediapipe:tasks-vision:0.10.14'
}

5.2 모델 다운로드 스크립트 만들기

App 모듈 하단에 download_models.gradle 파일을 생성합니다.

download_model.gradle

/*
* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*             http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

//모델 다운로드 task 정의 
tasks.register('downloadColorInkTask', Download) {
   src 'https://storage.googleapis.com/mediapipe-models/face_stylizer/blaze_face_stylizer/float32/latest/face_stylizer_color_ink.task'
   dest project.ext.ASSET_DIR + '/face_stylizer_color_ink.task'
   overwrite false
}

tasks.register('downloadColorSketchTask', Download) {
   src 'https://storage.googleapis.com/mediapipe-models/face_stylizer/blaze_face_stylizer/float32/latest/face_stylizer_color_sketch.task'
   dest project.ext.ASSET_DIR + '/face_stylizer_color_sketch.task'
   overwrite false
}

tasks.register('downloadOilPainting', Download) {
   src 'https://storage.googleapis.com/mediapipe-models/face_stylizer/blaze_face_stylizer/float32/latest/face_stylizer_oil_painting.task'
   dest project.ext.ASSET_DIR + '/face_stylizer_oil_painting.task'
   overwrite false
}

// 빌드 전에 모델 다운로드 
preBuild.dependsOn downloadColorInkTask, downloadColorSketchTask, downloadOilPainting

app/build.gradle 파일에서 asset 디렉토리 경로를 설정하고 모델 다운로드 스크립트를 가져옵니다.

app/build.gradle 파일 수정

plugins {
   // 모델 다운로드를 위해 추가
   id("de.undercouch.download")
}


// import DownloadModels task
extra["ASSET_DIR"] = "${projectDir}/src/main/assets"
extra["TEST_ASSETS_DIR"] = "${projectDir}/src/androidTest/assets"

// Download default models; if you wish to use your own models then
// place them in the "assets" directory and comment out this line.
apply(from = "download_models.gradle")


// 기존에 추가한 부분
dependencies {
    implementation 'com.google.mediapipe:tasks-vision:0.10.14'
}

최상위 build.gradle

buildscript {
   dependencies {
       classpath("de.undercouch:gradle-download-task:5.6.0")
   }
}

여기까지 추가하고 앱을 실행해주면, 앱이 빌드하면서 자동으로 main 밑에 asset 폴더를 만들고 download_models.gradle 스크립트에서 선언해준 기존 task 를 다운로드 받습니다.

assets 디렉토리 구조

app/
 └─ src/
     └─ main/
         └─ assets/
             └─ face_stylizer.task

5.3 FaceStylizationHelper

FaceStylizationHelper 클래스는 MediaPipe Face Stylizer 모델을 Android 앱에 쉽게 통합하기 위해 설계된 도우미(Helper) 클래스입니다.

이 클래스는 다음과 같은 기능을 제공합니다:

FaceStylizationHelper를 사용하면 복잡한 설정 과정을 직접 관리하지 않고, 간단한 API 호출만으로 스타일 변환 기능을 손쉽게 구현할 수 있습니다.

공식 예제에 있는 클래스를 그대로 활용합니다.

FaceStylizationHelper.kt

import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import com.google.mediapipe.framework.image.BitmapImageBuilder
import com.google.mediapipe.tasks.core.BaseOptions
import com.google.mediapipe.tasks.vision.facestylizer.FaceStylizer
import com.google.mediapipe.tasks.vision.facestylizer.FaceStylizer.FaceStylizerOptions
import com.google.mediapipe.tasks.vision.facestylizer.FaceStylizerResult

class FaceStylizationHelper(
   private val modelPosition: Int,
   private val context: Context,
   var faceStylizerListener: FaceStylizerListener? = null
) {

   private var faceStylizer: FaceStylizer? = null

   init {
       setupFaceStylizer()
   }

   /**
    * FaceStylizer를 초기화합니다.
    * 선택된 modelPosition에 따라 다른 모델 파일을 로드합니다.
    */
   private fun setupFaceStylizer() {
       val baseOptionsBuilder = BaseOptions.builder()
       // Sets the model selection.
       baseOptionsBuilder.setModelAssetPath(
           when (modelPosition) {
               0 -> MODEL_PATH_COLOR_SKETCH
               1 -> MODEL_PATH_COLOR_INK
               2 -> MODEL_PATH_OIL_PAINTING
               else -> throw Throwable("Invalid model type position")
           }
       )

       try {
           // 옵션 빌드 및 FaceStylizer 인스턴스 생성
           val baseOptions = baseOptionsBuilder.build()
           val optionsBuilder = FaceStylizerOptions.builder()
               .setBaseOptions(baseOptions)

           val options = optionsBuilder.build()
           faceStylizer = FaceStylizer.createFromOptions(context, options)
       } catch (e: IllegalStateException) {
           // 일반적인 초기화 오류 처리
           faceStylizerListener?.onError(
               "Face stylizer failed to initialize. See error logs for " +
                       "details"
           )
           Log.e(
               TAG,
               "Face stylizer failed to load model with error: " + e.message
           )
       } catch (e: RuntimeException) {
           // GPU 미지원 기기에서 발생할 수 있는 오류 처리
           // This occurs if the model being used does not support GPU
           faceStylizerListener?.onError(
               "Face stylizer failed to initialize. See error logs for " +
                       "details", GPU_ERROR
           )
           Log.e(
               TAG,
               "Face stylizer failed to load model with error: " + e.message
           )
       }
   }

   /**
    * 주어진 Bitmap을 스타일 변환하여 결과를 반환합니다.
    * - 변환 시간(inference time)을 함께 측정합니다.
    */
   fun stylize(bitmap: Bitmap): ResultBundle {
       val mpImage = BitmapImageBuilder(bitmap).build()
       var timestampMs = System.currentTimeMillis()
       val result = faceStylizer?.stylize(mpImage)
       timestampMs = System.currentTimeMillis() - timestampMs

       return ResultBundle(result, timestampMs)
   }

   /**
    * 스타일링 결과를 Bitmap으로 변환합니다.
    *
    * @param result 스타일링된 얼굴 정보와 추론 시간을 포함한 ResultBundle
    * @return 스타일링된 얼굴 이미지의 Bitmap, 또는 스타일링 결과가 없는 경우 null
    */
    @OptIn(ExperimentalStdlibApi::class)
    fun convertStylizedFaceToBitmap(result: ResultBundle): Bitmap? {
       // 결과가 없거나 스타일링된 이미지가 없는 경우 null 반환
       if (result.stylizedFace == null ||     result.stylizedFace.stylizedImage().getOrNull() == null) {
           return null
       }

       // 스타일링된 얼굴 이미지 정보 가져오기
       val image = result.stylizedFace
       // 이미지 데이터를 ByteBuffer로 추출
       val byteBuffer =     ByteBufferExtractor.extract(image.stylizedImage().get())

       // 이미지 크기 정보 가져오기
       val width = image.stylizedImage().get().width
       val height = image.stylizedImage().get().height

       // 추출한 정보로 새 Bitmap 생성
       val bitmap = createBitmap(width, height)
       // ByteBuffer의 픽셀 데이터를 Bitmap에 복사
       bitmap.copyPixelsFromBuffer(byteBuffer)
       return bitmap
    }


   /**
    * 사용이 끝난 후 FaceStylizer 리소스를 해제합니다.
    */
   fun close() {
       faceStylizer?.close()
   }

   /**
    * 스타일링 결과와 처리 시간을 묶어 반환하는 데이터 클래스입니다.
    */
   data class ResultBundle(
       val stylizedFace: FaceStylizerResult?, // 스타일링된 결과 (FaceStylizerResult)
       val inferenceTime: Long,  // 처리 소요 시간 (ms 단위)
   )

   companion object {
       // 모델 파일 경로 상수
       const val MODEL_PATH_OIL_PAINTING = "face_stylizer_oil_painting.task"
       const val MODEL_PATH_COLOR_INK = "face_stylizer_color_ink.task"
       const val MODEL_PATH_COLOR_SKETCH = "face_stylizer_color_sketch.task"
       const val OTHER_ERROR = 0
       const val GPU_ERROR = 1
       private const val TAG = "FaceStylizationHelper"
   }


   /**
    * FaceStylizer 오류 발생 시 호출되는 리스너 인터페이스
    */
   interface FaceStylizerListener {
       fun onError(error: String, errorCode: Int = OTHER_ERROR)
   }
}

5.4 Helper 초기화하기

FaceStylizerHelper 를 MainScreen 에서 사용하기 위해서 생성해서 넘겨줍니다.

MainActivity.kt 수정

class MainActivity : ComponentActivity() {

    private lateinit var faceStylizationHelper: FaceStylizationHelper

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // TODO
        // Activity 레벨에서 Helper 생성
        faceStylizationHelper = FaceStylizationHelper(
            modelPosition = 0, // 기본 모델 포지션 설정 (예: Color Sketch)
            context = this,
            faceStylizerListener = object : FaceStylizationHelper.FaceStylizerListener {
                override fun onError(error: String, errorCode: Int) {
                    Log.e("MainActivity", "FaceStylizer Error: $error (code: $errorCode)")
                    // 필요하면 Toast 띄우거나 UI 업데이트 가능
                }
            }
        )

        enableEdgeToEdge()

        setContent {
            FaceStylizerTestTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    // TODO
                    // MainScreen에 helper를 넘긴다!
                    MainScreen(
                        modifier = Modifier.padding(innerPadding),
                        helper = faceStylizationHelper
                    )
                }
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // Activity 종료 시 리소스 해제
        faceStylizationHelper.close()
    }
}

5.4 이미지에 스타일 적용하기

사용자가 갤러리에서 이미지를 선택 해서 "스타일 적용하기"를 클릭하면,

해당 이미지의 Uri를 가져와 Bitmap으로 변환한 후

val bitmap = loadBitmapFromUri(context, uri)

/**
* Helper 함수: Uri를 Bitmap으로 변환
*/
fun loadBitmapFromUri(context: Context, uri: Uri): Bitmap? {
   return try {
       context.contentResolver.openInputStream(uri)?.use {
           BitmapFactory.decodeStream(it)
       }
   } catch (e: Exception) {
       Log.e("MainScreen", "Failed to load Bitmap from Uri", e)
       null
   }
}

Face Stylizer helper 에 전달하는 코드를 생성해줍니다.

stylized 된 resultconvertStylizedFaceToBitmap(result) 을 통해 스타일링 된 결과 값을 반환합니다.

 bitmap?.let {
    val result = helper.stylize(it)
    onStylized(helper.convertStylizedFaceToBitmap(result))
 }

스타일 적용하기 버튼 전체 코드

StyleizeButton.kt

@Composable
fun StylizeButton(
   uri: Uri,
   context: Context,
   helper: FaceStylizationHelper,
   onStylized: (Bitmap?) -> Unit
) {
   Box(
       modifier = Modifier.fillMaxWidth(),
       contentAlignment = Alignment.TopCenter
   ) {
       Button(onClick = {
           val bitmap = loadBitmapFromUri(context, uri)
           bitmap?.let {
               val result = helper.stylize(it)
               onStylized(helper.convertStylizedFaceToBitmap(result))
           }
       }) {
           Text(text = "스타일 적용하기")
       }
   }
}

/**
* Helper 함수: Uri를 Bitmap으로 변환
*/
fun loadBitmapFromUri(context: Context, uri: Uri): Bitmap? {
   return try {
       context.contentResolver.openInputStream(uri)?.use {
           BitmapFactory.decodeStream(it)
       }
   } catch (e: Exception) {
       Log.e("MainScreen", "Failed to load Bitmap from Uri", e)
       null
   }
}

5.5 Compose 화면에 변환 결과 표시

Compose에서는 Image 컴포저블을 사용해 변환된 Bitmap을 바로 화면에 표시할 수 있습니다.

@Composable
private fun StylizedImage(bitmap: Bitmap) {
   Image(
       bitmap = bitmap.asImageBitmap(),
       contentDescription = "Stylized Image",
       modifier = Modifier
           .fillMaxWidth(0.8f) 
           .aspectRatio(1f)
   )
}

asImageBitmap() 확장 함수를 사용하여 Bitmap을 Compose 이미지로 변환합니다.

5.6 MainScreen 전체 코드

화면에 보기좋게 맞추기 위해 일부 컴포넌트 modifier 값을 조정한 코드입니다.

MainScreen.kt

@Composable
fun MainScreen(
   modifier: Modifier = Modifier,
   helper: FaceStylizationHelper
) {
   var selectedImageUri by remember { mutableStateOf<Uri?>(null) }
   var stylizedBitmap by remember { mutableStateOf<Bitmap?>(null) }

   val context = LocalContext.current

   Column(
       modifier = modifier.fillMaxSize(),
       verticalArrangement = Arrangement.spacedBy(16.dp),
       horizontalAlignment = Alignment.CenterHorizontally
   ) {
       // 갤러리 열기 버튼
       GalleryPicker(
           onImageSelected = { uri ->
               selectedImageUri = uri
           }
       )

       // 선택한 이미지 표시
       selectedImageUri?.let { uri ->
           SelectedImage(uri)
          
           StylizeButton(
               uri = uri,
               context = context,
               helper = helper,
               onStylized = { stylizedBitmap = it }
           )

           // 스타일 적용된 이미지 표시
           stylizedBitmap?.let { styledBitmap ->
               StylizedImage(styledBitmap)
           }
       }
   }
}

@Composable
private fun SelectedImage(uri: Uri) {
   AsyncImage(
       model = uri,
       contentDescription = "Selected Image",
       modifier = Modifier
           .fillMaxWidth(0.8f) 
           .aspectRatio(1f)
   )
}

@Composable
private fun StylizedImage(bitmap: Bitmap) {
   Image(
       bitmap = bitmap.asImageBitmap(),
       contentDescription = "Stylized Image",
       modifier = Modifier
           .fillMaxWidth(0.8f) 
           .aspectRatio(1f)
   )
}

5.7 결과 확인

5.8 [심화] 다양한 스타일 모델로 전환하기

MainActivity에서는 기본적으로 Color Sketch 스타일 모델을 사용하도록 설정되어 있습니다. 현재는 modelPosition = 0으로 고정되어 있으며, 앱을 실행하면 항상 "Color Sketch" 스타일로 변환됩니다.

MainActivity.kt

// Activity 레벨에서 Helper 생성
faceStylizationHelper = FaceStylizationHelper(
   modelPosition = 0, // 기본 모델 포지션 설정 (예: Color Sketch)
   context = this,
   faceStylizerListener = object : FaceStylizationHelper.FaceStylizerListener {
       override fun onError(error: String, errorCode: Int) {
           Log.e("MainActivity", "FaceStylizer Error: $error (code: $errorCode)")
           // 필요하면 Toast 띄우거나 UI 업데이트 가능
       }
   }
)

✨ 심화 도전: 다양한 스타일 전환 기능 추가하기

다양한 스타일을 선택해 바꾸는 기능은 Mediapipe 자체를 이해하는 것보다, 오히려 Compose 상태 관리, 리컴포지션 흐름을 활용하는 부분이 더 큰 비중을 차지합니다. 따라서, 이 부분은 "심화" 주제로 남겨두었습니다.

아래 목표를 스스로 구현해보세요!

목표

설명

1

여러 스타일 모델을 선택할 수 있도록 UI를 추가하세요 (예: 드롭다운, 버튼 목록 등)

2

사용자가 모델을 선택하면, 해당 스타일 모델로 FaceStylizationHelper를 다시 초기화하세요

3

새로운 스타일 모델을 적용한 후, 갤러리에서 선택한 이미지를 스타일링해보세요

4

필요하다면 모델 변경 시, 현재 선택된 이미지를 자동으로 다시 스타일링하는 기능도 도전해보세요!

6.1 개요

제공된 모델에서 지원하지 않는 새로운 스타일로 얼굴을 변환해야 하는 경우, 사전 학습된 모델을 사용자 데이터와 MediaPipe Model Maker를 사용하여 맞춤 설정할 수 있습니다 . 이 모델 수정 도구는 사용자가 제공한 데이터를 사용하여 모델의 일부를 미세 조정합니다. 이 방법은 새 모델을 처음부터 학습하는 것보다 빠르며, 특정 애플리케이션에 맞게 모델을 조정할 수 있습니다.

다음 섹션에서는 Model Maker를 사용하여 미리 빌드된 모델을 얼굴 스타일 지정을 위해 사용자 데이터로 재학습하는 방법을 보여줍니다. 이 데이터는 MediaPipe Face Stylizer 에서 사용할 수 있습니다 .

MediaPipe Model Maker 패키지는 디바이스 내 머신 러닝(ML) 모델을 사용자 정의하기 위한 low-code 솔루션입니다.

이 노트북은 스타일화된 얼굴의 스타일을 실제 사람 얼굴로 전환하기 위해 얼굴 스타일라이저 모델을 사용자 지정하는 처음부터 끝까지의 프로세스를 보여줍니다.

모델 학습은 아래의 Colab 환경에서 진행합니다.

Colab 링크

모델학습 커스터마이징 가이드

6.2 설치 명령어

pip install mediapipe-model-maker

다음 코드를 사용하여 필요한 Python 클래스를 가져옵니다.

from google.colab import files
import os
import tensorflow as tf
assert tf.__version__.startswith('2')

from mediapipe_model_maker import face_stylizer
from mediapipe_model_maker import image_utils

import matplotlib.pyplot as plt

6.3 데이터 준비 및 검토

얼굴 스타일라이저 모델을 재학습하려면 사용자가 스타일화된 얼굴 이미지 하나를 제공해야 합니다. 스타일화된 얼굴은 정면을 향하고 좌우 눈과 입이 모두 보이는 상태여야 합니다. 얼굴은 요, 피치, 롤 축을 기준으로 30도 미만의 미세한 회전만 있어야 합니다.

다음 예에서는 양식화된 얼굴 이미지를 제공하고 이미지를 시각화하는 방법을 보여줍니다.

style_image_path = 'color_sketch.jpg'
!wget -q -O {style_image_path} https://storage.googleapis.com/mediapipe-assets/face_stylizer_style_color_sketch.jpg
# @title Visualize the training image
import cv2
from google.colab.patches import cv2_imshow

style_image_tensor = image_utils.load_image(style_image_path)
style_cv_image = cv2.cvtColor(style_image_tensor.numpy(), cv2.COLOR_RGB2BGR)
cv2_imshow(style_cv_image)

6.4 데이터 세트 생성

얼굴 스타일라이저 모델을 훈련하기 위해 단일 훈련 이미지를 래핑하는 데이터 세트를 만들어야 합니다.

데이터 세트를 생성하려면 Dataset.from_image 메서드를 사용하여 style_image_path에 있는 이미지를 로드합니다. 얼굴 스타일라이저는 단일 스타일 이미지만 지원합니다.

data = face_stylizer.Dataset.from_image(filename=style_image_path)

6.5 모델 재학습

데이터 준비가 완료되면 얼굴 스타일러스 모델을 재학습하여 새로운 스타일에 적응시킬 수 있습니다. 이러한 모델 수정을 전이 학습 이라고 합니다 . 아래 지침에서는 이전 섹션에서 준비한 데이터를 사용하여 얼굴 스타일러스 모델을 재학습하여 원본 사람 얼굴에 만화 스타일을 적용합니다.

재교육 옵션 설정

훈련 데이터 세트 외에 재훈련을 실행하려면 몇 가지 필수 설정이 있습니다.

  1. 모델 아키텍처: SupportedModels클래스를 사용하여 모델 아키텍처를 지정합니다. 지원되는 유일한 아키텍처는 face_stylizer.SupportedModels.BLAZE_FACE_STYLIZER_256 입니다.
  2. 스왑 레이어: 이 매개변수는 학습된 스타일과 원시 얼굴 이미지 사이의 잠재 코드 레이어를 혼합하는 방법을 결정하는 데 사용됩니다. 잠재 코드는 [1, 12, 512] 모양의 텐서로 표현됩니다. 잠재 코드 텐서의 두 번째 차원을 레이어라고 합니다. 얼굴 스타일라이저는 스왑 레이어에서 두 잠재 코드의 가중 합을 생성하여 학습된 스타일과 원시 얼굴 이미지를 혼합합니다. 따라서 스왑 레이어는 [1, 12] 내의 정수입니다. 레이어가 많이 설정될수록 출력 이미지에 더 많은 스타일이 적용됩니다. 스타일 의미론과 레이어 인덱스 간에 명시적인 매핑은 없지만 얕은 레이어(예: 8, 9)는 얼굴의 전역적 특징을 나타내는 반면 깊은 레이어(예: 10, 11)는 얼굴의 세밀한 특징을 나타냅니다. 출력 스타일화된 이미지는 스왑 레이어 설정에 따라 달라집니다. 기본적으로 [8, 9, 10, 11]로 설정됩니다. 사용자는 ModelOptions를 통해 설정할 수 있습니다.
  3. 학습률 및 에포크: HParams 객체 learning_rate와 epoch를 사용하여 두 하이퍼파라미터를 지정합니다. learning_rate는 기본적으로 4e-4로 설정됩니다. epochs는 BlazeStyleGAN 모델을 미세 조정하는 데 필요한 반복 횟수를 정의하며, 기본적으로 100으로 설정됩니다. 학습률이 낮을수록 모델을 수렴하도록 재학습하는 데 필요한 에포크 횟수가 늘어납니다.
  4. 배치 크기: HParams객체 batch_size를 사용하여 지정합니다. 배치 크기는 인코더가 입력 이미지에서 추출한 잠재 코드 주변에서 샘플링하는 잠재 코드 샘플 수를 정의하는 데 사용됩니다. 잠재 코드 배치는 디코더를 미세 조정하는 데 사용됩니다. 배치 크기가 클수록 일반적으로 성능이 향상됩니다. 또한 하드웨어 메모리에 의해 제한됩니다. A100 GPU의 경우 최대 배치 크기는 8입니다. P100 및 T4 GPU의 경우 최대 배치 크기는 2입니다.
face_stylizer_options = face_stylizer.FaceStylizerOptions(
  model=face_stylizer.SupportedModels.BLAZE_FACE_STYLIZER_256,
  model_options=face_stylizer.ModelOptions(swap_layers=[10,11]),
  hparams=face_stylizer.HParams(
      learning_rate=8e-4, epochs=200, batch_size=2, export_dir="exported_model"
  )
)

재교육 실행

학습 데이터 세트와 재학습 옵션이 준비되었으므로 재학습 프로세스를 시작할 준비가 되었습니다. 이 프로세스는 GPU에서 실행되어야 하며, 사용 가능한 컴퓨팅 리소스에 따라 몇 분에서 몇 시간까지 걸릴 수 있습니다. GPU 런타임이 포함된 Google Colab 환경을 사용할 경우 아래 예시 재학습은 약 2분 정도 소요됩니다.

재교육 과정을 시작하려면 create()이전에 정의한 데이터 세트와 옵션을 사용하여 다음 방법을 사용하세요.

face_stylizer_model = face_stylizer.FaceStylizer.create(
  train_data=data, options=face_stylizer_options
)

6.6 성과평가

모델을 재학습한 후, 입력 스타일 이미지의 재구성 결과에 대한 주관적인 평가를 수행할 수 있습니다. 스타일 이미지의 주요 스타일 특징과 사람 얼굴이 잘 재구성되면, 모델은 해당 스타일에 수렴하는 것으로 정의되며 다른 원본 얼굴 이미지에도 적용할 수 있습니다. 스타일 이미지를 재구성할 수 없거나 재구성된 스타일 이미지에서 상당한 아티팩트가 관찰되면, 입력 스타일이 얼굴 스타일라이저 모델에 적합하지 않을 수 있습니다.

예제 모델을 평가하려면 아래와 같이 입력 스타일 이미지에 대해 실행하세요.

print('Input style image')
resized_style_cv_image = cv2.resize(style_cv_image, (256, 256))
cv2_imshow(resized_style_cv_image)

eval_output = face_stylizer_model.stylize(data)
eval_output_data = eval_output.gen_tf_dataset()
iterator = iter(eval_output_data)

reconstruct_style_image = (tf.squeeze(iterator.get_next()).numpy())
test_output_image = cv2.cvtColor(output_image, cv2.COLOR_RGB2BGR)
print('\nReconstructed style image')
cv2_imshow(test_output_image)

6.7 모델 내보내기

모델을 재학습한 후에는 애플리케이션의 MediaPipe와 함께 사용할 수 있도록 Tensorflow Lite 모델 형식으로 내보내야 합니다. 내보내기 과정에서 필요한 모델 메타데이터와 분류 레이블 파일이 생성됩니다.

애플리케이션에서 사용하기 위해 재학습된 모델을 내보내려면 다음 명령을 사용하세요.

face_stylizer_model.export_model()

Google Colab에서 다음 명령을 사용하여 모델을 나열하고 개발 환경에 다운로드하세요. 이 face_stylizer.task파일은 얼굴 스타일라이저 작업 라이브러리를 실행하는 데 필요한 세 개의 TFLite 모델로 구성되어 있습니다.

!ls exported_model
files.download('exported_model/face_stylizer.task')

6.8 재교육 매개변수

재학습 프로세스 실행 방식을 추가로 사용자 지정하여 학습 시간을 조정하고 재학습된 모델의 성능을 잠재적으로 향상시킬 수 있습니다. 이러한 매개변수는 선택 사항입니다 . FaceStylizerOptions클래스와 HParams클래스를 사용하여 이러한 추가 옵션을 설정하세요.

클래스 매개변수를 사용하여 FaceStylizerModelOptions기존 모델을 사용자 정의할 수 있습니다. 모델 정확도에 영향을 미치는 다음과 같은 사용자 정의 매개변수가 있습니다.

이 클래스를 사용하여 HParams모델의 학습 및 저장과 관련된 다른 매개변수를 사용자 정의합니다.

6.9 Android 앱에 통합

- assets 디렉토리에 face_stylizer_custom.task 파일 추가
- FaceStylizer 초기화 시 모델 경로를 새 파일로 지정:

FaceStylizationHelper.kt

val baseOptions = BaseOptions.builder()
    .setModelAssetPath("face_stylizer_custom.task")
    .build()

Face Stylizer 앱을 처음부터 직접 만들어보고, 기본 모델 적용뿐 아니라 다양한 스타일 전환, 심지어 커스텀 모델까지 성공적으로 다루었습니다.

이 모든 과정을 스스로 해낸 여러분은,
Android on-device AI 분야에 한 발 더 깊숙히 들어선 것입니다.

진심으로 축하드립니다!

🚀 What's next?

이제 여러분의 실력을 더 확장해볼 시간입니다.

✨ 추천 도전 과제

주제

설명

1. MediaPipe 다른 Vision Task 체험하기

Object Detection, Hand Tracking, Pose Landmark 등 다양한 Vision 솔루션을 MediaPipe로 직접 구현해보세요.

2. 실시간 카메라 스트림 적용

갤러리 이미지가 아니라, 카메라 실시간 입력에 스타일 변환을 적용해보세요. (약간 난이도 높음)

3. 커스텀 모델 다양화

하나의 스타일이 아니라, 다양한 커스텀 스타일을 전환할 수 있는 앱을 만들어보세요.

4. 성능 최적화

on-device 모델 실행 성능을 분석하고, 처리 속도 향상이나 메모리 최적화를 시도해보세요.

🧩 추천 예제 실습: MediaPipe 다른 예제 다루기

아래 MediaPipe Vision Task들도 Android에서 쉽게 사용할 수 있습니다.

👉MediaPipe Samples for Android on GitHub

➡️ 관심 있는 Task를 골라, 오늘 배운 흐름처럼 직접 Compose 앱에 적용해보세요.