Android에 머신러닝 탑재하기 다섯번째 시간으로 MediaPipe 공식 홈페이지에 나와있는 Hello world 예제를 하나하나 따라하여 Bazel 빌드까지 완료하는 것이 목표입니다. MediaPipe 공식 홈페이지 Android용 튜토리얼은 아래 링크를 통해 확인할 수 있습니다.
링크: https://google.github.io/mediapipe/getting_started/hello_world_android.html

(1) 프로젝트 준비 - 필요한 사항 점검하기

프로젝트를 진행하기 위해서 아래와 같이 환경이 구성되어 있어야 합니다.

  1. Bazel 설치(링크)
  2. MediaPipe 설치(링크)
  3. Android SDK와 NDK 설치

Bazel과 MediaPipe는 튜토리얼을 차례대로 따라왔다면 설치되어 있을 것이고 SDK도 Android Studio를 사용하고 있다면 설치되어 있을 것입니다. 그러므로 여기서는 NDK 설치하는 방법만 안내드립니다.

(1-1) Android Studio에서 File-Settings로 진입합니다.

(1-2) 검색창에 sdk를 검색하여 Appearance&Behavior - System Settings - Android SDK로 진입합니다.

(1-3) SDK Tools 탭을 클릭합니다.

(1-4) 아래 “Show Package Details” 앞의 체크박스를 활성화합니다. 이 옵션을 활성화하면 버전을 선택할 수 있습니다.

(1-5) NDK 버전을 선택한다. Bazel은 NDK 21버전 까지만 지원하므로 21버전을 선택합니다. 앞의 체크박스를 활성화하여 선택한 후 OK 버튼을 누릅니다.

(1-6) 변경을 확인하는 창이 뜨면 OK 버튼을 눌러 NDK를 설치합니다.

(1-7) 설치가 완료되면 Finish 버튼을 눌러 빠져나옵니다.

(2) Workspace 만들기

(2-1) 프로젝트 폴더를 만듭니다. 저는 편의를 위하여 폴더명을 “mediapipe-hello-world”로 정했습니다.

(2-2) 이전에 MediaPipe를 설치할 때 MediaPipe GitHub Repository를 클론해둔 곳으로 이동합니다. Mediapipe는 Workspace 구성에 필요한 파일과 소스를 공식 Repository에서 제공합니다. 필요한 파일과 폴더를 프로젝트 폴더로 옮깁니다. 필요한 파일/폴더 목록은 아래에 정리했습니다.

파일명 설명
thrid_party 빌드 스크립트를 모아둔 폴더.
.bazelrc Run Command 파일. 빌드 시 자동 실행될 스크립트.
.bazelversion 빌드에 사용할 버전을 명시한 파일.
.gitignore (선택) Git 업로드 시 무시할 파일 목록.
LICENSE 라이선스 관련 파일.
requirements.txt 빌드에 필요한 Python 패키지를 명시한 파일.
setup.py 빌드에 필요한 개발 환경을 구축해주는 Python 파일.
setup_android_sdk_and_ndk.sh Android SDK와 NDK를 설정하는 쉘스크립트 파일
setup_opencv.sh OpenCV를 설정하는 쉘스크립트 파일
WORKSPACE 개발에 필요한 패키지를 자동으로 다운로드 받아 개발 환경을 구축해주는 파일

(2-3) WORKSPACE 파일에서 파일 경로가 변경되어 제대로 실행이 안되는 경우가 있으므로 아래와 같이 수정이 필요합니다.

(2-3-1) Tensorflow 경로 수정(2021.11 기준)

“Tensorflow repo should always go after the other external dependencies.”라는 문장을 검색하면 아래와 같이 _TENSORFLOW_GIT_COMMIT과 _TENSORFLOW_SHA256 항목을 확인할 수 있습니다. 두 항목을 아래와 같이 수정해줍니다.

_TENSORFLOW_GIT_COMMIT
수정 전: 18a1dc0ba806dc023808531f0373d9ec068e64bf
수정 후: 52a2905cbc21034766c08041933053178c5d10e3

_TENSORFLOW_SHA256
수정 전: 85b90416f7a11339327777bccd634de00ca0de2cf334f5f0727edcb11ff9289a
수정 후: 06d4691bcdb700f3275fa0971a1585221c2b9f3dffe867963be565a6643d7f56

