• Tutorials >
  • PyTorch 사전 빌드된 라이브러리를 사용하는 네이티브 Android 애플리케이션 만들기
Shortcuts

PyTorch 사전 빌드된 라이브러리를 사용하는 네이티브 Android 애플리케이션 만들기

저자: Ivan Kobzarev

번역: 김현길, Ajin Jeong

이 레시피에서 배울 내용은:

  • 네이티브 코드 (C++) 에서 LibTorch API를 사용하여 Android 애플리케이션을 만드는 방법.

  • 이 애플리케이션에서 사용자 정의 연산자로 TorchScript 모델을 사용하는 방법.

이 앱의 전체 설정은 PyTorch Android Demo Application Repository 에서 찾을 수 있습니다.

설정

다음과 같은 패키지(및 종속성)가 설치된 Python 3 환경이 필요합니다:

  • PyTorch 1.6

안드로이드 개발을 위해 설치할 것들:

  • Android NDK

wget https://dl.google.com/android/repository/android-ndk-r19c-linux-x86_64.zip
unzip android-ndk-r19c-linux-x86_64.zip
export ANDROID_NDK=$(pwd)/android-ndk-r19c
  • Android SDK

wget https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
unzip sdk-tools-linux-3859397.zip -d android_sdk
export ANDROID_HOME=$(pwd)/android_sdk
  • Gradle 4.10.3

Gradle은 Android 애플리케이션을 위해 가장 많이 사용되는 빌드 시스템이며, 우리가 만들 애플리케이션을 빌드하기 위해 필요합니다. Gradle 을 command line에서 사용하기 위해 다운로드하고 path에 추가하십시오.

wget https://services.gradle.org/distributions/gradle-4.10.3-bin.zip
unzip gradle-4.10.3-bin.zip
export GRADLE_HOME=$(pwd)/gradle-4.10.3
export PATH="${GRADLE_HOME}/bin/:${PATH}"
  • JDK

Gradle은 JDK가 필요하기에, JDK를 설치하고 환경 변수 JAVA_HOME 을 설정하여 JDK 위치를 가리키도록 합니다. 예를 들어 OpenJDK를 설치한다면 이 설명 을 따릅니다.

  • Android용 OpenCV SDK

사용자 지정 연산자는 OpenCV 라이브러리를 사용하여 구현합니다. OpenCV를 Android를 위해 사용하려면 사전 빌드된 Android용 OpenCV 라이브러리를 다운로드해야 됩니다. OpenCV releases page 에서 다운로드합니다. 압축을 풀고 환경 변수 OPENCV_ANDROID_SDK 를 설정합니다.

사용자 지정 C++ 연산자로 TorchScript Model 준비하기

TorchScript는 사용자 정의 C++ 연산자를 사용하는 것을 허용하며, 사용자 정의 연산 관련된 세부사항은 the dedicated tutorial 여기에서 읽을 수 있습니다.

결과적으로 사용자 지정 연산자를 사용하는 모델을 스크립팅 할 수 있습니다. 이 사용자 지정 연산자는 OpenCV의 cv::warpPerspective 함수를 사용합니다.

import torch
import torch.utils.cpp_extension

print(torch.version.__version__)
op_source = """
#include <opencv2/opencv.hpp>
#include <torch/script.h>

torch::Tensor warp_perspective(torch::Tensor image, torch::Tensor warp) {
  cv::Mat image_mat(/*rows=*/image.size(0),
                    /*cols=*/image.size(1),
                    /*type=*/CV_32FC1,
                    /*data=*/image.data_ptr<float>());
  cv::Mat warp_mat(/*rows=*/warp.size(0),
                   /*cols=*/warp.size(1),
                   /*type=*/CV_32FC1,
                   /*data=*/warp.data_ptr<float>());

  cv::Mat output_mat;
  cv::warpPerspective(image_mat, output_mat, warp_mat, /*dsize=*/{64, 64});

  torch::Tensor output =
    torch::from_blob(output_mat.ptr<float>(), /*sizes=*/{64, 64});
  return output.clone();
}

static auto registry =
  torch::RegisterOperators("my_ops::warp_perspective", &warp_perspective);
"""

