막무가내 삽질 블로그

안드로이드 opencv 이미지 얼굴 판별 o,x 본문

Android

안드로이드 opencv 이미지 얼굴 판별 o,x

joong~ 2020. 1. 26. 20:15
728x90

약 1년전 쯤 opencv를 활용하여 얼굴인식 및 이미지 필터를 적용하였다.

 

이번 프로젝트는 이미지를 활용하여 사람인지 아닌지를 판별하는 기능이 있다. (위랑 거의비슷)

차이 = 영상기반, 이미지기반

 

순서

1. 이미지를 선택

2. 이미지를 얼굴,눈을 읽을 수 있게 셋팅 후 네이티브로 넘김

3. 네이티브에서 받아온 jlong 타입 input의 이미지를 위치를 확인 하고 얼굴형태,눈을 확인 후 사람이면 1

   아니면 0을 넘겨줌

4. 확인

 

 

 

 

테스트 프로젝트를 만들어서 정리해 본다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".GGgg">

    <Button
        android:id="@+id/btn_gallery"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:text="갤러리"/>

    <ImageView
        android:layout_below="@id/btn_gallery"
        android:layout_above="@id/btn_check"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/iv_image"/>

    <Button
        android:id="@+id/btn_check"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="확인"/>


</RelativeLayout>

 

 

    private static final String TAG = "ImTest";
    public static final int GALLERY = 1;
    private Button gallery, success;
    private ImageView image;
    private Uri uri;
    private String path;
    private Mat input, output;

    private long face = 0;
    private long eye = 0;
    private int num;

    // C++ JNI 함수와 연결할 메소드(native-lib.cpp 참조)
    public native int check(long face, long eye, long matInput, long matOut); // 사람체크
    public native long readFile(String fileName); // asset에 있는 파일

    // OpenCV 네이티브 라이브러리와 C++코드로 빌드된 라이브러리를 읽음(CMakeList.txt add_library and target 참조)
    static {
        System.loadLibrary("opencv_java4");
        System.loadLibrary("native-lib");
    }
    /**
     * Android 프로젝트의 assets 폴더에 넣었던 XML 파일은 프로젝트 deploy시 같이 폰에 올라가지만 읽어 오기 위해서는
     * 안드로이드 폰의 내부 저장소(internal storage)로 옮기는 작업이 필요(java read_cascade_file)
     * 내부 저장소로부터 xml파일을 읽어와 cascadeclassifler 객체를 생성 후 자바로 넘겨줌(C++ loadCascade)
     */
    private void read_cascade_file(){
        copyFile("haarcascade_frontalface_alt.xml");
        copyFile("haarcascade_eye_tree_eyeglasses.xml");
        face = readFile("haarcascade_frontalface_alt.xml");
        eye = readFile( "haarcascade_eye_tree_eyeglasses.xml");
    }
    
    
    private void copyFile(String filename) {
        String baseDir = Environment.getExternalStorageDirectory().getPath();
        String pathDir = baseDir + File.separator + filename;

        /**
         * 파일을 APK(android package kit)에 포함시켜 앱에서 사용할 수 있도록 만든 것을 Asset, 의미는 : 자산
         * AssetManager 클래스 사용시 에셋 폴더에 포함된 파일을 읽을 수 있음
         */
        AssetManager assetManager = this.getAssets();

        InputStream inputStream = null;
        OutputStream outputStream = null;

        try {
            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) {
            e.printStackTrace();
        }
    }
success.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                output = new Mat(input.rows(), input.cols(), input.type());
                num = check(face, eye, input.getNativeObjAddr(), output.getNativeObjAddr());
                Log.d(TAG, "반환값 : " + num);
            }
        });

사람 확인 완료

 

 

전체코드

