반응형

OpenCV 배포시 포함되어 있는 얼굴 검출 C++코드를 NDK를 이용하여 Android에서 동작하도록 수정하였습니다.  

 

안드로이드 +  NDK  카메라 기본코드에 단순히 C++코드만 옮겨오면 되는 줄 알았는데 고려해야 하는게 생각보다 많군요.. 

 

사용한 C++ 코드는 다음 위치에서 가져왔습니다.

https://github.com/opencv/opencv/blob/master/samples/cpp/tutorial_code/objectDetection/objectDetection.cpp




다음 순서로 설명합니다. 

 


1. 프로그램 흐름 및 실행결과


2. 코드 수정 및 사용방법


3. 캡쳐 버튼 추가하기


4. 검출된 얼굴 갯수 반환받기



2016. 12. 09 최초작성

2019. 08. 15  OpenCV 4.1.1용 추가

2020. 06. 27 Android 10.0, OpenCV 4.3

2021. 05. 05 Andorid 11.0, OpenCV 4.5.1

 

 

1. 프로그램 흐름 및 실행결과

1-1. Android 프로젝트의 assets 폴더에 넣었던 XML 파일(Harr cascade 트레이닝 데이터)는 프로젝트 deploy시 같이 폰에 올라가지만 읽어오기 위해서는 안드로이 폰의 내부 저장소(Internal Storage)로 옮기는 작업이 필요합니다. (JAVA read_cascade_file)

 

 

1-2. 내부 저장소로부터 XML 파일(Harr cascade 트레이닝 데이터)을 읽어와 CascadeClassifier 객체를 생성후 자바로 넘겨줍니다.(C++ loadCascade)




1-3. 카메라로부터 영상을 읽어올 때  전면 카메라의 경우 영상이 뒤집혀서 읽히기 때문에  180도 회전 시켜줘야 합니다.(JAVA onCameraFrame)

 

 

1-4. JAVA에서 영상이 들어오기 시작하면 CascadeClassifier 객체를 인자로 해서 호출되어 얼굴 검출 결과를 영상에 표시해줍니다. (C++ detect)




1-5. 최종 결과를 안드로이드폰의 화면에 보여지도록 결과 Mat 객체를 리턴합니다.(JAVA onCameraFrame)

 

 

실행결과입니다. 얼굴 위치와 눈 위치가 검출된 결과입니다.  

이상하게도 화면이 세로( Portrait ) 방향일 때에는 검출이 안되고  아래 화면처럼 가로(Landscape) 방향일 때에만 검출이 됩니다.

Portrait 방향일때 안되는 것은 OpenCV Camera API 문제인 듯 싶습니다. 

 

 

주의 할 점은 이미지 프로세싱 혹은 컴퓨터 비전 기술 특성상 장소에 따라 제대로 검출이 안되거나 오류가 있을 수 있습니다. 

가장 큰 원인은 장소마다 조명 상태가 다르기 때문입니다.  적절한 조명 상태에서 잘 검출이 됩니다.  






2. 코드 수정 및 사용방법

 

2-1.  다음 포스팅 중 하나를 참고하여 OpenCV 지원하는 안드로이드 프로젝트를 생성합니다.

참고로 현재 Android Studio에서 공식 지원하는 방법은 CMake 사용하는 방법입니다. 

 


Android NDK + OpenCV 카메라 예제 및 프로젝트 생성방법(ndk-build 사용)

http://webnautes.tistory.com/923 


Android NDK + OpenCV 카메라 예제 및 프로젝트 생성방법(CMake 사용)

http://webnautes.tistory.com/1054




2-2. 프로젝트 패널을 Project 뷰로 변경한 후  app / src / main을 선택한 상태에서 마우스 우클릭하여 보이는 메뉴에서 New > Directory를 선택합니다. 

 



assets라고 입력하고 OK 버튼을 누르면 assets 이름의 디렉토리가 생성됩니다. 

 

 




2-3. 아래 링크를 각각 클릭하여 해당 페이지로 이동하면, Ctrl + S를 눌러서 파일을 저장합니다.