torch.utils.cpp_extension.load_inline(
    name="warp_perspective",
    cpp_sources=op_source,
    extra_ldflags=["-lopencv_core", "-lopencv_imgproc"],
    is_python_module=False,
    verbose=True,
)

print(torch.ops.my_ops.warp_perspective)


@torch.jit.script
def compute(x, y):
    if bool(x[0][0] == 42):
        z = 5
    else:
        z = 10
    x = torch.ops.my_ops.warp_perspective(x, torch.eye(3))
    return x.matmul(y) + z


compute.save("compute.pt")

이 코드 조각은 compute.pt 파일을 생성합니다. 이 파일은 사용자 지정 연산자인 my_ops.warp_perspective 을 사용하는 TorchScript 모델입니다.

실행하려면 개발용 OpenCV를 설치해야 합니다. 리눅스 시스템은 다음 명령어를 통해 설치할 수 있습니다: CentOS:

yum install opencv-devel

Ubuntu:

apt-get install libopencv-dev

Android 애플리케이션 만들기

compute.pt 를 만들었으면 Android 애플리케이션 내에서 이 TorchScript 모델을 사용하겠습니다. Java API를 이용해서 Android상에서 일반적인 TorchScript 모델(사용자 지정 연산자 없이)을 사용하고자 한다면 여기 를 살펴 보십시오. 이 예제에서는 사용자 지정 연산자(my_ops.warp_perspective)를 사용해서 위와 같은 방밥을 사용할 수 없습니다. 기본 TorchScript 실행이 이 사용자 지정 연산자를 찾지 못하기 때문입니다.

연산자 동륵은 PyTorch Java API에 노출이 되지 않기에, Android 애플리케이션 네이티브 부분(C++)을 빌드하고, Android용 동일한 사용자 지정 연산자를 LibTorch C++ API를 이용해서 구현하고 등록해야 합니다. 연산자가 OpenCV 라이브러리를 사용하기에, 사전 빌드된 OpenCV Android 라이브러리와 OpenCV의 동일한 함수를 이용합니다.

Android 애플리케이션을 NativeApp 폴더 내에서 생성해 봅시다.

mkdir NativeApp
cd NativeApp

Android 애플리케이션 빌드 설정하기

Android 애플리케이션 빌드는 메인 gradle 부분과 네이티브 빌드 Cmake 부분으로 이루어집니다. 여기 나열된 목록은 전체 파일 목록입니다. 그래서 전체 구조를 새로이 만들고자 한다면 코드를 별도로 추가하지 않아도 결과로 나온 Android 애플리케이션을 빌드하고 설치할 수 있습니다.

Gradle 빌드 설정하기

이러한 gradle 설정 파일울 추가해야 합니다: build.gradle, gradle.properties, settings.gradle. 추가적인 Android Gradle 빌드 설정은 여기 에서 찾을 수 있습니다.

NativeApp/settings.gradle

include ':app'

NativeApp/gradle.properties

android.useAndroidX=true
android.enableJetifier=true

NativeApp/build.gradle

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

NativeApp/build.gradle 안에서 Android gradle 플러그인 버전을 `3.5.0`으로 명기합니다. 이 버전이 최신 버전은 아닙니다. 그럼에도, PyTorch Android gradle 빌드가 이 버전을 사용해서 우리도 이 버전을 사용합니다.

NativeApp/settings.gradle 이 보여주듯이 이 프로젝트는 app 이라는 모듈 하나만 포함하며, 이 모듈이 Android 애플리케이션이 됩니다.

mkdir app
cd app

NativeApp/app/build.gradle

apply plugin: 'com.android.application'

repositories {
  jcenter()
  maven {
    url "https://oss.sonatype.org/content/repositories/snapshots"
  }
}

android {
  configurations {
    extractForNativeBuild
  }
  compileSdkVersion 28
  buildToolsVersion "29.0.2"
  defaultConfig {
    applicationId "org.pytorch.nativeapp"
    minSdkVersion 21
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    externalNativeBuild {
      cmake {
        arguments "-DANDROID_STL=c++_shared"
      }
    }
  }
  buildTypes {
    release {
      minifyEnabled false
    }
  }
  externalNativeBuild {
    cmake {
      path "CMakeLists.txt"
    }
  }
  sourceSets {
    main {
      jniLibs.srcDirs = ['src/main/jniLibs']
    }
  }
}

