기본 콘텐츠로 건너뛰기

Kotlin + NDK, OpenCV

원래 이 블로그는 영어로만 작성하려고 했었으나, 코틀린 프로젝트에서 OpenCV를 사용하는 방법에 대해 정리한 한글 블로그가 거의 없어서 이 참에 블로그 방문자 유입도 좀 늘릴 겸하여 이번 포스트는 한글로 작성하려고 한다.
절대 영어로 쓰기 귀찮아서 한글로 쓰는 게 아니다.
내가 좀 쫄보여서 그런지는 몰라도 간단한 테스트도 iterative하게 진행하는 게 마음이 편하다. 그래서
1. Kotlin
2. Java + NDK(C++)
3. Kotlin + NDK(C++)
4. Java + NDK(C++) + JNI + OpenCV
5. Kotlin + NDK(C++) + JNI + OpenCV
순으로 프로젝트를 생성하여 한 단계씩 통과시켜가며 넘어갈 생각이다.
그런데 결론부터 말하자면, OpenCV에서 Kotlin을 지원하지 않는 것으로 보인다. OpenCV의 라이브러리 폴더(OpenCV-android-sdk\sdk\native\libs\mips64)를 열어보면 libopencv_java3.so 파일은 찾을 수 있지만 libopencv_kotlin 비슷한 이름을 가진 파일은 없다. Kotlin에서 C++을 돌려봤다는 사실 정도에 만족하고 넘어가도록 하자… ㅠㅠ

alt text

환경

다음의 환경에서 진행한다.
* Android Studio 2.3
* OpenCV 3.3.0

Kotlin Project 생성

먼저 안드로이드 스튜디오에서 간단한 hello world 자바코드를 생성하여 코틀린코드로 변환해보자.
그냥 처음부터 코틀린으로 만들면 되지 왜 굳이 자바코드를 변환하고 앉아있느냐 할 수도 있는데 안드로이드 스튜디오 2.3에서는 그런 기능을 제공하지 않는다. ㅠㅠ
3.0부터는 아예 코틀린이 안드로이드 스튜디오에 빌트인으로 제공되면서 처음부터 코틀린 프로젝트를 만들 수 있게 된다 카더라.

프로젝트 생성

그냥 자바 기반 안드로이드 프로젝트를 만들면 된다.

alt text
어플리케이션명을 적당히 정해주자.
Company Domain은 소속된 회사이름이나 팀명 같은걸로 넣어주면 되고 별로 중요하진 않다.
Include C++ support는 여기선 굳이 체크하지 않는다. 나중에 NDK 연결할 때 필요하다.

alt text
호환가능한 최소 버전을 정해주자.
버전이 낮을 수록 더 구형폰에서도 돌아가지만 그만큼 신형 OS에서 제공하는 기능을 쓸 수 없어진다.
만약 OpenCV를 쓸 생각이라면 최소한 API 21 이상이 되게끔 해주어야 한다.

alt text
그냥 Empty Project 선택해주면 된다.

alt text
MainActivity의 이름을 바꿀 수 있는데 굳이 바꾸지 말자. 어차피 나중에 바꿀 수 있다.
Finish를 눌러주면 안드로이드 프로젝트가 하나 자바파일로 만들어진다.

Java 기반 프로젝트를 Kotlin 기반 프로젝트로 변환

이제 이 프로젝트를 코틀린 기반으로 바꿔보자.

alt text
제일 먼저 할 일은 코틀린 플러그인을 받는 것이다.
Ctrl + Alt + S를 누르면 설정창이 나온다.
맥알못이라 맥북 단축키는 모르겠다.
좌측 메뉴에서 Plugins를 선택한 후 검색창에 kotlin을 검색하면 Kotlin 플러그인이 뿅 나타난다. 설치 눌러놓고 화장실이라도 갔다 오자.

alt text
Ctrl + Shift + A를 눌르면 요런 쪼끄만 창이 나온다.
여기다가 ‘convert java file to kotlin file’이라고 치면 메뉴 하나가 검색되어 나오는데 엔터를 눌러주자.

