막무가내 삽질 블로그
안드로이드 opencv 이미지 얼굴 판별 o,x 본문
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);
}
}
이제 프로젝트에 붙히면 된다ㅇㅇ
설치 및 참고
'Android' 카테고리의 다른 글
안드로이드 애니메이션 효과주기 정리 (0) | 2020.02.08 |
---|---|
안드로이드 커스텀 다이얼로그, android customdialog (0) | 2020.01.28 |
android glide image size (0) | 2020.01.21 |
안드로이드 fcm push notification, send fcm device to device (1) | 2019.12.31 |
android infinite/endless scroll (0) | 2019.12.25 |
Comments