dependencies {
  implementation 'com.android.support:appcompat-v7:28.0.0'

  implementation 'org.pytorch:pytorch_android:1.6.0-SNAPSHOT'
  extractForNativeBuild 'org.pytorch:pytorch_android:1.6.0-SNAPSHOT'
}

task extractAARForNativeBuild {
  doLast {
    configurations.extractForNativeBuild.files.each {
      def file = it.absoluteFile
      copy {
        from zipTree(file)
        into "$buildDir/$file.name"
        include "headers/**"
        include "jni/**"
      }
    }
  }
}

tasks.whenTaskAdded { task ->
  if (task.name.contains('externalNativeBuild')) {
    task.dependsOn(extractAARForNativeBuild)
  }
}

이 gradle 빌드 스크립트는 pytorch_android 스냅샷에 대한 종속성을 등록합니다. 이러한 스냅샷은 nightly 채널에 게시됩니다.

이러한 스냅샷은 nexus sonatype 저장소에 게시되므로, 해당 저장소를 등록해 줍니다: https://oss.sonatype.org/content/repositories/snapshots.

애플리케이션 내부의 네이티브 빌드 부분에서는 LibTorch C++ API를 사용해야 합니다. 이를 위해 사전 빌드된 바이너리와 헤더에 접근을 해야 합니다. 바이너리와 헤더는 PyTorch Android 빌드에 미리 패키징되어 있습니다. 이러한 것들은 Maven 저장소에 올라가 있습니다.

gradle 종속성(aar 파일들)에서 사전 빌드된 PyTorch Android 라이브러리를 사용하기 위해 extractForNativeBuild 에 대한 설정을 추가로 등록해야 합니다. 이 설정을 종속성에 추가하고 설정의 정의를 마지막 부분에 넣어 줍니다.

extractForNativeBuild 태스크는 pytorch_android aar을 gradle 빌드 디렉토리에 압축을 푸는 extractAARForNativeBuild 태스크를 호출합니다.

Pytorch_android aar은 headers 폴더 안에 LibTorch 헤더를 포함하고 jni 폴더 내부에는 다른 Android ABI들을 위한 사전 빌드된 라이브러리들을 포함합니다: $ANDROID_ABI/libpytorch_jni.so, $ANDROID_ABI/libfbjni.so. 이런 것들을 네이티브 빌드에 이용해 봅시다.

네이티브 빌드는 build.gradle 내부에 아래와 같은 코드 라인들로 등록이 되어 있습니다:

