반응형

 

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

2022. 06. 01 Android 12.0, OpenCV 4.5.5, 퍼미션 관련 코드 수정

                     ndk-build 방식에서 문제가 많이 발생하여 제외시켰습니다.

 

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 카메라 예제 및 프로젝트 생성방법(CMake 사용)
http://webnautes.tistory.com/1054




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

 



assets라고 입력하고 엔터를 누르면  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 디렉토리 위에서 왼쪽 마우스 버튼을 뗀 후, Refactor 버튼을 클릭하면 복사가 됩니다.

 



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

 

 

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

 




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

 

   android:requestLegacyExternalStorage="true"

 





2-5.  android 라이브러리를 사용하도록  CMakeLists.txt 파일을 수정합니다.  수정후 Sync Now를 클릭해줘야 합니다. 

 

 

find_library( android-lib android)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        native-lib

       ${android-lib}

        lib_opencv

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib} )




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

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





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

 




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

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



    protected void onCameraPermissionGranted() {
        List<? extends CameraBridgeViewBase> cameraViews = getCameraViewList();
        if (cameraViews == null) {
            return;
        }
        for (CameraBridgeViewBase cameraBridgeViewBase: cameraViews) {
            if (cameraBridgeViewBase != null) {
                cameraBridgeViewBase.setCameraPermissionGranted();

                read_cascade_file();
            }
        }
    }




    @Override
    protected void onStart() {
        super.onStart();
        boolean havePermission = true;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(CAMERA) != PackageManager.PERMISSION_GRANTED
                    || checkSelfPermission(WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
            ) {
                requestPermissions(new String[]{CAMERA, WRITE_EXTERNAL_STORAGE}, CAMERA_PERMISSION_REQUEST_CODE);
                havePermission = false;
            }
        }
        if (havePermission) {
            onCameraPermissionGranted();
        }
    }

 

  @Override
    @TargetApi(Build.VERSION_CODES.M)
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE && grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED
                && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
            onCameraPermissionGranted();
        }else{
            showDialogForPermission("앱을 실행하려면 퍼미션을 허가하셔야합니다.");
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }


    @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(new String[]{CAMERA, WRITE_EXTERNAL_STORAGE}, CAMERA_PERMISSION_REQUEST_CODE);
            }
        });
        builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface arg0, int arg1) {
                finish();
            }
        });
        builder.create().show();
    }




수정 2.  사용하는 카메라를 변경합니다. 0을 1로 변경합니다. 에뮬레이터로 하는 경우에는 웹캠을 에뮬레이터에 연결한 후,  0으로 두어야 합니다. 

 

    @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);

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



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



    //public native void ConvertRGBtoGray(long matAddrInput, long matAddrResult);
    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) {

        AssetManager assetManager = this.getAssets();
        File outputFile = new File( getFilesDir() + "/" + filename );

        InputStream inputStream = null;
        OutputStream outputStream = null;

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

            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( getFilesDir().getAbsolutePath() + "/haarcascade_frontalface_alt.xml");
        Log.d(TAG, "read_cascade_file:");

        cascadeClassifier_eye = loadCascade(  getFilesDir().getAbsolutePath() +"/haarcascade_eye_tree_eyeglasses.xml");
    }



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

 




수정 5. 기존 코드를 주석처리하고 얼굴 검출하는 cpp 코드를 호출하는 코드를 추가합니다. 

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

 

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



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

        matInput = inputFrame.rgba();

        if ( matResult == null )

            matResult = new Mat(matInput.rows(), matInput.cols(), matInput.type());

        //ConvertRGBtoGray(matInput.getNativeObjAddr(), matResult.getNativeObjAddr());
        Core.flip(matInput, matInput, 1);

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


        return matResult;
    }





2-7.  JNI 함수를 위한 처리입니다.  

 



자바 코드에서 loadCascade 메소드에 각각  마우스 커서를 가져가면 보이는 빨간 전구를 클릭한 후, 

 

 

보이는 메뉴에서  Create JNI function을 클릭합니다.

 

 

native-lib.cpp를 선택합니다. 

 

 

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



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

 



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

 



2-8. native-lib.cpp 파일을 다음처럼 수정합니다. 





 

