TensorFlow/Tensorflow Lite

Android 에서 TensorFlow Lite 사용하기 (C/C++) - ( 1/2 )

kim선달 2020. 4. 5. 17:49
Select Language

Android 에서 TensorFlow Lite 사용하기 (C/C++)

 

1편: JNI 로 C++ 함수 호출하기

2편: Tensorflow Lite C++ 코드 연결하기 https://cppmagister.tistory.com/22

 

 

Android 에서 Tensorflow Lite 를 이용하려면 Java를 사용할 수도 있지만, JNI 를 통해 C++ 에서도 마찬가지로 할 수 있습니다.

C++에서 하게되면 모델을 숨기는 것이 좀 더 용이하고, 애초에 Tensorflow 가 C/C++ 로 배포되고 있기 때문이죠

이번 글에서는 JNI를 통해 C++로 Tensorflow Lite 를 사용하는 방법에 대해 알아보겠습니다.

 

 

1. NDK 설치

NDK 설치하는 순서 1>2>3>4

Android Studio 에서 새 프로젝트를 만든 후, NDK가 설치되어 있지 않다면 설치해줍니다.

공식 문서에 따르면 17 이상이면 아무거나 해도 상관없습니다

만약 안된다면, NDK 버전을 높여보세요

 

 

2. native-lib 생성

Java에는 C++ 코드를 불러오기 위한 Java Native Interface(JNI)가 있습니다.

이를 Android 에서 이용하기 위해서는 일반적으로 다음과 같이 인터페이스를 설계하게 됩니다.

1. Java wrapper 클래스(native-lib 에 접근하는)

2. C++ native-lib

3. C++ 코드

(상황에 따라 여기에 wrapper가 추가될 수 도 있습니다)

 

native-lib 는 쉽게 말해서 C++ 부분을 호출하는 API 를 제공하는 부분입니다. (정확하게는 Java native 메서드들을 implement 하는 부분입니다)

Java에서는 직접적으로 native-lib 에 정의된 함수를 호출하고, native-lib 에서는 전달된 인자를 잘 조합해 C++ 함수를 호출하게 됩니다.

반대로 C++ 에서 Java의 함수들을 호출할 수도 있는데, 최초에 Java 에서 JNI Wrapper 를 호출해서 여러 설정들을 해줘야 가능합니다.

이 글에서는 C++ 에서 Java 의 함수들을 호출하는 부분은 Tensorflow랑 딱히 관련이 없고 너무 Android 쪽 부분이라 하지않겠습니다. 나중에 기회가 되면 그 부분도 다루도록 하겠습니다

 

Android Studio 에서는 C++ 프로젝트를 설정하기 위해 두가지 툴을 제공하는데, 하나는 ndk-build이고 다른 하나는 cmake 입니다.

여기서는 CMake 를 통해 설정해 보겠습니다.

 

ㄱ. native-lib.cpp 생성

native-lib.cpp 생성

Project 뷰 보기 속성을 Project로 바꾼 다음, 

위 사진처럼 app/src/main 폴더 안에 cpp/jni 폴더를 만들고, 그 안에 native-lib.cpp를 만들어줍니다.

아직 native-lib.cpp 의 내용은 작성하지 않아도 됩니다.

 

ㄴ. CMakeLists.txt 생성

CMakeLists.txt 만들기

 

그 다음, app 폴더 안에 CMakeLists.txt를 만들어 준 뒤, 아래 내용을 복사해서 넣습니다

cmake_minimum_required(VERSION 3.3)
project(native-lib)

set(CMAKE_CXX_STANDARD 14)

set(JNI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/jni)
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/main/jni")

add_library(
        native-lib
        SHARED
        ${JNI_DIR}/native-lib.cpp
)

target_include_directories(native-lib PUBLIC)

 

아직 연동이 안되서 코드가 다 흰색으로 나올텐데, 이제 app폴더 안의 build.gradle의 설정을 바꿔주어야 합니다.

 

ㄷ. build.gradle(app)

아래에 있는 버전들은 저랑 여러분하고 다를 수 있으니, 반드시 externalNativeBuild 부분만 복사해서 넣어주세요

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdkVersion 26
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

}

 