alt text
그럼 프로젝트 내의 모든 자바파일이 자동으로 코틀린 파일로 바뀐다.
코틀린 파일의 확장자는 스크린샷에서 보이다시피 ‘.kt’이다.
안드로이드 스튜디오의 설정파일인 gradle 파일들은 바뀌지 않았기 때문에 얘네의 싱크를 맞춰줘야 한다.

alt text
다시 Ctrl + Shift + A를 눌러 ‘sync project with gradle files’라고 쳐주고 엔터 눌러주면 모든 gradle 파일이 알아서 싱크된다.
우린 정말 좋은 세상에 살고 있는 것 같다.

alt text
코틀린이 설정되지 않았다고 뭐가 뜨는데 컴파일러버전을 안 골랐단 뜻이다. 우측 상단의 ‘Configure’를 눌러주자.

alt text
들여다 볼 것 없이 그냥 OK 눌러주면 된다.
이제 빌드 버튼을 눌러보자.
단축키는 Alt + Shift + X이다.
아까도 말했다시피 맥 단축키는 모른다. ㅠㅠ

alt text
안드로이드 개발을 해봤다면 익숙할 헬로월드 화면이 나온다. 우왕.

Java + NDK(C++)

이번엔 Java 안드로이드 프로젝트를 만들고 여기에 C++ 코드를 얹어볼 것이다.
C++ 파일에서 리턴한 문자열을 MainActivity.java가 받아 출력하게끔 만들어보자.

프로젝트 생성

프로젝트 생성은 아까와 거의 똑같다.

alt text
차이점이 딱 2개가 있는데 그 중 하나가 위 스크린샷처럼 ‘Include C++ support’에 체크를 해주는 것이다.

alt text
쭉 넘기다 보면 아까는 없었던 이런 화면이 등장하는데 그냥 Toolchain Default로 선택해주면 된다.
C++ 11을 선택하면 어떻게 되는지는 모르겠다.
컴파일러 종류인 것 같은데 C++은 잘 몰라서…

NDK, LLDB 설치

NDK는 안드로이드 스튜디오에서 C/C++ 파일을 컴파일할 수 있게 해준다.
LLDB는 디버거라고 보면 된다.

alt text
Ctrl + Alt + S를 눌러서 설정창을 켠다.
Appearance & Behavior > System Settings > Android SDK에 들어가서 SDK Tools 탭을 선택 후 NDK와 LLDB, 그리고 CMake를 체크하고 Apply를 눌러주면 된다.

코드 이해

프로젝트 폴더를 뒤져보면
  • MainActivity.java
  • native_lib.cpp
위와 같은 파일들을 볼 수 있다.
먼저 우리에게 친근한 MainActivity.java부터 살펴보자.
package com.softmilktea.ndkexample;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

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

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}
일반적인 MainActivity.java랑 다른 곳이 두 군데 있다.
첫 번째는 static으로 감싸진 System.loadLibrary 메소드이고
두 번째는 파일 맨 끝자락에 있는 stringFromJNI라는 이름의 native 메소드이다.
static 블록은 내 기억에 의하면 저 안이 전역스코프가 되는 것으로 알고 있다.
근데 그건 별로 안 중요하고 그냥 cpp 디렉토리에 있는 native-lib.cpp 파일을 가져온다는 뜻이라는 것만 알아두자.
각 Activity에서 사용할 C/C++ 파일을 이런 식으로 연결해주는구나 하는 정도만 알면 된다.
구글에서 제공하는 공식 문서를 보면 내부적으로 Android.mk, Application.mk 같은 설정파일이 있고 거기서 파일간 연결구조를 뭐 어떻게 설정해주고 이런 게 있기는 하다.
하지만 구글에서 근무하시는 멋진 형들이 안드로이드 스튜디오가 이런 작업을 알아서 하게끔 다 만들어놨으니 NDK 개발에 참여할 생각이 아니라면 굳이 알 필요가 없다.
native 메소드는 그냥 직관적으로 알 수 있듯이 loadLibrary로 가져온 cpp 파일에 있는 메소드를 사용하기 위해서 인터페이스에서 추상메소드 가져오듯이 헤드만 따로 명시해놓은 것이다.
#include <jni.h>
#include <string>