#include <jni.h>
//#include <string>
#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 void JNICALL
//Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_ConvertRGBtoGray(JNIEnv *env,
//                                                                            jobject thiz,
//                                                                            jlong mat_addr_input,
//                                                                            jlong mat_addr_result) {
//    // TODO: implement ConvertRGBtoGray()
//    Mat &matInput = *(Mat *)mat_addr_input;
//    Mat &matResult = *(Mat *)mat_addr_result;
//
//    cvtColor(matInput, matResult, COLOR_RGBA2GRAY);
//}


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("");
    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 파일에 버튼을 추가합니다.

 

<?xml version="1.0" encoding="utf-8"?>

    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="캡쳐" />
   
    <org.opencv.android.JavaCameraView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/activity_surface_view" />

</LinearLayout>



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

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

    private final Semaphore writeLock = new Semaphore(1);

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

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

 




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

 

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

        try {
            getWriteLock();
            matInput = inputFrame.rgba();

            if ( matResult == null )

                matResult = new Mat(matInput.rows(), matInput.cols(), matInput.type());

            Core.flip(matInput, matInput, 1);

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

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

        releaseWriteLock();
        return matResult;
    }





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

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

 

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

                try {
                    getWriteLock();

                    File path = new File(String.valueOf(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)));

                    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

 

반응형

해보고 확인한 것을 문서화하여 기록합니다.


부족함이 있지만 도움이 되었으면 합니다.
잘못된 부분이나 개선점을 알려주시면 감사하겠습니다.



포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
문제가 생기면 포스트와 바뀐 환경이 있나 먼저 확인해보세요.

질문을 남겨주면 가능한 빨리 답변드립니다.


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

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

댓글을 달아 주세요

