개발모음집

Android OpenCv 이미지 필터 + 얼굴인식 본문

Android

Android OpenCv 이미지 필터 + 얼굴인식

void 2017. 7. 26. 13:10

1. 이미지 필터

OpenCv를 이용하여 이미지 필터링을 하고 싶다.


OpenCv가 뭐지?

OpenCV(Open Source Computer Vision)은 오픈 소스 컴퓨터 비전 라이브러리이다. 원래는 인텔이 개발하였다. 윈도리눅스 등의 여러 플랫폼에서 사용할 수 있다. 실시간 이미지 프로세싱에 중점을 둔 라이브러리이다. 인텔 CPU에서 사용되는 경우 속도의 향상을 볼 수 있는 Intel Performance Primitives (IPP)를 지원한다.




참고 블로그를 보고 OpenCv 예제를 구현하였다.


여기서 글과 나의 차이는

native-lib.cpp 에서


extern "C"
JNIEXPORT void JNICALL
Java_com_blank_android_open_MainActivity_ConvertRGBtoGray(JNIEnv *env, jobject instance,
jlong matAddrInput, jlong matAddrResult) {

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

cvtColor(matInput, matResult, CV_RGBA2GRAY);

}



블로그같은 경우 extern "C"에 { } 로 묶어줬지만

나는 묶었을 경우 에러가 나고

풀어줄 경우 동작이 되었다.


위의 예제는 카메라에 필터를 주는 것이고

참고블로그2를 통해서 이미지에 필터를 넣는  것을 해보자


에러

1.

Error:error: '../../../../src/main/JniLibs/mips64/libopencv_java3.so',
 needed by '../../../../build/intermediates/cmake/debug/obj/mips64/libnative-lib.so'
, missing and no known rule to make it

==>

(다운받은 라이브러리)OpenCV-android-sdk\sdk\native에 위치한 libs 디렉토리를 안드로이드 프로젝트의 app\src\main에 복사해주는 것을 하지않았다. 


2. 또 다른 프로젝트 예제 앱을 만들어  실행하니 아래와 같은 에러가 발생

java.lang.UnsatisfiedLinkError: No implementation found 
for void com.blank.android.myapplication.MainActivity.loadImage(java.lang.String, long) 
(tried Java_com_void_android_myapplication_MainActivity_loadImage

and Java_com_void_android_myapplication_MainActivity_loadImage__Ljava_lang_String_2J)

==>

참고블로그3를 보면 네이티브 라이브러리 패스 설정이 잘못되어서 그렇다고 한다.

나같은 경우 native-ilb.cpp 파일에서 c파일들을 안묶어줘서 난 에러인 것 같다.


extern "C" {
}


3.  


E/cv::error(): OpenCV Error: Assertion failed (scn == 3 || scn == 4)
in void cv::cvtColor(cv::InputArray, cv::OutputArray, int, int), 
file /build/master_pack-android/opencv/modules/imgproc/src/color.cpp, line 9716

==>

app/src/main/assets의 이미지파일을 복사하라고 예제에 있다.

하지만 나는 drawable에 사진을 넣어놓고 복사를 시도했기때문에

앱이 이미지를 찾을 수 없어 notFoundError가 났고, 이 에러에 이어지는 에러였던 것


크~ 예제 성공!


OpenCv 예제도 해봤으니 실제 개발하고 있는 앱에 붙여 보고자 한다.


기존프로젝트에 include c++을 하기 위해 공홈을 참고하였다.


참고블로그에서 새 프로젝트를 생성할 때  include c++를 해준 것을

기존프로젝트에도 적용하기 위해 공홈을 보고 수작업으로 작업하고 2번부터는

블로그와 동일하게 구현하였다 


**  에러

1. java코드에서 c언어의 함수를 인식하지 못했다.


public native void loadImage(String imageFileName, long img);

public native void imageprocessing(long inputImage, long outputImage);

1.1

Gradle을 네이티브 라이브러리에 링크해야한다.

방법은 공홈에 있다.

1.2

그리고  native_lib.cpp 파일에

함수명이 틀렸어서 인식을 못했다.