extern "C" 
JNIEXPORT jstring JNICALL
Java_com_softmilktea_ndkexample_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
native-lib.cpp 파일을 열어보면 위와 같은 코드가 들어있다.
C/C++ 파일을 NDK로 빌드하기 위해서는 다음의 규칙을 따라야 한다.
1. 모든 자료형의 앞에는 j가 붙는다. ex) jstring, jint
2. 메소드의 선언부는 다음의 형태를 갖는다.
JNIEXPORT [Return Type] JNICALL Java_[Company Domain]_[Project Name]_[Activity Name]_[Method Name] (JNIEnv *env, jobject, [parameters]) 여기 색깔 넣어서 강조해야함
extern “C”라는 선언이 앞에 붙어있는데 굳이 없어도 무방하다.

alt text
빌드해보면 다음과 같이 ‘Hello from C++’이란 문구가 찍힌다.

코드 수정

대충 어떻게 돌아가는지 파악했으니 코드를 조금 고쳐보기로 하자.

alt text
MainActivity.java를 위 스크린샷처럼 고쳐주자.
기존에 있던 stringFromJNI 메소드 대신 intSumFromJNI 메소드를 만들어 사용할 것이다.

alt text
native-lib.cpp에 위 스크린샷처럼 intSumFromJNI 메소드를 추가해주자.

alt text
빌드해보면 잘 출력되는 것을 볼 수 있다.

Kotlin + NDK(C++)

이제 Java 파일을 Kotlin으로 고쳐보자.
매우 간단하다.
아까 Java 프로젝트 생성한 뒤 Ctrl+Shift+A 누르고 Configure 누르고 하던 그 작업 그대로 해주면 된다.
더 이상의 자세한 설명은 생략한다.

Java + NDK(C++) + JNI + OpenCV

드디어 대망의 OpenCV 사용 단계다.
크게 두 가지 방법이 있는데 ndk-build를 쓰는 방법과 CMake를 쓰는 방법이 있다.
ndk-build는 CMake가 나오기 전까지 쓰던 구식 방법이라고 한다.
javah 명령을 이용해서 어찌어찌 한다는데 환경변수도 건드리고 이것저것 해야 되는 게 많다. 그냥 CMake 쓰도록 하자.
+. javah는 java파일로부터 .h 파일을 자동으로 추출해주는 명령이라고 한다.

프로젝트 생성하기

그냥 Java+NDK와 똑같다.

alt text
다만 여기서 항목 두 개를 모두 체크하라고 하던데 체크 안하면 어떻게 되는지는 잘 모르겠다.
궁금한 분들은 체크 안하고 만들어서 결과 좀 알려주시길…

CMake 설치하기


alt text
Ctrl + Alt + S -> Appearance & Behavior > System Settings > Android SDK > SDK Tools
아까 설치한 NDK, LLDB 외에 여기서는 CMake 역시 필요하다.
설치해주도록 하자.

OpenCV 라이브러리 가져오기

Download

![alt text][]
https://github.com/opencv/opencv/releases
여기에 들어가서 최신버전을 받고 적당한 경로에 압축을 해제해주자.

Import


alt text
File > New > Import Module에 들어가서 OpenCV-android-sdk\sdk\java 폴더를 선택해주자.

alt text
이건 그냥 Finish만 눌러주면 된다.
다음과 같은 에러가 발생할 수도 있다.
Error:Failed to find target with hash string ‘android-14’ in: C:\Users\note\AppData\Local\Android\Sdk
Install missing platform(s) and sync
그냥 OpenCV를 쓰기 위해서 요구되는 최소한의 SDK가 없어서 그런 것이므로 ‘Install missing platform(s) and sync project’를 눌러 필요한 SDK와 Build Tool을 설치해주면 된다.

Gradle Sync


