最近在做虹軟的人臉識別接入介入過程中遇到了一些問題,下面記錄一下,防止其他人踩坑
首選先講下接入流程
1.權限
獲取設備唯一標識,用於SDK激活授權
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
允許應用聯網,用於SDK聯網激活授權
<uses-permission android:name="android.permission.INTERNET" />
2.支持圖片的顏色空間格式 (非常重要,如果自己本地的圖片進行註冊人臉這裏會用到)
3.工程配置
1. 新建一個Android Project,切換到Project視圖;
2. 將libarcsoft_face.so和libarcsoft_face_engine.so添加到src->main->jniLibs->armeabi-v7a路徑下;
3. 將arcsoft_face.jar放入,並依賴進工程;
4.調用流程
Step1:調用FaceEngine的active方法激活設備,一個設備安裝後僅需激活一次,卸載重新安裝後需要重新激活。
Step2:調用FaceEngine的init方法初始化SDK,初始化成功後才能進一步使用SDK的功能。
Step3:調用FaceEngine的detectFaces方法進行圖像數據或預覽數據的人臉檢測,若檢測成功,則可得到一個人臉列表。(初始化時combineMask需要ASF_FACE_DETECT)
Step4:調用FaceEngine的extractFaceFeature方法可對圖像中指定的人臉進行特徵提取。(初始化時combineMask需要ASF_FACE_RECOGNITION)
Step5:調用FaceEngine的compareFaceFeature方法可對傳入的兩個人臉特徵進行比對,獲取相似度。(初始化時combineMask需要ASF_FACE_RECOGNITION)
Step6:調用FaceEngine的process方法,傳入不同的combineMask組合可對Age、Gender、Face3Dangle、Liveness進行檢測,傳入的combineMask的任一屬性都需要在init時進行初始化。
Step7:調用FaceEngine的getAge、getGender、getFace3Dangle、getLiveness方法可獲取年齡、性別、三維角度、活體檢測結果,且每個結果在獲取前都需要在process中進行處理。
Step8:調用FaceEngine的unInit方法銷燬引擎。在init成功後如不unInit會導致內存泄漏。
5.按照官網的SDK說明中可以正常運行並使用demo但是,手動註冊人臉照片時會遇到註冊人臉失敗的問題主要是圖片註冊需要轉化爲NV21的格式,直接將圖片轉換爲byte是不能用的
下面是手動註冊人臉的方法
/** * 將準備註冊的狀態置爲{@link #REGISTER_STATUS_READY} * * @param view 註冊按鈕 */ public void test(View view) { Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.bst_test); Bitmap bitmap = ImageUtil.alignBitmapForNv21(mBitmap); // byte[] bytes = bitmapToNv21(mBitmap, mBitmap.getWidth(), mBitmap.getHeight()); byte[] bytes = ImageUtil.bitmapToNv21(bitmap, bitmap.getWidth(), bitmap.getHeight()); boolean success = FaceServer.getInstance().register(MainActivity.this, bytes.clone(), bitmap.getWidth(), bitmap.getHeight(), "registered " + "bst_test"); if (success){ showToast("註冊成功"); }else { showToast("註冊失敗"); } }
public boolean register(Context context, byte[] nv21, int width, int height, String name) { synchronized (this) { if (faceEngine == null || context == null || nv21 == null || width % 4 != 0 || nv21.length != width * height * 3 / 2) { return false; } if (ROOT_PATH == null) { ROOT_PATH = context.getFilesDir().getAbsolutePath(); } boolean dirExists = true; //特徵存儲的文件夾 File featureDir = new File(ROOT_PATH + File.separator + SAVE_FEATURE_DIR); if (!featureDir.exists()) { dirExists = featureDir.mkdirs(); } if (!dirExists) { return false; } //圖片存儲的文件夾 File imgDir = new File(ROOT_PATH + File.separator + SAVE_IMG_DIR); if (!imgDir.exists()) { dirExists = imgDir.mkdirs(); } if (!dirExists) { return false; } //1.人臉檢測 List<FaceInfo> faceInfoList = new ArrayList<>(); int code = faceEngine.detectFaces(nv21, width, height, FaceEngine.CP_PAF_NV21, faceInfoList); if (code == ErrorInfo.MOK && faceInfoList.size() > 0) { FaceFeature faceFeature = new FaceFeature(); //2.特徵提取 code = faceEngine.extractFaceFeature(nv21, width, height, FaceEngine.CP_PAF_NV21, faceInfoList.get(0), faceFeature); String userName = name == null ? String.valueOf(System.currentTimeMillis()) : name; try { //3.保存註冊結果(註冊圖、特徵數據) if (code == ErrorInfo.MOK) { YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, width, height, null); //爲了美觀,擴大rect截取註冊圖 Rect cropRect = getBestRect(width, height, faceInfoList.get(0).getRect()); if (cropRect == null) { return false; } File file = new File(imgDir + File.separator + userName + IMG_SUFFIX); FileOutputStream fosImage = new FileOutputStream(file); yuvImage.compressToJpeg(cropRect, 100, fosImage); fosImage.close(); Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); //判斷人臉旋轉角度,若不爲0度則旋轉註冊圖 boolean needAdjust = false; if (bitmap != null) { switch (faceInfoList.get(0).getOrient()) { case FaceEngine.ASF_OC_0: break; case FaceEngine.ASF_OC_90: bitmap = ImageUtil.getRotateBitmap(bitmap, 90); needAdjust = true; break; case FaceEngine.ASF_OC_180: bitmap = ImageUtil.getRotateBitmap(bitmap, 180); needAdjust = true; break; case FaceEngine.ASF_OC_270: bitmap = ImageUtil.getRotateBitmap(bitmap, 270); needAdjust = true; break; default: break; } } if (needAdjust) { fosImage = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fosImage); fosImage.close(); } FileOutputStream fosFeature = new FileOutputStream(featureDir + File.separator + userName); fosFeature.write(faceFeature.getFeatureData()); fosFeature.close(); //內存中的數據同步 if (faceRegisterInfoList == null) { faceRegisterInfoList = new ArrayList<>(); } faceRegisterInfoList.add(new FaceRegisterInfo(faceFeature.getFeatureData(), userName)); return true; } } catch (IOException e) { e.printStackTrace(); } } return false; } }
下面是官方工具類
package com.example.shine.util; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Matrix; import android.graphics.Rect; import android.net.Uri; import android.provider.MediaStore; import java.io.IOException; import java.nio.ByteBuffer; //官方提供的工具類 public class ImageUtil { private static final int VALUE_FOR_4_ALIGN = 0b11; private static final int VALUE_FOR_2_ALIGN = 0b01; /** * Bitmap轉化爲ARGB數據,再轉化爲NV21數據 * * @param src 傳入的Bitmap,格式爲{@link Bitmap.Config#ARGB_8888} * @param width NV21圖像的寬度 * @param height NV21圖像的高度 * @return nv21數據 */ public static byte[] bitmapToNv21(Bitmap src, int width, int height) { if (src != null && src.getWidth() >= width && src.getHeight() >= height) { int[] argb = new int[width * height]; src.getPixels(argb, 0, width, 0, 0, width, height); return argbToNv21(argb, width, height); } else { return null; } } /** * ARGB數據轉化爲NV21數據 * * @param argb argb數據 * @param width 寬度 * @param height 高度 * @return nv21數據 */ private static byte[] argbToNv21(int[] argb, int width, int height) { int frameSize = width * height; int yIndex = 0; int uvIndex = frameSize; int index = 0; byte[] nv21 = new byte[width * height * 3 / 2]; for (int j = 0; j < height; ++j) { for (int i = 0; i < width; ++i) { int R = (argb[index] & 0xFF0000) >> 16; int G = (argb[index] & 0x00FF00) >> 8; int B = argb[index] & 0x0000FF; int Y = (66 * R + 129 * G + 25 * B + 128 >> 8) + 16; int U = (-38 * R - 74 * G + 112 * B + 128 >> 8) + 128; int V = (112 * R - 94 * G - 18 * B + 128 >> 8) + 128; nv21[yIndex++] = (byte) (Y < 0 ? 0 : (Y > 255 ? 255 : Y)); if (j % 2 == 0 && index % 2 == 0 && uvIndex < nv21.length - 2) { nv21[uvIndex++] = (byte) (V < 0 ? 0 : (V > 255 ? 255 : V)); nv21[uvIndex++] = (byte) (U < 0 ? 0 : (U > 255 ? 255 : U)); } ++index; } } return nv21; } /** * bitmap轉化爲bgr數據,格式爲{@link Bitmap.Config#ARGB_8888} * * @param image 傳入的bitmap * @return bgr數據 */ public static byte[] bitmapToBgr(Bitmap image) { if (image == null) { return null; } int bytes = image.getByteCount(); ByteBuffer buffer = ByteBuffer.allocate(bytes); image.copyPixelsToBuffer(buffer); byte[] temp = buffer.array(); byte[] pixels = new byte[(temp.length / 4) * 3]; for (int i = 0; i < temp.length / 4; i++) { pixels[i * 3] = temp[i * 4 + 2]; pixels[i * 3 + 1] = temp[i * 4 + 1]; pixels[i * 3 + 2] = temp[i * 4]; } return pixels; } /** * 裁剪bitmap * * @param bitmap 傳入的bitmap * @param rect 需要被裁剪的區域 * @return 被裁剪後的bitmap */ public static Bitmap imageCrop(Bitmap bitmap, Rect rect) { if (bitmap == null || rect == null || rect.isEmpty() || bitmap.getWidth() < rect.right || bitmap.getHeight() < rect.bottom) { return null; } return Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.width(), rect.height(), null, false); } public static Bitmap getBitmapFromUri(Uri uri, Context context) { if (uri == null || context == null) { return null; } try { return MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri); } catch (IOException e) { e.printStackTrace(); return null; } } public static Bitmap getRotateBitmap(Bitmap b, float rotateDegree) { if (b == null) { return null; } Matrix matrix = new Matrix(); matrix.postRotate(rotateDegree); return Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, false); } /** * 確保傳給引擎的BGR24數據寬度爲4的倍數 * * @param bitmap 傳入的bitmap * @return 調整後的bitmap */ public static Bitmap alignBitmapForBgr24(Bitmap bitmap) { if (bitmap == null || bitmap.getWidth() < 4) { return null; } int width = bitmap.getWidth(); int height = bitmap.getHeight(); boolean needAdjust = false; //保證寬度是4的倍數 if ((width & VALUE_FOR_4_ALIGN) != 0) { width &= ~VALUE_FOR_4_ALIGN; needAdjust = true; } if (needAdjust) { bitmap = imageCrop(bitmap, new Rect(0, 0, width, height)); } return bitmap; } /** * 確保傳給引擎的NV21數據寬度爲4的倍數,高爲2的倍數 * * @param bitmap 傳入的bitmap * @return 調整後的bitmap */ public static Bitmap alignBitmapForNv21(Bitmap bitmap) { if (bitmap == null || bitmap.getWidth() < 4 || bitmap.getHeight() < 2) { return null; } int width = bitmap.getWidth(); int height = bitmap.getHeight(); boolean needAdjust = false; //保證寬度是4的倍數 if ((width & VALUE_FOR_4_ALIGN) != 0) { width &= ~VALUE_FOR_4_ALIGN; needAdjust = true; } //保證高度是2的倍數 if ((height & VALUE_FOR_2_ALIGN) != 0) { height--; needAdjust = true; } if (needAdjust) { bitmap = imageCrop(bitmap, new Rect(0, 0, width, height)); } return bitmap; } }
這樣就可以手動註冊成功了