JNIEXPORT void JNICALL
Java_com_blank_android_crafter_write_FilterImgActivity_loadImage(JNIEnv *env, jobject instance,
jstring imageFileName_,
jlong addrImage) {


그래서 자바코드에서 인식못하는 코드에 마우스를 올리면 경로가 나오는데

그걸 그대로 native_lib.cpp파일에 옮겨서 바꾼다.

에러 해결


예제를 앱으로 옮기는 것까지는 성공했다.

하지만 내가 하고 싶은 것은 사진을 찍어 비트맵으로 만든 파일을 이미지필터링하는 것이기 때문에 예제를 더 이해해야할 것 같다.


----------------------------------------------------------------------------------------------------------------------------------------

<!-- 공부한 부분 -->

우선 native-lib.cpp 파일이 이해가 가지 않았다.


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

using namespace cv;
using namespace std;


extern "C" {
JNIEXPORT jstring JNICALL
Java_com_void_android_tristan_Activity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
JNIEXPORT void JNICALL
Java_com_void_android_tristan_Activity_loadImage(JNIEnv *env, jobject instance,
jstring imageFileName_,
jlong addrImage) {
Mat &img_input = *(Mat *) addrImage;

const char *nativeFileNameString = env->GetStringUTFChars(imageFileName_, JNI_FALSE);

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

img_input = imread(pathDir, IMREAD_COLOR);
}

JNIEXPORT void JNICALL
Java_com_void_android_tristan_Activity_imageprocessing(JNIEnv *env,
jobject instance,
jlong addrInputImage,
jlong addrOutputImage) {

Mat &img_input = *(Mat *) addrInputImage;
Mat &img_output = *(Mat *) addrOutputImage;

cvtColor(img_input, img_input, CV_BGR2RGB);
cvtColor(img_input, img_output, CV_RGB2GRAY);
blur(img_output, img_output, Size(5, 5));
Canny(img_output, img_output, 50, 150, 5);

}
}


JNI란?

JNI는 Java Native Interface의 약자로 Java와 C/C++ 사이에 Interface를 제공하여 서로의 기능을 사용 가능하도록 만들어 줍니다.


자바와 JNI 타입

자바에서 건내준 변수는 아래 테이블에 나열된 것 같이 자동으로 해당 변수 타입으로 매핑됩니다.

Java

JNI (C/C++) 

JNI (배열)

boolean 

jboolean 

jbooleanArray 

byte 

jbyte 

jbyteArray 

char 

jchar 

jcharArray 

short 

jshort 

jshortArray 

int 

jint 

jintArray 

long 

jlong 

jlongArray 

float 

jfloat 

jfloatArray 

double 

jdouble 

jdoubleArray 

object 

jobject 

jobjectArray 



출처: http://lsit81.tistory.com/entry/JNI-기초 [Naked Foot]


JNIEnv *env는 자바와 네이티브 메소드를 연결하는 인터페이스 포인터


출처: http://kyleslab.tistory.com/59 


JNI는 jstring과 C의 문자열 처리를 위해 GetStringUTFChars (), ReleaseStringUTFChars() 함수를 제공한다.

GetString UTFChars() 함수는 UTF-8 포맷의 문자열을 널로 끝나는 C 스타일의 문자열로 전환해 포인터를 리턴하는 함수이다.



cvtColor는 컬러를 변환해주는 함수이다.


Java_com_void_android_tristan_Activity_imageprocessing(JNIEnv *env, jobject instance )

주목해서 봐야 할 부분은 argument 부분입니다. 첫번째 argument 로 전달되는 JNIEnv 는 JNI interface pointer 로 JNI function table 을 가르키고 있는 pointer 입니다. 두번째 argument 는 this에 해당하는 JNI object 그 자신입니다. 

출처: http://aroundck.tistory.com/571 [돼지왕 왕돼지 놀이터]


openCv 에러 찍는 법

openCv 라이브러리

라이브러리를 변경할 때마다 퍼미션거부에러가 나온다.
그럴땐 앱을 삭제후 빌드하면 해결

나중에 참고할 사이트 openCv핸드북: http://darkpgmr.tistory.com/46


<!-- 공부한 부분 끝 -->

----------------------------------------------------------------------------------------------------------------------------------------


이미지필터링 예제 내 앱에 적용한 방법

내 앱은 MediaStore로 다른 카메라앱을 호출후 사진데이터를 내 앱에 가져와 사진을 Crop한 후 Bitmap형식으로 서버에 저장하는 방식이었는데


openCv예제같은 경우 이미지를 빌드한 핸드폰의 내부저장소에 저장후 그 사진을 이미지 필터링을 하였다.


그래서 이 Bitmap을 바로 이미지 필터링을 하고 싶었으나 (hashcode에 질문도 함)

도저히 찾을 수 없어 예제에 맞춰 사진을 Crop한 후 Bitmap 형식으로 앱을 빌드한 핸드폰의 내부저장소에 저장한 후


이미지 필터링하는 방식으로 구현하였다.


2. 얼굴인식

이미지필터를 하고 나니 얼굴인식도 금방할 수 있겠다 싶어서 얼굴인식도 공부해본다.

이미지필터할 때 참고했던 참고블로그를 보고 얼굴인식도 시도해본다.


참고블로그에서 가져다가 쓰면 변수 및 메서드가  빠진 부분이 있어서 코딩을 해줘야한다.

이미지필터할 때 썼던 부분이 부족한 거라 이미지필터액티비티에서 참고하여 하였다.


그 전에 우선 Activity에 implements를 해줘야한다.

 implements CameraBridgeViewBase.CvCameraViewListener2{

변수 선언


private final int PERMISSIONS_REQUEST_CODE = 0;
private CameraBridgeViewBase mOpenCvCameraView;
private String TAG = "FaceActivity";
private Mat matInput;
private Mat matResult;

메서드

private boolean hasPermissions(String[] permissions) {
int ret = 0;
//스트링 배열에 있는 퍼미션들의 허가 상태 여부 확인
for (String perms : permissions){
ret = checkCallingOrSelfPermission(perms);
if (!(ret == PackageManager.PERMISSION_GRANTED)){
//퍼미션 허가 안된 경우
return false;
}

}
//모든 퍼미션이 허가된 경우
return true;
}


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

실행시켜보니



java.lang.ClassCastException: org.opencv.android.CameraGLSurfaceView cannot be cast to org.opencv.android.CameraBridgeViewBase


이러한 에러가 났다.

클래스캐스트에러라... 이 에러로 검색해도 딱히 뭐가 나오지 않으니

내가 모르는 surfaceView에 대해 알아보자


참고블로그에 surfaceView개념에 대해 나온다.


----------------------------------------------------------------------------------------------------------------------------------------

<!-- 공부한 부분 -->


(** 콜백함수에 대해 쉽게 설명해놨기에 써놔야지 

. 콜백 함수는 정의해 두기만 하면 사용자가 직접 호출하지 않더라도 운영체제가 알아서 호출해 주는 함수를 의미하는 용어입니다. 

출처: http://javaexpert.tistory.com/170 [나는 안드로이드다.]

)


<!-- 공부한 부분 끝-->

----------------------------------------------------------------------------------------------------------------------------------------



<org.opencv.android.CameraGLSurfaceView
android:id="@+id/activity_surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />

xml에서 뷰를 SurfaceView로 선언해서 그렇다.

<org.opencv.android.JavaCameraView
android:id="@+id/activity_surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />


그랬더니 실행이 된다. 그런데

앱을 실행했을 때 'It seems that your device does not support camera(or it is locked)~~ ' 라는 경고 다이얼로그가 떴다.

<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-feature android:name="android.hardware.camera.front"/>
<uses-feature android:name="android.hardware.camera.front.autofocus"/>

이 퍼미션을 허용해주지않아서이다.


되긴 되었는데 인식이 잘안된다.

핸드폰를 위아래로 돌리다보면 인식이 잘되는 방향이 있다.

이건 카메라로 인식시키는 것일 뿐이고 

나는카메라로 사진을 찍을 때 이 기능을 구현하고 싶다.


이미지필터를 할 때 했던 것처럼 C코드에서  MAT 형식의 이미지를 가져오고,

이 MAT 이미지를 비트맵으로 변환 후 저장하는 방식으로 구현해야겠다.


우선 코드파악을 해보자

----------------------------------------------------------------------------------------------------------------------------------------

<!-- 코드파악한 부분 -->




1. 코드를 보면 카메라뷰가 있고

2. 우선 퍼미션 요청을 한다

3. 퍼이션이 허용이 되면 ASSETS폴더에 있는 파일을 빌드한 폰의 내부저장소에 보관한다.

4. OnCameraFrame 메서드가 실행되면서 inputFrame이라는 영상데이터를 받는다.

5. OnCameraFrame 에서 C의 detect() 메서드가 실행되는데 detect에서 얼굴인식을 한다.

6. OnCameraFrame 에서 inputFrame을 MAT 변수에 넣는다.

<!-- 코드파악한 부분 끝-->

----------------------------------------------------------------------------------------------------------------------------------------


7. 나는 detect()에서 얼굴인식이 되면 값을 자바로 return 하여

8. MAT matInput 을 Bitmap으로 변환하여 저장한다


7에 대해 더 자세히 써놓자면

detect는 얼굴을 되어있는 코드인데

한명의 얼굴을 인식하면 faces.size가 1이 되고

두명의 얼굴을 인식하면 faces.size가 2가 된다.

그래서 detect()에서 faces.size를 return하면

자바에서 그 값을 받아 조건으로 쓴다.

그리고 faces.size는 long형이기에 자바코드에서 detect메서드를 long 형으로 바꿔줘야한다.

public static native void detect(long cascadeClassifier_face,
long cascadeClassifier_eye, long matAddrInput, long matAddrResult);
detect(cascadeClassifier_face, cascadeClassifier_eye, matInput.getNativeObjAddr(), matResult.getNativeObjAddr());

에서 

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


long detectValue = detect(cascadeClassifier_face, cascadeClassifier_eye, matInput.getNativeObjAddr(), matResult.getNativeObjAddr());

로 변경해줘야한다.


최초작성 17년 7월 26일

계속 수정중.


에러 1.


java.lang.UnsatisfiedLinkError: No implementation found for long 액티비티경로.loadCascade(java.lang.String)

(tried Java_액티비티경로_loadCascade and Java_액티비티경로_loadCascade__Ljava_lang_String_2)



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


파일들을 로드하지않았다..

후 이걸로 몇 시간날림


상황설명

예제앱에서는 이미지필터를 하는 액티비티에서 버튼을 클릭하면 얼굴인식하는 액티비티가 나와서 동작하도록 하였다.

그리고 현재 개발하는 앱에서는 바로 얼굴인식하도록 하였다.


추측하건데, 예제앱에서는 이미지필터하는 액티비티에서 라이브러리를 한 번 가져왔고 

스택상으로 얼굴인식 액티비티는 이미지필터하는 액티비티가 꺼짐 없이 위에 있으니까 문제가 없었던 것 같고 

현재 개발하는 앱에서는 그런 과정없이 얼굴인식액티비티에서 라이브러리 로딩없이 하니까 그런것인듯하다


에러 2.


나는 openCv에서 얼굴인식을 하면 그 때 사진을 찍어 다른 액티비티로 값을 넘기도록 구현하였다.

하지만...


JavaBinder: !!! FAILED BINDER TRANSACTION !!! (parcel size = 6221080)

라는 에러가 나왔고,

참고 블로그를 보니 Intent로 Bitmap을 put시킬 때 이미지 크기가 최대 40KB까지 가능하다고 한다.


아래는 보내기 전에 Bitmap을 byte 배열로 바꾸는 소스 입니다.


 ByteArrayOutputStream stream = new ByteArrayOutputStream();
    bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
    byte[] bytes = stream.toByteArray(); 
    setresult.putExtra("BMP",bytes);


받아온 byte 배열을 Bitmap으로 변환 하는 소스 입니다.

 byte[] bytes = data.getByteArrayExtra("BMP");
    Bitmap bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);



출처: http://gogorchg.tistory.com/entry/Android-FAILED-BINDER-TRANSACTION [항상 초심으로]


이런 식으로 해결