alt text
openCVLibrary 모듈의 build.gradle의 compileSdkVersion, buildToolsVersion, minSdkVersion, targetSdkVersion 값을 app 모듈의 build.gradle에 맞춰줘야 한다.

alt text
app 모듈의 build.gradle이 이렇다면

alt text
openCVLibrary 모듈의 build.gradle을 이렇게 바꿔주자.
변경하고 나면 Gradle 싱크가 안 맞는다고 위에 노란줄이 하나 생길 텐데 Sync Now를 살포시 눌러주면 된다.

OpenCV 모듈 사용 설정


alt text
File > Project 를 선택하여 위와 같은 창을 연 후, Module > app에서 Dependencies 탭을 선택한다.
오른쪽 위 구석에 박혀있는 녹색 + 버튼에서 Module dependency를 누른다.

alt text
openCVLibrary를 선택해주면

alt text
요렇게 app 모듈과의 의존성이 생긴다.
그리고 미안하지만 의존성이 뭔지는 잘 모른다.
아시는 분 있으면 알려주시면…;;

OpenCV 라이브러리 폴더 복사

이제 OpenCV 라이브러리 폴더에서 필요한 것을 프로젝트 내부로 직접 가져와야 한다.

alt text
우선 안드로이드 스튜디오의 좌측 패널에서 윗부분을 눌러 뷰를 Android에서 Project로 바꿔주자.

alt text
그리고 윈도우 탐색기를 열어 OpenCV 라이브러리가 있는 경로로 가서
OpenCV-android-sdk\sdk\native\libs 폴더를 찾아 복사한다.

alt text
방금 복사한 폴더를 프로젝트의 app/src/main 내부에 jniLibs라는 이름으로 붙여넣는다.
꼭 jniLibs일 필요는 없지만 일단 libs를 그대로 쓰면 내부적으로 충돌이 생기고
이 글에서는 해당 폴더의 이름이 jniLibs일 것으로 가정하고 모든 설정파일을 작성할 것이므로
굳이 다른 이름을 쓰실 분들은 주의하기 바란다.

CMake를 이용한 빌드옵션 설정

![alt text][opencv_cmakelists]
app 폴더 안을 들여다보면 cmakelists.txt라는 파일이 있다.
여길 열어서 다음 명령을 추가해주자.
set(pathOPENCV C:/Users/jinai/libraries/OpenCV-android-sdk)     # 각자 OpenCV-android-sdk 폴더가 있는 경로를 입력해주자.
set(pathPROJECT C:/Users/jinai/Camcha.Android/OpenCVExample)    # 각자 자신의 프로젝트가 있는 경로를 입력해주자.
set(pathLIBOPENCV_JAVA ${pathPROJECT}/app/src/main/JniLibs/${ANDROID_ABI}/libopencv_java3.so)

set(CMAKE_VERBOSE_MAKEFILE on)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")

include_directories(${pathOPENCV}/sdk/native/jni/include)

add_library( lib_opencv SHARED IMPORTED )