externalNativeBuild 항목을 추가하면 Android Studio 위쪽에 아래 그림과 같은 알림이 뜰 텐데, Sync Now 를 눌러줍니다.

build.gradle 을 수정하고 나면 반드시 sync 를 해줘야 한다

 

잠시 시간이 지나고 성공했다는 메세지가 뜨면 다음으로 넘아가면 됩니다

성공!

성공했다면 Android Studio 에서 CMake를 통해 C++ 프로젝트(native-lib)가 연결된 것입니다.

 

 

3. Java Wrapper 만들기

NativeWrapper 클래스를 만든다

이제 native-lib.cpp 에 만들어줄 C++ 함수들을 호출할 Java 래퍼 클래스를 만들어줍시다.

위 그림처럼 NativeWrapper 이라는 클래스를 하나 만들어줍시다

그리고 아래처럼 코드를 넣어 native-lib을 로드하게 만들어줍시다

package com.example.myapplication;

public class NativeWrapper {
    
    static {
        System.loadLibrary("native-lib");
    }
    
}

 

라이브러리 로드는 런타임에 이루어지므로 빌드만 성공했다고 안심하기는 이릅니다!

확인을 위해 MainActivity 에서 해당 인스턴스를 만들어준 뒤, 빌드 후 휴대폰에서 돌아가는지 테스트 해보면 됩니다.

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import java.lang.annotation.Native;

public class MainActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        NativeWrapper wrapper = new NativeWrapper();
        
    }
}

가상 Android 기기에서도 native 코드가 작동하는지는 모르겠는데, 어차피 Tensorflow Lite를 사용하려면 실제 하드웨어 기기가 있어야 합니다

 

4. JNI 테스트

 

저는 잘 실행되는군요. 그럼 C++ 함수도 잘 불러와지는지 확인하기 위해 NativeWrapper.java 에 아래처럼 native 매서드를 하나 추가해줍니다

package com.example.myapplication;

public class NativeWrapper {
    
    static {
        System.loadLibrary("native-lib");
    }
    
    native String getHello();
    
}

 

native 매서드들은 만드시 C++ 에서 implement 해 주어야 합니다.

이제 아까 만든 native-lib.cpp 로 <jni.h>를 헤더에 추가해줍니다.

아까만든 native 메서드를 정의해주어야 하는데, 보통 NativeWrapper을 타이핑하면 자동완성으로 밑에 나옵니다

 

이게 자동완성이 안되면 매우 슬픈일이 생깁니다..

 

//
// Created by YongGyu Lee on 2020/07/27.
//

#include <jni.h>

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_myapplication_NativeWrapper_getHello(JNIEnv *env, jobject thiz) {
    return env->NewStringUTF("Hello, world!");
}

C++ 에서 char 배열을 Java String 객체로 바꾸어 반환하는 함수입니다.

함수 이름은 더럽고 길지만(...) 엄연한 규칙에 따라 생성된 것이니 빨간줄이 뜨지 않는 이상 바꾸시면 안됩니다.

 

이제, 만든 테스트함수를 MainActivity.java 에서 호출해봅시다. 귀찮으니 토스트로..

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.Toast;

import java.lang.annotation.Native;

public class MainActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        NativeWrapper wrapper = new NativeWrapper();
        
        Toast.makeText(getApplicationContext(), wrapper.getHello(), Toast.LENGTH_LONG).show();
    }
}

 

 

Toast 함수를 추가한 다음, 어플리케이션을 실행 해 보면...

Hello, world!

위 사진처럼 아래에 Toast가 잠시 나왔다가 사라진다면 성공입니다!

 

Java 에서 C++ 함수를 호출하는 방법을 알았으니, 이제 native-lib 에서 Tensorflow Lite 함수를 호출하는 부분만 넣어주면 끝입니다!

2편을 참고해주세요: https://cppmagister.tistory.com/22

 

 

 

+ 궁금한 점이나 단순한 내용이라도 댓글이라도 남겨주시면 글쓴이에게 정말 큰 힘이 됩니다! +