android {
  ...
  externalNativeBuild {
    cmake {
      path "CMakeLists.txt"
    }
}
...
defaultConfig {
  externalNativeBuild {
    cmake {
      arguments "-DANDROID_STL=c++_shared"
    }
  }
}

네이티브 빌드를 위해 CMake 설정을 사용합니다. 우리가 사용하는 다양한 라이브러리들이 있기 때문에, STL과 동적으로 연결시키는 것도 명기합니다. 이에 대한 자세한 내용은 여기 에서 찾을 수 있습니다.

네이티브 빌드 CMake 설정하기

네이티브 빌드는 NativeApp/app/CMakeLists.txt 에서 설정합니다:

cmake_minimum_required(VERSION 3.4.1)
set(TARGET pytorch_nativeapp)
project(${TARGET} CXX)
set(CMAKE_CXX_STANDARD 14)

set(build_DIR ${CMAKE_SOURCE_DIR}/build)

set(pytorch_testapp_cpp_DIR ${CMAKE_CURRENT_LIST_DIR}/src/main/cpp)
file(GLOB pytorch_testapp_SOURCES
  ${pytorch_testapp_cpp_DIR}/pytorch_nativeapp.cpp
)

add_library(${TARGET} SHARED
    ${pytorch_testapp_SOURCES}
)

file(GLOB PYTORCH_INCLUDE_DIRS "${build_DIR}/pytorch_android*.aar/headers")
file(GLOB PYTORCH_LINK_DIRS "${build_DIR}/pytorch_android*.aar/jni/${ANDROID_ABI}")

target_compile_options(${TARGET} PRIVATE
  -fexceptions
)

set(BUILD_SUBDIR ${ANDROID_ABI})

find_library(PYTORCH_LIBRARY pytorch_jni
  PATHS ${PYTORCH_LINK_DIRS}
  NO_CMAKE_FIND_ROOT_PATH)
find_library(FBJNI_LIBRARY fbjni
  PATHS ${PYTORCH_LINK_DIRS}
  NO_CMAKE_FIND_ROOT_PATH)

# OpenCV
if(NOT DEFINED ENV{OPENCV_ANDROID_SDK})
  message(FATAL_ERROR "Environment var OPENCV_ANDROID_SDK is not set")
endif()

set(OPENCV_INCLUDE_DIR "$ENV{OPENCV_ANDROID_SDK}/sdk/native/jni/include")

target_include_directories(${TARGET} PRIVATE
 "${OPENCV_INCLUDE_DIR}"
  ${PYTORCH_INCLUDE_DIRS})

set(OPENCV_LIB_DIR "$ENV{OPENCV_ANDROID_SDK}/sdk/native/libs/${ANDROID_ABI}")

find_library(OPENCV_LIBRARY opencv_java4
  PATHS ${OPENCV_LIB_DIR}
  NO_CMAKE_FIND_ROOT_PATH)

target_link_libraries(${TARGET}
  ${PYTORCH_LIBRARY}
  ${FBJNI_LIBRARY}
  ${OPENCV_LIBRARY}
  log)

여기에서는 소스 파일 pytorch_nativeapp.cpp 하나만 등록합니다.

이전 단계인 NativeApp/app/build.gradle 에서, extractAARForNativeBuild 태스크를 사용해서 헤더와 네이티브 라이브러리를 빌드 디렉토리로 추출했습니다. 이것들을 위해 PYTORCH_INCLUDE_DIRSPYTORCH_LINK_DIRS 를 설정해 줍니다.

그 이후 libpytorch_jni.solibfbjni.so 라이브러리를 찾아 우리의 목표에 연결해 줍니다.

OpenCV 함수를 이용해서 사용자 지정 연산자 my_ops::warp_perspective 를 구현할 계획이었기에 libopencv_java4.so 를 연결해 줘야 합니다. 이 라이브러리는 설정 단계에서 다운로드한 Android용 OpenCV SDK에 패키징되어 있습니다. 이 설정 내부에서는 환경 변수 OPENCV_ANDROID_SDK 으로 찾을 수 있습니다.

또한 Android LogCat으로 로그를 남길 수 있도록 log 라이브러리도 연결합니다.

OpenCV Android SDK의 libopencv_java4.so 도 연결했기 때문에, 이 라이브러리를 NativeApp/app/src/main/jniLibs/${ANDROID_ABI} 에도 복사를 해야 합니다.

cp -R $OPENCV_ANDROID_SDK/sdk/native/libs/* NativeApp/app/src/main/jniLibs/

애플리케이션에 모델 파일 추가하기

애플리케이션 내부에 TorschScript 모델인 compute.pt 를 패키징하려면 모델 파일을 assets 폴더로 복사를 해야 합니다:

mkdir -p NativeApp/app/src/main/assets
cp compute.pt NativeApp/app/src/main/assets

Android 애플리케이션 매니페스트(Manifest)

모든 Android 애플리케이션은 매니페스트가 있습니다. 여기에 애플리케이션 이름, 패키지, 메인 액티비티를 명기합니다.

NativeApp/app/src/main/AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.pytorch.nativeapp">

    <application
        android:allowBackup="true"
        android:label="PyTorchNativeApp"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.DarkActionBar">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

소스코드

Java 코드

이제 MainActivity를 아래 파일에서 구현할 준비가 되었습니다

NativeApp/app/src/main/java/org/pytorch/nativeapp/MainActivity.java:

package org.pytorch.nativeapp;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class MainActivity extends AppCompatActivity {

  private static final String TAG = "PyTorchNativeApp";

  public static String assetFilePath(Context context, String assetName) {
    File file = new File(context.getFilesDir(), assetName);
    if (file.exists() && file.length() > 0) {
      return file.getAbsolutePath();
    }

    try (InputStream is = context.getAssets().open(assetName)) {
      try (OutputStream os = new FileOutputStream(file)) {
        byte[] buffer = new byte[4 * 1024];
        int read;
        while ((read = is.read(buffer)) != -1) {
          os.write(buffer, 0, read);
        }
        os.flush();
      }
      return file.getAbsolutePath();
    } catch (IOException e) {
      Log.e(TAG, "Error process asset " + assetName + " to file path");
    }
    return null;
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    final String modelFileAbsoluteFilePath =
        new File(assetFilePath(this, "compute.pt")).getAbsolutePath();
    NativeClient.loadAndForwardModel(modelFileAbsoluteFilePath);
  }
}

이전 단계에서 compute.ptNativeApp/app/src/main/assets 으로 복사했기 때문에, 이 파일은 Android 애플리케이션에 포함이 되는 asset이 되었습니다. Android 시스템은 그 파일에 접근할 수 있는 스트림만 제공합니다. 이 모듈을 LibTorch에서 사용하고자 한다면, 디스크 상에서 파일로 만들어야(materialize) 합니다. assetFilePath 함수는 asset 입력 스트림에서부터 데이터를 복사해서 디스크에 기록한 다음 파일의 절대 경로를 반환합니다.

액티비티의 OnCreate 메소드는 액티비티 생성 직후 호출됩니다. 이 메소드 안에서 assertFilePath 를 호출하고, 앞서 생성한 파일을 JNI 호출을 통해 네이티브 코드로 전달하는 NativeClient 클래스를 호출합니다.

NativeClient 는 내부에 private 클래스인 NativePeer 가 있는 헬퍼 클래스로서, 애플리케이션의 네이티브 부분과 같이 동작하는 클래스입니다. 이 클래스의 static 블록에선 이전 단계에서 추가했던 CMakeLists.txt 와 함께 빌드하는 libpytorch_nativeapp.so 를 읽습니다. static 블록은 NativePeer 를 처음 참조할 때에 같이 실행이 됩니다. NativeClient#loadAndForwardModel 안에서 일어납니다.

NativeApp/app/src/main/java/org/pytorch/nativeapp/NativeClient.java:

package org.pytorch.nativeapp;

public final class NativeClient {

  public static void loadAndForwardModel(final String modelPath) {
    NativePeer.loadAndForwardModel(modelPath);
  }

  private static class NativePeer {
    static {
      System.loadLibrary("pytorch_nativeapp");
    }

    private static native void loadAndForwardModel(final String modelPath);
  }
}

NativePeer#loadAndForwardModelnative 로 선언이 되어 있는데, Java를 위한 정의는 아닙니다. 이 메소드를 호출하면 JNI를 통해 NativeApp/app/src/main/cpp/pytorch_nativeapp.cpp 내부에 있는 libpytorch_nativeapp.so 의 C++ 메소드를 다시 가져옵니다.

네이티브 코드

이제 애플리케이션의 네이티브 부분을 작성할 준비가 되었습니다.

NativeApp/app/src/main/cpp/pytorch_nativeapp.cpp:

#include <android/log.h>
#include <cassert>
#include <cmath>
#include <pthread.h>
#include <unistd.h>
#include <vector>
#define ALOGI(...)                                                             \
  __android_log_print(ANDROID_LOG_INFO, "PyTorchNativeApp", __VA_ARGS__)
#define ALOGE(...)                                                             \
  __android_log_print(ANDROID_LOG_ERROR, "PyTorchNativeApp", __VA_ARGS__)

#include "jni.h"

#include <opencv2/opencv.hpp>
#include <torch/script.h>

namespace pytorch_nativeapp {
namespace {
torch::Tensor warp_perspective(torch::Tensor image, torch::Tensor warp) {
  cv::Mat image_mat(/*rows=*/image.size(0),
                    /*cols=*/image.size(1),
                    /*type=*/CV_32FC1,
                    /*data=*/image.data_ptr<float>());
  cv::Mat warp_mat(/*rows=*/warp.size(0),
                   /*cols=*/warp.size(1),
                   /*type=*/CV_32FC1,
                   /*data=*/warp.data_ptr<float>());

  cv::Mat output_mat;
  cv::warpPerspective(image_mat, output_mat, warp_mat, /*dsize=*/{8, 8});

  torch::Tensor output =
      torch::from_blob(output_mat.ptr<float>(), /*sizes=*/{8, 8});
  return output.clone();
}