set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${pathLIBOPENCV_JAVA})
그리고 아래쪽으로 내려보면 이런 코드가 있다.
target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
다음처럼 고쳐주자.
target_link_libraries( # Specifies the target library.
                       native-lib

                       lib_opencv

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
이제 OpenCV를 사용할 준비가 모두 끝났다.
cpp파일을 만들거나 native-lib.cpp 파일을 열어서 opencv2의 여러 헤더파일을 include해서 사용하면 된다.

사용예제

다음은 카메라로부터 들어온 화면에 흑백필터를 거는 예제다.

AndroidManifest.xml

카메라를 쓰기 위해 다음처럼 퍼미션을 요청한다.
<uses-permission android:name="android.permission.CAMERA"/>
    <uses-feature android:name="android.hardware.camera" android:required="false"/>
    <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
    <uses-feature android:name="android.hardware.camera.front" android:required="false"/>
    <uses-feature android:name="android.hardware.camera.front.autofocus"  android:required="false"/>
카메라 화면을 넓게 쓰기 위해 activity element를 다음처럼 수정한다.
<activity android:name=".MainActivity"
            android:screenOrientation="landscape"
            android:configChanges="keyboardHidden|orientation">

activity_main.xml

기존의 hello world를 출력하기 위해 존재하던 TextView를 없애버리고 다음 코드를 추가하여 카메라 입력 화면을 띄울 수 있돌고 하자.
<org.opencv.android.JavaCameraView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/activity_surface_view" />

MainActivity.java

여긴 뭐… 굳이 이해하려고 하지 말자. 어차피 나도 다른 곳에서 베껴온거라…
알아야 할 부분이라면 static 영역에서 opencv_java3을 가져온다는 것과
ConvertRGBtoGray라는 메소드를 사용한다는 것 정도가 되겠다.
나머지는 사용하면서 귀여운 버그들과 함께 차차 알아가자.
package com.softmilktea.opencvexample;  // 각자의 패키지명으로 수정

import android.annotation.TargetApi;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.SurfaceView;
import android.view.WindowManager;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Mat;


public class MainActivity extends AppCompatActivity
        implements CameraBridgeViewBase.CvCameraViewListener2 {

    private static final String TAG = "opencv";
    private CameraBridgeViewBase mOpenCvCameraView;
    private Mat matInput;
    private Mat matResult;

    public native void ConvertRGBtoGray(long matAddrInput, long matAddrResult);


    static {
        System.loadLibrary("opencv_java3");
        System.loadLibrary("native-lib");
    }



    private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                {
                    mOpenCvCameraView.enableView();
                } break;
                default:
                {
                    super.onManagerConnected(status);
                } break;
            }
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        setContentView(R.layout.activity_main);


        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //퍼미션 상태 확인
            if (!hasPermissions(PERMISSIONS)) {

                //퍼미션 허가 안되어있다면 사용자에게 요청
                requestPermissions(PERMISSIONS, PERMISSIONS_REQUEST_CODE);
            }
        }

        mOpenCvCameraView = (CameraBridgeViewBase)findViewById(R.id.activity_surface_view);
        mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
        mOpenCvCameraView.setCvCameraViewListener(this);
        mOpenCvCameraView.setCameraIndex(0); // front-camera(1),  back-camera(0)
        mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
    }

    @Override
    public void onPause()
    {
        super.onPause();
        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    @Override
    public void onResume()
    {
        super.onResume();

        if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "onResume :: Internal OpenCV library not found.");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_2_0, this, mLoaderCallback);
        } else {
            Log.d(TAG, "onResum :: OpenCV library found inside package. Using it!");
            mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }
    }

    public void onDestroy() {
        super.onDestroy();

        if (mOpenCvCameraView != null)
            mOpenCvCameraView.disableView();
    }

    @Override
    public void onCameraViewStarted(int width, int height) {

    }

    @Override
    public void onCameraViewStopped() {

    }

    @Override
    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {

        matInput = inputFrame.rgba();

        if ( matResult != null ) matResult.release();
        matResult = new Mat(matInput.rows(), matInput.cols(), matInput.type());

        ConvertRGBtoGray(matInput.getNativeObjAddr(), matResult.getNativeObjAddr());

        return matResult;
    }



    //여기서부턴 퍼미션 관련 메소드
    static final int PERMISSIONS_REQUEST_CODE = 1000;
    String[] PERMISSIONS  = {"android.permission.CAMERA"};


    private boolean hasPermissions(String[] permissions) {
        int result;

        //스트링 배열에 있는 퍼미션들의 허가 상태 여부 확인
        for (String perms : permissions){

            result = ContextCompat.checkSelfPermission(this, perms);

            if (result == PackageManager.PERMISSION_DENIED){
                //허가 안된 퍼미션 발견
                return false;
            }
        }

        //모든 퍼미션이 허가되었음
        return true;
    }



    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch(requestCode){

            case PERMISSIONS_REQUEST_CODE:
                if (grantResults.length > 0) {
                    boolean cameraPermissionAccepted = grantResults[0]
                            == PackageManager.PERMISSION_GRANTED;

                    if (!cameraPermissionAccepted)
                        showDialogForPermission("앱을 실행하려면 퍼미션을 허가하셔야합니다.");
                }
                break;
        }
    }


    @TargetApi(Build.VERSION_CODES.M)
    private void showDialogForPermission(String msg) {

        AlertDialog.Builder builder = new AlertDialog.Builder( MainActivity.this);
        builder.setTitle("알림");
        builder.setMessage(msg);
        builder.setCancelable(false);
        builder.setPositiveButton("예", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id){
                requestPermissions(PERMISSIONS, PERMISSIONS_REQUEST_CODE);
            }
        });
        builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface arg0, int arg1) {
                finish();
            }
        });
        builder.create().show();
    }
}

