카메라로 얼굴을 인식하여 딥러닝 모델을 거쳐 생성된 얼굴 객체를 데이터베이스에 저장하려고 했음. 처음엔 데이터베이스를 장바구니마냥 넣고 싶은대로 담아 넣으면 될 줄 알았지만 객체를 그대로 넣을 순 없었다(데이터베이스 강의 꼭 듣자). 그래서 알아보니 넣을 수 있는 타입이 정해져 있더라.. 아래를 참고하자.
내가 넣고자 했던 객체는 다음과 같다.
public class Recognition {
private final String id;
private final String title;
private final Float distance;
private Object extra;
private RectF location;
private Integer color;
private Bitmap crop;
...
}
String, Float, Integer는 별도의 설정 없이 데이터베이스에 들어가는데 Bitmap과 Object는 따로 설정을 거쳐주어야 하고, RectF는 또다른 클래스 객체이기 때문에 이것 또한 별도의 설정을 거쳐야 한다. Type 변환에 관련해서는 다음 포스팅에서 다룰 예정이다.
나같은 경우 PersonalInfo라는 클래스를 생성했다. 클래스 이름은 상황에 맞게 바꿔주자. 컬럼명을 변경할 수 있는데 따로 설정하지 않으면 원래의 클래스 이름과 변수명으로 매치된다. @Embedded는 하위 객체를 포함하는 객체에 붙여준다.
@Entity(tableName = "PersonalInfo")
public class PersonalInfo {
@PrimaryKey(autoGenerate = true) // autoGenerate를 true로 설정하면 main_id를 알아서 1씩 증가시켜 준다.
private int main_id;
private String name;
@Embedded // recognition이 여러 필드가 포함된 객체이기 때문에 이 annotation을 붙여준다.
private final SimilarityClassifier.Recognition recognition;
public PersonalInfo(String name, SimilarityClassifier.Recognition recognition){
this.name = name;
this.recognition = recognition;
}
public SimilarityClassifier.Recognition getRecognition() {
return recognition;
}
public int getMain_id() {
return main_id;
}
public void setMain_id(int main_id) {
this.main_id = main_id;
}
public String getName() {
return name;
}
}
Dao 인터페이스를 작성하여 쿼리문을 설정한다. 어노테이션을 보면 알 수 있는데 잘 모르겠으면 sqlite 개념을 찾아봅시다. 보면 짐작은 된다. 만약 @Insert처럼 쿼리문이 작성되어 있지 않으면 room에서 알아서 매개변수로 받은 객체를 알아서 제거해준다. LiveData는 데이터베이스 내용에 변화가 생기면 해당 변화를 감지해서 백그라운드 작업을 처리할 수 있게 해준다. 간단히 말하면, 메인 액티비티에 observer를 설정해두면 데이터베이스 내용에 변화가 생겼을 시 observer를 통해 값을 최신화할 수 있다(ex. 리사이클러 뷰 최신화). 스타크래프트에서 옵저버를 생각하면 재밌다!
참고로 DB 접근은 백그라운드에서 작업해야 한다.
@Dao
public interface PersonalInfoDao {
@Query("SELECT * FROM PersonalInfo")
LiveData<List<com.Detection.faceRecognition.room.PersonalInfo>> getAll();
@Insert
void insert(com.Detection.faceRecognition.room.PersonalInfo personalInfo);
@Update
void update(com.Detection.faceRecognition.room.PersonalInfo personalInfo);
@Delete
void delete(com.Detection.faceRecognition.room.PersonalInfo personalInfo);
@Query("DELETE FROM PersonalInfo")
void deleteAll();
}
데이터베이스 생성 클래스를 생성한다. 데이터베이스 객체는 리소스를 많이 잡아먹기 때문에 싱글톤이 권장된다. Dao 클래스도 포함하고 있다.
@Database(entities = com.Detection.faceRecognition.room.PersonalInfo.class, version = 1)
@TypeConverters(com.Detection.faceRecognition.room.Converters.class)
public abstract class PersonalInfoDatabase extends RoomDatabase {
//싱글톤으로 객체를 생성
private static PersonalInfoDatabase INSTANCE;
public abstract com.Detection.faceRecognition.room.PersonalInfoDao personalInfoDao();
// db 객체 생성 가져오기
public static PersonalInfoDatabase getAppDatabase(Context context){
if(INSTANCE == null){
/*INSTANCE = Room.databaseBuilder(
context, PersonalInfoDatabase.class, "personalInfo-db").setJournalMode(JournalMode.TRUNCATE).build();*/
INSTANCE = Room.databaseBuilder(
context, PersonalInfoDatabase.class, "personalInfo-db").build();
}
return INSTANCE;
}
// db 객체 제거
public static void destroyInstance(){
INSTANCE = null;
}
}
코드가 너무 길어서 일정 부분 지워서 매끄럽지 않다. 맥락만 파악하자. Observer를 설정하여 값이 바뀌면 detector에 새로운 값을 등록할 수 있도록 설정하였다. 그리고 버튼을 클릭하면 DB에 새로운 정보를 삽입할 수 있도록 했다. 여기서 Asynctask를 이용하여 백그라운드 작업을 해주었는데 이는 구식 방법이고 코루틴을 쓰는 것도 하나의 방법이다.
public class FaceRecognitionActivity extends FaceCameraActivity implements OnImageAvailableListener {
PersonalInfoDatabase db;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
db = PersonalInfoDatabase.getAppDatabase(this); // 데이터베이스 생성
@Override
public void onPreviewSizeChosen(final Size size, final int rotation) {
// Observer를 설정하여 해당 DB 값에 변화가 생기면 onChanged 함수가 실행됨
db.personalInfoDao().getAll().observe(this, new Observer<List<PersonalInfo>>() {
@Override
public void onChanged(List<PersonalInfo> personalInfos) {
if (personalInfos.size() != 0) {
for (int i = 0; i < personalInfos.size(); i++) {
detector.register(personalInfos.get(i).getName(), personalInfos.get(i).getRecognition());
}
}
}
});
}
private void showAddFaceDialog(SimilarityClassifier.Recognition rec) {
builder.setPositiveButton("OK", new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dlg, int i) {
String name = etName.getText().toString();
// DB에 데이터 삽입
new InsertAsyncTask(db.personalInfoDao()).execute(new PersonalInfo(name, rec));
detector.register(name, rec);
}
});
}
// 메인 스레드에서 DB 접근할 수 없으니 AsyncTask를 사용
public static class InsertAsyncTask extends AsyncTask<PersonalInfo, Void, Void> {
private final PersonalInfoDao personalInfoDao;
public InsertAsyncTask(PersonalInfoDao personalInfoDao){
this.personalInfoDao = personalInfoDao;
}
@Override
protected Void doInBackground(PersonalInfo... personalInfos) {
personalInfoDao.insert(personalInfos[0]);
return null;
}
}
}
다음 포스팅에서는 Bitmap 객체를 DB에 저장하는 방법, 리스트 객체를 DB에 저장하는 방법을 알아보겠다.