| package com.doumee.keyCabinet.utils.face; | 
|   | 
| import android.graphics.Bitmap; | 
| import android.graphics.BitmapFactory; | 
| import android.util.Log; | 
|   | 
| import com.baidu.idl.main.facesdk.FaceInfo; | 
| import com.baidu.idl.main.facesdk.model.BDFaceImageInstance; | 
| import com.baidu.idl.main.facesdk.model.BDFaceOcclusion; | 
| import com.baidu.idl.main.facesdk.model.BDFaceSDKCommon; | 
| import com.doumee.keyCabinet.utils.face.model.SingleBaseConfig; | 
| import com.example.datalibrary.api.FaceApi; | 
| import com.example.datalibrary.listener.OnImportListener; | 
| import com.example.datalibrary.manager.FaceSDKManager; | 
| import com.example.datalibrary.model.ImportFeatureResult; | 
| import com.example.datalibrary.model.User; | 
| import com.example.datalibrary.utils.BitmapUtils; | 
| import com.example.datalibrary.utils.FileUtils; | 
|   | 
| import java.io.File; | 
| import java.util.List; | 
| import java.util.concurrent.ExecutorService; | 
| import java.util.concurrent.Executors; | 
| import java.util.concurrent.Future; | 
| import java.util.concurrent.atomic.AtomicInteger; | 
|   | 
| /** | 
|  * 导入相关管理类 | 
|  * Created by v_liujialu01 on 2019/5/28. | 
|  */ | 
|   | 
| public class ImportFileManager { | 
|     private static final String TAG = "ImportFileManager"; | 
|   | 
|     private Future mFuture; | 
|     private ExecutorService mExecutorService; | 
|     private OnImportListener mImportListener; | 
|     // 是否需要导入 | 
|     private volatile boolean mIsNeedImport; | 
|   | 
|     private int mTotalCount; | 
|     private int mFinishCount; | 
|     private int mSuccessCount; | 
|     private int mFailCount; | 
|   | 
|     private static class HolderClass { | 
|         private static final ImportFileManager instance = new ImportFileManager(); | 
|     } | 
|   | 
|     public static ImportFileManager getInstance() { | 
|         return HolderClass.instance; | 
|     } | 
|   | 
|     // 私有构造,实例化ExecutorService | 
|     private ImportFileManager() { | 
|         if (mExecutorService == null) { | 
|             mExecutorService = Executors.newSingleThreadExecutor(); | 
|         } | 
|     } | 
|   | 
|     public void setOnImportListener(OnImportListener importListener) { | 
|         mImportListener = importListener; | 
|     } | 
|   | 
|     /** | 
|      * 开始批量导入 | 
|      */ | 
|     public void batchImport() { | 
|         // 1、获取导入目录 /sdcard/Face-Import | 
|         File batchImportDir = FileUtils.getBatchImportDirectory(); | 
|         // 2、遍历该目录下的所有文件 | 
|         File[] picFiles = batchImportDir.listFiles(); | 
|         if (picFiles == null || picFiles.length == 0) { | 
|             Log.i(TAG, "导入数据的文件夹没有数据"); | 
|             if (mImportListener != null) { | 
|                 mImportListener.showToastMessage("导入数据的文件夹没有数据"); | 
|             } | 
|             return; | 
|         } | 
|   | 
|         // 开启线程导入图片 | 
|         asyncImport(picFiles); | 
|     } | 
|   | 
|     public void setIsNeedImport(boolean isNeedImport) { | 
|         mIsNeedImport = isNeedImport; | 
|     } | 
|   | 
|     /** | 
|      * 开启线程导入图片 | 
|      * @param picFiles  要导入的图片集 | 
|      */ | 
|     private void asyncImport(final File[] picFiles) { | 
|         if (mFuture != null && !mFuture.isDone()) { | 
|             return; | 
|         } | 
|         mIsNeedImport = true;     // 判断是否需要导入 | 
|         mFinishCount = 0;         // 已完成的图片数量 | 
|         mSuccessCount = 0;        // 已导入成功的图片数量 | 
|         mFailCount = 0;           // 已导入失败的图片数量 | 
|   | 
|         if (mExecutorService == null) { | 
|             mExecutorService = Executors.newSingleThreadExecutor(); | 
|         } | 
|   | 
|         mFuture = mExecutorService.submit(new Runnable() { | 
|             @Override | 
|             public void run() { | 
|                 try { | 
|                     if (picFiles == null || picFiles.length == 0) { | 
|                         Log.i(TAG, "导入数据的文件夹没有数据"); | 
|                         if (mImportListener != null) { | 
|                             mImportListener.showToastMessage("导入数据的文件夹没有数据"); | 
|                         } | 
|                         return; | 
|                     } | 
|   | 
|                     // 读取图片成功,开始显示进度条 | 
|                     if (mImportListener != null) { | 
|                         mImportListener.showProgressView(); | 
|                     } | 
|   | 
|                     Thread.sleep(400); | 
|   | 
|                     mTotalCount = picFiles.length;  // 总图片数 | 
|   | 
|                     for (int i = 0; i < picFiles.length; i++) { | 
|                         if (!mIsNeedImport) { | 
|                             break; | 
|                         } | 
|                         File picFile = picFiles[i]; | 
|                         if (picFile.isDirectory()) { | 
|                             Log.e(TAG, "当前内容是文件夹"); | 
|                             mFinishCount++; | 
|                             mFailCount++; | 
|                             // 更新进度 | 
|                             updateProgress(mTotalCount, mSuccessCount, mFailCount, | 
|                                     ((float) mFinishCount / (float) mTotalCount)); | 
|                             continue; | 
|                         } | 
|                         // 3、获取图片名 | 
|                         String picName = picFiles[i].getName(); | 
|                         String name = picName.substring(0, picName.lastIndexOf(".")); | 
|                         Log.e(TAG, "i = " + i + ", picName = " + picName); | 
|                         // 4、判断图片后缀 | 
|                         if (!picName.endsWith(".jpg") && !picName.endsWith(".png") && | 
|                                 !picName.endsWith(".PNG") && !picName.endsWith(".JPG")) { | 
|                             Log.e(TAG, "图片后缀不满足要求"); | 
|                             FileUtils.saveFile(picFiles[i], "Face-Import-Fail" , | 
|                                     name + "_1"); | 
|                             mFinishCount++; | 
|                             mFailCount++; | 
|                             // 更新进度 | 
|                             updateProgress(mTotalCount, mSuccessCount, mFailCount, | 
|                                     ((float) mFinishCount / (float) mTotalCount)); | 
|                             continue; | 
|                         } | 
|   | 
|                         // 5、获取不带后缀的图片名,即用户名 | 
|                         String userName = FileUtils.getFileNameNoEx(picName).trim(); | 
|   | 
|                         boolean success = false;  // 判断成功状态 | 
|   | 
|                         // 6、判断姓名是否有效 | 
|                         String nameResult = FaceApi.getInstance().isValidName(userName); | 
|                         if (!"0".equals(nameResult)) { | 
|                             Log.i(TAG, nameResult); | 
|                             FileUtils.saveFile(picFiles[i], "Face-Import-Fail" , | 
|                                     name + "_10"); | 
|                             mFinishCount++; | 
|                             mFailCount++; | 
|                             // 更新进度 | 
|                             updateProgress(mTotalCount, mSuccessCount, mFailCount, | 
|                                     ((float) mFinishCount / (float) mTotalCount)); | 
|                             continue; | 
|                         } | 
|   | 
|                         // 7、根据姓名查询数据库与文件中对应的姓名是否相等,如果相等,则直接过滤 | 
|                         List<User> listUsers = FaceApi.getInstance().getUserListByUserName(userName); | 
|                         if (listUsers != null && listUsers.size() > 0) { | 
|                             Log.i(TAG, "与之前图片名称相同"); | 
|                             boolean isDelete = FaceApi.getInstance().userDeleteByName(userName); | 
|                             if (!isDelete){ | 
|                                 Log.i(TAG, "之前特征删除失败"); | 
|                                 FileUtils.saveFile(picFiles[i], "Face-Import-Fail" , | 
|                                         name + "_10"); | 
|                                 mFinishCount++; | 
|                                 mFailCount++; | 
| //                                 更新进度 | 
|                                 updateProgress(mTotalCount, mSuccessCount, mFailCount, | 
|                                         ((float) mFinishCount / (float) mTotalCount)); | 
|                                 continue; | 
|                             } | 
|                             /**/ | 
|                         } | 
|   | 
|                         // 8、根据图片的路径将图片转成Bitmap | 
|                         Bitmap bitmap = BitmapFactory.decodeFile(picFiles[i].getAbsolutePath()); | 
|   | 
|                         // 9、判断bitmap是否转换成功 | 
|                         if (bitmap == null) { | 
|                             Log.e(TAG, picName + ":该图片转成Bitmap失败"); | 
|                             mFinishCount++; | 
|                             mFailCount++; | 
|                             BitmapUtils.saveRgbBitmap(bitmap , "Face-Import-Fail" , | 
|                                     name + "_2"); | 
|                             // 更新进度 | 
|                             updateProgress(mTotalCount, mSuccessCount, mFailCount, | 
|                                     ((float) mFinishCount / (float) mTotalCount)); | 
|                             continue; | 
|                         } | 
|                         Bitmap bitmap1 ; | 
|                         // 图片缩放 | 
|                         if (bitmap.getWidth() * bitmap.getHeight() > 3000 * 2000) { | 
|                             if (bitmap.getWidth() > bitmap.getHeight()) { | 
|                                 float scale = 1 / (bitmap.getWidth() * 1.0f / 1000.0f); | 
|                                 bitmap1 = BitmapUtils.scale(bitmap, scale); | 
|                             } else { | 
|                                 float scale = 1 / (bitmap.getHeight() * 1.0f / 1000.0f); | 
|                                 bitmap1 = BitmapUtils.scale(bitmap, scale); | 
|                             } | 
|                         }else { | 
|                             bitmap1 = bitmap; | 
|                         } | 
|                         if (bitmap1 != bitmap && !bitmap.isRecycled()){ | 
|                             bitmap.recycle(); | 
|                         } | 
|   | 
|                         byte[] bytes = new byte[512]; | 
|                         ImportFeatureResult result; | 
|                         // 10、走人脸SDK接口,通过人脸检测、特征提取拿到人脸特征值 | 
|                         result = getFeature(bitmap1, bytes, | 
|                                 BDFaceSDKCommon.FeatureType.BDFACE_FEATURE_TYPE_LIVE_PHOTO); | 
|   | 
|                         // 11、判断是否提取成功:128为成功,-1为参数为空,-2表示未检测到人脸 | 
|                         Log.i(TAG, "live_photo = " + result.getResult()); | 
|                         if (result.getResult() == 128) { | 
|                             // 将用户信息保存到数据库中 | 
|                             boolean importDBSuccess = FaceApi.getInstance().registerUserIntoDBmanager(null,"", | 
|                                     userName, picName, null, bytes); | 
|   | 
|                             // 保存数据库成功 | 
|                             if (importDBSuccess) { | 
|                                 // 保存图片到新目录中 | 
|                                 File facePicDir = FileUtils.getBatchImportSuccessDirectory(); | 
|                                 if (facePicDir != null) { | 
|                                     File savePicPath = new File(facePicDir, picName); | 
|                                     if (FileUtils.saveBitmap(savePicPath, result.getBitmap())) { | 
|                                         Log.i(TAG, "头像保存成功"); | 
|                                         success = true; | 
|                                     } else { | 
|                                         Log.i(TAG, "头像保存失败"); | 
|                                     } | 
|                                 } | 
|                             } else { | 
|                                 Log.e(TAG, picName + ":保存到数据库失败"); | 
|                                 BitmapUtils.saveRgbBitmap(bitmap1 , "Face-Import-Fail" , | 
|                                         name + "_10"); | 
|                             } | 
|                         } else { | 
|                             Log.e(TAG, picName + " 错误码:" + result.getResult()); | 
|                             BitmapUtils.saveRgbBitmap(bitmap1 , "Face-Import-Fail" , | 
|                                     name + "_" + ((int) result.getResult())); | 
|                         } | 
|                         if (result.getBitmap() != null && !result.getBitmap().isRecycled()){ | 
|                             result.getBitmap().recycle(); | 
|                         } | 
|                         // 图片回收 | 
|                         if (!bitmap1.isRecycled()) { | 
|                             bitmap1.recycle(); | 
|                         } | 
|   | 
|                         // 判断成功与否 | 
|                         if (success) { | 
|                             mSuccessCount++; | 
|                         } else { | 
|                             mFailCount++; | 
|                             Log.e(TAG, "失败图片:" + picName); | 
|                         } | 
|                         mFinishCount++; | 
|                         // 导入中(用来显示进度) | 
|                         Log.i(TAG, "mFinishCount = " + mFinishCount | 
|                                 + " progress = " + ((float) mFinishCount / (float) mTotalCount)); | 
|   | 
|                         updateProgress(mTotalCount, mSuccessCount, mFailCount, | 
|                                 ((float) mFinishCount / (float) mTotalCount)); | 
|                         /*if (mImportListener != null) { | 
|                             mImportListener.onImporting(mTotalCount, mSuccessCount, mFailCount, | 
|                                     ((float) mFinishCount / (float) mTotalCount)); | 
|                         }*/ | 
|                     } | 
|   | 
|                     // 导入完成 | 
|                     if (mImportListener != null) { | 
|                         mImportListener.endImport(mTotalCount, mSuccessCount, mFailCount); | 
|                     } | 
|                 } catch (Exception e) { | 
|                     e.printStackTrace(); | 
|                     Log.e(TAG, "exception = " + e.getMessage()); | 
|                 } | 
|             } | 
|         }); | 
|     } | 
|   | 
|   | 
|     /** | 
|      * 提取特征值 | 
|      */ | 
|     public ImportFeatureResult getFeature(Bitmap bitmap, byte[] feature, BDFaceSDKCommon.FeatureType featureType) { | 
|         if (bitmap == null) { | 
|             return new ImportFeatureResult(2, null); | 
|         } | 
|   | 
|         BDFaceImageInstance imageInstance = new BDFaceImageInstance(bitmap); | 
|         // 最大检测人脸,获取人脸信息 | 
|         FaceInfo[] faceInfos = FaceSDKManager.getInstance().getFaceDetectPerson() | 
|                 .detect(BDFaceSDKCommon.DetectType.DETECT_VIS, imageInstance); | 
|         if (faceInfos == null || faceInfos.length == 0) { | 
|             imageInstance.destory(); | 
|             // 图片外扩 | 
|             Bitmap broadBitmap = BitmapUtils.broadImage(bitmap); | 
|             imageInstance = new BDFaceImageInstance(broadBitmap); | 
|             // 最大检测人脸,获取人脸信息 | 
|             faceInfos = FaceSDKManager.getInstance().getFaceDetectPerson() | 
|                     .detect(BDFaceSDKCommon.DetectType.DETECT_VIS, imageInstance); | 
|             // 若外扩后还未检测到人脸,则旋转图片检测 | 
|             if (faceInfos == null || faceInfos.length == 0) { | 
|                 return new ImportFeatureResult(/*rotationDetection(broadBitmap , 90)*/8, null); | 
|             } | 
|         } | 
|         // 判断多人脸 | 
|         if (faceInfos.length > 1){ | 
|             imageInstance.destory(); | 
|             return new ImportFeatureResult(9, null); | 
|         } | 
|         FaceInfo faceInfo = faceInfos[0]; | 
|         // 判断质量 | 
|         int quality = onQualityCheck(faceInfo); | 
|         if (quality != 0){ | 
|             return new ImportFeatureResult(quality, null); | 
|         } | 
|         // 人脸识别,提取人脸特征值 | 
|         float ret = FaceSDKManager.getInstance().getFacePersonFeature().feature( | 
|                 featureType, imageInstance, | 
|                 faceInfo.landmarks, feature); | 
|         // 人脸抠图 | 
|         BDFaceImageInstance cropInstance = FaceSDKManager.getInstance().getFaceCrop() | 
|                 .cropFaceByLandmark(imageInstance, faceInfo.landmarks, | 
|                         2.0f, true, new AtomicInteger()); | 
|         if (cropInstance == null) { | 
|             imageInstance.destory(); | 
|             return new ImportFeatureResult(10, null); | 
|         } | 
|   | 
|         Bitmap cropBmp = BitmapUtils.getInstaceBmp(cropInstance); | 
|         cropInstance.destory(); | 
|         imageInstance.destory(); | 
|         return new ImportFeatureResult(ret, cropBmp); | 
|     } | 
|     // 旋转bitmap检测是否存在人脸 | 
|     private int rotationDetection(Bitmap bitmap , int angle){ | 
|         return rotationDetection(bitmap , angle , 1); | 
|     } | 
|   | 
|     private int rotationDetection(Bitmap bitmap , int angle , int index){ | 
|         if (bitmap == null){ | 
|             return 2; | 
|         } | 
|         Bitmap angleBitmap = BitmapUtils.adjustPhotoRotation(bitmap , angle); | 
|         BDFaceImageInstance imageInstance = new BDFaceImageInstance(angleBitmap); | 
|         FaceInfo[] faceInfos = FaceSDKManager.getInstance().getFaceDetectPerson() | 
|                 .detect(BDFaceSDKCommon.DetectType.DETECT_VIS, imageInstance); | 
|         if (!angleBitmap.isRecycled()) { | 
|             angleBitmap.recycle(); | 
|         } | 
|         if (faceInfos == null || faceInfos.length == 0) { | 
|             imageInstance.destory(); | 
|             if (index == 3){ | 
|                 if (!bitmap.isRecycled()) { | 
|                     bitmap.recycle(); | 
|                 } | 
|                 return 8; | 
|             } else { | 
|                 return rotationDetection(bitmap , angle + 90 , index + 1); | 
|             } | 
|         } | 
|         if (!bitmap.isRecycled()) { | 
|             bitmap.recycle(); | 
|         } | 
|         return 3; | 
|     } | 
|   | 
|     /** | 
|      * 质量检测结果过滤,如果需要质量检测, | 
|      * 需要调用 SingleBaseConfig.getBaseConfig().setQualityControl(true);设置为true, | 
|      * 再调用  FaceSDKManager.getInstance().initConfig() 加载到底层配置项中 | 
|      * | 
|      * @return | 
|      */ | 
|     public int onQualityCheck(FaceInfo faceInfo) { | 
|   | 
|         if (!SingleBaseConfig.getBaseConfig().isQualityControl()) { | 
|             return 0; | 
|         } | 
|   | 
|         if (faceInfo != null) { | 
|   | 
|             // 角度过滤 | 
|             if (Math.abs(faceInfo.yaw) > SingleBaseConfig.getBaseConfig().getYaw()) { | 
|                 return 4; | 
|             } else if (Math.abs(faceInfo.roll) > SingleBaseConfig.getBaseConfig().getRoll()) { | 
|                 return 4; | 
|             } else if (Math.abs(faceInfo.pitch) > SingleBaseConfig.getBaseConfig().getPitch()) { | 
|                 return 4; | 
|             } | 
|   | 
|             // 模糊结果过滤 | 
|             float blur = faceInfo.bluriness; | 
|             if (blur > SingleBaseConfig.getBaseConfig().getBlur()) { | 
|                 return 5; | 
|             } | 
|   | 
|             // 光照结果过滤 | 
|             float illum = faceInfo.illum; | 
|             if (illum < SingleBaseConfig.getBaseConfig().getIllumination()) { | 
|                 return 7; | 
|             } | 
|   | 
|   | 
|             // 遮挡结果过滤 | 
|             if (faceInfo.occlusion != null) { | 
|                 BDFaceOcclusion occlusion = faceInfo.occlusion; | 
|   | 
|                 if (occlusion.leftEye > SingleBaseConfig.getBaseConfig().getLeftEye()) { | 
|                     // 左眼遮挡置信度 | 
|                     return 6; | 
|                 } else if (occlusion.rightEye > SingleBaseConfig.getBaseConfig().getRightEye()) { | 
|                     // 右眼遮挡置信度 | 
|                     return 6; | 
|                 } else if (occlusion.nose > SingleBaseConfig.getBaseConfig().getNose()) { | 
|                     // 鼻子遮挡置信度 | 
|                     return 6; | 
|                 } else if (occlusion.mouth > SingleBaseConfig.getBaseConfig().getMouth()) { | 
|                     // 嘴巴遮挡置信度 | 
|                     return 6; | 
|                 } else if (occlusion.leftCheek > SingleBaseConfig.getBaseConfig().getLeftCheek()) { | 
|                     // 左脸遮挡置信度 | 
|                     return 6; | 
|                 } else if (occlusion.rightCheek > SingleBaseConfig.getBaseConfig().getRightCheek()) { | 
|                     // 右脸遮挡置信度 | 
|                     return 6; | 
|                 } else if (occlusion.chin > SingleBaseConfig.getBaseConfig().getChinContour()) { | 
|                     // 下巴遮挡置信度 | 
|                     return 6; | 
|                 } else { | 
|                     return 0; | 
|                 } | 
|             } | 
|         } | 
|         return 0; | 
|     } | 
|   | 
|   | 
|   | 
|     private void updateProgress(int totalCount, int successCount, int failureCount, float progress) { | 
|         if (mImportListener != null) { | 
|             mImportListener.onImporting(totalCount, successCount, failureCount, progress); | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * 释放功能,用于关闭线程操作 | 
|      */ | 
|     public void release() { | 
|         if (mFuture != null && !mFuture.isCancelled()) { | 
|             mFuture.cancel(true); | 
|             mFuture = null; | 
|         } | 
|   | 
|         if (mExecutorService != null) { | 
|             mExecutorService.shutdown(); | 
|             mExecutorService = null; | 
|         } | 
|     } | 
| } |