native-lib.cpp

#include <jni.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;

extern "C" {
    JNIEXPORT void JNICALL
    Java_com_softmilktea_camcha_MainActivity_ConvertRGBtoGray(
            JNIEnv *env, jobject /* this */,
            jlong matAddrInput, jlong matAddrResult) {

        Mat &matInput = *(Mat *)matAddrInput;
        Mat &matResult = *(Mat *)matAddrResult;

        cvtColor(matInput, matResult, CV_RGBA2GRAY);
    }
}
opencv2의 core/core.hpp와 imgproc/imgproc.hpp를 include하였다.
cv라는 네임스페이스를 쓰고 있다.
http://docs.opencv.org/3.1.0/d2/d75/namespacecv.html
여기에 들어가보면 cv 네임스페이스 안에 무엇무엇이 있는지를 확인해볼 수 있다.

styles.xml

app/src/main/res/values/styles.xml이다.
앱의 상단에 뜨는 구린 타이틀바를 없애주기 위해 style 엘리먼트 안에 다음 코드를 추가한다.
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>

작동

잘 되더라 ㅎㅎ

alt text

OpenCV 사용에 대한 참고 및 코드출처 : http://webnautes.tistory.com/1054
Kotlin 부분 참고 : https://proandroiddev.com/android-ndk-interaction-of-kotlin-and-c-c-5e19e35bac74
NDK 부분 참고 : http://tigapgmr.tistory.com/1

댓글

이 블로그의 인기 게시물

Running Anaconda Python on Jupyter Notebook with Docker in Windows

I think there is no IDE more suitable for studying machine learning or deep learning with Python than Jupyter Notebook. However, it is so difficult to construct a Jupyter environment without crushing with the existing Python environment. So, I decided to make an only-for-deep-learning environment in the Ubuntu Docker container to avoid all annoyance. I will assume that you already installed Docker. Environment Ubuntu 17.04 LTS Anaconda 5.0.0 (containing Python 3.6) Creating Docker Container First of all, we are going to create a Docker container. The world is going better day after day! You can get a Docker image containing Anaconda that someone created and simultaneously make a container with the image you just downloaded just with this one-line command. docker run - it --name jupyter -p 8000:8000 --volume /c/Users/jinai/Dropbox/HaveToLearnToRun/CSE/3_1_MachineLearning/jupyter_workspace:/root/jupyter_workspace continuumio/anaconda3 /bin/bash It’s quite complex. So let m

Cooperation Tools That Support Scrum Development Process

We have to cooperate for our project, and we need to decide which tool to use. One of our mentors recommended 3 cooperation tools which support scrum board function, and suggested we learn about that tools, put into shape, and share them one another. So, to follow his order, I write this post. Scrum - Agile Software Development Process First of all, we should know what the hell the SCRUM is. I referred here  and wiki . In agile software development process, there are 3 elements that consist of it - roles, artifacts, workflow. Roles Product Owner They ponder on what they make. Scrum Master They ponder on how they make. Scrum Team They just make T_T Artifacts Product Backlog It contains general requirements. (I don't sure it is right explanation for product backlog.) User Stories It contains detail requirements. Estimates It contains the order of priority of all requirements(user stories). Workflow Iteration & Incremental Developme