static auto registry =
    torch::RegisterOperators("my_ops::warp_perspective", &warp_perspective);

template <typename T> void log(const char *m, T t) {
  std::ostringstream os;
  os << t << std::endl;
  ALOGI("%s %s", m, os.str().c_str());
}

struct JITCallGuard {
  torch::autograd::AutoGradMode no_autograd_guard{false};
  torch::AutoNonVariableTypeMode non_var_guard{true};
  torch::jit::GraphOptimizerEnabledGuard no_optimizer_guard{false};
};
} // namespace

static void loadAndForwardModel(JNIEnv *env, jclass, jstring jModelPath) {
  const char *modelPath = env->GetStringUTFChars(jModelPath, 0);
  assert(modelPath);
  JITCallGuard guard;
  torch::jit::Module module = torch::jit::load(modelPath);
  module.eval();
  torch::Tensor x = torch::randn({4, 8});
  torch::Tensor y = torch::randn({8, 5});
  log("x:", x);
  log("y:", y);
  c10::IValue t_out = module.forward({x, y});
  log("result:", t_out);
  env->ReleaseStringUTFChars(jModelPath, modelPath);
}
} // namespace pytorch_nativeapp

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *) {
  JNIEnv *env;
  if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
    return JNI_ERR;
  }

  jclass c = env->FindClass("org/pytorch/nativeapp/NativeClient$NativePeer");
  if (c == nullptr) {
    return JNI_ERR;
  }

  static const JNINativeMethod methods[] = {
      {"loadAndForwardModel", "(Ljava/lang/String;)V",
       (void *)pytorch_nativeapp::loadAndForwardModel},
  };
  int rc = env->RegisterNatives(c, methods,
                                sizeof(methods) / sizeof(JNINativeMethod));

  if (rc != JNI_OK) {
    return rc;
  }

  return JNI_VERSION_1_6;
}