Twibap 님이 알려주신 방법입니다. (크롬 웹브라우저에서만 가능한 방법입니다. )

 

https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_eye_tree_eyeglasses.xml

 

https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml



Ctrl키를 누른채 파일을 드래그하여 프로젝트 패널의 assets 디렉토리 위에서 왼쪽 마우스 버튼을 떼어서 복사해줍니다.

 




2-4. AndroidManifest.xml 파일에 외부 저장소 접근 권한을 추가해줍니다.

 

 

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

 




API 29이상에서 필요한 부분입니다.

 

   android:requestLegacyExternalStorage="true"

 





2-5. ndk-build 사용하도록 프로젝트 생성했다면 다음처럼 android 라이브러리를 사용하도록 Android.mk 파일을 수정합니다.

 

 

 

CMake 사용하도록 프로젝트 생성했다면 다음처럼 android 라이브러리를 사용하도록  CMakeLists.txt 파일을 수정합니다.  수정후 Sync Now를 클릭해줘야 합니다. 

 

 

find_library( android-lib android)

 

${android-lib}

 



오른쪽 상단에 보이는 Sync Now를 클릭합니다. 

또는 메뉴에서 File > Sync Project with Gradle Files를 선택합니다.





2-6.  자바 코드 수정합니다.

 




수정 1.  다음 노란색 코드들을 추가 또는 수정합니다.

 

read_cascade_file(); 

 

