반응형


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용 추가

                    2020. 6. 27 Android 4.0, OpenCV 4.3에서 진행


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.  자바 코드 수정합니다.





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



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




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




CMake 방식이라면..


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




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

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




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





ndk-build 방식이라면..


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


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


"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 *nativeFileNameString = env->GetStringUTFChars(cascade_file_name, 0);


    string baseDir("/storage/emulated/0/");

    baseDir.append(nativeFileNameString);

    const char *pathDir = baseDir.c_str();


    jlong ret = 0;

    ret = (jlong) new CascadeClassifier(pathDir);

    if (((CascadeClassifier *) ret)->empty()) {

        __android_log_print(ANDROID_LOG_DEBUG, "native-lib :: ",

                            "CascadeClassifier로 로딩 실패  %s", nativeFileNameString);

    }

    else

        __android_log_print(ANDROID_LOG_DEBUG, "native-lib :: ",

                            "CascadeClassifier로 로딩 성공 %s", nativeFileNameString);



    env->ReleaseStringUTFChars(cascade_file_name, nativeFileNameString);


    return ret;


}



extern "C"

JNIEXPORT void JNICALL

Java_com_tistory_webnautes_useopencvwithcmake_MainActivity_detect(JNIEnv *env, jobject instance,

                                                                  jlong cascadeClassifier_face,

                                                                  jlong cascadeClassifier_eye,

                                                                  jlong matAddrInput,

                                                                  jlong matAddrResult) {


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

        }

    }



}




2-10. API 29 이상에서 다음과 같은 에러와 함께 앱이 종료될 수 있습니다.


E/cv::error(): OpenCV(4.3.0) Error: Assertion failed (!empty()) in detectMultiScale, file /build/master_pack-android/opencv/modules/objdetect/src/cascadedetect.cpp, line 1689


E/libc++abi: terminating with uncaught exception of type cv::Exception: OpenCV(4.3.0) /build/master_pack-android/opencv/modules/objdetect/src/cascadedetect.cpp:1689: error: (-215:Assertion failed) !empty() in function 'detectMultiScale'


A/libc: Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 26339 (Thread-3), pid 26173 (s.facedetection)




AndroidManifest.xml에 다음을 추가하고 앱을 재설치하면 해결됩니다. 


    <application

        android:allowBackup="true"

        android:requestLegacyExternalStorage="true"

        android:icon="@mipmap/ic_launcher"



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

반응형

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

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