">
  1. 이전 댓글 더보기
  2. thumbnail
    익명
    2021.01.15 17:02

    비밀댓글입니다

  3. thumbnail
    익명
    2021.01.15 17:31

    비밀댓글입니다

  4. thumbnail
    익명
    2021.01.15 17:52

    비밀댓글입니다

  5. thumbnail
    익명
    2021.01.17 19:14

    비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.01.17 20:50 신고

      아마 설정변경이 적용되려면 해당 액티비티를 다시 실행해야 적용되지 않을까 싶습니다.

    • thumbnail
      익명
      2021.01.18 15:42

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.01.18 16:07 신고

      상황에 따라 달라서요. 구글에서 아래 키워드로 검색하여 참고해보세요

      안드로이드 액티비티 재시작

    • thumbnail
      익명
      2021.01.18 16:51

      비밀댓글입니다

  6. thumbnail
    Favicon of https://ksjmgrkks.tistory.com BlogIcon Titan04

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

  7. thumbnail
    Favicon of https://ddd66.tistory.com BlogIcon PeepPeep!

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

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

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

  8. thumbnail
    Favicon of https://ddd66.tistory.com BlogIcon PeepPeep!

    선생님 버튼 캡처하기 전에는 되나 버튼 캡처 코드를 추가하면 앱이 구동이 되기전에 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) 

  9. thumbnail
    익명
    2021.04.14 21:58

    비밀댓글입니다

  10. thumbnail
    익명
    2021.04.27 11:17

    비밀댓글입니다

  11. thumbnail
    익명
    2021.05.02 20:07

    비밀댓글입니다

  12. 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"

  13. thumbnail
    익명
    2021.06.04 22:09

    비밀댓글입니다

  14. thumbnail
    익명
    2021.08.05 13:44

    비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.08.05 13:48 신고

      포스트에서 API 29라고 언급된 부분을 검토해보세요. 이 부분과 관련있어보입니다. 최근 테스트시 SD카드 없이도 문제없었습니다

    • thumbnail
      익명
      2021.08.05 14:37

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.08.05 14:47 신고

      안드로이드 10에서 문제 없었는데 상위폰인가요? 또는 안드로이드 SDK가 상위버전인가요?

    • thumbnail
      익명
      2021.08.05 15:25

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.08.05 15:44 신고

      폰의 버전이 11이라서 발생한 문제로 보입니다. 퍼미션쪽에서 정책이 바뀌었나봅니다. 현재 버전 10인 폰을 사용중이라 확인 해볼수 없네요.

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.08.05 18:31 신고

      아래 링크를 검토해보세요

      https://stackoverflow.com/a/66366102

    • thumbnail
      익명
      2021.08.12 15:30

      비밀댓글입니다

    • thumbnail
      Favicon of https://devhyeon.tistory.com BlogIcon 뤼펭
      2021.08.12 16:31 신고

      글쓴이님이 공유해주신 깃헙에서

      Najaf Ali <<
      위에 사람이 작성한 게시글대로 하니 해결되었습니다.

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.08.12 16:37 신고

      해결방법 공유 감사합니다

  15. thumbnail
    익명
    2021.08.12 17:04

    비밀댓글입니다

  16. thumbnail
    익명
    2021.08.18 12:04

    비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.08.18 12:06 신고

      메인 액티비티에서만 OpenCV 카메라 API가 동작하는게 아닌지 문서에서 확인해보세요

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2021.08.18 17:21 신고

      아.. 질문을 다시보니.. MainActivity에서 레이아웃을 변경한건가요? 아님 다른 액티비티에서 진행한건가요?

  17. thumbnail
    익명
    2022.02.28 12:08

    비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2022.02.28 14:02 신고

      로그에서 원인을 찾아야 합니다. 추가한 코드 중 일부를 주석해서 에러난 부분을 찾을 필요도 있고요

    • thumbnail
      익명
      2022.03.03 14:14

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2022.03.03 15:07 신고

      xml 파일 저장 방법이 틀렸을 가능성이 있어보이네요

    • thumbnail
      익명
      2022.03.03 17:32

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2022.03.03 17:42 신고

      xml 파일 생성하는걸 다시 해보세요. 불로그에서 소개한 방법으로 저장해야해요

    • thumbnail
      익명
      2022.03.12 19:35

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2022.03.12 19:37 신고

      포스트에서 소개한방법대로 진행하시면 됩니다.

    • thumbnail
      익명
      2022.03.12 19:49

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2022.03.12 20:13 신고

      로그캣에서 에러 나서 앱이 멈추는 순간 에러를 확인해야합니나

    • thumbnail
      익명
      2022.03.12 20:19

      비밀댓글입니다

    • thumbnail
      익명
      2022.03.13 02:47

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2022.03.13 06:45 신고

      xml 파일을 못읽는거 같습니다. 다른방법으로 경로에서 파일을 읽는지 확인해보세요.

    • thumbnail
      익명
      2022.03.15 11:24

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2022.03.15 11:30 신고

      포스트 마지막 업데이트시 동작한 코드입니다. 이후 저장공간 관련 권한이나 경로가 바뀌었을 가능성이 있습니다.

    • thumbnail
      익명
      2022.03.15 12:33

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2022.03.15 12:35 신고

      로그로 출력해보세요. 글작성시점에선 문제없던 코드입니다. 지금 다시해보면 좋은데 시간이 안나네요

    • thumbnail
      익명
      2022.03.15 13:20

      비밀댓글입니다

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2022.03.15 13:37 신고

      궁금해하시는 파일을 로드하는 위치를 말합니다. 현재 코드에서는 함수를 사용하여 위치를 가져오는 것으로 기억합니다.

      안드로이드 권한 방침 변경으로 해당 위치를 못읽어올거 같아서 말씀드리는 겁니다.

    • thumbnail
      Favicon of https://webnautes.tistory.com BlogIcon webnautes
      2022.06.01 16:11 신고

      글을 업데이트했습니다. 다시 확인해보세요.

  18. thumbnail
    Favicon of https://che0802.tistory.com BlogIcon 못쨍

    안녕하세요. 현재 캡쳐버튼을 통해 사진 저장하는 방식을 따라 구현중인데, oncreat() 메서드에 버튼 클릭 시 사진 저장 코드를 추가하라고 하셨는데, 구현되지 않습니다. 빠뜨린것이 있을까요?

  19. thumbnail
    익명
    2022.06.09 05:45

    비밀댓글입니다

  20. thumbnail
    익명
    2022.06.12 20:20

    비밀댓글입니다

  21. thumbnail
    익명
    2022.06.12 21:19

    비밀댓글입니다