|| checkSelfPermission(WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED

 

, WRITE_EXTERNAL_STORAGE

 

&& grantResults[1] == PackageManager.PERMISSION_GRANTED

 

, WRITE_EXTERNAL_STORAGE

 



수정 2.  사용하는 카메라를 변경합니다.

 

 

수정 3.  기존 코드(흰색)를 주석처리하고 cpp에 추가할 jni함수를 위한 네이티브 메소드 선언(노란색)을 추가합니다.

 

    public native long loadCascade(String cascadeFileName );
    public native void detect(long cascadeClassifier_face, 
        long cascadeClassifier_eye, long matAddrInput, long matAddrResult);
    public long cascadeClassifier_face = 0;
    public long cascadeClassifier_eye = 0;

 



수정 4.  xml 파일을 가져오기 위한 메소드를 추가합니다. 

 

  • cpp 파일의 loadCascade 함수를 호출하도록 구현되어있는데 자바 함수를 사용하도록 변경해도 됩니다.
  • 현재 이미 파일을 copyFile 메소드를 이용해서 가져온 경우에 대한 처리가 빠져있습니다.  
   private void copyFile(String filename) {
        String baseDir = Environment.getExternalStorageDirectory().getPath();
        String pathDir = baseDir + File.separator + filename;

        AssetManager assetManager = this.getAssets();

        InputStream inputStream = null;
        OutputStream outputStream = null;

        try {
            Log.d( TAG, "copyFile :: 다음 경로로 파일복사 "+ pathDir);
            inputStream = assetManager.open(filename);
            outputStream = new FileOutputStream(pathDir);

            byte[] buffer = new byte[1024];
            int read;
            while ((read = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, read);
            }
            inputStream.close();
            inputStream = null;
            outputStream.flush();
            outputStream.close();
            outputStream = null;
        } catch (Exception e) {
            Log.d(TAG, "copyFile :: 파일 복사 중 예외 발생 "+e.toString() );
        }

    }

    private void read_cascade_file(){
        copyFile("haarcascade_frontalface_alt.xml");
        copyFile("haarcascade_eye_tree_eyeglasses.xml");

        Log.d(TAG, "read_cascade_file:");

        cascadeClassifier_face = loadCascade( "haarcascade_frontalface_alt.xml");
        Log.d(TAG, "read_cascade_file:");

        cascadeClassifier_eye = loadCascade( "haarcascade_eye_tree_eyeglasses.xml");
    }





추가한 코드에서 빨간선이 보이는 부분에서 Alt + Enter를 눌러서 필요한 패키지를 임포트 하도록합니다. 

 




수정 5. 기존 코드(흰색)를 주석처리하고 얼굴 검출하는 cpp 코드를 호출하는 코드(노란색)을 추가합니다. 

 

카메라로부터 영상을 가져올 때마다 호출되는 onCameraFrame 메소드에서  jni 함수 detect를 호출하도록 합니다. 

추가한 코드에서 빨간선이 보이는 부분에서 Alt + Enter를 눌러서 필요한 패키지를 임포트 하도록합니다. 

 

        Core.flip(matInput, matInput, 1);

        detect(cascadeClassifier_face,cascadeClassifier_eye, matInput.getNativeObjAddr(),
                matResult.getNativeObjAddr());

 



추가한 코드에서 빨간선이 보이는 부분에서 Alt + Enter를 눌러서 필요한 패키지를 임포트 하도록합니다. 




2-7.  Java 코드를 수정합니다.  진행한 방식에 따라 하는 방법이 다릅니다. 

 



CMake 방식이라면..

 

자바 코드에서 loadCascade 메소드에 각각  마우스 커서를 가져가면 보이는 빨간 전구를 클릭한 후, 보이는 메뉴에서  Create Function을 클릭합니다.

 



자바 코드의 loadCascade 메소드에 대응되는 cpp 함수가 생성된 후, 자바 코드에서 loadCascade 함수는 검은색으로 변합니다.

detect 메소드도 같은 방식으로 진행합니다.

 



두 메소드 이름이 모두 검은색으로 보여야합니다.

 




ndk-build 방식이라면..

 

자바코드 파일 MainActivity를 선택하고 마우스 우클릭하여 보이는 메뉴에서 External Tools > javah를 선택합니다. 

 

문제 없이 진행되면 기존 com_tistory_webnautes_useopencvwithndk_build_MainActivity.h 파일에 덮어쓰기 되었다는 메시지가 보입니다. 

 

 

ndk-build 사용하는 경우 2-8번을 진행합니다.

CMake 사용하는 경우 2-9번을 진행합니다. 




2-8.  com_tistory_webnautes_useopencvwithndk_build_MainActivity.h 파일을 열어보면 기존 함수 선언은 제거되고 자바 코드에 새로 추가된 네이티브 메소드 선언을 위한 함수 선언이 추가되어 있습니다.

 

 



main.cpp 파일을 다음처럼 수정합니다. com_tistory_webnautes_useopencvwithndk_build_MainActivity.h 파일에서 함수 선언을 복사해옵니다. detect, loadCascade 함수의 앞부분만 다릅니다. 

함수 내에 있는 코드만 복사해주세요. 두 함수외에 그외의 부분들은 차이 나는 부분을 복사해주면 됩니다. 

티스토리 신버전으로 바뀌면서 기존처럼 알려드릴 수 없으니 양애 부탁드립니다.

 

이제 안드로이드 디바이스에 설치해보면 됩니다. 

 



#include <jni.h>
#include "com_tistory_webnautes_useopencvwithndk_build_MainActivity.h"

#include <opencv2/opencv.hpp>
#include <android/log.h>

using namespace std;
using namespace cv;

extern "C"{

float resize(Mat img_src, Mat &img_resize, int resize_width){

    float scale = resize_width / (float)img_src.cols ;
    if (img_src.cols > resize_width) {
        int new_height = cvRound(img_src.rows * scale);
        resize(img_src, img_resize, Size(resize_width, new_height));
    }
    else {
        img_resize = img_src;
    }
    return scale;
}


JNIEXPORT jlong JNICALL Java_com_tistory_webnautes_useopencvwithndk_1build_MainActivity_loadCascade
        (JNIEnv *env, jobject type, jstring cascadeFileName_){

    const char *nativeFileNameString = env->GetStringUTFChars(cascadeFileName_, 0);

    string baseDir("/storage/emulated/0/");
    baseDir.append(nativeFileNameString);
    const char *pathDir = baseDir.c_str();

    jlong ret = 0;
    ret = (jlong) new CascadeClassifier(pathDir);
    if (((CascadeClassifier *) ret)->empty()) {
        __android_log_print(ANDROID_LOG_DEBUG, "native-lib :: ",
                            "CascadeClassifier로 로딩 실패  %s", nativeFileNameString);
    }
    else
        __android_log_print(ANDROID_LOG_DEBUG, "native-lib :: ",
                            "CascadeClassifier로 로딩 성공 %s", nativeFileNameString);


    env->ReleaseStringUTFChars(cascadeFileName_, nativeFileNameString);

    return ret;
}



JNIEXPORT void JNICALL Java_com_tistory_webnautes_useopencvwithndk_1build_MainActivity_detect
        (JNIEnv *env, jobject type, jlong cascadeClassifier_face, jlong cascadeClassifier_eye, jlong matAddrInput, jlong matAddrResult){

    Mat &img_input = *(Mat *) matAddrInput;
    Mat &img_result = *(Mat *) matAddrResult;

    img_result = img_input.clone();

    std::vector<Rect> faces;
    Mat img_gray;

    cvtColor(img_input, img_gray, COLOR_BGR2GRAY);
    equalizeHist(img_gray, img_gray);

    Mat img_resize;
    float resizeRatio = resize(img_gray, img_resize, 640);

    //-- Detect faces
    ((CascadeClassifier *) cascadeClassifier_face)->detectMultiScale( img_resize, faces, 1.1, 2, 0|CASCADE_SCALE_IMAGE, Size(30, 30) );


    __android_log_print(ANDROID_LOG_DEBUG, (char *) "native-lib :: ",
                        (char *) "face %d found ", faces.size());

    for (int i = 0; i < faces.size(); i++) {
        double real_facesize_x = faces[i].x / resizeRatio;
        double real_facesize_y = faces[i].y / resizeRatio;
        double real_facesize_width = faces[i].width / resizeRatio;
        double real_facesize_height = faces[i].height / resizeRatio;

        Point center( real_facesize_x + real_facesize_width / 2, real_facesize_y + real_facesize_height/2);
        ellipse(img_result, center, Size( real_facesize_width / 2, real_facesize_height / 2), 0, 0, 360,
                Scalar(255, 0, 255), 30, 8, 0);


        Rect face_area(real_facesize_x, real_facesize_y, real_facesize_width,real_facesize_height);
        Mat faceROI = img_gray( face_area );
        std::vector<Rect> eyes;

        //-- In each face, detect eyes
        ((CascadeClassifier *) cascadeClassifier_eye)->detectMultiScale( faceROI, eyes, 1.1, 2, 0 |CASCADE_SCALE_IMAGE, Size(30, 30) );

        for ( size_t j = 0; j < eyes.size(); j++ )
        {
            Point eye_center( real_facesize_x + eyes[j].x + eyes[j].width/2, real_facesize_y + eyes[j].y + eyes[j].height/2 );
            int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
            circle( img_result, eye_center, radius, Scalar( 255, 0, 0 ), 30, 8, 0 );
        }
    }


}
}





2-9. native-lib.cpp 파일을 다음처럼 수정합니다. detect, loadCascade 함수의 앞부분만 다릅니다. 

함수 내에 있는 코드만 복사해주세요. 두 함수외에 그외의 부분들은 차이 나는 부분을 복사해주면 됩니다. 

티스토리 신버전으로 바뀌면서 기존처럼 알려드릴 수 없으니 양애 부탁드립니다.

 

이제 안드로이드 디바이스에 설치해보면 됩니다. 

 

 

 

#include <jni.h>
#include <opencv2/opencv.hpp>

#include <android/log.h>

using namespace std;


using namespace cv;

float resize(Mat img_src, Mat &img_resize, int resize_width){

    float scale = resize_width / (float)img_src.cols ;
    if (img_src.cols > resize_width) {
        int new_height = cvRound(img_src.rows * scale);
        resize(img_src, img_resize, Size(resize_width, new_height));
    }
    else {
        img_resize = img_src;
    }
    return scale;
}


extern "C"
JNIEXPORT jlong JNICALL
Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_loadCascade(JNIEnv *env, jobject thiz, jstring cascade_file_name) {
    const char *nativeFileNameString = env->GetStringUTFChars(cascade_file_name, 0);

    string baseDir("/storage/emulated/0/");
    baseDir.append(nativeFileNameString);
    const char *pathDir = baseDir.c_str();

    jlong ret = 0;
    ret = (jlong) new CascadeClassifier(pathDir);
    if (((CascadeClassifier *) ret)->empty()) {
        __android_log_print(ANDROID_LOG_DEBUG, "native-lib :: ",
                            "CascadeClassifier로 로딩 실패  %s", nativeFileNameString);
    }
    else
        __android_log_print(ANDROID_LOG_DEBUG, "native-lib :: ",
                            "CascadeClassifier로 로딩 성공 %s", nativeFileNameString);


    env->ReleaseStringUTFChars(cascade_file_name, nativeFileNameString);

    return ret;

}extern "C"
JNIEXPORT void JNICALL
Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_detect(JNIEnv *env, jobject thiz,
                                                                  jlong cascade_classifier_face,
                                                                  jlong cascade_classifier_eye,
                                                                  jlong mat_addr_input,
                                                                  jlong mat_addr_result) {
    Mat &img_input = *(Mat *) mat_addr_input;
    Mat &img_result = *(Mat *) mat_addr_result;

    img_result = img_input.clone();

    std::vector<Rect> faces;
    Mat img_gray;

    cvtColor(img_input, img_gray, COLOR_BGR2GRAY);
    equalizeHist(img_gray, img_gray);

    Mat img_resize;
    float resizeRatio = resize(img_gray, img_resize, 640);

    //-- Detect faces
    ((CascadeClassifier *) cascade_classifier_face)->detectMultiScale( img_resize, faces, 1.1, 2, 0|CASCADE_SCALE_IMAGE, Size(30, 30) );


    __android_log_print(ANDROID_LOG_DEBUG, (char *) "native-lib :: ",
                        (char *) "face %d found ", faces.size());

    for (int i = 0; i < faces.size(); i++) {
        double real_facesize_x = faces[i].x / resizeRatio;
        double real_facesize_y = faces[i].y / resizeRatio;
        double real_facesize_width = faces[i].width / resizeRatio;
        double real_facesize_height = faces[i].height / resizeRatio;

        Point center( real_facesize_x + real_facesize_width / 2, real_facesize_y + real_facesize_height/2);
        ellipse(img_result, center, Size( real_facesize_width / 2, real_facesize_height / 2), 0, 0, 360,
                Scalar(255, 0, 255), 30, 8, 0);


        Rect face_area(real_facesize_x, real_facesize_y, real_facesize_width,real_facesize_height);
        Mat faceROI = img_gray( face_area );
        std::vector<Rect> eyes;

        //-- In each face, detect eyes
        ((CascadeClassifier *) cascade_classifier_eye)->detectMultiScale( faceROI, eyes, 1.1, 2, 0 |CASCADE_SCALE_IMAGE, Size(30, 30) );

        for ( size_t j = 0; j < eyes.size(); j++ )
        {
            Point eye_center( real_facesize_x + eyes[j].x + eyes[j].width/2, real_facesize_y + eyes[j].y + eyes[j].height/2 );
            int radius = cvRound( (eyes[j].width + eyes[j].height)*0.25 );
            circle( img_result, eye_center, radius, Scalar( 255, 0, 0 ), 30, 8, 0 );
        }
    }


}



3. 캡쳐 버튼 추가하기

영상에 검출된 얼굴 위치, 눈 위치를 그려넣는 코드와 최종 영상을 저장하는 코드간에 동기화를 맞추기 위해서 세마포어를 사용했습니다.

 

3-1. activity_main.xml 파일에 버튼을 추가합니다.

 

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="캡쳐" />




3-2. MainActivity.java 파일에 세마포어를 사용하기 위한 코드를 추가합니다. 

 

    private final Semaphore writeLock = new Semaphore(1);

    public void getWriteLock() throws InterruptedException {
        writeLock.acquire();
    }

    public void releaseWriteLock() {
        writeLock.release();
    }

 




3-3. 기존 코드에서 카메라 접근하는 부분을 세마포어로 둘러싸둡니다.

 

        try {
            getWriteLock();





        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        releaseWriteLock();

 




3-4. 버튼 클릭시 사진을 저장하기 위한 코드를 onCreate 메소드에 추가합니다. 

 

      Button button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {

                try {
                    getWriteLock();

                    File path = new File(Environment.getExternalStorageDirectory() + "/Images/");
                    path.mkdirs();
                    File file = new File(path, "image.jpg");

                    String filename = file.toString();

                    Imgproc.cvtColor(matResult, matResult, Imgproc.COLOR_BGR2RGBA);
                    boolean ret  = Imgcodecs.imwrite( filename, matResult);
                    if ( ret ) Log.d(TAG, "SUCESS");
                    else Log.d(TAG, "FAIL");


                    Intent mediaScanIntent = new Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                    mediaScanIntent.setData(Uri.fromFile(file));
                    sendBroadcast(mediaScanIntent);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


                releaseWriteLock();

            }
        });




버튼을 클릭하여 저장한 영상입니다. 

 



참고

https://www.javacodegeeks.com/2012/10/locking-with-semaphore-example.html




4. 검출된 얼굴 갯수 반환받기

4.1. native-lib.cpp의 Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_detect 함수 수정

 

1. 함수 반환형을 jint로 변환합니다. 



2. 함수에 리턴값 저장할 변수를 선언합니다.

 

 

3. 리턴 받을 검출된 얼굴 개수를 ret 변수에 대입합니다. 

 

4. 변수 ret를 리턴합니다.

 

4.2. MainActivity.java 수정

 

1. detect 함수 선언에서 리턴값을 int로 수정합니다.

  



2. onCameraFrame 메소드에서 detect의 리턴값을 출력합니다.

 

if ( ret != 0 )
Log.d(TAG, "face " + ret + " found");

 

 





반응형

포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
질문을 남겨주면 가능한 빨리 답변드립니다.

여러분의 응원으로 좋은 컨텐츠가 만들어집니다.
지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 ~~

유튜브 구독하기


제가 쓴 책도 한번 검토해보세요 ^^

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기

댓글을 달아 주세요

">
  1. 이전 댓글 더보기
  2. thumbnail
    name
    2020.07.28 19:29

    본 게시물 native-lib.cpp파일에 서
    float resizeRatio = resize(img_gray, img_resize, 640);부분에 사이즈를 640보다 작게하면
    속도는 빨라지는데 이미지인식 정확도가 떨어지는건가요?

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2020.07.28 20:14 신고

      영상을 축소했다면 다음 함수에서 1.1로 되어있는 파라미터를 1.1 이하로 낮춰보세요.

      detectMultiScale( img_resize, faces, 1.1, 2, 0|CASCADE_SCALE_IMAGE, Size(30, 30) );

      또는 원래 영상크기에서 1.1을 1.4로 증가시켜 보세요.

      이 숫자가 작아지면 정확도가 올라가고
      커지면 검출 속도가 빨라지는 대신 정확도가 떨어진다고 합니다.

      자세한 내용과 다른 파라미터에 대한 설명은 아래 링크에서 볼 수 있습니다.
      https://stackoverflow.com/a/20805153

    • thumbnail
      BlogIcon mystic
      2020.07.29 21:05

      친절한답변 정말 감사드립니다. ^^

  3. thumbnail
    이문규
    2020.08.07 14:52

    좋은 글 감사합니다.

    가로로 실행시켰을 때는 얼굴을 잘 검출하는데,
    세로로는 찾지못하는데, 혹시 어떤 이유인지, 해결법은 있는지 궁금합니다!

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2020.08.07 18:27 신고

      영상을 전체화면으로 보려면 영상 방향을 가로로 놓을수 없기때문입니다. OpenCV의 카메라 API 문제라 어쩔수 없습니다.

  4. thumbnail
    jyshin
    2020.08.28 20:15

    여기서 도움을 많이 받았습니다. 책도 사서보고 있습니다.
    PC에서 개발한 영상처리 알고리즘을 so로 만들어서 android 의 native daemon 이 handling 하게 처리하고자 합니다.
    물론 OpenCV로 영상처리를 하는 알고리즘인데.. 빌드 환경을 Cmake 와 ndk-build 어느 환경으로 가는게 좋을까요?
    둘다로도 android용 OpenCV를 add 해서 쓸 수 있는거 같은데..
    향후 VNDK로 AOSP의 framework과 분리하는 계획도 있거든요.. 이것과 상관없는건지..
    암튼 머가 더 유리 할까요?? 중간에 빌드 환경 갈아 타는게 어려울 것 같아서.. 미리 여쭙니다.

  5. thumbnail
    2020.09.27 20:27

    비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2020.09.27 20:40 신고

      검색해보니 간단한 방법은 없는 듯합니다.
      다음 링크에 있는 내용을 바탕으로 Android에서 OpenCV 처리 결과를 비디오 파일로 저장하는 방법을 찾아봐야 할듯합니다.

      https://stackoverflow.com/questions/28663864/recording-live-opencv-processing-on-android

    • thumbnail
      BlogIcon 바개런
      2020.09.27 21:55

      빠른 답변 감사드립니다 ~ 도전해보고 다시 찾아 올게요 !

  6. thumbnail
    Favicon of https://daubec.tistory.com BlogIcon Daub

    안녕하세요.

    게시글을 따라 안드로이드 스튜디오로 구동하는 것을 확인할 수 있었습니다.

    다만, OpenCV를 통해 카메라를 사용할 때 FHD 해상도인 1920x1080으로 설정하고 싶은데 이와 관련하여

    어떠한 방식으로 해야할지 잘 몰라 문의드립니다.

    감사합니다.

  7. thumbnail
    2021.01.15 12:56

    비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.01.15 12:59 신고

      수정전 이미지 저장안되었다면 안드로이드 버전이 높아서 생긴문제입니다. 매니페스트 파일에 퍼미션을 추가해야 해결됩니다.

  8. thumbnail
    2021.01.15 14:20

    비밀댓글입니다

  9. thumbnail
    2021.01.15 14:40

    비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.01.15 14:43 신고

      코드상으론 문제가 없습니다. 외부 저장소 접근 권한 문제로 보입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.01.15 14:45 신고

      메니페스트 파일에 다음은 이미 추가하셨겠군요

        <application
              android:requestLegacyExternalStorage="true"

  10. thumbnail
    2021.01.15 17:02

    비밀댓글입니다

  11. thumbnail
    2021.01.15 17:31

    비밀댓글입니다

  12. thumbnail
    2021.01.15 17:52

    비밀댓글입니다

  13. thumbnail
    2021.01.17 19:14

    비밀댓글입니다

  14. thumbnail
    Favicon of https://blogregister.tistory.com BlogIcon Blog Register

    안녕하세요. 이 글에 해당하는 부분까지는 구현한 상태고요
    이제 얼굴 검출을 통해 선글라스를 씌우는 것을 시도해보고 있는데요,
    https://webnautes.tistory.com/1348
    이것으로는 안드로이드 스튜디오에서 어떻게 구현해야 할지 감이 잘 안잡힙니다. ㅠ
    제가 이해력이 부족한걸 수도 있지만, 조금 더 친절한 설명이 가능하실까요

  15. thumbnail
    Favicon of https://ddd66.tistory.com BlogIcon dfd666

    안녕하세요 선생님 요즘 선생님 글을 보고 공부중인 학생입니다 선생님 글 덕분에 공부하는데 큰 도움이 되고있습니다 감사합니다 !
    하다가 안되는 부분이 있어 댓글남깁니다 ..!
    AndroidManifest.xml 파일에 외부 저장소 접근 권한을 추가했습니다 그리고 ndk-build 사용하도록 android.mk파일을 수정할려고하는데 main> jni 폴더가 보이지 않습니다. 왜 보이지 않을까요 ㅠㅠ?

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.04.12 17:07 신고

      폴더 이름이 바뀌었습니다. ndk-build를 위한 카메라 예제 문서에서 폴더이름을 확인해보세요

  16. thumbnail
    Favicon of https://ddd66.tistory.com BlogIcon dfd666

    선생님 버튼 캡처하기 전에는 되나 버튼 캡처 코드를 추가하면 앱이 구동이 되기전에 close가 됩니다.

    2021-04-12 23:46:20.723 27048-27048/? E/Zygote: isWhitelistProcess - Process is Whitelisted
    2021-04-12 23:46:20.727 27048-27048/? E/libpersona: scanKnoxPersonas
    2021-04-12 23:46:20.727 27048-27048/? E/libpersona: Couldn't open the File - /data/system/users/0/personalist.xml - No such file or directory
    '
    중간 생략
    2021-04-12 23:46:21.008 27048-27048/com.example.lala E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.lala, PID: 27048
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.lala/com.example.lala.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2957)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032)
    at android.app.ActivityThread.-wrap11(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696)
    at android.os.Handler.dispatchMessage(Handler.java:105)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6944)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
    Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
    at com.example.lala.MainActivity.onCreate(MainActivity.java:138)
    at android.app.Activity.performCreate(Activity.java:7183)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1220)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2910)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032) 
    at android.app.ActivityThread.-wrap11(Unknown Source:0) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696) 
    at android.os.Handler.dispatchMessage(Handler.java:105) 
    at android.os.Looper.loop(Looper.java:164) 
    at android.app.ActivityThread.main(ActivityThread.java:6944) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374) 

  17. thumbnail
    2021.04.14 21:58

    비밀댓글입니다

  18. thumbnail
    2021.04.27 11:17

    비밀댓글입니다

  19. thumbnail
    2021.05.02 20:07

    비밀댓글입니다

  20. thumbnail
    Favicon of https://present56.tistory.com BlogIcon present56

    2021-05-02 23:17:02.395 24420-24420/com.androidapp.opencv_with_ndk E/OpenCV/StaticHelper: OpenCV error: Cannot load info library for OpenCV
    2021-05-02 23:17:02.959 24420-24687/com.androidapp.opencv_with_ndk E/cv::error(): OpenCV(4.5.2) Error: Assertion failed (!empty()) in detectMultiScale, file /build/master_pack-android/opencv/modules/objdetect/src/cascadedetect.cpp, line 1689
    2021-05-02 23:17:02.961 24420-24687/com.androidapp.opencv_with_ndk E/libc++abi: terminating with uncaught exception of type cv::Exception: OpenCV(4.5.2) /build/master_pack-android/opencv/modules/objdetect/src/cascadedetect.cpp:1689: error: (-215:Assertion failed) !empty() in function 'detectMultiScale'
    2021-05-02 23:17:02.962 24420-24687/com.androidapp.opencv_with_ndk A/libc: Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 24687 (Thread-4), pid 24420 (opencv_with_ndk)

    이런 오류가 발생하는데 어떤 문제인지 알려주실수있을까요?

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.05.03 22:09 신고

      카메라에서 캡쳐된 이미지가 전달되지 않는듯합니다. 포스트 코드에서 변경된 것이 없다면 문제가 생기지 않아야 합니다.

    • thumbnail
      2021.05.03 22:38

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.05.03 22:40 신고

      에러상으론 자바코드에서 cpp코드로 이미지 전달이 안되는듯합니다

    • thumbnail
      2021.05.04 00:11

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.05.04 07:19 신고

      수정5를 확인해보세요

    • thumbnail
      2021.05.04 16:01

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.05.04 21:01 신고

      XML 파일을 잘못 다운로드 받았거나 안드로이드 스튜디오에서 잘못 넣어서 발생한거 같습니다. 혹 해당 XML 파일을 불러오지 못하는 거 일 수도 있습니다.

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.05.05 16:26 신고

      다시 검토해보니 2-4에서 언급한 다음 항목을 추가하면 해결되어야 합니다..

      혹시 모르니 앱을 재설치 해보세요

      android:requestLegacyExternalStorage="true"

  21. thumbnail
    2021.06.04 22:09

    비밀댓글입니다