이 목록은 꽤 긴데다 이런저런 것들이 혼합되어 있기 때문에 이 코드가 어떻게 동작하는지 이해하기 위해 제어 흐름을 따라가 보겠습니다. 제어 흐름이 처음 도착하는 곳은 JNI_OnLoad 입니다. 이 함수는 라이브러리를 읽어들인 이후 호출되며, NativePeer#loadAndForwardModel 가 호출되었을 때 네이티브 메소드를 등록할 책임을 가집니다. 여기에서는 pytorch_nativeapp::loadAndForwardModel 함수입니다.

pytorch_nativeapp::loadAndForwardModel 은 인자로 모델의 경로를 받습니다. 첫째로 인자의 const char* 값을 추출해서 torch::jit::load 로 모듈을 읽어 들입니다.

모바일용 TorchScript을 읽으려면, 가드(guards)를 설정해야 됩니다. 모바일 빌드는 더 작은 빌드 크기를 위한 autograd같은 기능을 지원하지 않기 때문입니다. 이 예제에서는 struct JITCallGuard 에 있는 autograd 기능입니다. 향후 변경될 수 있습니다. 최신 변경 사항을 추적하고자 한다면 아래를 확인하세요. PyTorch GitHub 내부 소스.

메소드 warp_perspective 구현과 등록은 tutorial for desktop build 내부에 있는 내용과 정확히 동일합니다.

앱 빌드하기

gradle에 Android SDK와 Android NDK를 명기하기 위해 NativeApp/local.properties 를 작성합니다.

cd NativeApp
echo "sdk.dir=$ANDROID_HOME" >> NativeApp/local.properties
echo "ndk.dir=$ANDROID_NDK" >> NativeApp/local.properties

