美文网首页
2020-05-28

2020-05-28

作者: BillBong | 来源:发表于2020-05-28 15:23 被阅读0次

    虹软人脸识别 - 采用数据库存取人脸特征数据

    前几天有个朋友遇到了个问题,他在使用虹软的人脸识别引擎时,想更换一下人脸识别的存储方式,原本demo中使用的是文件的方式进行存储,而他想要通过数据库的方式进行存储。由于他刚接触Android不久,对数据库这块不甚了解,再加之听上去要存特征数据,听上去就很难的样子,可愁坏了他。其实虹软的人脸特征数据就是一个byte[],存储起来还是相当方便的。我为他写了一个demo,顺便与大家分享一下,希望能帮到有需要的人,本文将分为以下几点讲述。

    • 两种数据库存储方式
    • 封装数据库操作接口
    • ArcFaceDemo接入

    一、两种数据库操作方式

    方案一:使用原生数据库存储人脸特征数据

    1. 建表

    首先我们需要建一张表来存储特征数据,但是光存储特征数据还是不够的,不足以满足人脸识别的需要,因此我们还需要人脸姓名、人脸图片等数据。下面我创建了一个User表,以id为主键,并自增长,faceName用TEXT类型存储,把人脸图片与特征都用Blob存储。

    注意:对于人脸库中人脸较多的场景,请将注册图存储至本地,数据库中仅保留文件路径,否则会占用很多内存。

    public class FaceDatabaseHelper extends SQLiteOpenHelper {
    
        private static final String TAG = "FaceDatabaseHelper";
    
        public FaceDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
            super(context, name, factory, version);
        }
    
        @Override
        public void onCreate(SQLiteDatabase db) {
            //创建Face表 ID 名字 照片 特征
            db.execSQL("CREATE TABLE IF NOT EXISTS Face" +
                    "(id INTEGER PRIMARY KEY AUTOINCREMENT, faceName TEXT, facePic Blob , faceFeature Blob)");
            //建库成功后给出提示
            Log.i(TAG, "dataBase Create Success");
        }
    
    
        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        }
    }
    

    2. 新增人脸数据

    表已经建好了,接下来就是往表中插入数据,需要注意的是,这里的最后一个参数需要传入jpeg格式的数据。

        public long addFace(String faceName, byte[] faceFeature, byte[] facePic) {
            //存人脸
            ContentValues values = new ContentValues();
            long index = -1;
            //写入表
            try {
                values.put("faceName", faceName);
                values.put("faceFeature", faceFeature);
                values.put("facePic", facePic);
                index = db.insert("Face", null, values);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return index;
        }
    

    3. 加载人脸数据

    人脸数据已经成功的插入到了表中,接下来我们就需要将表内所有的数据加载到内存。

        public ArrayList<FaceEntity> selectAllFaces() {
            ArrayList<FaceEntity> dataList = new ArrayList<>();
            Cursor cursor = db.query("Face", null, null, null, null, null, null);
            while (cursor.moveToNext()) {
                FaceEntity data = new FaceEntity();
                int id = cursor.getInt(cursor.getColumnIndex("id"));
                String faceName = cursor.getString(cursor.getColumnIndex("faceName"));
                byte[] facePic = cursor.getBlob(cursor.getColumnIndex("facePic"));
                byte[] faceFeature = cursor.getBlob(cursor.getColumnIndex("faceFeature"));
                data.setId(id);
                data.setFaceName(faceName);
                data.setFacePic(facePic);
                data.setFaceFeature(faceFeature);
                dataList.add(data);
            }
            cursor.close();
            return dataList;
        }
    

    4. 清空人脸数据

    支持删除所有人脸或单张人脸。

    public void deleteAllFace() {
        db.execSQL("delete from Face");
    }
    
    public void deleteFaceById(int id) {
        db.delete("Face", "id=?", new String[]{"" + id});
    }
    

    方案二:使用Room框架存储人脸特征数据

    上面使用的是原生数据库操作接口,这边还提供一种使用Android官方推荐的Room框架操作数据库的示例。

    1. 添加依赖

    首先我们要在app下的build.gradle文件内添加room的依赖。

    implementation 'android.arch.persistence.room:runtime:1.1.1'
    annotationProcessor 'android.arch.persistence.room:compiler:1.1.1'
    

    2. 建表

    Room的数据库操作相比于原生的方式来说简单的多,只需要对实体类进行注解即可。

    注意:对于人脸库中人脸较多的场景,请将注册图存储至本地,数据库中仅保留文件路径,否则会占用很多内存。

    
    // entity声明定义,并且指定了映射数据表明
    @Entity(tableName = "Face")
    public class FaceEntity {
        // 设置主键,并且定义自增增
        @PrimaryKey(autoGenerate = true)
        public int id;
        // 字段映射具体的数据表字段名
        @ColumnInfo(name = "faceName")
        private String faceName;
    
        @ColumnInfo(name = "faceFeature")
        private byte[] faceFeature;
    
        @ColumnInfo(name = "facePic")
        private byte[] facePic;
        
        // getter/setter就不贴了
        ...
    }
    

    3. 建库

    每次打开数据库文件都会产生比较大的开销,所以将FaceRoomDatabase设计成单例。

    //注解指定了database的表映射实体数据以及版本等信息
    @Database(entities = {FaceEntity.class}, version = 1)
    public abstract class FaceRoomDatabase extends RoomDatabase {
        //RoomDatabase提供直接访问底层数据库实现,我们通过定义抽象方法返回具体Dao
        //然后进行数据库增删该查的实现。
        public abstract FaceDao userDao();
    
        private static volatile FaceRoomDatabase instance;
        
        public static FaceRoomDatabase getInstance(Context context) {
            if (instance == null) {
                synchronized (FaceRoomDatabase.class) {
                    if (instance == null) {
                        instance = Room.databaseBuilder(context.getApplicationContext(), FaceRoomDatabase.class, "RoomFaceDB.db")
                                .build();
                    }
                }
            }
            return instance;
        }
    }
    

    4. 数据库操作

    基于Room框架的数据库操作较为简单,就不一一例举,统一放在一起进行说明。

    @Dao
    public interface FaceDao {
        //返回Long数据表示,插入条目的主键值(uid)
        @Insert
        Long addFace(FaceEntity user);
    
        //获取所有人脸数据
        @Query("SELECT * FROM Face")
        List<FaceEntity> getAllFace();
    
        //删除所有人脸数据
        @Query("DELETE FROM Face")
        int deleteAll();
    
        //删除指定人脸
        @Delete
        int delete(FaceEntity user);
    }
    

    二、封装数据库操作接口

    2.1 接口声明

    为了代码切换更便捷,能够在代码中快速切换使用Room或直接使用SQLite进行数据库操作:

        // ROOM方式
        private static FaceDatabaseAccessObject dao = new RoomFaceDao();
        // 直接使用SQLite方式
    //    private static FaceDatabaseAccessObject dao = new SQLiteFaceDao();
    

    我们可以预先定义一套接口FaceDatabaseAccessObject

    public interface FaceDatabaseAccessObject {
        /**
         * 初始化
         */
        void init();
    
        /**
         * 插入一个人脸
         *
         * @param userEntity 人脸信息
         * @return index
         */
        long insert(FaceEntity userEntity);
    
        /**
         * 获取所有人脸
         *
         * @return 所有人脸
         */
        List<FaceEntity> getAll();
    
        /**
         * 清空所有人脸
         */
        void clearAll();
    
        /**
         * 回收资源操作
         */
        void release();
    }
    

    2.2 SQLite实现FaceDatabaseAccessObject接口

    public class SQLiteFaceDao implements FaceDatabaseAccessObject {
        private static FaceDatabaseManager faceDataBaseManager;
    
        @Override
        public void init() {
            faceDataBaseManager = new FaceDatabaseManager(ArcFaceApp.getApplication());
        }
    
        @Override
        public long insert(FaceEntity userEntity) {
            if (faceDataBaseManager == null) {
                return -1;
            }
            return faceDataBaseManager.addFace(userEntity.getFaceName(), userEntity.getFaceFeature(), userEntity.getFacePic());
        }
    
        @Override
        public List<FaceEntity> getAll() {
            if (faceDataBaseManager == null) {
                return null;
            }
            return faceDataBaseManager.selectAllFaces();
        }
    
        @Override
        public void clearAll() {
            if (faceDataBaseManager == null) {
                return;
            }
            faceDataBaseManager.deleteAllFace();
        }
    
        @Override
        public void release() {
            if (faceDataBaseManager == null) {
                return;
            }
            faceDataBaseManager.release();
        }
    }
    

    2.3 ROOM实现FaceDatabaseAccessObject接口

    public class RoomFaceDao implements FaceDatabaseAccessObject {
        private static FaceDao dao;
        private FaceRoomDatabase appDatabase;
    
        @Override
        public void init() {
            appDatabase = FaceRoomDatabase.getInstance(ArcFaceApp.getApplication());
            dao = appDatabase.userDao();
        }
    
        @Override
        public long insert(FaceEntity userEntity) {
            return dao == null ? -1 : dao.addFace(userEntity);
        }
    
        @Override
        public List<FaceEntity> getAll() {
            return dao == null ? null : dao.getAllFace();
        }
    
        @Override
        public void clearAll() {
            if (dao == null) {
                return;
            }
            dao.deleteAll();
        }
    
        @Override
        public void release() {
            if (appDatabase != null) {
                appDatabase.close();
            }
        }
    }
    

    三、ArcFaceDemo接入

    数据库操作已经实现,接下来就要实际运用到项目中,首先是人脸注册,我们直接修改Demo中FaceServer类的register方法。

    3.1 注册

    // 创建一个头像的Bitmap,存放旋转结果图
    Bitmap headBmp = getHeadImage(bgr24, width, height, faceInfoList.get(0).getOrient(), cropRect, ArcSoftImageFormat.BGR24);
    // 录入人脸
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    headBmp.compress(Bitmap.CompressFormat.JPEG, 50, baos);
    byte[] facePic = baos.toByteArray();
    
    FaceEntity user = new FaceEntity();
    user.setFaceName(userName);
    user.setFaceFeature(faceFeature.getFeatureData());
    user.setFacePic(facePic);
    dao.insert(user);
    

    3.2 查询

    上面我们已经完成了注册,接下来就是查询,由于Room不推荐在主线程中进行UI操作,我们新创建一个线程,将原本Demo中的initFaceList替换为initFaceListByDataBase

        private void initFaceListByDataBase() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    faceRegisterInfoList = new ArrayList<>();
                    List<FaceEntity> userTableList = dao.getAll();
                    for (FaceEntity userInfo : userTableList) {
                        faceRegisterInfoList.add(new FaceRegisterInfo(userInfo.getFaceFeature(), userInfo.getFaceName(), userInfo.getFacePic()));
                    }
                }
            }).start();
        }
    

    3.3 删除

    删除人脸同样属于IO操作,推荐放在子线程中处理。

        public int clearAllFaces(Context context) {
            synchronized (this) {
                if (context == null) {
                    return 0;
                }
                int number = 0;
                if (faceRegisterInfoList != null) {
                    number = faceRegisterInfoList.size();
                    faceRegisterInfoList.clear();
                }
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        dao.clearAll();
                    }
                }).start();
                return number;
            }
        }
    

    3.4 显示

    由于原本图片的加载模式是加载本地文件,而现在图片是直接存储在数据库的,因此需要对CompareResult进行修改,同时要对FaceSearchResultAdapter的图片加载做一些修改。

    public class CompareResult {
        private String userName;
        private float similar;
        private int trackId;
        private byte[] facePic;
    
        public CompareResult(String userName, float similar) {
            this.userName = userName;
            this.similar = similar;
        }
    
        public CompareResult(String userName, float similar, byte[] facePic) {
            this.userName = userName;
            this.similar = similar;
            this.facePic = facePic;
        }
    }
    
    public CompareResult getTopOfFaceLib(FaceFeature faceFeature) {
       //......
       //.....
        if (maxSimilarIndex != -1) {
            //return new CompareResult(faceRegisterInfoList.get(maxSimilarIndex).getName(), maxSimilar);
            return new CompareResult(faceRegisterInfoList.get(maxSimilarIndex).getName(), maxSimilar, faceRegisterInfoList.get(maxSimilarIndex).getFacePic());
        }
        return null;
    }
    

    FaceSearchResultAdapter内修改注册照显示的图像加载。

    @Override
    public void onBindViewHolder(@NonNull CompareResultHolder holder, int position) {
        if (compareResultList == null) {
            return;
        }
    //        File imgFile = new File(FaceServer.ROOT_PATH + File.separator + FaceServer.SAVE_IMG_DIR + File.separator + compareResultList.get(position).getUserName() + FaceServer.IMG_SUFFIX);
    //        Glide.with(holder.imageView)
    //                .load(imgFile)
    //                .into(holder.imageView);
        Glide.with(holder.imageView)
                .load(compareResultList.get(position).getFacePic())
                .into(holder.imageView);
        holder.textView.setText(compareResultList.get(position).getUserName());
    }
    

    3.5 效果

    至此为止我们已经搭建好数据库,并且完成了识别界面相关的代码修改,下面是实际运行的效果。

    image

    四、附录

    本文通过两种数据库操作方式介绍了人脸特征数据的存取,供大家参考,若有不对的地方,请大家指正!

    如果对您有所帮助,可以为我的demo点个star!

    提示:本文示例代码在使用前需先修改com.arcsoft.arcfacedemo.common.Constants.java中的APP_ID与SDK_KEY。

    源码地址:https://github.com/1244975831/ArcFaceDemoDatabase

    相关产品在虹软人脸识别开放平台进一步了解喔

    相关文章

      网友评论

          本文标题:2020-05-28

          本文链接:https://www.haomeiwen.com/subject/ydlaahtx.html