TensorFlow/Tensorflow Lite

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

kim선달 2020. 7. 27. 21:07
Select Language

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

 

1편: JNI 로 C++ 함수 호출하기 https://cppmagister.tistory.com/7

2편: Tensorflow Lite C++ 코드 연결하기

 

 

쓰다보니 내용이 상당히 많군요..

이제 얼마 안남았으니 힘 내시길 바랍니다 ㅎㅎ

 

1. Tensorflow Lite C++ 연결

제가 편의를 위해 Tensorflow Lite C++ 라이브러리와 wrapper 를 미리 빌드해서 만들어놓은 repository가 있습니다(https://github.com/lackhole/Tensorflow-Lite)

CPU, GPU, NNAPI 모두 가능하니 위 레포를 쓰는게 편하실겁니다

제가 주기적으로 업데이트를 하고 있으니, 원본을 빌드하나 저걸 쓰나 차이는 없습니다

여기서는 제가 만든 레포를 이용하여 진행하겠습니다

만약 직접 원본 소스를 빌드하고 싶다면, https://cppmagister.tistory.com/6 글을 참고해주세요

 

ㄱ. Tensorflow Lite prebuilt libraries & wrapper

터미널을 통해 cpp 디렉터리에 들어간 후, 레포를 받아줍니다

cd app/src/main/cpp
git clone https://github.com/lackhole/Tensorflow-Lite

받고 난 이후에는 레포의 브랜치를 android로 바꿔준 후, 모든 서브모듈들을 다운받아줍니다.

cd Tensorflow-Lite
git checkout android
git submodule update --init --recursive

 

 

다운로드가 완료되면, Android Studio를 아무곳이나 클릭해 다운로드한 파일들을 Android Studio 에서 인덱싱을 완료할 때 까지 기다려줍니다.

인덱싱이 완료되고, 아래처럼 파일들이 다 나오면 Tensorflow Lite를 사용할 준비가 끝났습니다.

lackhole's prebuilt libraries & wrapper

ㄴ. 새로운 CMakeLists.txt 만들기 및 기존 CMakeLists.txt 수정

그럼 이제 Tensorflow Lite Wrapper (이하 CuteModel)을 사용할 C++ 클래스를 만들고, 이를 연결할 CMakeLists.txt 를 cpp 폴더 안에 다음 처럼 만들어줍니다

이름 짓기가 너무 힘들다..

Java 에서 JNI 를 이용해 C++ 코드를 호출 할 때는 자원 관리를 잘 해야 합니다.

원래 C++ 코드에서처럼 main 함수가 없고, Java 호출 부분이 main 함수의 역할을 대신 하기 때문에 C++의 모든 리소스를 담는 객체를 하나 만들고, 이 객체를 Java 에서 다루는게 일반적입니다.

예시에서는 그냥 스태틱으로 만들겠습니다..ㅎㅎ

여러분들이 실제로 쓰실 때는 상황에 알맞는 인터페이스들을 잘 설계하셔야 합니다

 

위에서 만들어준 cpp 디렉토리 안의 CMakeLists.txt 를 아래처럼 수정해서, tflite 를 JustClass.cpp 에 연결해줍니다.

나중에 보시다가 어 저거 뭐야 하고 지우실 수 가 있는데, z 와 log 를 무조건 추가해 주어야 합니다.

안그러면 링크 시 에러가 납니다

CMAKE_MINIMUM_REQUIRED(VERSION 3.3)
PROJECT(lackhole)

SET(CMAKE_CXX_STANDARD 14)

add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/Tensorflow-Lite")

add_library(lackhole SHARED
        JustClass.cpp)

target_link_libraries(lackhole PUBLIC tflite z log)

 

그리고 1편에서 만든 app 디렉토리 안의 CMakeLists.txt

  • add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp")

  • target_link_libraries(native-lib lackhole tflite)

두 줄을 아래처럼 추가해줍니다.

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_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp")

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

target_link_libraries(native-lib lackhole tflite)
target_include_directories(native-lib PUBLIC)

 

ㄷ. build.gradle(app) 수정

미리 빌드된 라이브러리가 android 버전에 따라 armeabi-v7 와 arm64-v8a 두 종류로 나뉘어져 있는데, 이것을 build.gradle 에 명시해 주어야 합니다.

 

ndk 부분을 각각 release 와 debug 안에 아래 처럼 붙여넣어줍니다. (debug 가 없으면 만드시면 됩니다)

그리고 sourceSets 안에 빌드된 라이브러리가 포함된 경로를 넣어줍니다.

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'
            ndk {
                abiFilters "armeabi-v7a", "arm64-v8a"
            }
        }

        debug {
            ndk {
                abiFilters "armeabi-v7a", "arm64-v8a"
            }
        }
    }

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    
    sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/cpp/Tensorflow-Lite/lib']
        }
    }
}

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'

}

 

ㄹ. 빌드 해보기

 

아래 처럼 gradle sync, refresh C++, make project 를 차례차례 해 줍니다.

보통 make project 만 하면 되는데, 가끔 안 될 수가 있어서 3개 모두 해 줍니다.

 

gradle sync
Reload CMake Project
make project
성공!

모두 에러가 나지 않으시면 성공입니다

 

2. CuteModel 사용하기

 

JustClass.hpp

#ifndef MY_APPLICATION_JUSTCLASS_H
#define MY_APPLICATION_JUSTCLASS_H

#include "cutemodel/CuteModel.hpp"

class JustClass {
public:
    static ct::CuteModel model;
};


#endif //MY_APPLICATION_JUSTCLASS_H

CuteModel 헤더를 넣고, CuteModel 객체를 하나 만들어줍니다

 

JustClass.cpp

#include "JustClass.h"

ct::CuteModel JustClass::model;

스태틱이니까 정의해주는것도 까먹지 맙시다 ㅎㅎ

기본 생성자가 있어서 저렇게만 써 줘도 됩니다.

 

예시로 사용할 모델은 Google 에서 만든 Object Detection 모델입니다.

원래 tflite 파일로 되어 있던 것을 순수 바이너리 헤더로 바꾸었습니다.

이처럼 바이트 버퍼를 이용하여 Tensorflow Lite Interpreter 을 빌드하는 것도 가능합니다.

파일로도 빌드할 수 있습니다.

 

Interpreter 빌드, 인풋 넣기, 추론, 아웃풋을 뱉는 함수를 native 함수에서 만들어줍니다.

 

 

 

 

C++ 부분은 여기를 참고하셔서 따라하시면 됩니다 https://cppmagister.tistory.com/18

예전 코드가 날아가서 예시로 들 Java 메서드들을 만들고 하려니 시간이 오래 걸리네요. 

정말 필수적인 부분들은 다 들어갔으니 CuteModel 사용법만 익히시면 금방 가능할겁니다! ㅠㅠ

1, 2편 작성하는데 벌써 5시간이 넘게 지났군요..

이번주 안에 글 작성을 마치도록 하겠습니다

 

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