결과 apk 파일을 빌드하기 위해 실행합니다:

cd NativeApp
gradle app:assembleDebug

연결된 디바이스에 앱을 설치하기:

cd NativeApp
gradle app::installDebug

그러고 나면 PyTorchNativeApp 아이콘을 클릭하여 앱을 디바이스에서 실행할 수 있습니다. 또는 command line에서 실행할 수도 있습니다:

adb shell am start -n org.pytorch.nativeapp/.MainActivity

Android logcat을 확인하려면:

adb logcat -v brief | grep PyTorchNativeApp

‘PyTorchNativeApp’ 태그의 로그에서 x, y, 모델 순전파의 결과를 확인할 수 있어야 합니다. 이러한 로그는 NativeApp/app/src/main/cpp/pytorch_nativeapp.cpp 내부의 log 함수에서 출력합니다.

I/PyTorchNativeApp(26968): x: -0.9484 -1.1757 -0.5832  0.9144  0.8867  1.0933 -0.4004 -0.3389
I/PyTorchNativeApp(26968): -1.0343  1.5200 -0.7625 -1.5724 -1.2073  0.4613  0.2730 -0.6789
I/PyTorchNativeApp(26968): -0.2247 -1.2790  1.0067 -0.9266  0.6034 -0.1941  0.7021 -1.5368
I/PyTorchNativeApp(26968): -0.3803 -0.0188  0.2021 -0.7412 -0.2257  0.5044  0.6592  0.0826
I/PyTorchNativeApp(26968): [ CPUFloatType{4,8} ]
I/PyTorchNativeApp(26968): y: -1.0084  1.8733  0.5435  0.1087 -1.1066
I/PyTorchNativeApp(26968): -1.9926  1.1047  0.5311 -0.4944  1.9178
I/PyTorchNativeApp(26968): -1.5451  0.8867  1.0473 -1.7571  0.3909
I/PyTorchNativeApp(26968):  0.4039  0.5085 -0.2776  0.4080  0.9203
I/PyTorchNativeApp(26968):  0.3655  1.4395 -1.4467 -0.9837  0.3335
I/PyTorchNativeApp(26968): -0.0445  0.8039 -0.2512 -1.3122  0.6543
I/PyTorchNativeApp(26968): -1.5819  0.0525  1.5680 -0.6442 -1.3090
I/PyTorchNativeApp(26968): -1.6197 -0.0773 -0.5967 -0.1105 -0.3122
I/PyTorchNativeApp(26968): [ CPUFloatType{8,5} ]
I/PyTorchNativeApp(26968): result:  16.0274   9.0330   6.0124   9.8644  11.0493
I/PyTorchNativeApp(26968):   8.7633   6.9657  12.3469  10.3159  12.0683
I/PyTorchNativeApp(26968):  12.4529   9.4559  11.7038   7.8396   6.9716
I/PyTorchNativeApp(26968):   8.5279   9.1780  11.3849   8.4368   9.1480
I/PyTorchNativeApp(26968):  10.0000  10.0000  10.0000  10.0000  10.0000
I/PyTorchNativeApp(26968):  10.0000  10.0000  10.0000  10.0000  10.0000
I/PyTorchNativeApp(26968):  10.0000  10.0000  10.0000  10.0000  10.0000
I/PyTorchNativeApp(26968):  10.0000  10.0000  10.0000  10.0000  10.0000
I/PyTorchNativeApp(26968): [ CPUFloatType{8,5} ]

이 앱의 전체 설정은 PyTorch Android 데모 애플리케이션 저장소 에서 찾을 수 있습니다.


이 튜토리얼이 어떠셨나요?

© Copyright 2021, PyTorch & PyTorch Korea Community.

Built with Sphinx using a theme provided by Read the Docs.

PyTorchKorea @ GitHub

파이토치 한국 사용자 모임을 GitHub에서 만나보세요.

GitHub로 이동

한국어 튜토리얼

한국어로 번역 중인 PyTorch 튜토리얼입니다.

튜토리얼로 이동

커뮤니티

다른 사용자들과 의견을 나누고, 도와주세요!

커뮤니티로 이동