ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • OpenCV와 NDK를 사용하여 Android에서 Face Detection(얼굴 검출)
    OpenCV/Android 개발 환경 및 예제 2019. 8. 15. 23:39




    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. 9

    최종 작성 -  2019. 8. 15  OpenCV 4.1.1용 추가



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

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


        private void read_cascade_file(){

            //copyFile 메소드 Assets에서 해당 파일을 가져와 

            //외부 저장소 특정위치에 저장하도록 구현된 메소드입니다.
            copyFile("haarcascade_frontalface_alt.xml");
            copyFile("haarcascade_eye_tree_eyeglasses.xml");

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


            //loadCascade 메소드는 외부 저장소의 특정 위치에서 해당 파일을 읽어와서 

            //CascadeClassifier 객체로 로드합니다.
            cascadeClassifier_face = loadCascade( "haarcascade_frontalface_alt.xml");
            Log.d(TAG, "read_cascade_file:");

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




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




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

     

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

            matInput = inputFrame.rgba();

            //if ( matResult != null ) matResult.release(); fix 2018. 8. 18

            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;
        }




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




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


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

            matInput = inputFrame.rgba();

            //if ( matResult != null ) matResult.release(); fix 2018. 8. 18

            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;
        }




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

    이상하게도 화면이 세로( 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 파일에 외부 저장소 접근 권한을 추가해줍니다.



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

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


       <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"/>




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



    LOCAL_MODULE    := native-lib
    LOCAL_SRC_FILES := main.cpp
    LOCAL_LDLIBS += -llog -landroid

    include $(BUILD_SHARED_LIBRARY)



    CMake 사용하도록 프로젝트 생성했다면 다음처럼 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.  자바 코드 수정합니다.






    OpenCV 4.1.1과 OpenCV 4.1.0에서의 수정 방법이 다릅니다. 



    우선 OpenCV 4.1.1 에서의 수정입니다. 


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


      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.  사용하는 카메라를 변경합니다.


        @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 메소드를 이용해서 가져온 경우에 대한 처리가 빠져있습니다.  


      static {

            System.loadLibrary("opencv_java3");

            System.loadLibrary("native-lib");

       }

     

       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");
        }


        private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {

         



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





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


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

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


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

            matInput = inputFrame.rgba();

            //if ( matResult != null ) matResult.release(); fix 2018. 8. 18

            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;
        }



    ..


    ..


    OpenCV 4.1.0에서의 수정입니다. 


    수정 1. 기존 코드(흰색)를 주석 처리하고 외장 저장소에 파일 저장하기 위한 퍼미션(노란색)을 추가합니다.


        // String[] PERMISSIONS  = {"android.permission.CAMERA"};
        String[] PERMISSIONS  = {"android.permission.CAMERA",
                "android.permission.WRITE_EXTERNAL_STORAGE"};



    수정 2.   기존 코드(흰색)를 주석처리하고 WRITE_EXTERNAL_STORAGE 퍼미션을 위해 필요한 코드(노란색)를 추가합니다.


        @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("앱을 실행하려면 퍼미션을 허가하셔야합니다.");


                        boolean writePermissionAccepted = grantResults[1]
                                == PackageManager.PERMISSION_GRANTED;

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

        }



    수정 3.  기존 코드(흰색)를 주석처리하고 퍼미션이 이미 허가된 경우의 처리를 위한 코드(노란색)를 추가하고 사용하는 카메라를 전면 카메라로 바꾸기 위한 코드를 수정합니다.


        @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);
                }
               else  read_cascade_file(); //추가
            }

            else  read_cascade_file(); //추가

            mOpenCvCameraView = (CameraBridgeViewBase)findViewById(R.id.activity_surface_view);
            mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
            mOpenCvCameraView.setCvCameraViewListener(this);

            // 기존 코드 주석처리
            //mOpenCvCameraView.setCameraIndex(0); // front-camera(1),  back-camera(0)
            mOpenCvCameraView.setCameraIndex(1); // front-camera(1),  back-camera(0)

           mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);



    수정 4.  기존 코드(흰색)를 주석처리하고 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;



    수정 5.  xml 파일을 가져오기 위한 메소드(노란색)를 추가합니다. 


    • cpp 파일의 loadCascade 함수를 호출하도록 구현되어있는데 자바 함수를 사용하도록 변경해도 됩니다.

    • 현재 이미 파일을 copyFile 메소드를 이용해서 가져온 경우에 대한 처리가 빠져있습니다.  


      static {

            System.loadLibrary("opencv_java3");

            System.loadLibrary("native-lib");

       }

     

       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");
        }


        private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {

            @Override

            public void onManagerConnected(int status) {



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




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


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

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


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

            matInput = inputFrame.rgba();

            //if ( matResult != null ) matResult.release(); fix 2018. 8. 18

            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.  Java 코드를 수정합니다.  진행한 방식에 따라 하는 방법이 다릅니다. 




    CMake 방식이라면..


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




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

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




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





    ndk-build 방식이라면..


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


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


    "C:\Program Files\Android\Android Studio\jre\bin\javah.exe" -v -jni -d C:\Users\webnautes\AndroidStudioProjects\UseOpenCVwithndkbuild\app/src/main/jni com.tistory.webnautes.useopencvwithndk_build.MainActivity
    Error: unmappable character for encoding MS949
    .........................................................................
    Error: unmappable character for encoding MS949
    [Overwriting file RegularFileObject[C:\Users\webnautes\AndroidStudioProjects\UseOpenCVwithndkbuild\app\src\main\jni\com_tistory_webnautes_useopencvwithndk_build_MainActivity.h]]

    Process finished with exit code 0



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

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




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



    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_tistory_webnautes_useopencvwithndk_build_MainActivity */

    #ifndef _Included_com_tistory_webnautes_useopencvwithndk_build_MainActivity
    #define _Included_com_tistory_webnautes_useopencvwithndk_build_MainActivity
    #ifdef __cplusplus
    extern "C" {
    #endif
    #undef com_tistory_webnautes_useopencvwithndk_build_MainActivity_PERMISSIONS_REQUEST_CODE
    #define com_tistory_webnautes_useopencvwithndk_build_MainActivity_PERMISSIONS_REQUEST_CODE 1000L
    /*
    * Class:     com_tistory_webnautes_useopencvwithndk_build_MainActivity
    * Method:    loadCascade
    * Signature: (Ljava/lang/String;)J
    */
    JNIEXPORT jlong JNICALL Java_com_tistory_webnautes_useopencvwithndk_1build_MainActivity_loadCascade
      (JNIEnv *, jobject, jstring);

    /*
    * Class:     com_tistory_webnautes_useopencvwithndk_build_MainActivity
    * Method:    detect
    * Signature: (JJJJ)V
    */
    JNIEXPORT void JNICALL Java_com_tistory_webnautes_useopencvwithndk_1build_MainActivity_detect
      (JNIEnv *, jobject, jlong, jlong, jlong, jlong);

    #ifdef __cplusplus
    }
    #endif
    #endif




    main.cpp 파일을 수정합니다. 기존 코드를 주석처리(흰색)하고  com_tistory_webnautes_useopencvwithndk_build_MainActivity.h 파일에서 함수 선언(노란색)을 복사해옵니다. 

    추가로 필요한 코드(파란색)을 추가합니다. 


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



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


    using namespace cv;
    using namespace std;

    extern "C"{

    /*
        JNIEXPORT void JNICALL
      Java_com_tistory_webnautes_useopencvwithndk_1build_MainActivity_ConvertRGBtoGray(
                JNIEnv *env,
                jobject instance,
                jlong matAddrInput,
                jlong matAddrResult){


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

            cvtColor(matInput, matResult, CV_RGBA2GRAY);


          }
    */


        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 파일을 다음처럼 수정합니다. 기존 코드를 주석처리(흰색)하고 필요한 코드(파란색)을 추가합니다. 

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



    #include <jni.h>

    #include <opencv2/opencv.hpp>

    #include <android/log.h>


    using namespace cv;

    using namespace std;



    //extern "C"

    //JNIEXPORT void JNICALL

    //Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_ConvertRGBtoGray(JNIEnv *env,

    //                                                                            jobject instance,

    //                                                                            jlong matAddrInput,

    //                                                                            jlong matAddrResult) {

    //

    //    Mat &matInput = *(Mat *)matAddrInput;

    //    Mat &matResult = *(Mat *)matAddrResult;

    //

    //    cvtColor(matInput, matResult, CV_RGBA2GRAY);

    //

    //}




    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 instance,

                                                                           jstring cascadeFileName_) {

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


        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;


    //    env->ReleaseStringUTFChars(cascadeFileName_, cascadeFileName);

    }



    extern "C"

    JNIEXPORT void JNICALL

    Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_detect(JNIEnv *env, jobject instance,

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

            }

        }



    }




    3. 캡쳐 버튼 추가하기

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


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


    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        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 파일에 세마포어를 사용하기 위한 코드를 추가합니다. 


        private final Semaphore writeLock = new Semaphore(1);

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

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

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




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


     

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

            try {
               getWriteLock();

                matInput = inputFrame.rgba();

                //if ( matResult != null ) matResult.release(); fix 2018. 8. 18

                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 메소드에 추가합니다. 


            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로 변환합니다. 


    JNIEXPORT jint JNICALL
    Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_detect(JNIEnv *env, jobject instance,




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


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

        int ret = 0;


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


        //-- 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());
        ret = faces.size();


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


    return ret;

    }



    4.2. MainActivity.java 수정


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


       public native int detect(long cascadeClassifier_face,
                                  long cascadeClassifier_eye, long matAddrInput, long matAddrResult);

      



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


    int ret = detect(cascadeClassifier_face,cascadeClassifier_eye, matInput.getNativeObjAddr(),
    matResult.getNativeObjAddr());

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



    포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
    댓글로 알려주시면 빠른 시일내에 답변을 드리겠습니다.

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

    유튜브 구독하기


    댓글 601

    • 이전 댓글 더보기
    • 2019.07.12 11:00


      비밀댓글입니다

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.12 12:12 신고


        윈도우탐색기를 사용하여 프로젝트 폴더에 있는 assets 폴더에 직접 넣어도 됩니다.

        app\src\main 위치에 assets 폴더가 있습니다.

      • Favicon of https://electroniceng.tistory.com BlogIcon wiiin 2019.07.12 14:06 신고


        아! 감사합니다!
        방금 또 진행 중에 궁금한게 생겨서 리댓답니당..
        해당 진행과정 중에 2-4 진행 할 때, androidManifest.xml에 외부 저장소 접근 권한을 추가하라고 하셨는데,
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        이 코드만 추가하면 되는건가요??..

        제 안드로이드 스튜디오에있는 androidManifest.xml 기존 코드는

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

        <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <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>

        인데, 올려주신 코드에서

        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"/>

        이 부분도 제 코드에는 존재하지 않는데, 그럼 저는 컬러로 되어있는 코드와 제 코드에 없는 코드를 같이 넣어서

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

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


        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"/>

        <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <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>

        이렇게 추가하는게 맞나요..?

        이렇게 추가하고 2-5부분을 진행하는데..저는 CMake를 사용했는데, CMakeLists.txt파일이 빌드를 해도 생기지않습니다.. 오류만 나오네요..ㅠㅠ

        sync project with gradle files 눌렀더니
        ERROR: Failed to parse XML in C:\YYHL\Fa\app\src\main\AndroidManifest.xml
        ParseError at [row,col]:[9,5]
        Message: expected start or end tag
        Affected Modules: app

        이런 오류가 생겨요..ㅜㅜ

        정리하자면
        1. androidManifest.xml 이 파일에 추가하는게 제가 위에 쓴 코드처럼 제가 없는 부분까지 다 추가하는게 맞는건가요..?
        2. 다 추가하고 sync project with gradle files을 눌렀더니 저런 오류가 생겼는데..뭐가 잘못된지 잘 모르겠습니다ㅜㅜㅜㅜ

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.12 16:00 신고


        1. 언급하신 카메라 관련 퍼미션은 NDK 카메라 예제에서 추가하는 거였습니다..
        안해도 동작하는데는 문제 없나 보군요..


        2. 에러 난것은 AndroidManifest.xml 파일 9번째 줄에 오타가 있어서입니다. 다시 확인해보세요.


      • Favicon of https://electroniceng.tistory.com BlogIcon wiiin 2019.07.12 16:39 신고


        저..혹시 2-5진행할 때 cmake 사용하면 CMakeLists.txt 파일을 수정해야하는데.. 올려주신 위치에 저는 CMakeLists.txt 파일이 없어요...
        혹시 뭐가 문제인지 아시나요??ㅠㅠ

      • Favicon of https://electroniceng.tistory.com BlogIcon wiiin 2019.07.12 16:54 신고


        CMakeLists.txt 파일이 올려주신 사진에는 app/src안에 들어있는거로 보이는데,
        저는 app/src/main/cpp안에 들어있는데..괜찮은건가요?

      • Favicon of https://electroniceng.tistory.com BlogIcon wiiin 2019.07.12 17:06 신고


        저...그냥 윗 댓글에 있는 cmakelist.txt파일에 코드 넣고, 2-6 자바코드를 수정해보려고 MainActivity에 들어갔는데
        package com.example.f;

        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 = 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();
        }

        저는 기존 코드가 이런데..페이지에 적어주신 코드와는 다르더라구요...ㅠㅠ
        보고 그대로 따라했는데...뭐가 문젠지 모르겠어요ㅠㅠ
        도와주세요ㅜㅜ

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.12 18:08 신고


        이유는 모르겠는데 프로젝트가 초기화 된듯하네요..

        아래 포스트 보고 다시 진행해야 할 듯합니다.

        Android NDK + OpenCV 카메라 예제 및 프로젝트 생성방법(CMake 사용)
        https://webnautes.tistory.com/1054

      • Favicon of https://electroniceng.tistory.com BlogIcon wiiin 2019.07.15 16:25 신고


        오류가 뜨는데...혹시 무슨 오류인지 아시나요?ㅠㅠ

        org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:externalNativeBuildDebug'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:95)
        at org.gradle.api.internal.tasks.execution.ResolveTaskOutputCachingStateExecuter.execute(ResolveTaskOutputCachingStateExecuter.java:91)
        at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:57)
        at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:119)
        at org.gradle.api.internal.tasks.execution.ResolvePreviousStateExecuter.execute(ResolvePreviousStateExecuter.java:43)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:93)
        at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:45)
        at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:94)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:56)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:67)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:49)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:315)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:305)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:175)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:101)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.call(DelegatingBuildOperationExecutor.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:49)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:43)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:355)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:343)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:336)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:322)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker$1.execute(DefaultPlanExecutor.java:134)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker$1.execute(DefaultPlanExecutor.java:129)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:202)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:193)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:129)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
        at java.lang.Thread.run(Thread.java:745)
        Caused by: org.gradle.api.UncheckedIOException: java.io.FileNotFoundException: C:\YYHL\FaceDetection\app\.externalNativeBuild\cmake\debug\x86_64\android_gradle_build_native-lib_x86_64.command.txt (액세스가 거부되었습니다)
        at org.gradle.internal.UncheckedException.throwAsUncheckedException(UncheckedException.java:61)
        at org.gradle.internal.UncheckedException.throwAsUncheckedException(UncheckedException.java:41)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:76)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:48)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:41)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:28)
        at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:704)
        at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:671)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$2.run(ExecuteActionsTaskExecuter.java:284)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:301)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:293)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:175)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:91)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:273)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:258)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.access$200(ExecuteActionsTaskExecuter.java:67)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$TaskExecution.execute(ExecuteActionsTaskExecuter.java:145)
        at org.gradle.internal.execution.impl.steps.ExecuteStep.execute(ExecuteStep.java:49)
        at org.gradle.internal.execution.impl.steps.CancelExecutionStep.execute(CancelExecutionStep.java:34)
        at org.gradle.internal.execution.impl.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:69)
        at org.gradle.internal.execution.impl.steps.TimeoutStep.execute(TimeoutStep.java:49)
        at org.gradle.internal.execution.impl.steps.CatchExceptionStep.execute(CatchExceptionStep.java:33)
        at org.gradle.internal.execution.impl.steps.CreateOutputsStep.execute(CreateOutputsStep.java:50)
        at org.gradle.internal.execution.impl.steps.SnapshotOutputStep.execute(SnapshotOutputStep.java:43)
        at org.gradle.internal.execution.impl.steps.SnapshotOutputStep.execute(SnapshotOutputStep.java:29)
        at org.gradle.internal.execution.impl.steps.CacheStep.executeWithoutCache(CacheStep.java:134)
        at org.gradle.internal.execution.impl.steps.CacheStep.lambda$execute$3(CacheStep.java:83)
        at java.util.Optional.orElseGet(Optional.java:267)
        at org.gradle.internal.execution.impl.steps.CacheStep.execute(CacheStep.java:82)
        at org.gradle.internal.execution.impl.steps.CacheStep.execute(CacheStep.java:36)
        at org.gradle.internal.execution.impl.steps.PrepareCachingStep.execute(PrepareCachingStep.java:33)
        at org.gradle.internal.execution.impl.steps.StoreSnapshotsStep.execute(StoreSnapshotsStep.java:38)
        at org.gradle.internal.execution.impl.steps.StoreSnapshotsStep.execute(StoreSnapshotsStep.java:23)
        at org.gradle.internal.execution.impl.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:96)
        at org.gradle.internal.execution.impl.steps.SkipUpToDateStep.lambda$execute$0(SkipUpToDateStep.java:89)
        at java.util.Optional.map(Optional.java:215)
        at org.gradle.internal.execution.impl.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:52)
        at org.gradle.internal.execution.impl.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:36)
        at org.gradle.internal.execution.impl.DefaultWorkExecutor.execute(DefaultWorkExecutor.java:34)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:91)
        ... 35 more
        Caused by: java.io.FileNotFoundException: C:\YYHL\FaceDetection\app\.externalNativeBuild\cmake\debug\x86_64\android_gradle_build_native-lib_x86_64.command.txt (액세스가 거부되었습니다)
        at java.io.FileOutputStream.open0(Native Method)
        at java.io.FileOutputStream.open(FileOutputStream.java:270)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:213)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:162)
        at kotlin.io.FilesKt__FileReadWriteKt.writeBytes(FileReadWrite.kt:84)
        at kotlin.io.FilesKt__FileReadWriteKt.writeText(FileReadWrite.kt:110)
        at kotlin.io.FilesKt__FileReadWriteKt.writeText$default(FileReadWrite.kt:110)
        at com.android.build.gradle.internal.cxx.process.ProcessOutputJunction.execute(ProcessOutputJunction.kt:70)
        at com.android.build.gradle.internal.cxx.process.ProcessOutputJunction.execute(ProcessOutputJunction.kt:119)
        at com.android.build.gradle.tasks.ExternalNativeBuildTask.executeProcessBatch(ExternalNativeBuildTask.java:331)
        at com.android.build.gradle.tasks.ExternalNativeBuildTask.buildImpl(ExternalNativeBuildTask.java:199)
        at com.android.build.gradle.tasks.ExternalNativeBuildTask.build(ExternalNativeBuildTask.java:90)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
        ... 73 more

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.15 17:29 신고


        컴퓨터를 재부팅후 다시 해보세요.

        진행할때 안드로이드 스튜디오가 처리중일때에는 아무 작업도 하면 안됩니다.

      • Favicon of https://electroniceng.tistory.com BlogIcon wiiin 2019.07.16 17:46 신고


        혹시...제가 다 따라하고 진행하고 빌드를 눌렀는데...빌드가 6시간 이상 째 계속 빌드 중이라고 뜨는데...
        이게 원래 이렇게 빌드가 오래 걸리는 프로젝트인가요..?

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.16 17:50 신고


        보통 다른 앱하고 빌드 시간이 비슷하거나 약간 더 걸리는 정도입니다.

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.16 23:03 신고


        관련 내용을 구글로 찾아봐도 확실한 해결방법이 보이지 않네요..

        경험상 NDK를 사용한 안드로이드 프로젝트가 문제 있는 경우 새로 생성하면 해결되는 경우가 많았습니다.


      • Favicon of https://electroniceng.tistory.com BlogIcon wiiin 2019.07.17 10:40 신고


        넵..ㅜㅜ
        감사합니다!

      • Favicon of https://electroniceng.tistory.com BlogIcon wiiin 2019.07.18 15:11 신고


        결국 해결했습니다!!
        도와주신 덕분입니다!
        감사합니다ㅎㅎ

        제가 이번에 눈동자를 검출하는 코드를 짜야하게됐는데...혹시 힌트 얻을 수 있을까요??ㅠㅠ

        코드 이해가 부분부분만 돼서 어떻게 시작해야할지 감이 안잡히네요ㅜㅜ

        그리고 혹시
        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

        이 사이트들은 왜 추가하는지 알 수 있을까요?

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.18 15:19 신고


        해결되서 다행입니다.

        눈동자에대해서는 진행해보지 않아서 도움을 못드리겠네요.
        구글에서 다음 키워드로 검색해보세요.

        opencv detect pupil



        haarcascade_eye_tree_eyeglasses.xml, haarcascade_frontalface_alt.xml 파일은 눈과 얼굴에 대해 훈련시킨 데이터입니다.

        다음 포스트를 참고하면 좋을듯합니다.

        OpenCV 강좌 - Haar Cascades에 대해 알아보자.
        https://webnautes.tistory.com/1352

      • Favicon of https://electroniceng.tistory.com BlogIcon wiiin 2019.07.18 15:42 신고


        정말 감사합니다!!ㅎㅎ

      • Favicon of https://electroniceng.tistory.com BlogIcon wiiin 2019.07.18 16:04 신고


        혹시 그 훈련시킨 데이터 어디서 찾으신지 알 수 있을까요...?
        눈동자에 해당하는 것도 찾아서 넣어야 할 것 같아서 여쭤봅니당...!!

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.18 16:09 신고


        아래 링크에 있습니다.
        https://github.com/opencv/opencv/tree/master/data

        하지만 눈동자에 관한 것은 제공되지 않습니다.

    • 개발자 2019.07.24 13:50


      Build command failed.
      Error while executing process C:\Users\Administrator\AppData\Local\Android\Sdk\cmake\3.10.2.4988404\bin\cmake.exe with arguments {--build C:\Users\Administrator\Desktop\FocusHelper2\app\.externalNativeBuild\cmake\debug\x86_64 --target native-lib}
      [1/2] Building CXX object CMakeFiles/native-lib.dir/native-lib.cpp.o
      [2/2] Linking CXX shared library C:\Users\Administrator\Desktop\FocusHelper2\app\build\intermediates\cmake\debug\obj\x86_64\libnative-lib.so
      FAILED: C:/Users/Administrator/Desktop/FocusHelper2/app/build/intermediates/cmake/debug/obj/x86_64/libnative-lib.so
      cmd.exe /C "cd . && C:\Users\Administrator\AppData\Local\Android\Sdk\ndk-bundle\toolchains\llvm\prebuilt\windows-x86_64\bin\clang++.exe --target=x86_64-none-linux-android24 --gcc-toolchain=C:/Users/Administrator/AppData/Local/Android/Sdk/ndk-bundle/toolchains/llvm/prebuilt/windows-x86_64 --sysroot=C:/Users/Administrator/AppData/Local/Android/Sdk/ndk-bundle/toolchains/llvm/prebuilt/windows-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -fno-addrsig -Wa,--noexecstack -Wformat -Werror=format-security -std=gnu++11 -O0 -fno-limit-debug-info -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libatomic.a -static-libstdc++ -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -Wl,--no-undefined -Qunused-arguments -Wl,-z,noexecstack -shared -Wl,-soname,libnative-lib.so -o C:\Users\Administrator\Desktop\FocusHelper2\app\build\intermediates\cmake\debug\obj\x86_64\libnative-lib.so CMakeFiles/native-lib.dir/native-lib.cpp.o -landroid C:/Users/Administrator/Desktop/FocusHelper2/opencv/native/libs/x86_64/libopencv_java4.so -latomic -lm && cd ."
      C:/Users/Administrator/Desktop/FocusHelper2/app/src/main/cpp/native-lib.cpp:51: error: undefined reference to '__android_log_print'
      C:/Users/Administrator/Desktop/FocusHelper2/app/src/main/cpp/native-lib.cpp:57: error: undefined reference to '__android_log_print'
      clang++: error: linker command failed with exit code 1 (use -v to see invocation)
      ninja: build stopped: subcommand failed.

      프로젝트 폴더 명을 변경하고 나니까 위와 같은 에러가 뜹니다...

      이름 다시 복구 후에도 빌드해도 같은 에러가 나고

      백업해뒀던 정상 작동 하던 프로젝트로 빌드해봐도 같은 에러가 나오네요...

      어디에 문제가 있는지 아실까요..

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.24 15:02 신고


        2-5를 다시 확인해보세요.

        __android_log_print 함수 사용을 위해 필요한 설정이 잘못된듯합니다.

      • 개발자 2019.07.24 19:25


        코드는 다시 한번 확인해봤지만 문제 없습니다 ㅠㅠ... 본문 그대로 따라해서 얼굴인식 잘 되던 프로젝트였는데 폴더명 변경했다고 에러를 뱉는게 이해가 안되네요;;

      • 개발자 2019.07.24 20:15


        build.gradle(app)에

        compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
        }

        추가해주니까 해결됐습니다 ㅡㅡ;;;; 원인은 아직도 모르겠습니다..

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.07.24 22:06 신고


        해결되서 다행입니다.


        확실치 않지만 아래 포스트처럼 일부 라이브러리가 자바 1.8을 사용하도록 변경된 게 있어서 그런게 아닌가 싶습니다.

        https://webnautes.tistory.com/1320


        최근들어 언급한 다음 옵션이 디폴트로 추가되서 프로젝트 가 생성됩니다.

        compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
        }


    • KWSA 2019.08.07 23:45


      안녕하세요 고수님
      이 코드에서 전방차량검출을 하려고 xml파일을 구해서 인식할려고하는데 위에 코드는 2개의 xml파일을 사용하고 저는 1개의 파일을 사용합니다
      그리고 저는 이미 차선인식 코드를 cpp파일에 추가 되어있는데 어떻게 해야될까요...

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.08 10:14 신고


        사용하는 XML 파일에 따라 사용하는 방법이 달라서 포스트의 코드를 그대로 사용하기는 힘들듯합니다.

        XML파일을 불러와서 OpenCV 함수에서 로드하도록한 loadCascade 함수를 참고하여 코드를 수정해야 할 듯합니다.

    • 개발자1 2019.08.08 11:53


      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;

      혹시 이 코드에서 / resizeRaito는 왜하는건가요..??
      리사이즈된 영상에서 faces을 검출에 벡터에 넣으면 x,y,width,height 그대로 사용하면 되지 않나요???

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.08 12:24 신고


        속도를 빠르게 하기 위해 얼굴 검출시 축소된 이미지를 사용했는데..

        화면에 보여지는 건 원래 사이즈의 영상이기 때문에 좌표를 맞추기 위해서입니다.

      • 개발자1 2019.08.08 13:16


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

        이 코드에서 resize된 영상에서 얼굴을 찾아 faces 벡터에 x,y,width,height 값을 넣는거 아닌가요??? 그러면 줄여진 영상에서 검출됬으면 곱해야할거같은데 나눗셈을 사용해서 이해가 잘 안되네요...

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.08 15:33 신고


        이미지를 줄일때 사용한 비율이 0.5처럼 실수입니다.

        그래서 실수를 곱하면 이미지가 줄어들게 되고..

        반대로
        실수로 나누면 이미지가 원래 크기로 됩니다.


        resized된 영상에서 얼굴 찾으면 좌표가 resize된 영상에 맞는 값이라서

        원래 크기의 영상에 얼굴 위치로 사용할수 없기 때문입니다.


    • 질문 2019.08.15 21:59


      PERMISSIONS_REQUEST_CODE
      이 부분이 계속 빨간색으로 떠서 빨간 전구 클릭 후 어떤 것을 추가해주어야 될까요?

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.15 22:14 신고


        현재 포스트를 진행하기 전에

        다음 중 어떤 코드를 사용하여 진행했나요?


        CMake를 사용한 방식 중 OpenCV 4.1.1용은 아직 얼굴인식 코드를 동작시키는데 문제가 있습니다.

      • 질문 2019.08.15 23:28


        아 그렇군요 CMake OpenCV 4.1.1 버전으로 하였습니다 그렇다면 OpenCV 버전 한단계 을 낮춰서 진행하면 될까요?

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.16 00:47 신고


        4.1.1에서 가능하도록 포스트를 수정했습니다. 다시 진행해보세요.

      • 질문 2019.08.18 14:04


        Build command failed.
        Error while executing process C:\Users\Xeung_bo\AppData\Local\Android\Sdk\cmake\3.10.2.4988404\bin\cmake.exe with arguments {--build C:\Users\Xeung_bo\AndroidStudioProjects\OpenCVCameraExample\app\.externalNativeBuild\cmake\debug\arm64-v8a --target native-lib}
        [1/1] Linking CXX shared library C:\Users\Xeung_bo\AndroidStudioProjects\OpenCVCameraExample\app\build\intermediates\cmake\debug\obj\arm64-v8a\libnative-lib.so
        FAILED: C:/Users/Xeung_bo/AndroidStudioProjects/OpenCVCameraExample/app/build/intermediates/cmake/debug/obj/arm64-v8a/libnative-lib.so
        cmd.exe /C "cd . && C:\Users\Xeung_bo\AppData\Local\Android\Sdk\ndk-bundle\toolchains\llvm\prebuilt\windows-x86_64\bin\clang++.exe --target=aarch64-none-linux-android21 --gcc-toolchain=C:/Users/Xeung_bo/AppData/Local/Android/Sdk/ndk-bundle/toolchains/llvm/prebuilt/windows-x86_64 --sysroot=C:/Users/Xeung_bo/AppData/Local/Android/Sdk/ndk-bundle/toolchains/llvm/prebuilt/windows-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -fno-addrsig -Wa,--noexecstack -Wformat -Werror=format-security -std=gnu++11 -O0 -fno-limit-debug-info -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libatomic.a -static-libstdc++ -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -Wl,--no-undefined -Qunused-arguments -Wl,-z,noexecstack -shared -Wl,-soname,libnative-lib.so -o C:\Users\Xeung_bo\AndroidStudioProjects\OpenCVCameraExample\app\build\intermediates\cmake\debug\obj\arm64-v8a\libnative-lib.so CMakeFiles/native-lib.dir/native-lib.cpp.o -landroid -llib_opencv -llog -latomic -lm && cd ."
        C:/Users/Xeung_bo/AppData/Local/Android/Sdk/ndk-bundle/toolchains/llvm/prebuilt/windows-x86_64/lib/gcc/aarch64-linux-android/4.9.x/../../../../aarch64-linux-android/bin\ld: cannot find -llib_opencv
        clang++: error: linker command failed with exit code 1 (use -v to see invocation)
        ninja: build stopped: subcommand failed.

        이 에러는 무슨 에러인가요?ㅠ

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.18 14:10 신고


        CMakeLists.txt에서 라이브러리 이름을 잘못 지정한듯합니다.

        cannot find -llib_opencv

    • Lazyrabbit 2019.08.16 15:05


      안녕하세요!!
      친절한 설명 덕분에 많은 도움을 받은 1인입니다.^^

      CMake를 사용한 방식으로 따라하여 2번대까지 참고하여 진행하였습니다. (이미지 캡쳐는 불필요하기에..)

      최종목적은 카메라 화면 중에 얼굴 모션 감지를 위해 (정확히는 눈,코,기울기 등 실시간 얼굴 감지용)
      OpenCV 를 활용하여 알아보고 있습니다.

      다만 현재 2번대까지 따라하여 Run시 정상적으로 감지까지 (동그라미 표시) 정상적으로 동작하나,
      생각보다 감지율이 낮은거 같고 그리고 버벅이는게 상당히 심하네요.. ㅠㅠ

      처음에는 android.media.facedetector.face 를 활용하여 진행하려고 하였으나, 감지율 및 속도는 엄청 자연스럽고 빠른데.. 핵심 부위(코, 얼굴 기울기 등)를 감지하기 힘들어 OpenCV를 알아보고 있습니다.

      혹시나 목적에 맞게 사용하려면 감지율 및 속도를 최적화 할수 있는 방법이 있을까요?
      실례를 무릅쓰고 도움을 받을 곳이 없어 이렇게 여쭤보네요 ㅠㅠ 죄송합니다.

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.16 17:16 신고




        감사합니다 : )

        다음 두 가지 부분을 수정하여 개선할 수 있을 듯합니다.


        우선 코드의 다음 부분에서 이미지 크기를 좀 더 작게하고 ..
        (현재는 가로길이 640으로 하고 있습니다. 테스트를 통해 더 작은 값으로 조정해보세요.)
        float resizeRatio = resize(img_gray, img_resize, 640);


        다음 함수의 파라미터를 조정해보세요.
        구글링해도 딱히 속도를 최적화하는 파라미터라고 알려주는 곳은 없더라구요.
        테스트를 통해 조정이 필요한듯합니다.

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

    • ㅇㅇ 2019.08.18 18:55


      assest 디렉토리에 2가지 xml 파일을 추가해주는데 각각의 파일이 무슨 역할을 해주는지 궁금합니다!

    • 초심자 2019.08.19 15:19


      extern "C"
      JNIEXPORT jlong JNICALL
      Java_com_example_opencvface_MainActivity_loadCascade(JNIEnv *env, jobject instance,
      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;

      }

      이 부분은 어떤 작업을 수행해주는건가요?

      • 초심자 2019.08.19 16:26


        opencv 얼굴과 눈 검출하는 소스 예제를 한줄 한줄 주석을 달며 공부중인 학생입니다.

        추가할만한 곳이나 수정해야될 부분이 있을까요? 모르는 부분이 많은데 검색해도 잘 안나와 헤매고 있습니다 ㅠㅠ

        주석이 안달린 부분들은 무슨 역할을 하는지 배우고싶어요!

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

        using namespace cv;
        using namespace std;

        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_example_opencvface_MainActivity_loadCascade(JNIEnv *env, jobject instance,
        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;

        }

        extern "C"
        JNIEXPORT void JNICALL
        Java_com_example_opencvface_MainActivity_detect(JNIEnv *env, jobject instance,
        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);

        // 인풋 이미지를 분류해줌

        ((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++) {

        // facas.size가 0이상이면 얼굴을 발견한것으로 처리

        double real_facesize_x = faces[i].x / resizeRatio; // 얼굴을 찾은 x값과 y값을 변환

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

        // img_result = 원이 그려질 이미지
        // center = 원의 중심 좌표
        // Size = 원의 반지름
        // Scalar = 원의 색깔 BRG 순서 이 부분에서는 분홍색 원을 그려준다

        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; // 영상에서 눈을 검출하여 검출된 정보를 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++ ) // eyes.size() 값은 눈이 검출된 개수입니다.

        {
        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 );
        // img_result = 원이 그려질 이미지
        // eye_center = 원의 중심 좌표
        // radius = 원의 반지름
        // Scalar = 원의 색깔 BRG 순서 이 부분에서는 빨간색 원을 그려준다
        // 눈이 인식되면 원을 그리는 부분
        }

        }
        }

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


        Java_com_example_opencvface_MainActivity_loadCascade는 안드로이드에서 XML파일을 불러와서 CascadeClassifier 함수에서 불러오게 만들기 위해서 입니다.


        본 포스팅의 "1. 프로그램 흐름 및 실행결과" 부분과

        아래 유튜브 영상의 8:00 부터 보시면 코드를 이해하는데 도움이 될듯합니다.
        https://youtu.be/F2LYkh1Y1eo?list=PLwfJJiO20qkB7EhtRITL3znme8mIgOwID&t=480



    • 구독자 2019.08.25 21:03


      선생님 글을 보며 열심히 공부하고 있는 학생입니다.
      위 프로젝트에서 카메라가 들어오는 이미지 부분과 얼굴과 눈을 인식하여 원을 그려주는 부분을 스레드를 사용하여
      각각 따로 작업을 실행하게 하고 싶습니다.
      그래서 AsyncTask를 공부 중 인데 조언을 받고 싶습니다.
      위 프로젝트에서 제가 말한대로 할 수 있을까요?
      할 수 있다면 어떤 부분을 고쳐주고 추가해줘야 할지... 감이 잘 안잡혀서요
      유투브와 글 잘 보고 있습니다!

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.25 21:05 신고


        감사합니다 : )

        얼굴을 검출하고나서 얼굴 영역에서 눈을 찾는 거라 두가지를 따로 쓰레드로 돌리는게 의미없을 듯합니다.

      • 구독자 2019.08.25 21:27


        아 그렇군요 그러면 화면을 더 매끄럽게
        출력하기 위해서는 어떻게 해야될까요?
        끊김 현상이 좀 있어서 좀 더 부드럽게
        화면을 출력시키고 싶어요

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.25 21:32 신고


        이미지를 좀 더 축소해보세요.

      • 구독자 2019.08.25 21:37


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

        이 부분에서 이미지를 줄여 주면 되나요?

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.25 21:38 신고


        640 이하로 값을 조정해보면서 얼굴 검출과 속도를 체크하면 됩니다

      • 구독자 2019.08.27 14:40


        말씀하신대로 진행 하였더니 화면은 부드럽게 잘 출력이 됩니다! 쓰레드 공부 할 겸 처음에 질문 했던건데 카메라가 들어오는 이미지 부분과 얼굴과 눈을 인식하여 원을 그려주는 부분을 두가지를 따로 돌려보려고 합니다. 틀만 좀 잡아주시면 감사하겠습니다 ㅠ

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.27 18:23 신고


        AsyncTask 실행시 카메라 영상을 넘겨주고 얼굴 또는 눈을 디텍트하는 함수를 각각 호출하면 될듯합니다.

    • 학생 2019.08.29 09:50


      안녕하세요 혹시 native-lib.cpp에서 변수하나가 일정 값 이상되면 비프음(경고음)을 내고싶은데
      다른 자료들보니까 java 혹은 터치할때만 소리가 나는 예제만 있더라구요... 혹시 cpp에서 경고음 내는 방법을 아시나요..??

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.29 10:07 신고


        다음 링크를 참고하세요.. 해보지는 않았습니다.

        https://github.com/googlesamples/android-ndk/tree/master/audio-echo

      • 학생 2019.08.29 10:30


        봐도 모르겠네요...ㅎㅎ 아니면 native-lib.cpp에서 계산한 변수의 값을 자바로 리턴할수있는 방법이 있나요???
        cpp에서 계산한 변수 값을 자바로 가져와서 자바에서 경고음을 출력하는 방법도 있을거같아서요!

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.29 10:38 신고


        "4. 검출된 얼굴 갯수 반환받기"를 참고하세요

      • 학생 2019.08.29 10:45


        네... 혹시 jint로 해도 화면에 모양은 표시되나요???
        그리는거 하나, 갯수 반환하는거 하나 함수를 두개써야되나여??

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.29 10:57 신고


        함수 리턴시 얼굴 갯수는 리턴받으면 되고 좌표는 아규먼트에서 참조로 받으면 됩니다.

      • 학생 2019.08.29 13:37


        그러니까 함수는 하나쓰되 위에있는 4번을 사용하면 얼굴도 인식하고 자바에서 변수에 저장할수 있다는 말씀이시죠??

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.29 14:03 신고


        자바와 C++에서는 리턴값을 하나만 줄 수 있어서 얼굴 위치 좌표를 자바로 받고 싶으면 아규먼트로 리턴값을 추가로 받으면 된다는 의미입니다.

        4번 코드는 얼굴 개수만 리턴 받을 수 있습니다.

      • 학생 2019.08.29 14:56


        c++에서 그리면서 얼굴갯수, 즉 2가지를 한번에는 못한다는 말씀이신가요???
        그러면 제가 원하는걸 하려면 int rat에 함수를 써서 갯수를 넣고 다시 함수를 한번더 써서 얼굴을 검출해 그리고
        rat에서 몇개 이상이 되면 비프음(경고음)을 내도록 코딩을 해야되는건가요??

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.29 15:04 신고


        필요한게 cpp에서 검출된 얼굴 갯수만 리턴하는 건가요? cpp에서 이미지에 얼굴 위치 그려주는 건 다른것과 상관없이 가능합니다

      • 학생 2019.08.30 03:06


        지금은 제가 하고있는건 얼굴검출은 아니고 차선인식에서 일정한 기준값이 지나면 경고음을 내는 코딩을 하고있습니다
        이때 cpp에서 차선을 그리면서 차선의 시작점을 자바로 가져와서 일정한 값 이상이 지나면 경고음을 내는 프로그램을 짜고있습니다. 얼굴 위치(차선)이 다른것과 상관없다는 것은 int a = 차선인식함수(~~,~~); 했을때 a라는 변수에 일정한 값을 리턴하는과 동시에 같이 차선에 그리는것이 가능 한건가요??

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.08.30 13:09 신고


        cpp 함수에서 opencv 드로우 함수로 그리면 됩니다.

      • 학생 2019.08.30 14:57


        그러면 그리는거와 동시에 자바로 변수 리턴이 가능하다는 말씀이시죠??ㅎㅎ

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


        포스트의 코드에선 결과이미지에 얼굴 위치를 그려서 반환하고 얼굴개수는 리턴하고 있습니다. 코드를 분석해보세요

      • 학생 2019.09.06 09:26


        네! 감사합니다.
        계속 시도해보고 안되면 다시 질문 해도 될까요?? ㅎㅎ

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.09.06 09:58 신고


        괜찮습니다

    • 예민한eye들 2019.09.29 16:59


      버튼을 추가하면 흰색 기다란 공간안에 버튼이 들어가는데 혹시 그냥 버튼만 들어가게끔 할 수 없을까요?

    • robotics 2019.10.13 20:26


      안녕하세요. 이번에 opencv 공부를 하면서 접하게 된 학생입니다.
      이번에 프로젝트로 핸드폰 카메라로 영상을 받아 사람을 인식하고 따라가는 로봇을 만들어 보려고 합니다.
      사람 상체를 인식하고 화면상에 나타난 사람의 상체의 크기를 받아 아두이노로 값을 보내려고 하는데
      혹시 이 작업을 opencv로 할 수 있는지 궁금합니다.

    • student01 2019.10.14 15:37


      안녕하세요!! 혹시 인식한 얼굴 하나만 트레킹 하려고하는데 어떻게해야 하나요??

    • qazxsw 2019.10.15 00:51


      안녕하세요 잘 보고있습니다! 좋은 게시물 감사드려요..
      저는 webrtc를 이용해서 영상통화하는 어플에 opencv 얼굴 검출 기능을 추가하고 싶어서 시도해보는 중 입니다.
      원래 만들고있던 어플이 리눅스 우분투환경에서 사용하고 있었는데, 위 게시물은 윈도우 기준인것 같아서 혹시 나중에 문제가 생기지는 않을지 걱정입니다. 애초에 실시간으로 통화하면서 얼굴 검출이 가능한것인지도 사실 모르겠구요ㅠㅠ
      관련된 참고할만한 자료 혹시 아시는 것 있으시면 조언해주시면 정말 감사하겠습니다...!

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.10.15 12:39 신고


        포스트는 안드로이드 기반입니다. 혹시 PC 기반이라면 OpenCV를 사용하여 얼굴 인식을 적용할 수 있습니다. 검출 정확도가 높지는 않습니다.

    • aaaa 2019.10.22 20:35


      upperbody랑 frontface 두개를 같이 돌렸는데 인식이 되는동안 영상의 움직임이 자꾸 끊겨서 부자연스럽습니다.
      좀 더 부드럽게 영상이 나오려면 어떻게 해야하나요???

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.10.22 23:28 신고


        이미지를 줄여보거나...

        테스트해보면서 detectMultiScale의 파리미터를 조정해봐야 합니다.
        파라미터 수정방법은 아래 링크를 참고하세요
        https://stackoverflow.com/a/35432155

    • 2019.11.11 17:55


      비밀댓글입니다

    • 1111 2019.12.26 23:46


      고수님! 사진이 그냥 검은색으로 저장되는 경우는 어떻게 해결할 수 있나요?ㅠㅠ

    • 2020.01.26 16:36


      비밀댓글입니다

    • TomKong 2020.01.27 00:46


      안녕하세요. webnautes님의 블로그를 통해 제 개인 프로젝트를 수행하는데 정말 많은 도움을 받고 있습니다.
      그런데 이 포스팅과 관련하여 막히는 부분이 있어 조언을 얻고자 댓글을 씁니다.

      저는 MOG2 background Subtractor를 사용하려고 합니다. 이전 영상을 누적해야하는 알고리즘 특성상.
      public void onCameraViewStarted 시 한번만 Ptr<BackgroundSubtractor> pMOG2 = createBackgroundSubtractorMOG2(); 통해서 객체를 생성하고 (JNI로 생성한 함수이름 = loadBackgoundSubtractor)
      public Mat onCameraFrame에서 (또다른 JNI로 생성한 cpp 함수를 call하게 하여) pMOG2->apply(matInput, matResult);를 반복적으로 실행시키려고 합니다.

      때문에 JNI로 만든 C++ 함수에서 객체를 한번만 생성 후 return으로 객체를 넘겨주기 위해. 이 포스팅에 남겨주신것과 비슷하게 이런 식으로 코드를 짜보았습니다.
      JNIEXPORT jlong JNICALL
      Java_com_example_useropencvwithndk_1build2_MainActivity_loadBackgoundSubtractor(JNIEnv *env, jobject instance, jstring subtractorName)
      {
      jlong ret;
      Ptr<BackgroundSubtractor> pMOG2 = createBackgroundSubtractorMOG2();
      ret = (jlong)pMOG2; ////에러 발생지점

      return ret;
      }

      그런데 위의 코드 중 '에러 발생지점'이라고 적어놓은 부분에서 error: no matching conversion for C-style cast from 'Ptr<cv::BackgroundSubtractor>' to 'jlong' (aka 'long') 에러가 발생합니다. 정말 여러가지 시도를 해보았는데 해결을 못하고 있습니다.

      혹시 해결 방안 생각나는 것이 있으신지 질문드립니다.
      감사합니다.

    • 캉캉 2020.01.28 23:57


      항상 공부할때 도움이 되 주셔서 감사합니다. 인식되는 얼굴 부분에 저런 이미지가 아닌 가면? 같은걸 씌우고 싶은데(스노우같은 어플처럼요!) 그 부분을 하고싶다면 어떤 부분을 수정하면 좋을까요? 계속 봐도 생각이 잘 안떠올라서 질문드립니다!

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.01.29 09:18 신고


        다음처럼 할 수 있습니다. PC에서 한 것입니다.

        OpenCV를 사용하여 얼굴에 선글라스 씌우기
        https://webnautes.tistory.com/1348

Designed by Tistory.