수정하면 아래와 같습니다.

(2-3-2) SDK와 NDK 경로 삽입

”# iOS basic build deps.”라는 문장을 검색 후 그 위에 Android SDK와 NDK 로컬 경로를 삽입합니다.

1
2
3
4
5
6
7
8
9
10
# You may run setup_android.sh to install Android SDK and NDK.
android_sdk_repository(
    name = "androidsdk",
    path = "{로컬 SDK 경로}"
)

android_ndk_repository(
    name = "androidndk",
    path = "{로컬 NDK 경로}"
)

(3) 프로젝트 구조 만들기

(3-1) 프로젝트 폴더에 “app” 폴더를 만듭니다.

(3-2) “app” 폴더 안에 “java” 폴더와 “res” 폴더를 생성합니다.

(3-3) “java” 폴더 안에는 “com/example/mediapipe/helloworld” 경로를 생성합니다.

(3-4) “res” 폴더 안에는 “layout” 폴더와 “values” 폴더를 생성합니다.

(4) 소스 코드 작성

(4-1) 메모장이나 워드패드에 아래 코드를 작성한 후 “app/java/com/example/mediapipe/helloworld” 경로 안에 “MainActivity.java”라는 이름으로 저장합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.mediapipe.helloworld;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }
}

(4-2) 아래 코드를 작성한 뒤 “app/res/layout” 경로 안에 “activity_main.xml”라는 이름으로 저장합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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">

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

(4-3) 아래 코드를 작성한 뒤 “app” 경로 안에 “AndroidManifest.xml”라는 이름으로 저장합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.mediapipe.helloworld">

  <uses-sdk
      android:minSdkVersion="19"
      android:targetSdkVersion="31" />

  <application
      android:allowBackup="true"
      android:label="${appName}"
      android:supportsRtl="true"
      android:theme="@style/AppTheme">
      <activity
          android:name="${mainActivity}"
          android:exported="true"
          android:screenOrientation="portrait">
          <intent-filter>
              <action android:name="android.intent.action.MAIN" />
              <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
      </activity>
  </application>

</manifest>

(4-4) 아래 코드를 작성한 뒤 “app” 경로 안에 “BUILD”라는 이름으로 저장합니다. “BUILD” 파일은 빌드 시 타겟을 명시해주는 파일입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
android_library(
    name = "basic_lib",
    custom_package = "com.example.mediapipe.helloworld",
    srcs = ["java/com/example/mediapipe/helloworld/MainActivity.java"],
    manifest = "AndroidManifest.xml",
    resource_files = glob(["res/**"]),
    deps = [
        "//third_party:androidx_constraint_layout",
        "//third_party:androidx_appcompat",
    ],
)

android_binary(
    name = "helloworld",
    custom_package = "com.example.mediapipe.helloworld",
    manifest = "AndroidManifest.xml",
    manifest_values = {
        "appName": "MediaPipe Hello World",
        "mainActivity": ".MainActivity",
    },
    multidex = "native",
    deps = [
        ":basic_lib",
    ],
)

(4-5) 마지막으로 아래 두 파일을 “app/res/values” 디렉토리에 생성합니다.

colors.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
</resources>

styles.xml

1
2
3
4
5
6
7
8
9
10
11
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

(5) 앱 빌드

(5-1) CMD 창에서 아래와 같이 명령어를 입력하여 앱을 빌드합니다.

1
bazel build -c opt --define MEDAPIPE_DISABLE_GPU=1 --action_env PYTHON_BIN_PATH="{Python 경로}" //app:helloworld

(6) 앱 설치

(6-1) 프로젝트 폴더에 “bazel-bin/app” 경로로 들어가면 helloworld.apk 파일을 확인할 수 있습니다.

(6-2) CMD 창에서 bazel-bin/app 폴더로 이동한 뒤 아래 명령어로 모바일 디바이스에 앱을 설치합니다.

1
adb install helloworld.apk

(6-3) 아래처럼 BUILD 파일에 설정한 이름대로 앱이 설치된 것을 확인할 수 있고 설치된 앱을 실행해보면 Hellow World가 정상적으로 출력되는 것 또한 확인할 수 있습니다.