유튜브 구독하기


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

  1. 이전 댓글 더보기
  2. robotics 2019.10.13 20:26

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

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.10.13 21:48 신고

      구글에서 다음 키워드를 사용하여 검색하여 나오는 결과들을 검토해보세요..

      harr body detection

    • robotics 2019.10.14 15:38

      네 감사합니다.

  3. student01 2019.10.14 15:37

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

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.10.14 16:01 신고

      해보지 않은 부분이라 답을 하기 어렵네요^^;;

    • student01 2019.10.14 17:26

      배열을 리턴하려면 어떻게 해야할까요..?
      reference를 리턴이 가능한가요??

    • student01 2019.10.14 17:29

      얼굴의 x,y좌표를 리턴하고 싶습니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.10.14 19:29 신고

      다음 링크를 참고하세요

      http://forum.falinux.com/zbxe/index.php?document_srl=571214&mid=lecture_tip

    • student01 2019.10.14 22:09

      감사합니다.

    • student01 2019.10.14 23:16

      배열을 리턴하는건 해결됐는데
      onCameraFrame안에서 TextView의 text를 바꾸려고
      setText()함수를 호출했는데 앱이 자꾸 중지되네요... 왜그런걸까요..?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.10.14 23:56 신고

      자세한 원인은 에러 메시지를 보세요...

      UI 쓰레드나 핸들러 안에서 UI변경을 해야 할 듯합니다.

    • student01 2019.10.19 12:49

      다 해결되었습니다..정말 감사합니다ㅎㅎ

  4. qazxsw 2019.10.15 00:51

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

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

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

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

  6. 2019.11.11 17:55

    비밀댓글입니다

  7. 1111 2019.12.26 23:46

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

  8. 2020.01.26 16:36

    비밀댓글입니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.01.26 16:39 신고

      이미지를 불러오는 다음 예제를 얼굴인식이 가능하게 수정해야할듯합니다.

      https://webnautes.tistory.com/1090

    • 2020.01.26 17:55

      비밀댓글입니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.01.26 17:58 신고

      카메라 영상을 기반으로 하는 코드를 참고해야 합니다. 이미지 기반으로 하더라도 거의 그대로 사용해서 동작할듯합니다

    • 2020.01.26 19:26

      비밀댓글입니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.01.26 19:28 신고

      다행입니다

  9. 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') 에러가 발생합니다. 정말 여러가지 시도를 해보았는데 해결을 못하고 있습니다.

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

  10. 캉캉 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

  11. 옹삼 2020.03.18 16:20

    CMake 게시글을 보고 캡쳐 버튼을 만들기 위해 이 게시물을 보는중인데요
    3-4. 버튼 클릭시 사진을 저장하기 위한 코드를 onCreate 메소드에 추가합니다.

    onCreate 메소드가 정확히 어디인지 잘 모르겠습니다 ㅜㅜ



    3-2. MainActivity.java 파일에 세마포어를 사용하기 위한 코드를 추가합니다.
    그리고 이 부분도 정확히 어디인지 모르겠습니다


    bit.ly/88-05
    이것을 사용하였습니다

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.03.18 16:55 신고

      3-4는 MainActivity.java 파일에 추가하면 됩니다.

      3-2는 다음 부분 위에 추가하면 됩니다.

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

    • 옹삼 2020.03.19 03:28

      3-2

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

      이 부분이 없습니다..

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

      그나마 비슷한 문구가 이거입니다.

      그럼 저 java4 위에 3-2 코드를 추가하면 되는건가요?

      3-3
      저의 코드는
      @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());



      //return matResult;
      return matInput;

      }



      이렇게 되어있습니다.
      위 코드에서 Core. 부분부터는 없는데 괜찮은건가요?


      3-4
      onCreate 메소드에 추가합니다.
      이게 무슨뜻인지 잘 모르겠군요 ㅜㅜ

      MainActivity.java 파일에 추가하라는게 코딩소스 안에다 집어넣으라는건지.
      아니면 MainActivity.java > New > File
      이렇게해서 새로 만들라는건지

      조금더 자세하게 설명해주시면 감사하겠습니다!

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.03.19 08:39 신고

      그 앞에 넣으면 됩니다

  12. 태민 2020.04.08 22:44

    안녕하세요. 블로그와 함게 유튜브로 선생님에게 많은것을 배우고 있는 학생입니다.

    다름이아니라, 제가 OpenCV 기반으로 android canny detection을 작업하고 있습니다.
    이제 버튼을 통해서 사진촬영을 하고 저장하려고 하는데,
    선생님께서 하신대로 똑같이 해봤지만, canny detection과 app 구동은 잘 되지만,
    막상 버튼을 눌러서 사진을 찍으려하면 app이 오류가 뜨고, "사용이 중지되었습니다"라며 꺼집니다..

    즉,
    "Canny" 버튼을 통해서 CannyEdgeDetection은 잘 구동되며,
    "Click" 버튼을 누르면 사진이 찍히기는 커녕 오류로 인해 앱 사용이 중지됩니다...


    다음은 해당 Logcat입니다.

    04-08 22:09:10.618 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:10.618 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 19, frame_idx 229, stream type 6, time stamp 1586351350 630381000
    04-08 22:09:10.618 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 283.282410 exp_index 282 real_gain 1.947439
    04-08 22:09:10.618 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4398 gain r=1.825073 g=1.000000 b=1.916122
    04-08 22:09:10.618 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:10.628 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 5, frame_idx 228, stream type 1, time stamp 3417 9328000
    04-08 22:09:10.648 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=230
    04-08 22:09:10.648 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:10.648 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 283.282410 exp_index 282 real_gain 1.947439
    04-08 22:09:10.648 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4398 gain r=1.825180 g=1.000000 b=1.916061
    04-08 22:09:10.658 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 0, frame_idx 230, stream type 6, time stamp 1586351350 664054000
    04-08 22:09:10.658 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:10.658 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 6, frame_idx 229, stream type 1, time stamp 3417 42999000
    04-08 22:09:10.688 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=231
    04-08 22:09:10.688 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:10.688 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 282.916016 exp_index 282 real_gain 1.958013
    04-08 22:09:10.688 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4398 gain r=1.825302 g=1.000000 b=1.916000
    04-08 22:09:10.688 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 1, frame_idx 231, stream type 6, time stamp 1586351350 697724000
    04-08 22:09:10.688 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:10.688 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 0, frame_idx 230, stream type 1, time stamp 3417 76698000
    04-08 22:09:10.718 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=232
    04-08 22:09:10.718 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:10.718 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 282.549591 exp_index 282 real_gain 1.958013
    04-08 22:09:10.718 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4398 gain r=1.825424 g=1.000000 b=1.915939
    04-08 22:09:10.718 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 2, frame_idx 232, stream type 6, time stamp 1586351350 731390000
    04-08 22:09:10.718 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:10.728 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 1, frame_idx 231, stream type 1, time stamp 3417 110336000
    04-08 22:09:10.748 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=233
    04-08 22:09:10.748 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:10.748 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 282.549591 exp_index 282 real_gain 1.958013
    04-08 22:09:10.748 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4398 gain r=1.825546 g=1.000000 b=1.915863
    04-08 22:09:10.758 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 3, frame_idx 233, stream type 6, time stamp 1586351350 765065000
    04-08 22:09:10.758 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:10.758 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 2, frame_idx 232, stream type 1, time stamp 3417 144008000
    04-08 22:09:10.788 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=234
    04-08 22:09:10.788 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:10.788 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 282.549591 exp_index 282 real_gain 1.958013
    04-08 22:09:10.788 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4399 gain r=1.825684 g=1.000000 b=1.915787
    04-08 22:09:10.788 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 4, frame_idx 234, stream type 6, time stamp 1586351350 798731000
    04-08 22:09:10.788 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:10.788 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 3, frame_idx 233, stream type 1, time stamp 3417 177672000
    04-08 22:09:10.818 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=235
    04-08 22:09:10.818 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:10.818 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 282.549591 exp_index 282 real_gain 1.958013
    04-08 22:09:10.818 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4399 gain r=1.825821 g=1.000000 b=1.915695
    04-08 22:09:10.818 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 5, frame_idx 235, stream type 6, time stamp 1586351350 832398000
    04-08 22:09:10.828 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:10.828 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 4, frame_idx 234, stream type 1, time stamp 3417 211345000
    04-08 22:09:10.858 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=236
    04-08 22:09:10.858 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:10.858 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 282.183197 exp_index 282 real_gain 1.958013
    04-08 22:09:10.858 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4400 gain r=1.825958 g=1.000000 b=1.915604
    04-08 22:09:10.858 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 6, frame_idx 236, stream type 6, time stamp 1586351350 866068000
    04-08 22:09:10.858 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:10.858 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 5, frame_idx 235, stream type 1, time stamp 3417 245017000
    04-08 22:09:10.888 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=237
    04-08 22:09:10.888 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:10.888 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 282.183197 exp_index 282 real_gain 1.958013
    04-08 22:09:10.888 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4400 gain r=1.826096 g=1.000000 b=1.915512
    04-08 22:09:10.888 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 7, frame_idx 237, stream type 6, time stamp 1586351350 899737000
    04-08 22:09:10.888 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:10.888 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 6, frame_idx 236, stream type 1, time stamp 3417 278684000
    04-08 22:09:10.918 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=238
    04-08 22:09:10.918 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:10.918 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 281.541992 exp_index 282 real_gain 1.958013
    04-08 22:09:10.918 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4400 gain r=1.826233 g=1.000000 b=1.915405
    04-08 22:09:10.918 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 8, frame_idx 238, stream type 6, time stamp 1586351350 933410000
    04-08 22:09:10.918 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:10.928 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 0, frame_idx 237, stream type 1, time stamp 3417 312352000
    04-08 22:09:10.958 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=239
    04-08 22:09:10.958 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:10.958 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 280.992401 exp_index 282 real_gain 1.958013
    04-08 22:09:10.958 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4401 gain r=1.826370 g=1.000000 b=1.915298
    04-08 22:09:10.958 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 9, frame_idx 239, stream type 6, time stamp 1586351350 967077000
    04-08 22:09:10.958 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:10.958 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 1, frame_idx 238, stream type 1, time stamp 3417 346022000
    04-08 22:09:10.988 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=240
    04-08 22:09:10.988 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:10.988 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 280.992401 exp_index 282 real_gain 1.958013
    04-08 22:09:10.988 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4401 gain r=1.826508 g=1.000000 b=1.915192
    04-08 22:09:10.988 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 10, frame_idx 240, stream type 6, time stamp 1586351351 750000
    04-08 22:09:10.988 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:10.998 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 2, frame_idx 239, stream type 1, time stamp 3417 379702000
    04-08 22:09:11.018 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=241
    04-08 22:09:11.018 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:11.018 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 282.091614 exp_index 282 real_gain 1.958013
    04-08 22:09:11.018 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4401 gain r=1.826630 g=1.000000 b=1.915085
    04-08 22:09:11.018 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 11, frame_idx 241, stream type 6, time stamp 1586351351 34419000
    04-08 22:09:11.028 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:11.028 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 3, frame_idx 240, stream type 1, time stamp 3417 413364000
    04-08 22:09:11.058 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=242
    04-08 22:09:11.058 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:11.058 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 282.091614 exp_index 282 real_gain 1.958013
    04-08 22:09:11.058 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4402 gain r=1.826752 g=1.000000 b=1.914978
    04-08 22:09:11.058 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 12, frame_idx 242, stream type 6, time stamp 1586351351 68088000
    04-08 22:09:11.058 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:11.058 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 4, frame_idx 241, stream type 1, time stamp 3417 447037000
    04-08 22:09:11.078 18610-18610/com.example.testapp E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.testapp, PID: 18610
    java.lang.NullPointerException: Attempt to read from field 'long org.opencv.core.Mat.nativeObj' on a null object reference
    at org.opencv.imgproc.Imgproc.cvtColor(Imgproc.java:2223)
    at com.example.testapp.MainActivity$1.onClick(MainActivity.java:75)
    at android.view.View.performClick(View.java:5246)
    at android.widget.TextView.performClick(TextView.java:10573)
    at android.view.View$PerformClick.run(View.java:21256)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:145)
    at android.app.ActivityThread.main(ActivityThread.java:6912)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
    04-08 22:09:11.088 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=243
    04-08 22:09:11.088 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:11.088 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 283.099213 exp_index 282 real_gain 1.958013
    04-08 22:09:11.088 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4401 gain r=1.826874 g=1.000000 b=1.914871
    04-08 22:09:11.088 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 13, frame_idx 243, stream type 6, time stamp 1586351351 101752000
    04-08 22:09:11.088 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:11.098 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 5, frame_idx 242, stream type 1, time stamp 3417 480699000
    04-08 22:09:11.118 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=244
    04-08 22:09:11.118 444-18673/? E/mm-camera: cpp_module_handle_sof_notify_event:574, sending bus msg, session_id=2, R1=1, R2=1
    04-08 22:09:11.118 444-18673/? E/mm-camera: isp_pipeline_save_aec_params lux_idx 282.458008 exp_index 282 real_gain 1.958013
    04-08 22:09:11.118 444-18673/? E/mm-camera: isp_pipeline_awb_trigger_update: color 4402 gain r=1.826996 g=1.000000 b=1.914764
    04-08 22:09:11.118 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 14, frame_idx 244, stream type 6, time stamp 1586351351 135435000
    04-08 22:09:11.128 444-18675/? E/mm-camera: isp_pipeline_stats_config_update Received stats_config_update for pipeline 0xb4756000
    04-08 22:09:11.128 431-18662/? E/mm-camera-intf: mm_stream_read_msm_frame: VIDIOC_DQBUF buf_index 6, frame_idx 243, stream type 1, time stamp 3417 514371000
    04-08 22:09:11.128 957-18712/? E/android.os.Debug: ro.product_ship = true
    04-08 22:09:11.128 957-18712/? E/android.os.Debug: ro.debug_level = 0x4f4c
    04-08 22:09:11.158 444-18673/? E/mm-camera: isp_hw_proc_subdev_event: ISP_EVENT_SOF=245

    얼핏, NullPointerException 문제인것같은데....
    뭐가 문제인지 잘 모르겠습니다.

    다음은 해당 MainActivity.java 입니다. (android studio는 3.4를, OpenCV-sdk-andriod는 3.4.5를 사용하고 있습니다.)

    package com.example.testapp;

    import android.content.Intent;
    import android.net.Uri;
    import android.os.Environment;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    //import android.view.SurfaceView;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.Toast;

    import org.opencv.android.BaseLoaderCallback;
    import org.opencv.android.CameraBridgeViewBase;
    import org.opencv.android.JavaCameraView;
    import org.opencv.android.OpenCVLoader;
    //import org.opencv.core.Core;
    import org.opencv.core.Mat;
    //import org.opencv.*;
    //import org.opencv.core.Point;
    //import org.opencv.core.Size;
    import org.opencv.imgcodecs.Imgcodecs;
    import org.opencv.imgproc.Imgproc;

    import java.io.File;
    import java.util.concurrent.Semaphore;
    //import java.util.Random;

    public class MainActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2 {

    CameraBridgeViewBase cameraBridgeViewBase;
    BaseLoaderCallback baseLoaderCallback;
    boolean startCanny = false;
    private final Semaphore writeLock = new Semaphore(1);
    private Mat frame;


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

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

    public void Canny(View Button) {

    if (startCanny == false) {
    startCanny = true;
    } else {
    startCanny = false;
    }
    }

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

    cameraBridgeViewBase = findViewById(R.id.CameraView);
    cameraBridgeViewBase.setCvCameraViewListener(this);

    Button button = findViewById(R.id.button2);
    button.setOnClickListener(new View.OnClickListener() {
    public void onClick(View view) {
    try {
    getWriteLock();

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

    String filename = file.toString();
    Imgproc.cvtColor(frame, frame, Imgproc.COLOR_BGR2RGBA, 4);
    boolean ret = Imgcodecs.imwrite(filename,frame);
    if ( ret ) Toast.makeText(getApplicationContext(), "SUCCESS!", Toast.LENGTH_SHORT).show();
    else Toast.makeText(getApplicationContext(), "FAIL!", Toast.LENGTH_SHORT).show();

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

    }
    }
    );

    baseLoaderCallback = new BaseLoaderCallback(this) {

    @Override
    public void onManagerConnected(int status) {
    super.onManagerConnected(status);

    switch (status) {
    case BaseLoaderCallback.SUCCESS:
    cameraBridgeViewBase.enableView();
    break;
    default:
    super.onManagerConnected(status);
    break;
    } }
    };
    }
    @Override
    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {

    Mat frame = null;
    try {
    frame = inputFrame.rgba();

    if (startCanny == true) {

    Imgproc.cvtColor(frame, frame, Imgproc.COLOR_RGBA2GRAY);
    Imgproc.Canny(frame, frame, 100, 80);
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    releaseWriteLock();

    return frame;
    }
    @Override
    public void onCameraViewStarted(int width, int height) {
    }

    @Override
    public void onCameraViewStopped() {
    }

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

    if (!OpenCVLoader.initDebug()) {
    Toast.makeText(getApplicationContext(), "There's a problem, yo!", Toast.LENGTH_SHORT).show();
    } else {
    baseLoaderCallback.onManagerConnected(baseLoaderCallback.SUCCESS);
    }
    }

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

    @Override
    protected void onDestroy() {
    super.onDestroy();
    if (cameraBridgeViewBase != null) {
    cameraBridgeViewBase.disableView();
    }
    }

    }

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.04.09 13:21 신고

      에러 메시지에서 관련 있는 부분은 다음입니다.

      java.lang.NullPointerException: Attempt to read from field 'long org.opencv.core.Mat.nativeObj' on a null object reference
      at org.opencv.imgproc.Imgproc.cvtColor(Imgproc.java:2223)
      at com.example.testapp.MainActivity$1.onClick(MainActivity.java:75)


      cvtColor 함수의 입력으로 사용되는 이미지가 없는 듯합니다.

      MainActivity.java의 75번째 줄을
      확인해보세요.

    • 태민 2020.04.10 17:37

      감사합니다!

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


      try {
      frame = inputFrame.rgba();

      if (startCanny == true) {

      Imgproc.cvtColor(frame, frame, Imgproc.COLOR_RGBA2GRAY);
      Imgproc.Canny(frame, frame, 100, 80);
      }
      } catch (Exception e) {
      e.printStackTrace();
      }
      releaseWriteLock();

      return frame;
      }

      이쪽 부분이 잘못되어서 cvtclolor 함수의 입력으로 사용되는 이미지(frame)가 없던 게 맞았습니다!

      이제 저장되는 부분까지 완성은 하였는데,

      ------------
      몇가지 질문 좀 더 드려도 될까요?

      1. 촬영을 하면 이미지가 계속 추가로 저장되는게 아니라, 한장찍고 또 다시 찍으면 전에 찍은 이미지는 지워지고 마지막에 찍은 이미지 한장만 남습니다. 계속 이미지를 추가로 저장하는 식으로 바꾸려하는데 어떻게 해야할까요??

      2. Mat frame으로 이미지가 Gray로, Canny로 변한뒤 사진을 찍을때, 저장은 Canny가 적용이 안된 상태이거나 Gray 와 Canny가 조금 섞인 상태 혹은 그냥 컬러 등등으로 뒤죽박죽 저장이 됩니다. 어떤 이유일까요..? 선생님의 도움이 절실합니다 ㅠㅠㅠ

      다음은 MainActivity.java 코드입니다.

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

      cameraBridgeViewBase = findViewById(R.id.CameraView);
      cameraBridgeViewBase.setCvCameraViewListener(this);

      Button button = findViewById(R.id.button2);
      button.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) {
      try {
      getWriteLock();

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

      String filename = file.toString();

      boolean ret = Imgcodecs.imwrite(filename, frame);
      if ( ret ) Toast.makeText(getApplicationContext(), "SUCCESS!", Toast.LENGTH_SHORT).show();
      else Toast.makeText(getApplicationContext(), "FAIL!", Toast.LENGTH_SHORT).show();

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

      }
      }
      );
      ...생략
      @Override
      public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {


      try {
      frame = inputFrame.rgba();

      if (startCanny == true) {

      Imgproc.cvtColor(frame, frame, Imgproc.COLOR_RGBA2GRAY);
      Imgproc.Canny(frame, frame, 100, 80);
      }
      } catch (Exception e) {
      e.printStackTrace();
      }
      releaseWriteLock();

      return frame;
      }
      .....생략

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.04.10 20:55 신고

      1. 동영상 파일로 저장하거나 아니면 배열에 저장하면 될듯합니다.

      배열에 저장할 경우 계속 저장할 수 없으니 일정 크기만 유지되도록 해야 할듯합니다.


      2. 다음처럼 단계별로 변수 이름을 바꾸면 해결될듯합니다.

      Imgproc.cvtColor(frame, gray, Imgproc.COLOR_RGBA2GRAY);
      Imgproc.Canny(gray, canny, 100, 80);


      gray와 canny의 경우 아래처럼 메모리를 할당해줘야 합니다.

      if ( gray == null )

      gray = new Mat(frame.rows(), frame.cols(), frame.type());



    • 태민 2020.04.11 12:37

      Button button = findViewById(R.id.button2);
      button.setOnClickListener(new View.OnClickListener() {
      public void onClick(View view) {
      try {
      getWriteLock();

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

      String filename = file.toString();

      boolean ret = Imgcodecs.imwrite(filename, frame);
      if ( ret ) Toast.makeText(getApplicationContext(), "SUCCESS!", Toast.LENGTH_SHORT).show();
      else Toast.makeText(getApplicationContext(), "FAIL!", Toast.LENGTH_SHORT).show();

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

      이부분에서 경로지정과 함께,
      String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
      이런 함수를 파일 이름에 지정해서 이미지파일의 이름을 새롭게 한 다음, 파일을 캡쳐할때마다 새로운 파일로 저장할 수 있는 방법은 없을까요??

      *현재 이미지파일명이 "image.jpg" 로 지정되어있어서 같은 이름으로 저장되기때문에 파일이 덮어씌어져서 두개 이상의 파일 저장이 안되는것같습니다
      (제가 이해한 부분이 맞을까요?)

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.04.11 14:26 신고

      생각하신게 맞습니다.
      아래처럼 하면 될듯합니다.

      String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());

      String filename = timeStamp + ".jpg";

      Imgcodecs.imwrite(filename, image);

    • 태민 2020.04.11 15:53

      private Mat matinput;
      private Mat canny;
      private Mat Gray;

      ...

      public void Canny(View Button) {

      if (startCanny == false) {
      startCanny = true;
      } else {
      startCanny = false;
      }
      }


      ...

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


      try {
      matinput = inputFrame.rgba();



      if (startCanny == true) {
      if(Gray == null)
      Gray = new Mat(matinput.rows(), matinput.cols(), matinput.type());

      Imgproc.cvtColor(matinput, Gray, Imgproc.COLOR_RGBA2GRAY);

      if(canny == null)
      canny = new Mat(Gray.rows(), Gray.cols(), Gray.type());

      Imgproc.Canny(Gray, canny, 50, 20);

      }
      } catch (Exception e) {
      e.printStackTrace();
      }
      releaseWriteLock();

      return matinput;
      }

      말씀하신대로 단계별 변수 이름을 각각 바꾸었습니다.

      캡쳐 후, 이미지를 확인할때에는 Canny 변환이 확실하게 된 모습으로 저장된것을 볼 수 있지만,
      화면상에서 canny 버튼을 눌렀을 때, Canny가 실시간으로 적용되던 기능은 작동되지 않습니다..
      말씀해주신대로 코드를 추가했을뿐인데....

      **원래는 canny 버튼을 누르면 화면상에서 실시간으로 Canny가 적용된 모습으로 보이고, capture 버튼을 누르면 그 모습이 저장되는 형식이었습니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.04.11 15:57 신고

      onCameraFrame메소드에서 리턴되는 것이 화면에 보여지게 됩니다.


      현재 matinput를 리턴하고 있으므로 컬러 영상이 보일겁니다.

      return matinput;

  13. 개린이 2020.04.12 20:26

    다 잘 따라갔는데 마지막에 돌려봤는데 알림으로 퍼미션을 허가하셔야 합니다 에서 예를 클릭해도 똑같은 알림이 계속 뜹니다,,,

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.04.12 20:33 신고

      전단계의 포스트를 다시 진행해서 실행이 잘되는지 확인.후 본 포스트를 다시 진행하는게 좋을듯합니다.

  14. 도움 2020.05.14 00:14

    앱이 구동이 되기전에 close가 됩니다. 그러나 실행시에는 문제가 없습니다.
    logcat
    2020-05-14 00:13:49.564 8557-8557/com.example.sung E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.sung, PID: 8557
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.sung/com.example.sung.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2646)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707)
    at android.app.ActivityThread.-wrap12(ActivityThread.java)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6077)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
    Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
    at com.example.sung.MainActivity.onCreate(MainActivity.java:134)
    at android.app.Activity.performCreate(Activity.java:6662)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2599)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707) 
    at android.app.ActivityThread.-wrap12(ActivityThread.java) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460) 
    at android.os.Handler.dispatchMessage(Handler.java:102) 
    at android.os.Looper.loop(Looper.java:154) 
    at android.app.ActivityThread.main(ActivityThread.java:6077) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756) 
    이러한 에러가 나옵니다
    실행만 되면 될것같은데 혹시 알려주실수 있으신가요??

  15. Favicon of https://u-n-joe.tistory.com BlogIcon 훈빠는 못말려 2020.05.20 06:31 신고

    안녕하세요 강의 잘 보고 있는 구독자 입니다~! 아래에 이런 에러가 발생해서 그런데... 어떻게 해결할수 있을까요?
    일주일정도 에러를 못 잡고 있네요 ,,,

    E/cv::error(): OpenCV(4.3.0) Error: Assertion failed (!empty()) in detectMultiScale, file /build/master_pack-android/opencv/modules/objdetect/src/cascadedetect.cpp, line 1689
    E/libc++abi: terminating with uncaught exception of type cv::Exception: OpenCV(4.3.0) /build/master_pack-android/opencv/modules/objdetect/src/cascadedetect.cpp:1689: error: (-215:Assertion failed) !empty() in function 'detectMultiScale'
    A/libc: Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 6535 (Thread-6), pid 6118 (on.facedetector)

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.05.20 07:58 신고

      detectMultiScale 함수의 입력이 잘못되었다는 에러입니다. 입력이미지와 xml 이 잘못되지 않았나 확인해보세요

    • 2020.05.20 09:57

      비밀댓글입니다

    • 2020.05.20 10:03

      비밀댓글입니다

    • 2020.05.20 10:03

      비밀댓글입니다

    • 2020.05.20 10:04

      비밀댓글입니다

    • Granola 2020.06.25 03:41

      혹시 타겟 api가 29로 되어있지않으신가요?

      저도 같은 에러가떠서 찾아보니

      haarcascade_frontalface_alt 이 파일이 복사가 안되었더라구요

      로그보니 권한문제때문이였고

      관련해서 또 찾아보니 Android 10 (API level 29)를 Target API로 설정한 앱이 Android 10이상의 단말에 설치되는 경우, 외부저장소에 대해 Scoped storage 모드로 동작하게 됩니다.

      이런 말이 있어서 타겟api를 28로 다운시켰더니 정상 작동 하더라구요

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.06.25 19:08 신고

      확인해봐야 겠군요.. 글 남겨주셔서 감사합니다

  16. mystic 2020.07.26 19:54

    강아지얼굴을 인식하여서 선글라스를 씌워보려고 하고있습니다. https://webnautes.tistory.com/1348 이 게시물을 참고하여 씌워보려고 하는데,
    얼굴위에 선글라스이미지를 어떻게 입혀야 할지 모르겠습니다.. 소스코드입니다
    #include <jni.h>
    #include <opencv2/opencv.hpp>
    #include <opencv2/objdetect.hpp>
    #include <opencv2/highgui.hpp>
    #include <opencv2/imgproc.hpp>
    #include <android/log.h>

    using namespace cv;
    using namespace std;

    void overlayImage(const Mat &background, const Mat &foreground,
    Mat &output, Point2i location)
    {
    background.copyTo(output);


    // start at the row indicated by location, or at row 0 if location.y is negative.
    for (int y = std::max(location.y, 0); y < background.rows; ++y)
    {
    int fY = y - location.y; // because of the translation

    // we are done of we have processed all rows of the foreground image.
    if (fY >= foreground.rows){
    break;
    }

    // start at the column indicated by location,

    // or at column 0 if location.x is negative.
    for (int x = std::max(location.x, 0); x < background.cols; ++x)
    {
    int fX = x - location.x; // because of the translation.

    // we are done with this row if the column is outside of the foreground image.
    if (fX >= foreground.cols){
    break;
    }

    // determine the opacity of the foregrond pixel, using its fourth (alpha) channel.
    double opacity =
    ((double)foreground.data[fY * foreground.step + fX * foreground.channels() + 3])

    / 255.;


    // and now combine the background and foreground pixel, using the opacity,

    // but only if opacity > 0.
    for (int c = 0; opacity > 0 && c < output.channels(); ++c)
    {
    unsigned char foregroundPx =
    foreground.data[fY * foreground.step + fX * foreground.channels() + c];
    unsigned char backgroundPx =
    background.data[y * background.step + x * background.channels() + c];
    output.data[y*output.step + output.channels()*x + c] =
    backgroundPx * (1. - opacity) + foregroundPx * opacity;
    }
    }
    }
    }




    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_opencvpractice_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_opencvpractice_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,444);


    //-- 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)); //얼굴부분 표시하는거



    string glasseImage="sunglasses.png";
    Mat glasses=imread(glasseImage, IMREAD_UNCHANGED);
    Mat result;
    overlayImage(img_result, glasses, result, center );


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

    //circle( img_result, eye_center, radius, Scalar( 255, 0, 0 ) ); //눈부분 표시하는거 (255,0,0)<-이부분이 원의색상

    }

    }



    }

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

      검출된 다음 눈 중심 위치를 기준으로 썬그라스가 자연스럽게 보이도록 썬그라스 이미지 위치를 조정하면 됩니다.

      Point eye_center( real_facesize_x + eyes[j].x + eyes[j].width/2, real_facesize_y + eyes[j].y + eyes[j].height/2 );

    • BlogIcon mystic 2020.07.26 20:18

      선글라스 위치는 구하였는데 선글라스이미지를 사용자얼굴위에 입혀주는 코드를 모르겠습니다. overlayImage함수를 호출하였는데 선글라스가 얼굴에 입혀지지 않아서..

    • BlogIcon mystic 2020.07.26 20:32

      result라는 Mat변수에 오버레이한 이미지가 리턴된거 같은데 이result변수에 들어있는 선글라스낀 이미지를 어떻게 화면에 보여주는지 모르겠습니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.07.26 20:34 신고

      overlayImage함수가 안드로이드 환경을 고려한게 아니라서 제대로 동작안하는 듯합니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.07.26 20:36 신고

      jni함수에서 img_result 변수에 이미지를 넣으면 자바 코드에 결과 영상으로 전달됩니다.

      Mat &img_result = *(Mat *) mat_addr_result;

    • BlogIcon mystic 2020.07.26 20:49

      img_result변수에 이미지를 넣어서 결과영상으로 전달하는거 까지는 하였는데 결과영상에 선글라스 이미지가 보이지 않습니다. 선글라스 이미지를 찾지못하거나 다른오류가 있는거같습니다.
      overlay(img_input,glasses,img_result,center(추후수정예정)) 이렇게 하였는데 적용이 안됩니다. 다른오류가 있는걸까요?.. 자꾸물어서 죄송합니다..
      #include <jni.h>
      #include <opencv2/opencv.hpp>
      #include <opencv2/objdetect.hpp>
      #include <opencv2/highgui.hpp>
      #include <opencv2/imgproc.hpp>
      #include <android/log.h>

      using namespace cv;
      using namespace std;

      void overlayImage(const Mat &background, const Mat &foreground,
      Mat &output, Point2i location)
      {
      background.copyTo(output);


      // start at the row indicated by location, or at row 0 if location.y is negative.
      for (int y = std::max(location.y, 0); y < background.rows; ++y)
      {
      int fY = y - location.y; // because of the translation

      // we are done of we have processed all rows of the foreground image.
      if (fY >= foreground.rows){
      break;
      }

      // start at the column indicated by location,

      // or at column 0 if location.x is negative.
      for (int x = std::max(location.x, 0); x < background.cols; ++x)
      {
      int fX = x - location.x; // because of the translation.

      // we are done with this row if the column is outside of the foreground image.
      if (fX >= foreground.cols){
      break;
      }

      // determine the opacity of the foregrond pixel, using its fourth (alpha) channel.
      double opacity =
      ((double)foreground.data[fY * foreground.step + fX * foreground.channels() + 3])

      / 255.;


      // and now combine the background and foreground pixel, using the opacity,

      // but only if opacity > 0.
      for (int c = 0; opacity > 0 && c < output.channels(); ++c)
      {
      unsigned char foregroundPx =
      foreground.data[fY * foreground.step + fX * foreground.channels() + c];
      unsigned char backgroundPx =
      background.data[y * background.step + x * background.channels() + c];
      output.data[y*output.step + output.channels()*x + c] =
      backgroundPx * (1. - opacity) + foregroundPx * opacity;
      }
      }
      }
      }




      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_opencvpractice_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_opencvpractice_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,444);


      //-- 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)); //얼굴부분 표시하는거
      //cvtColor(img_input,img_result,COLOR_BGR2GRAY); // 강아지 얼굴을 발견하면 화면을 회색으로 바꾸는거



      string glasseImage="sunglasses.png";
      Mat glasses=imread(glasseImage, IMREAD_UNCHANGED);
      Mat result;
      overlayImage(img_input, glasses, img_result, center );


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

      //circle( img_result, eye_center, radius, Scalar( 255, 0, 0 ) ); //눈부분 표시하는거 (255,0,0)<-이부분이 원의색상

      }

      }



      }

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.07.26 21:22 신고

      api29 이상에서 외부 저장소를 못읽어서 그럴 수 있습니다. 매니페스트의 application 태그에 다음 줄을 추가해보세요

        <application
            android:requestLegacyExternalStorage="true"


      이 문제가 아니라면 로그에서 원인을 찾아야 합니다

    • BlogIcon mystic 2020.07.26 21:41

      여러줄을 주석처리해보면서 해보았는데 overlayimage함수자체에 문제가 있는거 같습니다. 다음 overlayimage함수를 안드로이드에서 사용했을때 영상에 이미지가 입혀지도록 수정하려면 어떻게 해야할까요?
      void overlayImage(const Mat &background, const Mat &foreground,
      Mat &output, Point2i location)
      {
      background.copyTo(output);


      // start at the row indicated by location, or at row 0 if location.y is negative.
      for (int y = std::max(location.y, 0); y < background.rows; ++y)
      {
      int fY = y - location.y; // because of the translation

      // we are done of we have processed all rows of the foreground image.
      if (fY >= foreground.rows){
      break;
      }

      // start at the column indicated by location,

      // or at column 0 if location.x is negative.
      for (int x = std::max(location.x, 0); x < background.cols; ++x)
      {
      int fX = x - location.x; // because of the translation.

      // we are done with this row if the column is outside of the foreground image.
      if (fX >= foreground.cols){
      break;
      }

      // determine the opacity of the foregrond pixel, using its fourth (alpha) channel.
      double opacity =
      ((double)foreground.data[fY * foreground.step + fX * foreground.channels() + 3])

      / 255.;


      // and now combine the background and foreground pixel, using the opacity,

      // but only if opacity > 0.
      for (int c = 0; opacity > 0 && c < output.channels(); ++c)
      {
      unsigned char foregroundPx =
      foreground.data[fY * foreground.step + fX * foreground.channels() + c];
      unsigned char backgroundPx =
      background.data[y * background.step + x * background.channels() + c];
      output.data[y*output.step + output.channels()*x + c] =
      backgroundPx * (1. - opacity) + foregroundPx * opacity;
      }
      }
      }
      }

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.07.26 22:23 신고

      썬글라스 이미지를 읽을 수 있는지부터 확인해야 할듯합니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.07.26 22:24 신고

      안드로이드와 일반 C++ 환경에서 무슨 차이가 있어 동작을 안했는지 확인해야 할듯합니다. 혹은 구글에서 다른 사람이 안다로이드에서 한 비슷한 작업을 찾아봐야 할듯합니다.

    • BlogIcon mystic 2020.07.26 22:31

      디버깅해보면서 확인해본결과
      선글라스이미지를 읽어오는부분인 이부분이 제대로 동작하지 않아서 glasses변수가 비어있는걸 확인하였습니다.
      string glasseImage="sunglasses.png";

      Mat glasses=imread(glasseImage, IMREAD_UNCHANGED);
      선글라스이미지는 cpp폴더와 assets폴더 두개에다 다 넣어놨는데 제대로 불러와지지 않는거같습니다. 혹시 해결방안이 있을까요?..

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.07.26 22:33 신고

      api 29이상이면 위에 답변한걸 매니페스트에 넣어보세요. 아니면 로그캣에서 원인을 찾아야 합니다

    • BlogIcon mystic 2020.07.26 22:35

      처음설정할떄 api21 롤리팝으로 설정하여 프로젝트를 만들었습니다. 매니페스트에 위에답변한 코드도넣었습니다. 그런데도 안되네요..ㅠㅠ

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.07.26 22:37 신고

      로그캣을 볼수 밖에 업습니다

    • BlogIcon mystic 2020.07.26 22:41

      로그캣은 어디서볼수있나요?

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.07.26 22:47 신고

      구글에서 로그캣 사용법을 찾아서 확인해보세요.. 그래야 원인을 빨리 찾을듯 합니다

    • BlogIcon mystic 2020.07.26 22:49

      로그캣이너무 길어서 텍스트파일형식으로 메일로 보내드리겠습니다.

    • BlogIcon mystic 2020.07.26 23:03

      혹시몰라서 전체프로젝트를 올려놓은 깃허브주소도 보내드렸습니다. 같이 확인하면 좀 더 수월할 것 같습니다. 정말 감사합니다.

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.07.26 23:08 신고

      이미지를 로드하는 순간 로그캣을 봐야합니다. 전체를 봐서는 확인하기 힘듭니다

  17. mystic 2020.07.26 23:13

    혹시 전체프로젝트에서 어느부분이 문제인지 봐주실수 있을까요? 봐주신다면 정말 감사하겠습니다 전체프로젝트는 메일로 보내드렸습니다..

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.07.26 23:14 신고

      디버깅까지는 힘듭니다

    • mystic 2020.07.26 23:21

      넵 나중에도 안되면 다시 오겠습니다. 친절한 답변 정말 감사드립니다.

    • BlogIcon mystic 2020.07.27 08:51

      이미지를 로드하여 눈위치에 씌우는거 까지는 완성하였습니다. 하지만 선글라스이미지색이 원래는 빨간색이었는데 로드하고나니까 파란색으로 로드됩니다. 어떤부분을 수정하면 될까요? 휴대폰갤러리에 저장시킨 선글라스 사진은 빨간색으로 저장되었습니다.이미지를 로드하는 부분입니다.

      Mat img_gray;

      Mat glasses = imread("/storage/emulated/0/sunglasses.png",IMREAD_UNCHANGED);

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.07.27 09:07 신고

      컬러이미지는 bgr 또는 rgb 입니다. red와 blue 순서가 바뀐듯합니다. COLOR_BGR2RGB를 사용하여 변환해보세요

    • BlogIcon mystic 2020.07.27 09:20

      Mat glasses = imread("/storage/emulated/0/sunglasses.png", COLOR_BGR2RGB);
      이렇게바꾸었는데 선글라스색이 선명한파란색에서 약간투명한 보라색으로 바뀌었습니다. 어떤부분을 더 추가할까요?

    • BlogIcon mystic 2020.07.27 09:45

      해결하였습니다 감사합니당

  18. name 2020.07.28 19:29

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

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

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

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

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

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

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

    • BlogIcon mystic 2020.07.29 21:05

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

  19. 이문규 2020.08.07 14:52

    좋은 글 감사합니다.

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

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

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

  20. jyshin 2020.08.28 20:15

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

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.08.28 21:15 신고

      현재는 cmake 방식을 공식 지원하기 때문에 유리하지 않을까 싶습니다.

  21. 2020.09.27 20:27

    비밀댓글입니다

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

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

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

    • BlogIcon 바개런 2020.09.27 21:55

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

+ Recent posts