import android.content.Intent;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.Mat;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class ImTest extends AppCompatActivity {
    private static final String TAG = "ImTest";
    public static final int GALLERY = 1;
    private Button gallery, success;
    private ImageView image;
    private Uri uri;
    private String path;
    private Mat input, output;

    private long face = 0;
    private long eye = 0;
    private int num;

    // C++ JNI 함수와 연결할 메소드(native-lib.cpp 참조)
    public native int check(long face, long eye, long matInput, long matOut); // 사람체크
    public native long readFile(String fileName); // asset에 있는 파일

    // OpenCV 네이티브 라이브러리와 C++코드로 빌드된 라이브러리를 읽음(CMakeList.txt add_library and target 참조)
    static {
        System.loadLibrary("opencv_java4");
        System.loadLibrary("native-lib");
    }

    /**
     * Android 프로젝트의 assets 폴더에 넣었던 XML 파일은 프로젝트 deploy시 같이 폰에 올라가지만 읽어 오기 위해서는
     * 안드로이드 폰의 내부 저장소(internal storage)로 옮기는 작업이 필요(java read_cascade_file)
     * 내부 저장소로부터 xml파일을 읽어와 cascadeclassifler 객체를 생성 후 자바로 넘겨줌(C++ loadCascade)
     */
    private void read_cascade_file(){
        copyFile("haarcascade_frontalface_alt.xml");
        copyFile("haarcascade_eye_tree_eyeglasses.xml");
        face = readFile("haarcascade_frontalface_alt.xml");
        eye = readFile( "haarcascade_eye_tree_eyeglasses.xml");
    }

    private void copyFile(String filename) {
        // 안드로이드 저장소(SD card)에서 파일 가져옴
        String baseDir = Environment.getExternalStorageDirectory().getPath();
        String pathDir = baseDir + File.separator + filename;

        /**
         * 파일을 APK(android package kit)에 포함시켜 앱에서 사용할 수 있도록 만든 것을 Asset, 의미는 : 자산
         * AssetManager 클래스 사용시 에셋 폴더에 포함된 파일을 읽을 수 있음
         */
        // 파일을 가져옴
        AssetManager assetManager = this.getAssets();

        InputStream inputStream = null;
        OutputStream outputStream = null;

        try {
            // 파일 이름으로 읽고 outputStream 객체화
            inputStream = assetManager.open(filename);
            outputStream = new FileOutputStream(pathDir);

            byte[] buffer = new byte[1024];
            int read;

            /**
             * read는 입력 스트림에서 하나의 바이트를 읽어 들이기 위한...EoF(End of file)을 만나면 -1을 반환
             */
            while ((read = inputStream.read(buffer)) != -1) {

                /**
                 * .write 메소드를 사용하면 출력하고자 하는 배열의(buffer) 지정된 위치에서 정해진 크기만큼 출력이 가능
                 * .flush는 데이터가 쌓인 후 명령을 받으면 버퍼에 있던 모든 내용을 도착 지점으로 보내고 버퍼를 비워버림(나중에 확인)
                 */
                outputStream.write(buffer, 0, read);
            }

            inputStream.close();
            inputStream = null;
            outputStream.flush();
            outputStream.close();
            outputStream = null;
        } catch (Exception e) {
            e.printStackTrace();
            Log.d(TAG, "copyFile :: 파일 복사 중 예외 발생 "+e.toString() );
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gggg);
        gallery = findViewById(R.id.btn_gallery);
        success = findViewById(R.id.btn_check);
        image = findViewById(R.id.iv_image);
        gallery.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                gallery();
            }
        });

        success.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                output = new Mat(input.rows(), input.cols(), input.type());
                num = check(face, eye, input.getNativeObjAddr(), output.getNativeObjAddr());
                Log.d(TAG, "반환값 : " + num);
            }
        });
    }

    private void gallery() {
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType(MediaStore.Images.Media.CONTENT_TYPE);
        startActivityForResult(intent, GALLERY);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == GALLERY) {
            if (data.getData() != null) {
                uri = data.getData();
                path = getPath(uri);
                image.setImageURI(uri);
                try {
                    read_cascade_file();
                    uri = data.getData();
                    path = getPath(uri);
                    int orientation = getOrientationOfImage(path);
                    Bitmap temp = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
                    Bitmap bitmap = getRotatedBitmap(temp, orientation);
                    image.setImageBitmap(bitmap);
                    input = new Mat();
                    output = new Mat();

                    // ARGB_8888(4바이트) 값으로 비트맵 복사
                    Bitmap bmp32 = bitmap.copy(Bitmap.Config.ARGB_8888, true);
                    // 복사한 bmp32 이미지를 opencv에 맞게 Mat형식으로 변환
                    Utils.bitmapToMat(bmp32, input);

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

    // 로컬에서 사진을 가져와 이미지의 회전 정보를 가져오는 메소드
    public int getOrientationOfImage(String filepath) {

        /**
         * Exif 태그를 JPEG 파일 또는 RAW 이미지 파일로 읽고 쓰는 클래스
         * 즉, 이미지가 갖고 있는 정보의 집합 클래스
         */
        ExifInterface exif = null;

        try {
            exif = new ExifInterface(filepath); // exif 객체에 사진경로 할당
        } catch (IOException e) {
            return -1; // 음수는 false, 양수는 true(표준 약속)
        }

        /**
         * getAttributeInt = 지정된 태그의 정수 값을 반환(String tag, int defaultValue)
         */
        // orientation 으로 지정된 정수 값을 할당
        int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);

        // 사진의 회전에 맞게 바르게 설정
        if (orientation != -1) {
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    return 90;

                case ExifInterface.ORIENTATION_ROTATE_180:
                    return 180;

                case ExifInterface.ORIENTATION_ROTATE_270:
                    return 270;
            }
        }

        return 0; // 회전 성공
    }

    // 이미지 회전하는 메소드
    public Bitmap getRotatedBitmap(Bitmap bitmap, int degrees) throws Exception {
        if (bitmap == null) return null;
        if (degrees == 0) return bitmap;

        Matrix m = new Matrix();

        /**
         * setRotate 속성(float degrees, float px, float py)
         * ㅡ> 피벗 점이(px,py)로 지정된 행렬을 지정된 각도로 회전하도록 설정
         */
        m.setRotate(degrees, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2);

        /**
         * createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter)
         * ㅡ> 옵션의 행렬에 의해 변환 된, 소스 비트맵의 부분 집합으로부터 비트맵을 돌려줌
         */
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, true);
    }

    // 가져온 사진 절대경로로 바꾸는 메소드
    private String getPath(Uri contentUri) {
        String[] proj = {MediaStore.Images.Media.DATA};
        Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
        cursor.moveToFirst();
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        return cursor.getString(column_index);
    }
}

 

이제 프로젝트에 붙히면 된다ㅇㅇ

 

 

 

설치 및 참고

https://webnautes.tistory.com/category/OpenCV/Android%20%EA%B0%9C%EB%B0%9C%20%ED%99%98%EA%B2%BD%20%EB%B0%8F%20%EC%98%88%EC%A0%9C

 

Comments