正在显示
23 个修改的文件
包含
1003 行增加
和
3837 行删除
@@ -13,6 +13,7 @@ | @@ -13,6 +13,7 @@ | ||
13 | <option value="$PROJECT_DIR$/arcface" /> | 13 | <option value="$PROJECT_DIR$/arcface" /> |
14 | <option value="$PROJECT_DIR$/bdface" /> | 14 | <option value="$PROJECT_DIR$/bdface" /> |
15 | <option value="$PROJECT_DIR$/sdk" /> | 15 | <option value="$PROJECT_DIR$/sdk" /> |
16 | + <option value="$PROJECT_DIR$/test" /> | ||
16 | </set> | 17 | </set> |
17 | </option> | 18 | </option> |
18 | <option name="resolveModulePerSourceSet" value="false" /> | 19 | <option name="resolveModulePerSourceSet" value="false" /> |
@@ -24,9 +24,6 @@ android { | @@ -24,9 +24,6 @@ android { | ||
24 | dependencies { | 24 | dependencies { |
25 | api fileTree(dir: 'libs', include: ['*.jar']) | 25 | api fileTree(dir: 'libs', include: ['*.jar']) |
26 | implementation 'com.android.support:appcompat-v7:28.0.0' | 26 | implementation 'com.android.support:appcompat-v7:28.0.0' |
27 | - testImplementation 'junit:junit:4.12' | ||
28 | - androidTestImplementation 'com.android.support.test:runner:1.0.2' | ||
29 | - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' | ||
30 | implementation 'com.android.support:recyclerview-v7:28.0.0' | 27 | implementation 'com.android.support:recyclerview-v7:28.0.0' |
31 | implementation 'com.github.bumptech.glide:glide:4.9.0' | 28 | implementation 'com.github.bumptech.glide:glide:4.9.0' |
32 | implementation 'io.reactivex.rxjava2:rxjava:2.2.6' | 29 | implementation 'io.reactivex.rxjava2:rxjava:2.2.6' |
1 | -package com.arcsoft.arcfacedemo; | ||
2 | - | ||
3 | -import android.content.Context; | ||
4 | -import android.support.test.InstrumentationRegistry; | ||
5 | -import android.support.test.runner.AndroidJUnit4; | ||
6 | - | ||
7 | -import org.junit.Test; | ||
8 | -import org.junit.runner.RunWith; | ||
9 | - | ||
10 | -import static org.junit.Assert.*; | ||
11 | - | ||
12 | -/** | ||
13 | - * Instrumented test, which will execute on an Android device. | ||
14 | - * | ||
15 | - * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||
16 | - */ | ||
17 | -@RunWith(AndroidJUnit4.class) | ||
18 | -public class ExampleInstrumentedTest { | ||
19 | - @Test | ||
20 | - public void useAppContext() { | ||
21 | - // Context of the app under test. | ||
22 | - Context appContext = InstrumentationRegistry.getTargetContext(); | ||
23 | - | ||
24 | - assertEquals("com.arcsoft.arcfacedemo", appContext.getPackageName()); | ||
25 | - } | ||
26 | -} |
@@ -2,61 +2,11 @@ | @@ -2,61 +2,11 @@ | ||
2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | 2 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
3 | package="com.arcsoft.arcfacedemo"> | 3 | package="com.arcsoft.arcfacedemo"> |
4 | 4 | ||
5 | - <uses-permission android:name="android.permission.CAMERA" /> | ||
6 | - <uses-permission android:name="android.permission.READ_PHONE_STATE" /> | ||
7 | - <uses-permission android:name="android.permission.INTERNET" /> | ||
8 | - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> | ||
9 | - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||
10 | 5 | ||
11 | - <application | ||
12 | - android:allowBackup="true" | ||
13 | - android:icon="@mipmap/ic_launcher" | 6 | + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
7 | + <application android:allowBackup="false" | ||
14 | android:label="@string/app_name" | 8 | android:label="@string/app_name" |
15 | - android:supportsRtl="true" | ||
16 | - android:theme="@style/AppTheme"> | ||
17 | - <activity | ||
18 | - android:name=".activity.FaceAttrPreviewActivity" | ||
19 | - android:launchMode="singleTop" /> | ||
20 | - <activity | ||
21 | - android:name=".activity.ChooseFunctionActivity" | ||
22 | - android:launchMode="singleTop"> | ||
23 | - <intent-filter> | ||
24 | - <action android:name="android.intent.action.MAIN" /> | ||
25 | - | ||
26 | - <category android:name="android.intent.category.LAUNCHER" /> | ||
27 | - </intent-filter> | ||
28 | - </activity> | ||
29 | - | ||
30 | - <activity | ||
31 | - android:name=".activity.SingleImageActivity" | ||
32 | - android:configChanges="orientation|screenSize" | ||
33 | - android:launchMode="singleTop" /> | ||
34 | - | ||
35 | - <activity | ||
36 | - android:name=".activity.MultiImageActivity" | ||
37 | - android:launchMode="singleTop" /> | ||
38 | - | ||
39 | - <activity | ||
40 | - android:name=".activity.IrRegisterAndRecognizeActivity" | ||
41 | - android:launchMode="singleTop" /> | ||
42 | - | ||
43 | - <activity | ||
44 | - android:name=".activity.RegisterAndRecognizeActivity" | ||
45 | - android:launchMode="singleTop" /> | ||
46 | - | ||
47 | - <activity | ||
48 | - android:name=".activity.FaceManageActivity" | ||
49 | - android:launchMode="singleTop" /> | ||
50 | - | ||
51 | - <provider | ||
52 | - android:name="android.support.v4.content.FileProvider" | ||
53 | - android:authorities="${applicationId}.provider" | ||
54 | - android:exported="false" | ||
55 | - android:grantUriPermissions="true"> | ||
56 | - <meta-data | ||
57 | - android:name="android.support.FILE_PROVIDER_PATHS" | ||
58 | - android:resource="@xml/provider_paths" /> | ||
59 | - </provider> | 9 | + android:supportsRtl="true"> |
60 | 10 | ||
61 | </application> | 11 | </application> |
62 | 12 |
1 | -package com.arcsoft.arcfacedemo.activity; | ||
2 | - | ||
3 | -import android.content.pm.PackageManager; | ||
4 | -import android.support.annotation.NonNull; | ||
5 | -import android.support.v4.content.ContextCompat; | ||
6 | -import android.support.v7.app.AppCompatActivity; | ||
7 | -import android.widget.Toast; | ||
8 | - | ||
9 | -public abstract class BaseActivity extends AppCompatActivity { | ||
10 | - /** | ||
11 | - * 权限检查 | ||
12 | - * | ||
13 | - * @param neededPermissions 需要的权限 | ||
14 | - * @return 是否全部被允许 | ||
15 | - */ | ||
16 | - protected boolean checkPermissions(String[] neededPermissions) { | ||
17 | - if (neededPermissions == null || neededPermissions.length == 0) { | ||
18 | - return true; | ||
19 | - } | ||
20 | - boolean allGranted = true; | ||
21 | - for (String neededPermission : neededPermissions) { | ||
22 | - allGranted &= ContextCompat.checkSelfPermission(this, neededPermission) == PackageManager.PERMISSION_GRANTED; | ||
23 | - } | ||
24 | - return allGranted; | ||
25 | - } | ||
26 | - | ||
27 | - @Override | ||
28 | - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { | ||
29 | - super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||
30 | - boolean isAllGranted = true; | ||
31 | - for (int grantResult : grantResults) { | ||
32 | - isAllGranted &= (grantResult == PackageManager.PERMISSION_GRANTED); | ||
33 | - } | ||
34 | - afterRequestPermission(requestCode, isAllGranted); | ||
35 | - } | ||
36 | - | ||
37 | - /** | ||
38 | - * 请求权限的回调 | ||
39 | - * | ||
40 | - * @param requestCode 请求码 | ||
41 | - * @param isAllGranted 是否全部被同意 | ||
42 | - */ | ||
43 | - abstract void afterRequestPermission(int requestCode, boolean isAllGranted); | ||
44 | - | ||
45 | - protected void showToast(String s) { | ||
46 | - Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show(); | ||
47 | - } | ||
48 | - protected void showLongToast(String s) { | ||
49 | - Toast.makeText(getApplicationContext(), s, Toast.LENGTH_LONG).show(); | ||
50 | - } | ||
51 | - | ||
52 | -} |
1 | -package com.arcsoft.arcfacedemo.activity; | ||
2 | - | ||
3 | -import android.Manifest; | ||
4 | -import android.content.Intent; | ||
5 | -import android.content.pm.ApplicationInfo; | ||
6 | -import android.os.Bundle; | ||
7 | -import android.support.v4.app.ActivityCompat; | ||
8 | -import android.util.Log; | ||
9 | -import android.view.View; | ||
10 | - | ||
11 | -import com.arcsoft.arcfacedemo.R; | ||
12 | -import com.arcsoft.arcfacedemo.common.Constants; | ||
13 | -import com.arcsoft.arcfacedemo.fragment.ChooseDetectDegreeDialog; | ||
14 | -import com.arcsoft.face.ActiveFileInfo; | ||
15 | -import com.arcsoft.face.ErrorInfo; | ||
16 | -import com.arcsoft.face.FaceEngine; | ||
17 | -import com.arcsoft.face.VersionInfo; | ||
18 | -import com.arcsoft.face.enums.RuntimeABI; | ||
19 | - | ||
20 | -import java.io.File; | ||
21 | -import java.util.ArrayList; | ||
22 | -import java.util.List; | ||
23 | - | ||
24 | -import io.reactivex.Observable; | ||
25 | -import io.reactivex.ObservableEmitter; | ||
26 | -import io.reactivex.ObservableOnSubscribe; | ||
27 | -import io.reactivex.Observer; | ||
28 | -import io.reactivex.android.schedulers.AndroidSchedulers; | ||
29 | -import io.reactivex.disposables.Disposable; | ||
30 | -import io.reactivex.schedulers.Schedulers; | ||
31 | - | ||
32 | - | ||
33 | -public class ChooseFunctionActivity extends BaseActivity { | ||
34 | - private static final String TAG = "ChooseFunctionActivity"; | ||
35 | - private static final int ACTION_REQUEST_PERMISSIONS = 0x001; | ||
36 | - // 在线激活所需的权限 | ||
37 | - private static final String[] NEEDED_PERMISSIONS = new String[]{ | ||
38 | - Manifest.permission.READ_PHONE_STATE | ||
39 | - }; | ||
40 | - boolean libraryExists = true; | ||
41 | - // Demo 所需的动态库文件 | ||
42 | - private static final String[] LIBRARIES = new String[]{ | ||
43 | - // 人脸相关 | ||
44 | - "libarcsoft_face_engine.so", | ||
45 | - "libarcsoft_face.so", | ||
46 | - // 图像库相关 | ||
47 | - "libarcsoft_image_util.so", | ||
48 | - }; | ||
49 | - // 修改配置项的对话框 | ||
50 | - ChooseDetectDegreeDialog chooseDetectDegreeDialog; | ||
51 | - | ||
52 | - @Override | ||
53 | - protected void onCreate(Bundle savedInstanceState) { | ||
54 | - super.onCreate(savedInstanceState); | ||
55 | - setContentView(R.layout.activity_choose_function); | ||
56 | - libraryExists = checkSoFile(LIBRARIES); | ||
57 | - ApplicationInfo applicationInfo = getApplicationInfo(); | ||
58 | - Log.i(TAG, "onCreate: " + applicationInfo.nativeLibraryDir); | ||
59 | - if (!libraryExists) { | ||
60 | - showToast(getString(R.string.library_not_found)); | ||
61 | - }else { | ||
62 | - VersionInfo versionInfo = new VersionInfo(); | ||
63 | - int code = FaceEngine.getVersion(versionInfo); | ||
64 | - Log.i(TAG, "onCreate: getVersion, code is: " + code + ", versionInfo is: " + versionInfo); | ||
65 | - } | ||
66 | - } | ||
67 | - | ||
68 | - /** | ||
69 | - * 检查能否找到动态链接库,如果找不到,请修改工程配置 | ||
70 | - * | ||
71 | - * @param libraries 需要的动态链接库 | ||
72 | - * @return 动态库是否存在 | ||
73 | - */ | ||
74 | - private boolean checkSoFile(String[] libraries) { | ||
75 | - File dir = new File(getApplicationInfo().nativeLibraryDir); | ||
76 | - File[] files = dir.listFiles(); | ||
77 | - if (files == null || files.length == 0) { | ||
78 | - return false; | ||
79 | - } | ||
80 | - List<String> libraryNameList = new ArrayList<>(); | ||
81 | - for (File file : files) { | ||
82 | - libraryNameList.add(file.getName()); | ||
83 | - } | ||
84 | - boolean exists = true; | ||
85 | - for (String library : libraries) { | ||
86 | - exists &= libraryNameList.contains(library); | ||
87 | - } | ||
88 | - return exists; | ||
89 | - } | ||
90 | - | ||
91 | - /** | ||
92 | - * 打开相机,显示年龄性别 | ||
93 | - * | ||
94 | - * @param view | ||
95 | - */ | ||
96 | - public void jumpToPreviewActivity(View view) { | ||
97 | - checkLibraryAndJump(FaceAttrPreviewActivity.class); | ||
98 | - } | ||
99 | - | ||
100 | - /** | ||
101 | - * 处理单张图片,显示图片中所有人脸的信息,并且一一比对相似度 | ||
102 | - * | ||
103 | - * @param view | ||
104 | - */ | ||
105 | - public void jumpToSingleImageActivity(View view) { | ||
106 | - checkLibraryAndJump(SingleImageActivity.class); | ||
107 | - } | ||
108 | - | ||
109 | - /** | ||
110 | - * 选择一张主照,显示主照中人脸的详细信息,然后选择图片和主照进行比对 | ||
111 | - * | ||
112 | - * @param view | ||
113 | - */ | ||
114 | - public void jumpToMultiImageActivity(View view) { | ||
115 | - checkLibraryAndJump(MultiImageActivity.class); | ||
116 | - } | ||
117 | - | ||
118 | - /** | ||
119 | - * 打开相机,RGB活体检测,人脸注册,人脸识别 | ||
120 | - * | ||
121 | - * @param view | ||
122 | - */ | ||
123 | - public void jumpToFaceRecognizeActivity(View view) { | ||
124 | - checkLibraryAndJump(RegisterAndRecognizeActivity.class); | ||
125 | - } | ||
126 | - | ||
127 | - /** | ||
128 | - * 打开相机,IR活体检测,人脸注册,人脸识别 | ||
129 | - * | ||
130 | - * @param view | ||
131 | - */ | ||
132 | - public void jumpToIrFaceRecognizeActivity(View view) { | ||
133 | - checkLibraryAndJump(IrRegisterAndRecognizeActivity.class); | ||
134 | - } | ||
135 | - | ||
136 | - /** | ||
137 | - * 批量注册和删除功能 | ||
138 | - * | ||
139 | - * @param view | ||
140 | - */ | ||
141 | - public void jumpToBatchRegisterActivity(View view) { | ||
142 | - checkLibraryAndJump(FaceManageActivity.class); | ||
143 | - } | ||
144 | - | ||
145 | - /** | ||
146 | - * 激活引擎 | ||
147 | - * | ||
148 | - * @param view | ||
149 | - */ | ||
150 | - public void activeEngine(final View view) { | ||
151 | - if (!libraryExists) { | ||
152 | - showToast(getString(R.string.library_not_found)); | ||
153 | - return; | ||
154 | - } | ||
155 | - if (!checkPermissions(NEEDED_PERMISSIONS)) { | ||
156 | - ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS); | ||
157 | - return; | ||
158 | - } | ||
159 | - if (view != null) { | ||
160 | - view.setClickable(false); | ||
161 | - } | ||
162 | - Observable.create(new ObservableOnSubscribe<Integer>() { | ||
163 | - @Override | ||
164 | - public void subscribe(ObservableEmitter<Integer> emitter) { | ||
165 | - RuntimeABI runtimeABI = FaceEngine.getRuntimeABI(); | ||
166 | - Log.i(TAG, "subscribe: getRuntimeABI() " + runtimeABI); | ||
167 | - | ||
168 | - long start = System.currentTimeMillis(); | ||
169 | - int activeCode = FaceEngine.activeOnline(ChooseFunctionActivity.this, Constants.APP_ID, Constants.SDK_KEY); | ||
170 | - Log.i(TAG, "subscribe cost: " + (System.currentTimeMillis() - start)); | ||
171 | - emitter.onNext(activeCode); | ||
172 | - } | ||
173 | - }) | ||
174 | - .subscribeOn(Schedulers.io()) | ||
175 | - .observeOn(AndroidSchedulers.mainThread()) | ||
176 | - .subscribe(new Observer<Integer>() { | ||
177 | - @Override | ||
178 | - public void onSubscribe(Disposable d) { | ||
179 | - | ||
180 | - } | ||
181 | - | ||
182 | - @Override | ||
183 | - public void onNext(Integer activeCode) { | ||
184 | - if (activeCode == ErrorInfo.MOK) { | ||
185 | - showToast(getString(R.string.active_success)); | ||
186 | - } else if (activeCode == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) { | ||
187 | - showToast(getString(R.string.already_activated)); | ||
188 | - } else { | ||
189 | - showToast(getString(R.string.active_failed, activeCode)); | ||
190 | - } | ||
191 | - | ||
192 | - if (view != null) { | ||
193 | - view.setClickable(true); | ||
194 | - } | ||
195 | - ActiveFileInfo activeFileInfo = new ActiveFileInfo(); | ||
196 | - int res = FaceEngine.getActiveFileInfo(ChooseFunctionActivity.this, activeFileInfo); | ||
197 | - if (res == ErrorInfo.MOK) { | ||
198 | - Log.i(TAG, activeFileInfo.toString()); | ||
199 | - } | ||
200 | - | ||
201 | - } | ||
202 | - | ||
203 | - @Override | ||
204 | - public void onError(Throwable e) { | ||
205 | - showToast(e.getMessage()); | ||
206 | - if (view != null) { | ||
207 | - view.setClickable(true); | ||
208 | - } | ||
209 | - } | ||
210 | - | ||
211 | - @Override | ||
212 | - public void onComplete() { | ||
213 | - | ||
214 | - } | ||
215 | - }); | ||
216 | - | ||
217 | - } | ||
218 | - | ||
219 | - @Override | ||
220 | - void afterRequestPermission(int requestCode, boolean isAllGranted) { | ||
221 | - if (requestCode == ACTION_REQUEST_PERMISSIONS) { | ||
222 | - if (isAllGranted) { | ||
223 | - activeEngine(null); | ||
224 | - } else { | ||
225 | - showToast(getString(R.string.permission_denied)); | ||
226 | - } | ||
227 | - } | ||
228 | - } | ||
229 | - | ||
230 | - void checkLibraryAndJump(Class activityClass) { | ||
231 | - if (!libraryExists) { | ||
232 | - showToast(getString(R.string.library_not_found)); | ||
233 | - return; | ||
234 | - } | ||
235 | - startActivity(new Intent(this, activityClass)); | ||
236 | - } | ||
237 | - | ||
238 | - | ||
239 | - public void chooseDetectDegree(View view) { | ||
240 | - if (chooseDetectDegreeDialog == null) { | ||
241 | - chooseDetectDegreeDialog = new ChooseDetectDegreeDialog(); | ||
242 | - } | ||
243 | - if (chooseDetectDegreeDialog.isAdded()) { | ||
244 | - chooseDetectDegreeDialog.dismiss(); | ||
245 | - } | ||
246 | - chooseDetectDegreeDialog.show(getSupportFragmentManager(), ChooseDetectDegreeDialog.class.getSimpleName()); | ||
247 | - } | ||
248 | - | ||
249 | -} |
1 | -package com.arcsoft.arcfacedemo.activity; | ||
2 | - | ||
3 | -import android.Manifest; | ||
4 | -import android.content.pm.ActivityInfo; | ||
5 | -import android.graphics.Point; | ||
6 | -import android.hardware.Camera; | ||
7 | -import android.os.Build; | ||
8 | -import android.os.Bundle; | ||
9 | -import android.support.v4.app.ActivityCompat; | ||
10 | -import android.util.DisplayMetrics; | ||
11 | -import android.util.Log; | ||
12 | -import android.view.View; | ||
13 | -import android.view.ViewTreeObserver; | ||
14 | -import android.view.WindowManager; | ||
15 | - | ||
16 | -import com.arcsoft.arcfacedemo.R; | ||
17 | -import com.arcsoft.arcfacedemo.model.DrawInfo; | ||
18 | -import com.arcsoft.arcfacedemo.util.ConfigUtil; | ||
19 | -import com.arcsoft.arcfacedemo.util.DrawHelper; | ||
20 | -import com.arcsoft.arcfacedemo.util.camera.CameraHelper; | ||
21 | -import com.arcsoft.arcfacedemo.util.camera.CameraListener; | ||
22 | -import com.arcsoft.arcfacedemo.util.face.RecognizeColor; | ||
23 | -import com.arcsoft.arcfacedemo.widget.FaceRectView; | ||
24 | -import com.arcsoft.face.AgeInfo; | ||
25 | -import com.arcsoft.face.ErrorInfo; | ||
26 | -import com.arcsoft.face.Face3DAngle; | ||
27 | -import com.arcsoft.face.FaceEngine; | ||
28 | -import com.arcsoft.face.FaceInfo; | ||
29 | -import com.arcsoft.face.GenderInfo; | ||
30 | -import com.arcsoft.face.LivenessInfo; | ||
31 | -import com.arcsoft.face.enums.DetectMode; | ||
32 | - | ||
33 | -import java.util.ArrayList; | ||
34 | -import java.util.List; | ||
35 | - | ||
36 | -public class FaceAttrPreviewActivity extends BaseActivity implements ViewTreeObserver.OnGlobalLayoutListener { | ||
37 | - private static final String TAG = "FaceAttrPreviewActivity"; | ||
38 | - private CameraHelper cameraHelper; | ||
39 | - private DrawHelper drawHelper; | ||
40 | - private Camera.Size previewSize; | ||
41 | - private Integer rgbCameraId = Camera.CameraInfo.CAMERA_FACING_BACK; | ||
42 | - private FaceEngine faceEngine; | ||
43 | - private int afCode = -1; | ||
44 | - private int processMask = FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS; | ||
45 | - /** | ||
46 | - * 相机预览显示的控件,可为SurfaceView或TextureView | ||
47 | - */ | ||
48 | - private View previewView; | ||
49 | - private FaceRectView faceRectView; | ||
50 | - | ||
51 | - private static final int ACTION_REQUEST_PERMISSIONS = 0x001; | ||
52 | - /** | ||
53 | - * 所需的所有权限信息 | ||
54 | - */ | ||
55 | - private static final String[] NEEDED_PERMISSIONS = new String[]{ | ||
56 | - Manifest.permission.CAMERA, | ||
57 | - Manifest.permission.READ_PHONE_STATE | ||
58 | - }; | ||
59 | - | ||
60 | - @Override | ||
61 | - protected void onCreate(Bundle savedInstanceState) { | ||
62 | - super.onCreate(savedInstanceState); | ||
63 | - setContentView(R.layout.activity_face_attr_preview); | ||
64 | - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | ||
65 | - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | ||
66 | - WindowManager.LayoutParams attributes = getWindow().getAttributes(); | ||
67 | - attributes.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; | ||
68 | - getWindow().setAttributes(attributes); | ||
69 | - } | ||
70 | - | ||
71 | - // Activity启动后就锁定为启动时的方向 | ||
72 | - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); | ||
73 | - | ||
74 | - previewView = findViewById(R.id.texture_preview); | ||
75 | - faceRectView = findViewById(R.id.face_rect_view); | ||
76 | - //在布局结束后才做初始化操作 | ||
77 | - previewView.getViewTreeObserver().addOnGlobalLayoutListener(this); | ||
78 | - | ||
79 | - } | ||
80 | - | ||
81 | - private void initEngine() { | ||
82 | - faceEngine = new FaceEngine(); | ||
83 | - afCode = faceEngine.init(this, DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(this), | ||
84 | - 16, 20, FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_AGE | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_GENDER | FaceEngine.ASF_LIVENESS); | ||
85 | - Log.i(TAG, "initEngine: init: " + afCode); | ||
86 | - if (afCode != ErrorInfo.MOK) { | ||
87 | - showToast( getString(R.string.init_failed, afCode)); | ||
88 | - } | ||
89 | - } | ||
90 | - | ||
91 | - private void unInitEngine() { | ||
92 | - | ||
93 | - if (afCode == 0) { | ||
94 | - afCode = faceEngine.unInit(); | ||
95 | - Log.i(TAG, "unInitEngine: " + afCode); | ||
96 | - } | ||
97 | - } | ||
98 | - | ||
99 | - | ||
100 | - @Override | ||
101 | - protected void onDestroy() { | ||
102 | - if (cameraHelper != null) { | ||
103 | - cameraHelper.release(); | ||
104 | - cameraHelper = null; | ||
105 | - } | ||
106 | - unInitEngine(); | ||
107 | - super.onDestroy(); | ||
108 | - } | ||
109 | - | ||
110 | - private void initCamera() { | ||
111 | - DisplayMetrics metrics = new DisplayMetrics(); | ||
112 | - getWindowManager().getDefaultDisplay().getMetrics(metrics); | ||
113 | - | ||
114 | - CameraListener cameraListener = new CameraListener() { | ||
115 | - @Override | ||
116 | - public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) { | ||
117 | - Log.i(TAG, "onCameraOpened: " + cameraId + " " + displayOrientation + " " + isMirror); | ||
118 | - previewSize = camera.getParameters().getPreviewSize(); | ||
119 | - drawHelper = new DrawHelper(previewSize.width, previewSize.height, previewView.getWidth(), previewView.getHeight(), displayOrientation | ||
120 | - , cameraId, isMirror, false, true); | ||
121 | - } | ||
122 | - | ||
123 | - | ||
124 | - @Override | ||
125 | - public void onPreview(byte[] nv21, Camera camera) { | ||
126 | - | ||
127 | - if (faceRectView != null) { | ||
128 | - faceRectView.clearFaceInfo(); | ||
129 | - } | ||
130 | - List<FaceInfo> faceInfoList = new ArrayList<>(); | ||
131 | -// long start = System.currentTimeMillis(); | ||
132 | - int code = faceEngine.detectFaces(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList); | ||
133 | - if (code == ErrorInfo.MOK && faceInfoList.size() > 0) { | ||
134 | - code = faceEngine.process(nv21, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, faceInfoList, processMask); | ||
135 | - if (code != ErrorInfo.MOK) { | ||
136 | - return; | ||
137 | - } | ||
138 | - } else { | ||
139 | - return; | ||
140 | - } | ||
141 | - | ||
142 | - List<AgeInfo> ageInfoList = new ArrayList<>(); | ||
143 | - List<GenderInfo> genderInfoList = new ArrayList<>(); | ||
144 | - List<Face3DAngle> face3DAngleList = new ArrayList<>(); | ||
145 | - List<LivenessInfo> faceLivenessInfoList = new ArrayList<>(); | ||
146 | - int ageCode = faceEngine.getAge(ageInfoList); | ||
147 | - int genderCode = faceEngine.getGender(genderInfoList); | ||
148 | - int face3DAngleCode = faceEngine.getFace3DAngle(face3DAngleList); | ||
149 | - int livenessCode = faceEngine.getLiveness(faceLivenessInfoList); | ||
150 | - | ||
151 | - // 有其中一个的错误码不为ErrorInfo.MOK,return | ||
152 | - if ((ageCode | genderCode | face3DAngleCode | livenessCode) != ErrorInfo.MOK) { | ||
153 | - return; | ||
154 | - } | ||
155 | - if (faceRectView != null && drawHelper != null) { | ||
156 | - List<DrawInfo> drawInfoList = new ArrayList<>(); | ||
157 | - for (int i = 0; i < faceInfoList.size(); i++) { | ||
158 | - drawInfoList.add(new DrawInfo(drawHelper.adjustRect(faceInfoList.get(i).getRect()), genderInfoList.get(i).getGender(), ageInfoList.get(i).getAge(), faceLivenessInfoList.get(i).getLiveness(), RecognizeColor.COLOR_UNKNOWN, null)); | ||
159 | - } | ||
160 | - drawHelper.draw(faceRectView, drawInfoList); | ||
161 | - } | ||
162 | - } | ||
163 | - | ||
164 | - @Override | ||
165 | - public void onCameraClosed() { | ||
166 | - Log.i(TAG, "onCameraClosed: "); | ||
167 | - } | ||
168 | - | ||
169 | - @Override | ||
170 | - public void onCameraError(Exception e) { | ||
171 | - Log.i(TAG, "onCameraError: " + e.getMessage()); | ||
172 | - } | ||
173 | - | ||
174 | - @Override | ||
175 | - public void onCameraConfigurationChanged(int cameraID, int displayOrientation) { | ||
176 | - if (drawHelper != null) { | ||
177 | - drawHelper.setCameraDisplayOrientation(displayOrientation); | ||
178 | - } | ||
179 | - Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation); | ||
180 | - } | ||
181 | - }; | ||
182 | - cameraHelper = new CameraHelper.Builder() | ||
183 | - .previewViewSize(new Point(previewView.getMeasuredWidth(), previewView.getMeasuredHeight())) | ||
184 | - .rotation(getWindowManager().getDefaultDisplay().getRotation()) | ||
185 | - .specificCameraId(rgbCameraId != null ? rgbCameraId : Camera.CameraInfo.CAMERA_FACING_FRONT) | ||
186 | - .isMirror(false) | ||
187 | - .previewOn(previewView) | ||
188 | - .cameraListener(cameraListener) | ||
189 | - .build(); | ||
190 | - cameraHelper.init(); | ||
191 | - cameraHelper.start(); | ||
192 | - } | ||
193 | - | ||
194 | - @Override | ||
195 | - void afterRequestPermission(int requestCode, boolean isAllGranted) { | ||
196 | - if (requestCode == ACTION_REQUEST_PERMISSIONS) { | ||
197 | - if (isAllGranted) { | ||
198 | - initEngine(); | ||
199 | - initCamera(); | ||
200 | - } else { | ||
201 | - showToast(getString( R.string.permission_denied)); | ||
202 | - } | ||
203 | - } | ||
204 | - } | ||
205 | - | ||
206 | - /** | ||
207 | - * 在{@link #previewView}第一次布局完成后,去除该监听,并且进行引擎和相机的初始化 | ||
208 | - */ | ||
209 | - @Override | ||
210 | - public void onGlobalLayout() { | ||
211 | - previewView.getViewTreeObserver().removeOnGlobalLayoutListener(this); | ||
212 | - if (!checkPermissions(NEEDED_PERMISSIONS)) { | ||
213 | - ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS); | ||
214 | - } else { | ||
215 | - initEngine(); | ||
216 | - initCamera(); | ||
217 | - } | ||
218 | - } | ||
219 | - | ||
220 | - | ||
221 | - /** | ||
222 | - * 切换相机。注意:若切换相机发现检测不到人脸,则极有可能是检测角度导致的,需要销毁引擎重新创建或者在设置界面修改配置的检测角度 | ||
223 | - * | ||
224 | - * @param view | ||
225 | - */ | ||
226 | - public void switchCamera(View view) { | ||
227 | - if (cameraHelper != null) { | ||
228 | - boolean success = cameraHelper.switchCamera(); | ||
229 | - if (!success) { | ||
230 | - showToast(getString(R.string.switch_camera_failed)); | ||
231 | - } else { | ||
232 | - showLongToast(getString(R.string.notice_change_detect_degree)); | ||
233 | - } | ||
234 | - } | ||
235 | - } | ||
236 | -} |
1 | -package com.arcsoft.arcfacedemo.activity; | ||
2 | - | ||
3 | -import android.Manifest; | ||
4 | -import android.content.DialogInterface; | ||
5 | -import android.graphics.Bitmap; | ||
6 | -import android.graphics.BitmapFactory; | ||
7 | -import android.os.Environment; | ||
8 | -import android.support.v4.app.ActivityCompat; | ||
9 | -import android.support.v7.app.AlertDialog; | ||
10 | -import android.os.Bundle; | ||
11 | -import android.util.Log; | ||
12 | -import android.view.View; | ||
13 | -import android.view.WindowManager; | ||
14 | -import android.widget.TextView; | ||
15 | - | ||
16 | -import com.arcsoft.arcfacedemo.R; | ||
17 | -import com.arcsoft.arcfacedemo.widget.ProgressDialog; | ||
18 | -import com.arcsoft.arcfacedemo.faceserver.FaceServer; | ||
19 | -import com.arcsoft.imageutil.ArcSoftImageFormat; | ||
20 | -import com.arcsoft.imageutil.ArcSoftImageUtil; | ||
21 | -import com.arcsoft.imageutil.ArcSoftImageUtilError; | ||
22 | - | ||
23 | -import java.io.File; | ||
24 | -import java.io.FilenameFilter; | ||
25 | -import java.util.concurrent.ExecutorService; | ||
26 | -import java.util.concurrent.Executors; | ||
27 | - | ||
28 | -/** | ||
29 | - * 批量注册页面 | ||
30 | - */ | ||
31 | -public class FaceManageActivity extends BaseActivity { | ||
32 | - //注册图所在的目录 | ||
33 | - private static final String ROOT_DIR = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "arcfacedemo"; | ||
34 | - private static final String REGISTER_DIR = ROOT_DIR + File.separator + "register"; | ||
35 | - private static final String REGISTER_FAILED_DIR = ROOT_DIR + File.separator + "failed"; | ||
36 | - private ExecutorService executorService; | ||
37 | - | ||
38 | - private TextView tvNotificationRegisterResult; | ||
39 | - | ||
40 | - ProgressDialog progressDialog = null; | ||
41 | - private static final int ACTION_REQUEST_PERMISSIONS = 0x001; | ||
42 | - private static String[] NEEDED_PERMISSIONS = new String[]{ | ||
43 | - Manifest.permission.READ_EXTERNAL_STORAGE, | ||
44 | - Manifest.permission.WRITE_EXTERNAL_STORAGE | ||
45 | - }; | ||
46 | - | ||
47 | - @Override | ||
48 | - protected void onCreate(Bundle savedInstanceState) { | ||
49 | - super.onCreate(savedInstanceState); | ||
50 | - setContentView(R.layout.activity_face_manage); | ||
51 | - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | ||
52 | - executorService = Executors.newSingleThreadExecutor(); | ||
53 | - tvNotificationRegisterResult = findViewById(R.id.notification_register_result); | ||
54 | - progressDialog = new ProgressDialog(this); | ||
55 | - FaceServer.getInstance().init(this); | ||
56 | - } | ||
57 | - | ||
58 | - @Override | ||
59 | - protected void onDestroy() { | ||
60 | - if (executorService != null && !executorService.isShutdown()) { | ||
61 | - executorService.shutdownNow(); | ||
62 | - } | ||
63 | - if (progressDialog != null && progressDialog.isShowing()) { | ||
64 | - progressDialog.dismiss(); | ||
65 | - } | ||
66 | - | ||
67 | - FaceServer.getInstance().unInit(); | ||
68 | - super.onDestroy(); | ||
69 | - } | ||
70 | - | ||
71 | - public void batchRegister(View view) { | ||
72 | - if (checkPermissions(NEEDED_PERMISSIONS)) { | ||
73 | - doRegister(); | ||
74 | - } else { | ||
75 | - ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS); | ||
76 | - } | ||
77 | - } | ||
78 | - | ||
79 | - private void doRegister() { | ||
80 | - File dir = new File(REGISTER_DIR); | ||
81 | - if (!dir.exists()) { | ||
82 | - showToast(getString(R.string.batch_process_path_is_not_exists, REGISTER_DIR)); | ||
83 | - return; | ||
84 | - } | ||
85 | - if (!dir.isDirectory()) { | ||
86 | - showToast(getString(R.string.batch_process_path_is_not_dir, REGISTER_DIR)); | ||
87 | - return; | ||
88 | - } | ||
89 | - final File[] jpgFiles = dir.listFiles(new FilenameFilter() { | ||
90 | - @Override | ||
91 | - public boolean accept(File dir, String name) { | ||
92 | - return name.endsWith(FaceServer.IMG_SUFFIX); | ||
93 | - } | ||
94 | - }); | ||
95 | - executorService.execute(new Runnable() { | ||
96 | - @Override | ||
97 | - public void run() { | ||
98 | - final int totalCount = jpgFiles.length; | ||
99 | - | ||
100 | - int successCount = 0; | ||
101 | - runOnUiThread(new Runnable() { | ||
102 | - @Override | ||
103 | - public void run() { | ||
104 | - progressDialog.setMaxProgress(totalCount); | ||
105 | - progressDialog.show(); | ||
106 | - tvNotificationRegisterResult.setText(""); | ||
107 | - tvNotificationRegisterResult.append(getString(R.string.batch_process_processing_please_wait)); | ||
108 | - } | ||
109 | - }); | ||
110 | - for (int i = 0; i < totalCount; i++) { | ||
111 | - final int finalI = i; | ||
112 | - runOnUiThread(new Runnable() { | ||
113 | - @Override | ||
114 | - public void run() { | ||
115 | - if (progressDialog != null) { | ||
116 | - progressDialog.refreshProgress(finalI); | ||
117 | - } | ||
118 | - } | ||
119 | - }); | ||
120 | - final File jpgFile = jpgFiles[i]; | ||
121 | - Bitmap bitmap = BitmapFactory.decodeFile(jpgFile.getAbsolutePath()); | ||
122 | - if (bitmap == null) { | ||
123 | - File failedFile = new File(REGISTER_FAILED_DIR + File.separator + jpgFile.getName()); | ||
124 | - if (!failedFile.getParentFile().exists()) { | ||
125 | - failedFile.getParentFile().mkdirs(); | ||
126 | - } | ||
127 | - jpgFile.renameTo(failedFile); | ||
128 | - continue; | ||
129 | - } | ||
130 | - bitmap = ArcSoftImageUtil.getAlignedBitmap(bitmap, true); | ||
131 | - if (bitmap == null) { | ||
132 | - File failedFile = new File(REGISTER_FAILED_DIR + File.separator + jpgFile.getName()); | ||
133 | - if (!failedFile.getParentFile().exists()) { | ||
134 | - failedFile.getParentFile().mkdirs(); | ||
135 | - } | ||
136 | - jpgFile.renameTo(failedFile); | ||
137 | - continue; | ||
138 | - } | ||
139 | - byte[] bgr24 = ArcSoftImageUtil.createImageData(bitmap.getWidth(), bitmap.getHeight(), ArcSoftImageFormat.BGR24); | ||
140 | - int transformCode = ArcSoftImageUtil.bitmapToImageData(bitmap, bgr24, ArcSoftImageFormat.BGR24); | ||
141 | - if (transformCode != ArcSoftImageUtilError.CODE_SUCCESS) { | ||
142 | - runOnUiThread(new Runnable() { | ||
143 | - @Override | ||
144 | - public void run() { | ||
145 | - progressDialog.dismiss(); | ||
146 | - tvNotificationRegisterResult.append(""); | ||
147 | - } | ||
148 | - }); | ||
149 | - return; | ||
150 | - } | ||
151 | - boolean success = FaceServer.getInstance().registerBgr24(FaceManageActivity.this, bgr24, bitmap.getWidth(), bitmap.getHeight(), | ||
152 | - jpgFile.getName().substring(0, jpgFile.getName().lastIndexOf("."))); | ||
153 | - if (!success) { | ||
154 | - File failedFile = new File(REGISTER_FAILED_DIR + File.separator + jpgFile.getName()); | ||
155 | - if (!failedFile.getParentFile().exists()) { | ||
156 | - failedFile.getParentFile().mkdirs(); | ||
157 | - } | ||
158 | - jpgFile.renameTo(failedFile); | ||
159 | - } else { | ||
160 | - successCount++; | ||
161 | - } | ||
162 | - } | ||
163 | - final int finalSuccessCount = successCount; | ||
164 | - runOnUiThread(new Runnable() { | ||
165 | - @Override | ||
166 | - public void run() { | ||
167 | - progressDialog.dismiss(); | ||
168 | - tvNotificationRegisterResult.append(getString(R.string.batch_process_finished_info, totalCount, finalSuccessCount, totalCount - finalSuccessCount, REGISTER_FAILED_DIR)); | ||
169 | - } | ||
170 | - }); | ||
171 | - Log.i(FaceManageActivity.class.getSimpleName(), "run: " + executorService.isShutdown()); | ||
172 | - } | ||
173 | - }); | ||
174 | - } | ||
175 | - | ||
176 | - @Override | ||
177 | - void afterRequestPermission(int requestCode, boolean isAllGranted) { | ||
178 | - if (requestCode == ACTION_REQUEST_PERMISSIONS) { | ||
179 | - if (isAllGranted) { | ||
180 | - doRegister(); | ||
181 | - } else { | ||
182 | - showToast(getString(R.string.permission_denied)); | ||
183 | - } | ||
184 | - } | ||
185 | - } | ||
186 | - | ||
187 | - public void clearFaces(View view) { | ||
188 | - int faceNum = FaceServer.getInstance().getFaceNumber(this); | ||
189 | - if (faceNum == 0) { | ||
190 | - showToast(getString(R.string.batch_process_no_face_need_to_delete)); | ||
191 | - } else { | ||
192 | - AlertDialog dialog = new AlertDialog.Builder(this) | ||
193 | - .setTitle(R.string.batch_process_notification) | ||
194 | - .setMessage(getString(R.string.batch_process_confirm_delete, faceNum)) | ||
195 | - .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { | ||
196 | - @Override | ||
197 | - public void onClick(DialogInterface dialog, int which) { | ||
198 | - int deleteCount = FaceServer.getInstance().clearAllFaces(FaceManageActivity.this); | ||
199 | - showToast(deleteCount + " faces cleared!"); | ||
200 | - } | ||
201 | - }) | ||
202 | - .setNegativeButton(R.string.cancel, null) | ||
203 | - .create(); | ||
204 | - dialog.show(); | ||
205 | - } | ||
206 | - } | ||
207 | -} |
arcface/src/main/java/com/arcsoft/arcfacedemo/activity/IrRegisterAndRecognizeActivity.java
已删除
100644 → 0
1 | -package com.arcsoft.arcfacedemo.activity; | ||
2 | - | ||
3 | -import android.Manifest; | ||
4 | -import android.content.pm.ActivityInfo; | ||
5 | -import android.graphics.Color; | ||
6 | -import android.graphics.Point; | ||
7 | -import android.graphics.Rect; | ||
8 | -import android.hardware.Camera; | ||
9 | -import android.os.Build; | ||
10 | -import android.os.Bundle; | ||
11 | -import android.support.annotation.Nullable; | ||
12 | -import android.support.v4.app.ActivityCompat; | ||
13 | -import android.support.v7.widget.DefaultItemAnimator; | ||
14 | -import android.support.v7.widget.GridLayoutManager; | ||
15 | -import android.support.v7.widget.RecyclerView; | ||
16 | -import android.util.DisplayMetrics; | ||
17 | -import android.util.Log; | ||
18 | -import android.view.View; | ||
19 | -import android.view.ViewGroup; | ||
20 | -import android.view.ViewTreeObserver; | ||
21 | -import android.view.WindowManager; | ||
22 | -import android.widget.CompoundButton; | ||
23 | -import android.widget.FrameLayout; | ||
24 | -import android.widget.Switch; | ||
25 | -import android.widget.TextView; | ||
26 | - | ||
27 | -import com.arcsoft.arcfacedemo.R; | ||
28 | -import com.arcsoft.arcfacedemo.common.Constants; | ||
29 | -import com.arcsoft.arcfacedemo.faceserver.CompareResult; | ||
30 | -import com.arcsoft.arcfacedemo.faceserver.FaceServer; | ||
31 | -import com.arcsoft.arcfacedemo.model.DrawInfo; | ||
32 | -import com.arcsoft.arcfacedemo.model.FacePreviewInfo; | ||
33 | -import com.arcsoft.arcfacedemo.util.ConfigUtil; | ||
34 | -import com.arcsoft.arcfacedemo.util.DrawHelper; | ||
35 | -import com.arcsoft.arcfacedemo.util.camera.CameraListener; | ||
36 | -import com.arcsoft.arcfacedemo.util.camera.DualCameraHelper; | ||
37 | -import com.arcsoft.arcfacedemo.util.face.FaceHelper; | ||
38 | -import com.arcsoft.arcfacedemo.util.face.FaceListener; | ||
39 | -import com.arcsoft.arcfacedemo.util.face.LivenessType; | ||
40 | -import com.arcsoft.arcfacedemo.util.face.RecognizeColor; | ||
41 | -import com.arcsoft.arcfacedemo.util.face.RequestFeatureStatus; | ||
42 | -import com.arcsoft.arcfacedemo.util.face.RequestLivenessStatus; | ||
43 | -import com.arcsoft.arcfacedemo.widget.FaceRectView; | ||
44 | -import com.arcsoft.arcfacedemo.widget.FaceSearchResultAdapter; | ||
45 | -import com.arcsoft.face.AgeInfo; | ||
46 | -import com.arcsoft.face.ErrorInfo; | ||
47 | -import com.arcsoft.face.FaceEngine; | ||
48 | -import com.arcsoft.face.FaceFeature; | ||
49 | -import com.arcsoft.face.FaceInfo; | ||
50 | -import com.arcsoft.face.GenderInfo; | ||
51 | -import com.arcsoft.face.LivenessInfo; | ||
52 | -import com.arcsoft.face.VersionInfo; | ||
53 | -import com.arcsoft.face.enums.DetectFaceOrientPriority; | ||
54 | -import com.arcsoft.face.enums.DetectMode; | ||
55 | - | ||
56 | -import java.util.ArrayList; | ||
57 | -import java.util.Enumeration; | ||
58 | -import java.util.List; | ||
59 | -import java.util.Map; | ||
60 | -import java.util.concurrent.ConcurrentHashMap; | ||
61 | -import java.util.concurrent.TimeUnit; | ||
62 | - | ||
63 | -import io.reactivex.Observable; | ||
64 | -import io.reactivex.ObservableEmitter; | ||
65 | -import io.reactivex.ObservableOnSubscribe; | ||
66 | -import io.reactivex.Observer; | ||
67 | -import io.reactivex.android.schedulers.AndroidSchedulers; | ||
68 | -import io.reactivex.disposables.CompositeDisposable; | ||
69 | -import io.reactivex.disposables.Disposable; | ||
70 | -import io.reactivex.schedulers.Schedulers; | ||
71 | - | ||
72 | -/** | ||
73 | - * 1.活体检测使用IR摄像头数据,其他都使用RGB摄像头数据 | ||
74 | - * <p> | ||
75 | - * 2.本界面仅实现IR数据和RGB数据预览大小相同且画面十分接近的情况(RGB数据和IR数据无旋转、镜像的关系)的活体检测, | ||
76 | - * <p> | ||
77 | - * 3.若IR数据和RGB数据预览大小不同或两者成像的人脸位置差别很大,需要自己实现人脸框的调整方案。 | ||
78 | - * <p> | ||
79 | - * 4.由于不同的厂商对IR Camera和RGB Camera的CameraId设置可能会有所不同,开发者可能需要根据实际情况修改 | ||
80 | - * {@link IrRegisterAndRecognizeActivity#cameraRgbId}和 | ||
81 | - * {@link IrRegisterAndRecognizeActivity#cameraIrId}的值 | ||
82 | - * <p> | ||
83 | - * 5.由于一般情况下android设备的前置摄像头,即cameraId为{@link Camera.CameraInfo#CAMERA_FACING_FRONT}的摄像头在打开后会自动被镜像预览。 | ||
84 | - * 为了便于开发者们更直观地了解两个摄像头成像的关系,实现人脸框的调整方案,本demo对cameraId为{@link Camera.CameraInfo#CAMERA_FACING_FRONT} | ||
85 | - * 的预览画面做了再次镜像的处理,也就是恢复为原画面。 | ||
86 | - */ | ||
87 | -public class IrRegisterAndRecognizeActivity extends BaseActivity implements ViewTreeObserver.OnGlobalLayoutListener { | ||
88 | - private static final String TAG = "IrRegisterAndRecognize"; | ||
89 | - private static final int MAX_DETECT_NUM = 10; | ||
90 | - /** | ||
91 | - * 当FR成功,活体未成功时,FR等待活体的时间 | ||
92 | - */ | ||
93 | - private static final int WAIT_LIVENESS_INTERVAL = 50; | ||
94 | - /** | ||
95 | - * 失败重试间隔时间(ms) | ||
96 | - */ | ||
97 | - private static final long FAIL_RETRY_INTERVAL = 1000; | ||
98 | - /** | ||
99 | - * 出错重试最大次数 | ||
100 | - */ | ||
101 | - private static final int MAX_RETRY_TIME = 3; | ||
102 | - | ||
103 | - private DualCameraHelper cameraHelper; | ||
104 | - private DualCameraHelper cameraHelperIr; | ||
105 | - private DrawHelper drawHelperRgb; | ||
106 | - private DrawHelper drawHelperIr; | ||
107 | - private Camera.Size previewSize; | ||
108 | - private Camera.Size previewSizeIr; | ||
109 | - | ||
110 | - /** | ||
111 | - * RGB摄像头和IR摄像头的ID,若和实际不符,需要修改以下两个值。 | ||
112 | - * 同时,可能需要修改默认的VIDEO模式人脸检测角度 | ||
113 | - */ | ||
114 | - private Integer cameraRgbId = Camera.CameraInfo.CAMERA_FACING_BACK; | ||
115 | - private Integer cameraIrId = Camera.CameraInfo.CAMERA_FACING_FRONT; | ||
116 | - | ||
117 | - private FaceEngine ftEngine; | ||
118 | - private FaceEngine frEngine; | ||
119 | - private FaceEngine flEngine; | ||
120 | - | ||
121 | - private int ftInitCode = -1; | ||
122 | - private int frInitCode = -1; | ||
123 | - private int flInitCode = -1; | ||
124 | - | ||
125 | - private FaceHelper faceHelperIr; | ||
126 | - private List<CompareResult> compareResultList; | ||
127 | - private FaceSearchResultAdapter adapter; | ||
128 | - /** | ||
129 | - * 活体检测的开关 | ||
130 | - */ | ||
131 | - private boolean livenessDetect = true; | ||
132 | - | ||
133 | - /** | ||
134 | - * 注册人脸状态码,准备注册 | ||
135 | - */ | ||
136 | - private static final int REGISTER_STATUS_READY = 0; | ||
137 | - /** | ||
138 | - * 注册人脸状态码,注册中 | ||
139 | - */ | ||
140 | - private static final int REGISTER_STATUS_PROCESSING = 1; | ||
141 | - /** | ||
142 | - * 注册人脸状态码,注册结束(无论成功失败) | ||
143 | - */ | ||
144 | - private static final int REGISTER_STATUS_DONE = 2; | ||
145 | - | ||
146 | - private int registerStatus = REGISTER_STATUS_DONE; | ||
147 | - | ||
148 | - | ||
149 | - /** | ||
150 | - * 用于记录人脸识别相关状态 | ||
151 | - */ | ||
152 | - private ConcurrentHashMap<Integer, Integer> requestFeatureStatusMap = new ConcurrentHashMap<>(); | ||
153 | - /** | ||
154 | - * 用于记录人脸特征提取出错重试次数 | ||
155 | - */ | ||
156 | - private ConcurrentHashMap<Integer, Integer> extractErrorRetryMap = new ConcurrentHashMap<>(); | ||
157 | - /** | ||
158 | - * 用于存储活体值 | ||
159 | - */ | ||
160 | - private ConcurrentHashMap<Integer, Integer> livenessMap = new ConcurrentHashMap<>(); | ||
161 | - /** | ||
162 | - * 用于存储活体检测出错重试次数 | ||
163 | - */ | ||
164 | - private ConcurrentHashMap<Integer, Integer> livenessErrorRetryMap = new ConcurrentHashMap<>(); | ||
165 | - | ||
166 | - private CompositeDisposable getFeatureDelayedDisposables = new CompositeDisposable(); | ||
167 | - private CompositeDisposable delayFaceTaskCompositeDisposable = new CompositeDisposable(); | ||
168 | - /** | ||
169 | - * 相机预览显示的控件,可为SurfaceView或TextureView | ||
170 | - */ | ||
171 | - private View previewViewRgb; | ||
172 | - private View previewViewIr; | ||
173 | - /** | ||
174 | - * 绘制人脸框的控件 | ||
175 | - */ | ||
176 | - private FaceRectView faceRectView; | ||
177 | - private FaceRectView faceRectViewIr; | ||
178 | - | ||
179 | - private Switch switchLivenessDetect; | ||
180 | - | ||
181 | - private static final int ACTION_REQUEST_PERMISSIONS = 0x001; | ||
182 | - | ||
183 | - /** | ||
184 | - * 识别阈值 | ||
185 | - */ | ||
186 | - private static final float SIMILAR_THRESHOLD = 0.8F; | ||
187 | - | ||
188 | - /** | ||
189 | - * 所需的所有权限信息 | ||
190 | - */ | ||
191 | - private static final String[] NEEDED_PERMISSIONS = new String[]{ | ||
192 | - Manifest.permission.CAMERA, | ||
193 | - Manifest.permission.READ_PHONE_STATE, | ||
194 | - Manifest.permission.READ_EXTERNAL_STORAGE, | ||
195 | - Manifest.permission.WRITE_EXTERNAL_STORAGE | ||
196 | - }; | ||
197 | - | ||
198 | - private volatile byte[] rgbData; | ||
199 | - private volatile byte[] irData; | ||
200 | - | ||
201 | - @Override | ||
202 | - protected void onCreate(Bundle savedInstanceState) { | ||
203 | - super.onCreate(savedInstanceState); | ||
204 | - setContentView(R.layout.activity_register_and_recognize_ir); | ||
205 | - | ||
206 | - //保持亮屏 | ||
207 | - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | ||
208 | - | ||
209 | - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | ||
210 | - WindowManager.LayoutParams attributes = getWindow().getAttributes(); | ||
211 | - attributes.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; | ||
212 | - getWindow().setAttributes(attributes); | ||
213 | - } | ||
214 | - | ||
215 | - // Activity启动后就锁定为启动时的方向 | ||
216 | - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); | ||
217 | - //本地人脸库初始化 | ||
218 | - FaceServer.getInstance().init(this); | ||
219 | - | ||
220 | - initView(); | ||
221 | - } | ||
222 | - | ||
223 | - private void initView() { | ||
224 | - previewViewRgb = findViewById(R.id.dual_camera_texture_preview_rgb); | ||
225 | - //在布局结束后才做初始化操作 | ||
226 | - previewViewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this); | ||
227 | - previewViewIr = findViewById(R.id.dual_camera_texture_previewIr); | ||
228 | - faceRectView = findViewById(R.id.dual_camera_face_rect_view); | ||
229 | - faceRectViewIr = findViewById(R.id.dual_camera_face_rect_viewIr); | ||
230 | - switchLivenessDetect = findViewById(R.id.dual_camera_switch_liveness_detect); | ||
231 | - | ||
232 | - switchLivenessDetect.setChecked(livenessDetect); | ||
233 | - switchLivenessDetect.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { | ||
234 | - @Override | ||
235 | - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { | ||
236 | - livenessDetect = isChecked; | ||
237 | - } | ||
238 | - }); | ||
239 | - RecyclerView recyclerShowFaceInfo = findViewById(R.id.dual_camera_recycler_view_person); | ||
240 | - compareResultList = new ArrayList<>(); | ||
241 | - adapter = new FaceSearchResultAdapter(compareResultList, this); | ||
242 | - recyclerShowFaceInfo.setAdapter(adapter); | ||
243 | - DisplayMetrics dm = getResources().getDisplayMetrics(); | ||
244 | - int spanCount = (int) (dm.widthPixels / (getResources().getDisplayMetrics().density * 100 + 0.5f)); | ||
245 | - recyclerShowFaceInfo.setLayoutManager(new GridLayoutManager(this, spanCount)); | ||
246 | - recyclerShowFaceInfo.setItemAnimator(new DefaultItemAnimator()); | ||
247 | - } | ||
248 | - | ||
249 | - | ||
250 | - /** | ||
251 | - * 初始化引擎 | ||
252 | - */ | ||
253 | - private void initEngine() { | ||
254 | - ftEngine = new FaceEngine(); | ||
255 | - ftInitCode = ftEngine.init(this, DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(this), | ||
256 | - 16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_DETECT); | ||
257 | - | ||
258 | - frEngine = new FaceEngine(); | ||
259 | - frInitCode = frEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY, | ||
260 | - 16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_RECOGNITION); | ||
261 | - | ||
262 | - flEngine = new FaceEngine(); | ||
263 | - flInitCode = flEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY, | ||
264 | - 16, MAX_DETECT_NUM, FaceEngine.ASF_IR_LIVENESS); | ||
265 | - | ||
266 | - Log.i(TAG, "initEngine: init: " + ftInitCode); | ||
267 | - | ||
268 | - if (ftInitCode != ErrorInfo.MOK) { | ||
269 | - String error = getString(R.string.specific_engine_init_failed, "ftEngine", ftInitCode); | ||
270 | - Log.i(TAG, "initEngine: " + error); | ||
271 | - showToast(error); | ||
272 | - } | ||
273 | - if (frInitCode != ErrorInfo.MOK) { | ||
274 | - String error = getString(R.string.specific_engine_init_failed, "frEngine", ftInitCode); | ||
275 | - Log.i(TAG, "initEngine: " + error); | ||
276 | - showToast(error); | ||
277 | - } | ||
278 | - if (flInitCode != ErrorInfo.MOK) { | ||
279 | - String error = getString(R.string.specific_engine_init_failed, "flEngine", ftInitCode); | ||
280 | - Log.i(TAG, "initEngine: " + error); | ||
281 | - showToast(error); | ||
282 | - } | ||
283 | - } | ||
284 | - | ||
285 | - /** | ||
286 | - * 销毁引擎,faceHelperIr中可能会有特征提取耗时操作仍在执行,加锁防止crash | ||
287 | - */ | ||
288 | - private void unInitEngine() { | ||
289 | - if (ftInitCode == ErrorInfo.MOK && ftEngine != null) { | ||
290 | - synchronized (ftEngine) { | ||
291 | - int ftUnInitCode = ftEngine.unInit(); | ||
292 | - Log.i(TAG, "unInitEngine: " + ftUnInitCode); | ||
293 | - } | ||
294 | - } | ||
295 | - if (frInitCode == ErrorInfo.MOK && frEngine != null) { | ||
296 | - synchronized (frEngine) { | ||
297 | - int frUnInitCode = frEngine.unInit(); | ||
298 | - Log.i(TAG, "unInitEngine: " + frUnInitCode); | ||
299 | - } | ||
300 | - } | ||
301 | - if (flInitCode == ErrorInfo.MOK && flEngine != null) { | ||
302 | - synchronized (flEngine) { | ||
303 | - int flUnInitCode = flEngine.unInit(); | ||
304 | - Log.i(TAG, "unInitEngine: " + flUnInitCode); | ||
305 | - } | ||
306 | - } | ||
307 | - } | ||
308 | - | ||
309 | - @Override | ||
310 | - protected void onResume() { | ||
311 | - super.onResume(); | ||
312 | - try { | ||
313 | - if (cameraHelper != null) { | ||
314 | - cameraHelper.start(); | ||
315 | - } | ||
316 | - if (cameraHelperIr != null) { | ||
317 | - cameraHelperIr.start(); | ||
318 | - } | ||
319 | - } catch (RuntimeException e) { | ||
320 | - showToast(e.getMessage() + getString(R.string.camera_error_notice)); | ||
321 | - } | ||
322 | - } | ||
323 | - | ||
324 | - @Override | ||
325 | - protected void onPause() { | ||
326 | - if (cameraHelper != null) { | ||
327 | - cameraHelper.stop(); | ||
328 | - } | ||
329 | - if (cameraHelperIr != null) { | ||
330 | - cameraHelperIr.stop(); | ||
331 | - } | ||
332 | - super.onPause(); | ||
333 | - } | ||
334 | - | ||
335 | - @Override | ||
336 | - protected void onDestroy() { | ||
337 | - | ||
338 | - if (cameraHelper != null) { | ||
339 | - cameraHelper.release(); | ||
340 | - cameraHelper = null; | ||
341 | - } | ||
342 | - if (cameraHelperIr != null) { | ||
343 | - cameraHelperIr.release(); | ||
344 | - cameraHelperIr = null; | ||
345 | - } | ||
346 | - | ||
347 | - unInitEngine(); | ||
348 | - | ||
349 | - if (getFeatureDelayedDisposables != null) { | ||
350 | - getFeatureDelayedDisposables.clear(); | ||
351 | - } | ||
352 | - if (delayFaceTaskCompositeDisposable != null) { | ||
353 | - delayFaceTaskCompositeDisposable.clear(); | ||
354 | - } | ||
355 | - | ||
356 | - if (faceHelperIr != null) { | ||
357 | - ConfigUtil.setTrackedFaceCount(this, faceHelperIr.getTrackedFaceCount()); | ||
358 | - faceHelperIr.release(); | ||
359 | - faceHelperIr = null; | ||
360 | - } | ||
361 | - | ||
362 | - FaceServer.getInstance().unInit(); | ||
363 | - super.onDestroy(); | ||
364 | - } | ||
365 | - | ||
366 | - private void initRgbCamera() { | ||
367 | - DisplayMetrics metrics = new DisplayMetrics(); | ||
368 | - getWindowManager().getDefaultDisplay().getMetrics(metrics); | ||
369 | - final FaceListener faceListener = new FaceListener() { | ||
370 | - @Override | ||
371 | - public void onFail(Exception e) { | ||
372 | - Log.e(TAG, "onFail: " + e.getMessage()); | ||
373 | - } | ||
374 | - | ||
375 | - //请求FR的回调 | ||
376 | - @Override | ||
377 | - public void onFaceFeatureInfoGet(@Nullable final FaceFeature faceFeature, final Integer requestId, final Integer errorCode) { | ||
378 | - //FR成功 | ||
379 | - if (faceFeature != null) { | ||
380 | -// Log.i(TAG, "onPreview: fr end = " + System.currentTimeMillis() + " trackId = " + requestId); | ||
381 | - Integer liveness = livenessMap.get(requestId); | ||
382 | - //不做活体检测的情况,直接搜索 | ||
383 | - if (!livenessDetect) { | ||
384 | - searchFace(faceFeature, requestId); | ||
385 | - } | ||
386 | - //活体检测通过,搜索特征 | ||
387 | - else if (liveness != null && liveness == LivenessInfo.ALIVE) { | ||
388 | - searchFace(faceFeature, requestId); | ||
389 | - } | ||
390 | - //活体检测未出结果,或者非活体,延迟执行该函数 | ||
391 | - else { | ||
392 | - | ||
393 | - if (requestFeatureStatusMap.containsKey(requestId)) { | ||
394 | - Observable.timer(WAIT_LIVENESS_INTERVAL, TimeUnit.MILLISECONDS) | ||
395 | - .subscribe(new Observer<Long>() { | ||
396 | - Disposable disposable; | ||
397 | - | ||
398 | - @Override | ||
399 | - public void onSubscribe(Disposable d) { | ||
400 | - disposable = d; | ||
401 | - getFeatureDelayedDisposables.add(disposable); | ||
402 | - } | ||
403 | - | ||
404 | - @Override | ||
405 | - public void onNext(Long aLong) { | ||
406 | - onFaceFeatureInfoGet(faceFeature, requestId, errorCode); | ||
407 | - } | ||
408 | - | ||
409 | - @Override | ||
410 | - public void onError(Throwable e) { | ||
411 | - | ||
412 | - } | ||
413 | - | ||
414 | - @Override | ||
415 | - public void onComplete() { | ||
416 | - getFeatureDelayedDisposables.remove(disposable); | ||
417 | - } | ||
418 | - }); | ||
419 | - } | ||
420 | - } | ||
421 | - | ||
422 | - } | ||
423 | - //特征提取失败 | ||
424 | - else { | ||
425 | - if (increaseAndGetValue(extractErrorRetryMap, requestId) > MAX_RETRY_TIME) { | ||
426 | - extractErrorRetryMap.put(requestId, 0); | ||
427 | - String msg; | ||
428 | - // 传入的FaceInfo在指定的图像上无法解析人脸,此处使用的是RGB人脸数据,一般是人脸模糊 | ||
429 | - if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) { | ||
430 | - msg = getString(R.string.low_confidence_level); | ||
431 | - } else { | ||
432 | - msg = "ExtractCode:" + errorCode; | ||
433 | - } | ||
434 | - faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, msg)); | ||
435 | - // 在尝试最大次数后,特征提取仍然失败,则认为识别未通过 | ||
436 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED); | ||
437 | - retryRecognizeDelayed(requestId); | ||
438 | - } else { | ||
439 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY); | ||
440 | - } | ||
441 | - } | ||
442 | - } | ||
443 | - | ||
444 | - @Override | ||
445 | - public void onFaceLivenessInfoGet(@Nullable LivenessInfo livenessInfo, final Integer requestId, Integer errorCode) { | ||
446 | - if (livenessInfo != null) { | ||
447 | - int liveness = livenessInfo.getLiveness(); | ||
448 | - livenessMap.put(requestId, liveness); | ||
449 | - // 非活体,重试 | ||
450 | - if (liveness == LivenessInfo.NOT_ALIVE) { | ||
451 | - faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_ALIVE")); | ||
452 | - // 延迟 FAIL_RETRY_INTERVAL 后,将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测 | ||
453 | - retryLivenessDetectDelayed(requestId); | ||
454 | - } | ||
455 | - } else { | ||
456 | - if (increaseAndGetValue(livenessErrorRetryMap, requestId) > MAX_RETRY_TIME) { | ||
457 | - livenessErrorRetryMap.put(requestId, 0); | ||
458 | - String msg; | ||
459 | - // 传入的FaceInfo在指定的图像上无法解析人脸,此处使用RGB人脸框 + IR数据,一般是人脸模糊或画面中无人脸 | ||
460 | - if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) { | ||
461 | - msg = getString(R.string.low_confidence_level); | ||
462 | - } else { | ||
463 | - msg = "ProcessCode:" + errorCode; | ||
464 | - } | ||
465 | - faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, msg)); | ||
466 | - // 在尝试最大次数后,活体检测仍然失败,则认定为非活体 | ||
467 | - livenessMap.put(requestId, LivenessInfo.NOT_ALIVE); | ||
468 | - retryLivenessDetectDelayed(requestId); | ||
469 | - } else { | ||
470 | - livenessMap.put(requestId, LivenessInfo.UNKNOWN); | ||
471 | - } | ||
472 | - } | ||
473 | - } | ||
474 | - | ||
475 | - }; | ||
476 | - CameraListener rgbCameraListener = new CameraListener() { | ||
477 | - @Override | ||
478 | - public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) { | ||
479 | - previewSize = camera.getParameters().getPreviewSize(); | ||
480 | - ViewGroup.LayoutParams layoutParams = adjustPreviewViewSize(previewViewRgb, faceRectView, previewSize, displayOrientation); | ||
481 | - drawHelperRgb = new DrawHelper(previewSize.width, previewSize.height, layoutParams.width, layoutParams.height, displayOrientation, | ||
482 | - cameraId, isMirror, false, false); | ||
483 | - if (faceHelperIr == null) { | ||
484 | - faceHelperIr = new FaceHelper.Builder() | ||
485 | - .ftEngine(ftEngine) | ||
486 | - .frEngine(frEngine) | ||
487 | - .flEngine(flEngine) | ||
488 | - .frQueueSize(MAX_DETECT_NUM) | ||
489 | - .flQueueSize(MAX_DETECT_NUM) | ||
490 | - .previewSize(previewSize) | ||
491 | - .faceListener(faceListener) | ||
492 | - .trackedFaceCount(ConfigUtil.getTrackedFaceCount(IrRegisterAndRecognizeActivity.this.getApplicationContext())) | ||
493 | - .build(); | ||
494 | - } | ||
495 | - | ||
496 | - TextView textViewRgb = new TextView(IrRegisterAndRecognizeActivity.this, null); | ||
497 | - textViewRgb.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); | ||
498 | - textViewRgb.setText(getString(R.string.camera_rgb) + "\n" + previewSize.width + "x" + previewSize.height); | ||
499 | - textViewRgb.setTextColor(Color.WHITE); | ||
500 | - textViewRgb.setBackgroundColor(getResources().getColor(R.color.color_bg_notification)); | ||
501 | - ((FrameLayout) previewViewRgb.getParent()).addView(textViewRgb); | ||
502 | - } | ||
503 | - | ||
504 | - | ||
505 | - @Override | ||
506 | - public void onPreview(final byte[] nv21, Camera camera) { | ||
507 | - rgbData = nv21; | ||
508 | - processPreviewData(); | ||
509 | - } | ||
510 | - | ||
511 | - @Override | ||
512 | - public void onCameraClosed() { | ||
513 | - Log.i(TAG, "onCameraClosed: "); | ||
514 | - } | ||
515 | - | ||
516 | - @Override | ||
517 | - public void onCameraError(Exception e) { | ||
518 | - Log.i(TAG, "onCameraError: " + e.getMessage()); | ||
519 | - } | ||
520 | - | ||
521 | - @Override | ||
522 | - public void onCameraConfigurationChanged(int cameraID, int displayOrientation) { | ||
523 | - if (drawHelperRgb != null) { | ||
524 | - drawHelperRgb.setCameraDisplayOrientation(displayOrientation); | ||
525 | - } | ||
526 | - Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation); | ||
527 | - } | ||
528 | - }; | ||
529 | - cameraHelper = new DualCameraHelper.Builder() | ||
530 | - .previewViewSize(new Point(previewViewRgb.getMeasuredWidth(), previewViewRgb.getMeasuredHeight())) | ||
531 | - .rotation(getWindowManager().getDefaultDisplay().getRotation()) | ||
532 | - .specificCameraId(cameraRgbId != null ? cameraRgbId : Camera.CameraInfo.CAMERA_FACING_BACK) | ||
533 | - .previewOn(previewViewRgb) | ||
534 | - .cameraListener(rgbCameraListener) | ||
535 | - .isMirror(cameraRgbId != null && Camera.CameraInfo.CAMERA_FACING_FRONT == cameraRgbId) | ||
536 | - .build(); | ||
537 | - cameraHelper.init(); | ||
538 | - try { | ||
539 | - cameraHelper.start(); | ||
540 | - } catch (RuntimeException e) { | ||
541 | - showToast(e.getMessage() + getString(R.string.camera_error_notice)); | ||
542 | - } | ||
543 | - } | ||
544 | - | ||
545 | - private void initIrCamera() { | ||
546 | - CameraListener irCameraListener = new CameraListener() { | ||
547 | - @Override | ||
548 | - public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) { | ||
549 | - previewSizeIr = camera.getParameters().getPreviewSize(); | ||
550 | - ViewGroup.LayoutParams layoutParams = adjustPreviewViewSize(previewViewIr, faceRectViewIr, previewSizeIr, displayOrientation); | ||
551 | - drawHelperIr = new DrawHelper(previewSizeIr.width, previewSizeIr.height, layoutParams.width, layoutParams.height, displayOrientation, | ||
552 | - cameraId, isMirror, false, false); | ||
553 | - TextView textViewIr = new TextView(IrRegisterAndRecognizeActivity.this, null); | ||
554 | - textViewIr.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); | ||
555 | - textViewIr.setText(getString(R.string.camera_ir) + "\n" + previewSizeIr.width + "x" + previewSizeIr.height); | ||
556 | - textViewIr.setTextColor(Color.WHITE); | ||
557 | - textViewIr.setBackgroundColor(getResources().getColor(R.color.color_bg_notification)); | ||
558 | - ((FrameLayout) previewViewIr.getParent()).addView(textViewIr); | ||
559 | - } | ||
560 | - | ||
561 | - | ||
562 | - @Override | ||
563 | - public void onPreview(final byte[] nv21, Camera camera) { | ||
564 | - irData = nv21; | ||
565 | - } | ||
566 | - | ||
567 | - @Override | ||
568 | - public void onCameraClosed() { | ||
569 | - Log.i(TAG, "onCameraClosed: "); | ||
570 | - } | ||
571 | - | ||
572 | - @Override | ||
573 | - public void onCameraError(Exception e) { | ||
574 | - Log.i(TAG, "onCameraError: " + e.getMessage()); | ||
575 | - } | ||
576 | - | ||
577 | - @Override | ||
578 | - public void onCameraConfigurationChanged(int cameraID, int displayOrientation) { | ||
579 | - if (drawHelperIr != null) { | ||
580 | - drawHelperIr.setCameraDisplayOrientation(displayOrientation); | ||
581 | - } | ||
582 | - Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation); | ||
583 | - } | ||
584 | - }; | ||
585 | - | ||
586 | - cameraHelperIr = new DualCameraHelper.Builder() | ||
587 | - .previewViewSize(new Point(previewViewIr.getMeasuredWidth(), previewViewIr.getMeasuredHeight())) | ||
588 | - .rotation(getWindowManager().getDefaultDisplay().getRotation()) | ||
589 | - .specificCameraId(cameraIrId != null ? cameraIrId : Camera.CameraInfo.CAMERA_FACING_FRONT) | ||
590 | - .previewOn(previewViewIr) | ||
591 | - .cameraListener(irCameraListener) | ||
592 | - .isMirror(cameraIrId != null && Camera.CameraInfo.CAMERA_FACING_FRONT == cameraIrId) | ||
593 | -// .previewSize(new Point(1280, 960)) //相机预览大小设置,RGB与IR需使用相同大小 | ||
594 | -// .additionalRotation(270) //额外旋转角度 | ||
595 | - .build(); | ||
596 | - cameraHelperIr.init(); | ||
597 | - try { | ||
598 | - cameraHelperIr.start(); | ||
599 | - } catch (RuntimeException e) { | ||
600 | - showToast(e.getMessage() + getString(R.string.camera_error_notice)); | ||
601 | - } | ||
602 | - } | ||
603 | - | ||
604 | - /** | ||
605 | - * 调整View的宽高,使2个预览同时显示 | ||
606 | - * | ||
607 | - * @param previewView 显示预览数据的view | ||
608 | - * @param faceRectView 画框的view | ||
609 | - * @param previewSize 预览大小 | ||
610 | - * @param displayOrientation 相机旋转角度 | ||
611 | - * @return 调整后的LayoutParams | ||
612 | - */ | ||
613 | - private ViewGroup.LayoutParams adjustPreviewViewSize(View previewView, FaceRectView faceRectView, Camera.Size previewSize, int displayOrientation) { | ||
614 | - ViewGroup.LayoutParams layoutParams = previewView.getLayoutParams(); | ||
615 | - int measuredWidth = previewView.getMeasuredWidth(); | ||
616 | - int measuredHeight = previewView.getMeasuredHeight(); | ||
617 | - float ratio = ((float) previewSize.height) / (float) previewSize.width; | ||
618 | - if (ratio > 1) { | ||
619 | - ratio = 1 / ratio; | ||
620 | - } | ||
621 | - if (displayOrientation % 180 == 0) { | ||
622 | - layoutParams.width = measuredWidth; | ||
623 | - layoutParams.height = (int) (measuredWidth * ratio); | ||
624 | - } else { | ||
625 | - layoutParams.height = measuredHeight; | ||
626 | - layoutParams.width = (int) (measuredHeight * ratio); | ||
627 | - } | ||
628 | - Log.i(TAG, "adjustPreviewViewSize: " + layoutParams.width + "x" + layoutParams.height); | ||
629 | - previewView.setLayoutParams(layoutParams); | ||
630 | - faceRectView.setLayoutParams(layoutParams); | ||
631 | - return layoutParams; | ||
632 | - } | ||
633 | - | ||
634 | - /** | ||
635 | - * 处理预览数据 | ||
636 | - */ | ||
637 | - private synchronized void processPreviewData() { | ||
638 | - if (rgbData != null && irData != null) { | ||
639 | - final byte[] cloneNv21Rgb = rgbData.clone(); | ||
640 | - if (faceRectView != null) { | ||
641 | - faceRectView.clearFaceInfo(); | ||
642 | - } | ||
643 | - if (faceRectViewIr != null) { | ||
644 | - faceRectViewIr.clearFaceInfo(); | ||
645 | - } | ||
646 | - List<FacePreviewInfo> facePreviewInfoList = faceHelperIr.onPreviewFrame(cloneNv21Rgb); | ||
647 | - if (facePreviewInfoList != null && faceRectView != null && drawHelperRgb != null | ||
648 | - && faceRectViewIr != null && drawHelperIr != null) { | ||
649 | - drawPreviewInfo(facePreviewInfoList); | ||
650 | - } | ||
651 | - registerFace(cloneNv21Rgb, facePreviewInfoList); | ||
652 | - clearLeftFace(facePreviewInfoList); | ||
653 | - | ||
654 | - if (facePreviewInfoList != null && facePreviewInfoList.size() > 0 && previewSize != null) { | ||
655 | - for (int i = 0; i < facePreviewInfoList.size(); i++) { | ||
656 | - // 注意:这里虽然使用的是IR画面活体检测,RGB画面特征提取,但是考虑到成像接近,所以只用了RGB画面的图像质量检测 | ||
657 | - Integer status = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId()); | ||
658 | - /** | ||
659 | - * 在活体检测开启,在人脸活体状态不为处理中(ANALYZING)且不为处理完成(ALIVE、NOT_ALIVE)时重新进行活体检测 | ||
660 | - */ | ||
661 | - if (livenessDetect && (status == null || status != RequestFeatureStatus.SUCCEED)) { | ||
662 | - Integer liveness = livenessMap.get(facePreviewInfoList.get(i).getTrackId()); | ||
663 | - if (liveness == null | ||
664 | - || (liveness != LivenessInfo.ALIVE && liveness != LivenessInfo.NOT_ALIVE && liveness != RequestLivenessStatus.ANALYZING)) { | ||
665 | - livenessMap.put(facePreviewInfoList.get(i).getTrackId(), RequestLivenessStatus.ANALYZING); | ||
666 | - // IR数据偏移 | ||
667 | - FaceInfo faceInfo = facePreviewInfoList.get(i).getFaceInfo().clone(); | ||
668 | - faceInfo.getRect().offset(Constants.HORIZONTAL_OFFSET, Constants.VERTICAL_OFFSET); | ||
669 | - faceHelperIr.requestFaceLiveness(irData.clone(), faceInfo, previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, facePreviewInfoList.get(i).getTrackId(), LivenessType.IR); | ||
670 | - } | ||
671 | - } | ||
672 | - /** | ||
673 | - * 对于每个人脸,若状态为空或者为失败,则请求特征提取(可根据需要添加其他判断以限制特征提取次数), | ||
674 | - * 特征提取回传的人脸特征结果在{@link FaceListener#onFaceFeatureInfoGet(FaceFeature, Integer, Integer)}中回传 | ||
675 | - */ | ||
676 | - if (status == null | ||
677 | - || status == RequestFeatureStatus.TO_RETRY) { | ||
678 | - requestFeatureStatusMap.put(facePreviewInfoList.get(i).getTrackId(), RequestFeatureStatus.SEARCHING); | ||
679 | - faceHelperIr.requestFaceFeature(cloneNv21Rgb, facePreviewInfoList.get(i).getFaceInfo(), | ||
680 | - previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, | ||
681 | - facePreviewInfoList.get(i).getTrackId()); | ||
682 | - } | ||
683 | - } | ||
684 | - } | ||
685 | - rgbData = null; | ||
686 | - irData = null; | ||
687 | - } | ||
688 | - | ||
689 | - } | ||
690 | - | ||
691 | - /** | ||
692 | - * 绘制预览相关数据 | ||
693 | - * | ||
694 | - * @param facePreviewInfoList {@link FaceHelper#onPreviewFrame(byte[])}回传的处理结果 | ||
695 | - */ | ||
696 | - private void drawPreviewInfo(List<FacePreviewInfo> facePreviewInfoList) { | ||
697 | - List<DrawInfo> drawInfoList = new ArrayList<>(); | ||
698 | - List<DrawInfo> drawInfoListIr = new ArrayList<>(); | ||
699 | - for (int i = 0; i < facePreviewInfoList.size(); i++) { | ||
700 | - int trackId = facePreviewInfoList.get(i).getTrackId(); | ||
701 | - String name = faceHelperIr.getName(trackId); | ||
702 | - Integer liveness = livenessMap.get(trackId); | ||
703 | - Rect ftRect = facePreviewInfoList.get(i).getFaceInfo().getRect(); | ||
704 | - | ||
705 | - | ||
706 | - Integer recognizeStatus = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId()); | ||
707 | - | ||
708 | - // 根据识别结果和活体结果设置颜色 | ||
709 | - int color = RecognizeColor.COLOR_UNKNOWN; | ||
710 | - if (recognizeStatus != null) { | ||
711 | - if (recognizeStatus == RequestFeatureStatus.FAILED) { | ||
712 | - color = RecognizeColor.COLOR_FAILED; | ||
713 | - } | ||
714 | - if (recognizeStatus == RequestFeatureStatus.SUCCEED) { | ||
715 | - color = RecognizeColor.COLOR_SUCCESS; | ||
716 | - } | ||
717 | - } | ||
718 | - if (liveness != null && liveness == LivenessInfo.NOT_ALIVE) { | ||
719 | - color = RecognizeColor.COLOR_FAILED; | ||
720 | - } | ||
721 | - | ||
722 | - | ||
723 | - drawInfoList.add(new DrawInfo(drawHelperRgb.adjustRect(ftRect), | ||
724 | - GenderInfo.UNKNOWN, AgeInfo.UNKNOWN_AGE, | ||
725 | - liveness != null ? liveness : LivenessInfo.UNKNOWN, color, | ||
726 | - name == null ? String.valueOf(trackId) : name)); | ||
727 | - | ||
728 | - Rect offsetFtRect = new Rect(ftRect); | ||
729 | - offsetFtRect.offset(Constants.HORIZONTAL_OFFSET, Constants.VERTICAL_OFFSET); | ||
730 | - drawInfoListIr.add(new DrawInfo(drawHelperIr.adjustRect(offsetFtRect), | ||
731 | - GenderInfo.UNKNOWN, AgeInfo.UNKNOWN_AGE, | ||
732 | - liveness != null ? liveness : LivenessInfo.UNKNOWN, color, | ||
733 | - name == null ? String.valueOf(trackId) : name)); | ||
734 | - } | ||
735 | - drawHelperRgb.draw(faceRectView, drawInfoList); | ||
736 | - drawHelperIr.draw(faceRectViewIr, drawInfoListIr); | ||
737 | - } | ||
738 | - | ||
739 | - /** | ||
740 | - * 注册人脸 | ||
741 | - * | ||
742 | - * @param nv21Rgb RGB摄像头的帧数据 | ||
743 | - * @param facePreviewInfoList {@link FaceHelper#onPreviewFrame(byte[])}回传的处理结果 | ||
744 | - */ | ||
745 | - private void registerFace(final byte[] nv21Rgb, final List<FacePreviewInfo> facePreviewInfoList) { | ||
746 | - if (registerStatus == REGISTER_STATUS_READY && facePreviewInfoList != null && facePreviewInfoList.size() > 0) { | ||
747 | - registerStatus = REGISTER_STATUS_PROCESSING; | ||
748 | - Observable.create(new ObservableOnSubscribe<Boolean>() { | ||
749 | - @Override | ||
750 | - public void subscribe(ObservableEmitter<Boolean> emitter) { | ||
751 | - boolean success = FaceServer.getInstance().registerNv21( | ||
752 | - IrRegisterAndRecognizeActivity.this, nv21Rgb, | ||
753 | - previewSize.width, previewSize.height, facePreviewInfoList.get(0).getFaceInfo(), "registered " + faceHelperIr.getTrackedFaceCount()); | ||
754 | - emitter.onNext(success); | ||
755 | - } | ||
756 | - }) | ||
757 | - .subscribeOn(Schedulers.computation()) | ||
758 | - .observeOn(AndroidSchedulers.mainThread()) | ||
759 | - .subscribe(new Observer<Boolean>() { | ||
760 | - @Override | ||
761 | - public void onSubscribe(Disposable d) { | ||
762 | - | ||
763 | - } | ||
764 | - | ||
765 | - @Override | ||
766 | - public void onNext(Boolean success) { | ||
767 | - String result = success ? "register success!" : "register failed!"; | ||
768 | - showToast(result); | ||
769 | - registerStatus = REGISTER_STATUS_DONE; | ||
770 | - } | ||
771 | - | ||
772 | - @Override | ||
773 | - public void onError(Throwable e) { | ||
774 | - e.printStackTrace(); | ||
775 | - showToast("register failed!"); | ||
776 | - registerStatus = REGISTER_STATUS_DONE; | ||
777 | - } | ||
778 | - | ||
779 | - @Override | ||
780 | - public void onComplete() { | ||
781 | - | ||
782 | - } | ||
783 | - }); | ||
784 | - } | ||
785 | - } | ||
786 | - | ||
787 | - @Override | ||
788 | - void afterRequestPermission(int requestCode, boolean isAllGranted) { | ||
789 | - if (requestCode == ACTION_REQUEST_PERMISSIONS) { | ||
790 | - if (isAllGranted) { | ||
791 | - initEngine(); | ||
792 | - initRgbCamera(); | ||
793 | - initIrCamera(); | ||
794 | - } else { | ||
795 | - showToast(getString(R.string.permission_denied)); | ||
796 | - } | ||
797 | - } | ||
798 | - } | ||
799 | - | ||
800 | - | ||
801 | - /** | ||
802 | - * 删除已经离开的人脸 | ||
803 | - * | ||
804 | - * @param facePreviewInfoList 人脸和trackId列表 | ||
805 | - */ | ||
806 | - private void clearLeftFace(List<FacePreviewInfo> facePreviewInfoList) { | ||
807 | - if (compareResultList != null) { | ||
808 | - for (int i = compareResultList.size() - 1; i >= 0; i--) { | ||
809 | - if (!requestFeatureStatusMap.containsKey(compareResultList.get(i).getTrackId())) { | ||
810 | - compareResultList.remove(i); | ||
811 | - adapter.notifyItemRemoved(i); | ||
812 | - } | ||
813 | - } | ||
814 | - } | ||
815 | - if (facePreviewInfoList == null || facePreviewInfoList.size() == 0) { | ||
816 | - requestFeatureStatusMap.clear(); | ||
817 | - livenessMap.clear(); | ||
818 | - livenessErrorRetryMap.clear(); | ||
819 | - extractErrorRetryMap.clear(); | ||
820 | - if (getFeatureDelayedDisposables != null) { | ||
821 | - getFeatureDelayedDisposables.clear(); | ||
822 | - } | ||
823 | - return; | ||
824 | - } | ||
825 | - Enumeration<Integer> keys = requestFeatureStatusMap.keys(); | ||
826 | - while (keys.hasMoreElements()) { | ||
827 | - int key = keys.nextElement(); | ||
828 | - boolean contained = false; | ||
829 | - for (FacePreviewInfo facePreviewInfo : facePreviewInfoList) { | ||
830 | - if (facePreviewInfo.getTrackId() == key) { | ||
831 | - contained = true; | ||
832 | - break; | ||
833 | - } | ||
834 | - } | ||
835 | - if (!contained) { | ||
836 | - requestFeatureStatusMap.remove(key); | ||
837 | - livenessMap.remove(key); | ||
838 | - livenessErrorRetryMap.remove(key); | ||
839 | - extractErrorRetryMap.remove(key); | ||
840 | - } | ||
841 | - } | ||
842 | - | ||
843 | - | ||
844 | - } | ||
845 | - | ||
846 | - private void searchFace(final FaceFeature frFace, final Integer requestId) { | ||
847 | - Observable | ||
848 | - .create(new ObservableOnSubscribe<CompareResult>() { | ||
849 | - @Override | ||
850 | - public void subscribe(ObservableEmitter<CompareResult> emitter) { | ||
851 | -// Log.i(TAG, "subscribe: fr search start = " + System.currentTimeMillis() + " trackId = " + requestId); | ||
852 | - CompareResult compareResult = FaceServer.getInstance().getTopOfFaceLib(frFace); | ||
853 | -// Log.i(TAG, "subscribe: fr search end = " + System.currentTimeMillis() + " trackId = " + requestId); | ||
854 | - emitter.onNext(compareResult); | ||
855 | - | ||
856 | - } | ||
857 | - }) | ||
858 | - .subscribeOn(Schedulers.computation()) | ||
859 | - .observeOn(AndroidSchedulers.mainThread()) | ||
860 | - .subscribe(new Observer<CompareResult>() { | ||
861 | - @Override | ||
862 | - public void onSubscribe(Disposable d) { | ||
863 | - | ||
864 | - } | ||
865 | - | ||
866 | - @Override | ||
867 | - public void onNext(CompareResult compareResult) { | ||
868 | - if (compareResult == null || compareResult.getUserName() == null) { | ||
869 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED); | ||
870 | - faceHelperIr.setName(requestId, "VISITOR " + requestId); | ||
871 | - return; | ||
872 | - } | ||
873 | - | ||
874 | -// Log.i(TAG, "onNext: fr search get result = " + System.currentTimeMillis() + " trackId = " + requestId + " similar = " + compareResult.getSimilar()); | ||
875 | - if (compareResult.getSimilar() > SIMILAR_THRESHOLD) { | ||
876 | - boolean isAdded = false; | ||
877 | - if (compareResultList == null) { | ||
878 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED); | ||
879 | - faceHelperIr.setName(requestId, "VISITOR " + requestId); | ||
880 | - return; | ||
881 | - } | ||
882 | - for (CompareResult compareResult1 : compareResultList) { | ||
883 | - if (compareResult1.getTrackId() == requestId) { | ||
884 | - isAdded = true; | ||
885 | - break; | ||
886 | - } | ||
887 | - } | ||
888 | - if (!isAdded) { | ||
889 | - //对于多人脸搜索,假如最大显示数量为 MAX_DETECT_NUM 且有新的人脸进入,则以队列的形式移除 | ||
890 | - if (compareResultList.size() >= MAX_DETECT_NUM) { | ||
891 | - compareResultList.remove(0); | ||
892 | - adapter.notifyItemRemoved(0); | ||
893 | - } | ||
894 | - //添加显示人员时,保存其trackId | ||
895 | - compareResult.setTrackId(requestId); | ||
896 | - compareResultList.add(compareResult); | ||
897 | - adapter.notifyItemInserted(compareResultList.size() - 1); | ||
898 | - } | ||
899 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.SUCCEED); | ||
900 | - faceHelperIr.setName(requestId, getString(R.string.recognize_success_notice, compareResult.getUserName())); | ||
901 | - | ||
902 | - } else { | ||
903 | - faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_REGISTERED")); | ||
904 | - retryRecognizeDelayed(requestId); | ||
905 | - } | ||
906 | - } | ||
907 | - | ||
908 | - @Override | ||
909 | - public void onError(Throwable e) { | ||
910 | - faceHelperIr.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_REGISTERED")); | ||
911 | - retryRecognizeDelayed(requestId); | ||
912 | - } | ||
913 | - | ||
914 | - @Override | ||
915 | - public void onComplete() { | ||
916 | - | ||
917 | - } | ||
918 | - }); | ||
919 | - } | ||
920 | - | ||
921 | - | ||
922 | - /** | ||
923 | - * 将准备注册的状态置为{@link #REGISTER_STATUS_READY} | ||
924 | - * | ||
925 | - * @param view 注册按钮 | ||
926 | - */ | ||
927 | - public void register(View view) { | ||
928 | - if (registerStatus == REGISTER_STATUS_DONE) { | ||
929 | - registerStatus = REGISTER_STATUS_READY; | ||
930 | - } | ||
931 | - } | ||
932 | - | ||
933 | - /** | ||
934 | - * 在{@link #previewViewRgb}第一次布局完成后,去除该监听,并且进行引擎和相机的初始化 | ||
935 | - */ | ||
936 | - @Override | ||
937 | - public void onGlobalLayout() { | ||
938 | - previewViewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this); | ||
939 | - if (!checkPermissions(NEEDED_PERMISSIONS)) { | ||
940 | - ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS); | ||
941 | - } else { | ||
942 | - initEngine(); | ||
943 | - initRgbCamera(); | ||
944 | - initIrCamera(); | ||
945 | - } | ||
946 | - } | ||
947 | - | ||
948 | - public void drawIrRectVerticalMirror(View view) { | ||
949 | - if (drawHelperIr != null) { | ||
950 | - drawHelperIr.setMirrorVertical(!drawHelperIr.isMirrorVertical()); | ||
951 | - } | ||
952 | - } | ||
953 | - | ||
954 | - public void drawIrRectHorizontalMirror(View view) { | ||
955 | - if (drawHelperIr != null) { | ||
956 | - drawHelperIr.setMirrorHorizontal(!drawHelperIr.isMirrorHorizontal()); | ||
957 | - } | ||
958 | - } | ||
959 | - | ||
960 | - | ||
961 | - /** | ||
962 | - * 将map中key对应的value增1回传 | ||
963 | - * | ||
964 | - * @param countMap map | ||
965 | - * @param key key | ||
966 | - * @return 增1后的value | ||
967 | - */ | ||
968 | - public int increaseAndGetValue(Map<Integer, Integer> countMap, int key) { | ||
969 | - if (countMap == null) { | ||
970 | - return 0; | ||
971 | - } | ||
972 | - Integer value = countMap.get(key); | ||
973 | - if (value == null) { | ||
974 | - value = 0; | ||
975 | - } | ||
976 | - countMap.put(key, ++value); | ||
977 | - return value; | ||
978 | - } | ||
979 | - | ||
980 | - /** | ||
981 | - * 延迟 FAIL_RETRY_INTERVAL 重新进行活体检测 | ||
982 | - * | ||
983 | - * @param requestId 人脸ID | ||
984 | - */ | ||
985 | - private void retryLivenessDetectDelayed(final Integer requestId) { | ||
986 | - Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS) | ||
987 | - .subscribe(new Observer<Long>() { | ||
988 | - Disposable disposable; | ||
989 | - | ||
990 | - @Override | ||
991 | - public void onSubscribe(Disposable d) { | ||
992 | - disposable = d; | ||
993 | - delayFaceTaskCompositeDisposable.add(disposable); | ||
994 | - } | ||
995 | - | ||
996 | - @Override | ||
997 | - public void onNext(Long aLong) { | ||
998 | - | ||
999 | - } | ||
1000 | - | ||
1001 | - @Override | ||
1002 | - public void onError(Throwable e) { | ||
1003 | - e.printStackTrace(); | ||
1004 | - } | ||
1005 | - | ||
1006 | - @Override | ||
1007 | - public void onComplete() { | ||
1008 | - // 将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测 | ||
1009 | - if (livenessDetect) { | ||
1010 | - faceHelperIr.setName(requestId, Integer.toString(requestId)); | ||
1011 | - } | ||
1012 | - livenessMap.put(requestId, LivenessInfo.UNKNOWN); | ||
1013 | - delayFaceTaskCompositeDisposable.remove(disposable); | ||
1014 | - } | ||
1015 | - }); | ||
1016 | - } | ||
1017 | - | ||
1018 | - /** | ||
1019 | - * 延迟 FAIL_RETRY_INTERVAL 重新进行人脸识别 | ||
1020 | - * | ||
1021 | - * @param requestId 人脸ID | ||
1022 | - */ | ||
1023 | - private void retryRecognizeDelayed(final Integer requestId) { | ||
1024 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED); | ||
1025 | - Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS) | ||
1026 | - .subscribe(new Observer<Long>() { | ||
1027 | - Disposable disposable; | ||
1028 | - | ||
1029 | - @Override | ||
1030 | - public void onSubscribe(Disposable d) { | ||
1031 | - disposable = d; | ||
1032 | - delayFaceTaskCompositeDisposable.add(disposable); | ||
1033 | - } | ||
1034 | - | ||
1035 | - @Override | ||
1036 | - public void onNext(Long aLong) { | ||
1037 | - | ||
1038 | - } | ||
1039 | - | ||
1040 | - @Override | ||
1041 | - public void onError(Throwable e) { | ||
1042 | - e.printStackTrace(); | ||
1043 | - } | ||
1044 | - | ||
1045 | - @Override | ||
1046 | - public void onComplete() { | ||
1047 | - // 将该人脸特征提取状态置为FAILED,帧回调处理时会重新进行活体检测 | ||
1048 | - faceHelperIr.setName(requestId, Integer.toString(requestId)); | ||
1049 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY); | ||
1050 | - delayFaceTaskCompositeDisposable.remove(disposable); | ||
1051 | - } | ||
1052 | - }); | ||
1053 | - } | ||
1054 | -} |
1 | -package com.arcsoft.arcfacedemo.activity; | ||
2 | - | ||
3 | -import android.Manifest; | ||
4 | -import android.content.Intent; | ||
5 | -import android.content.pm.PackageManager; | ||
6 | -import android.graphics.Bitmap; | ||
7 | -import android.graphics.Canvas; | ||
8 | -import android.graphics.Color; | ||
9 | -import android.graphics.Paint; | ||
10 | -import android.graphics.Typeface; | ||
11 | -import android.os.Build; | ||
12 | -import android.os.Bundle; | ||
13 | -import android.provider.MediaStore; | ||
14 | -import android.support.annotation.NonNull; | ||
15 | -import android.support.annotation.Nullable; | ||
16 | -import android.support.v4.app.ActivityCompat; | ||
17 | -import android.support.v4.content.ContextCompat; | ||
18 | -import android.support.v7.app.AppCompatActivity; | ||
19 | -import android.support.v7.widget.DividerItemDecoration; | ||
20 | -import android.support.v7.widget.LinearLayoutManager; | ||
21 | -import android.support.v7.widget.RecyclerView; | ||
22 | -import android.text.style.StyleSpan; | ||
23 | -import android.util.Log; | ||
24 | -import android.view.View; | ||
25 | -import android.view.WindowManager; | ||
26 | -import android.widget.ImageView; | ||
27 | -import android.widget.TextView; | ||
28 | - | ||
29 | -import com.arcsoft.arcfacedemo.R; | ||
30 | -import com.arcsoft.arcfacedemo.model.ItemShowInfo; | ||
31 | -import com.arcsoft.arcfacedemo.widget.MultiFaceInfoAdapter; | ||
32 | -import com.arcsoft.face.AgeInfo; | ||
33 | -import com.arcsoft.face.ErrorInfo; | ||
34 | -import com.arcsoft.face.Face3DAngle; | ||
35 | -import com.arcsoft.face.FaceEngine; | ||
36 | -import com.arcsoft.face.FaceFeature; | ||
37 | -import com.arcsoft.face.FaceInfo; | ||
38 | -import com.arcsoft.face.FaceSimilar; | ||
39 | -import com.arcsoft.face.GenderInfo; | ||
40 | -import com.arcsoft.face.enums.DetectFaceOrientPriority; | ||
41 | -import com.arcsoft.face.enums.DetectMode; | ||
42 | -import com.arcsoft.face.util.ImageUtils; | ||
43 | -import com.arcsoft.imageutil.ArcSoftImageFormat; | ||
44 | -import com.arcsoft.imageutil.ArcSoftImageUtil; | ||
45 | -import com.arcsoft.imageutil.ArcSoftImageUtilError; | ||
46 | -import com.bumptech.glide.Glide; | ||
47 | - | ||
48 | -import java.io.IOException; | ||
49 | -import java.util.ArrayList; | ||
50 | -import java.util.Arrays; | ||
51 | -import java.util.List; | ||
52 | - | ||
53 | - | ||
54 | -public class MultiImageActivity extends BaseActivity { | ||
55 | - private static final String TAG = "MultiImageActivity"; | ||
56 | - | ||
57 | - private static final int ACTION_CHOOSE_MAIN_IMAGE = 0x201; | ||
58 | - private static final int ACTION_ADD_RECYCLER_ITEM_IMAGE = 0x202; | ||
59 | - | ||
60 | - private static final int ACTION_REQUEST_PERMISSIONS = 0x001; | ||
61 | - | ||
62 | - private ImageView ivMainImage; | ||
63 | - private TextView tvMainImageInfo; | ||
64 | - /** | ||
65 | - * 选择图片时的类型 | ||
66 | - */ | ||
67 | - private static final int TYPE_MAIN = 0; | ||
68 | - private static final int TYPE_ITEM = 1; | ||
69 | - | ||
70 | - /** | ||
71 | - * 主图的第0张人脸的特征数据 | ||
72 | - */ | ||
73 | - private FaceFeature mainFeature; | ||
74 | - | ||
75 | - private MultiFaceInfoAdapter multiFaceInfoAdapter; | ||
76 | - private List<ItemShowInfo> showInfoList; | ||
77 | - | ||
78 | - private FaceEngine faceEngine; | ||
79 | - private int faceEngineCode = -1; | ||
80 | - | ||
81 | - private Bitmap mainBitmap; | ||
82 | - | ||
83 | - private static String[] NEEDED_PERMISSIONS = new String[]{ | ||
84 | - Manifest.permission.READ_PHONE_STATE | ||
85 | - }; | ||
86 | - | ||
87 | - @Override | ||
88 | - protected void onCreate(Bundle savedInstanceState) { | ||
89 | - super.onCreate(savedInstanceState); | ||
90 | - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | ||
91 | - setContentView(R.layout.activity_multi_image); | ||
92 | - /** | ||
93 | - * 在选择图片的时候,在android 7.0及以上通过FileProvider获取Uri,不需要文件权限 | ||
94 | - */ | ||
95 | - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { | ||
96 | - List<String> permissionList = new ArrayList<>(Arrays.asList(NEEDED_PERMISSIONS)); | ||
97 | - permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE); | ||
98 | - NEEDED_PERMISSIONS = permissionList.toArray(new String[0]); | ||
99 | - } | ||
100 | - | ||
101 | - if (!checkPermissions(NEEDED_PERMISSIONS)) { | ||
102 | - ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS); | ||
103 | - } else { | ||
104 | - initEngine(); | ||
105 | - } | ||
106 | - initView(); | ||
107 | - } | ||
108 | - | ||
109 | - private void initView() { | ||
110 | - ivMainImage = findViewById(R.id.iv_main_image); | ||
111 | - tvMainImageInfo = findViewById(R.id.tv_main_image_info); | ||
112 | - RecyclerView recyclerFaces = findViewById(R.id.recycler_faces); | ||
113 | - showInfoList = new ArrayList<>(); | ||
114 | - multiFaceInfoAdapter = new MultiFaceInfoAdapter(showInfoList, this); | ||
115 | - recyclerFaces.setAdapter(multiFaceInfoAdapter); | ||
116 | - recyclerFaces.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); | ||
117 | - recyclerFaces.setLayoutManager(new LinearLayoutManager(this)); | ||
118 | - } | ||
119 | - | ||
120 | - private void initEngine() { | ||
121 | - | ||
122 | - faceEngine = new FaceEngine(); | ||
123 | - faceEngineCode = faceEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY, | ||
124 | - 16, 6, FaceEngine.ASF_FACE_RECOGNITION | FaceEngine.ASF_AGE | FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE); | ||
125 | - | ||
126 | - Log.i(TAG, "initEngine: init " + faceEngineCode); | ||
127 | - | ||
128 | - if (faceEngineCode != ErrorInfo.MOK) { | ||
129 | - showToast(getString(R.string.init_failed, faceEngineCode)); | ||
130 | - } | ||
131 | - } | ||
132 | - | ||
133 | - private void unInitEngine() { | ||
134 | - if (faceEngine != null) { | ||
135 | - faceEngineCode = faceEngine.unInit(); | ||
136 | - Log.i(TAG, "unInitEngine: " + faceEngineCode); | ||
137 | - } | ||
138 | - } | ||
139 | - | ||
140 | - @Override | ||
141 | - protected void onDestroy() { | ||
142 | - unInitEngine(); | ||
143 | - super.onDestroy(); | ||
144 | - } | ||
145 | - | ||
146 | - @Override | ||
147 | - protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { | ||
148 | - super.onActivityResult(requestCode, resultCode, data); | ||
149 | - | ||
150 | - if (data == null || data.getData() == null) { | ||
151 | - showToast(getString(R.string.get_picture_failed)); | ||
152 | - return; | ||
153 | - } | ||
154 | - if (requestCode == ACTION_CHOOSE_MAIN_IMAGE) { | ||
155 | - try { | ||
156 | - mainBitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), data.getData()); | ||
157 | - } catch (IOException e) { | ||
158 | - e.printStackTrace(); | ||
159 | - showToast(getString(R.string.get_picture_failed)); | ||
160 | - return; | ||
161 | - } | ||
162 | - if (mainBitmap == null) { | ||
163 | - showToast(getString(R.string.get_picture_failed)); | ||
164 | - return; | ||
165 | - } | ||
166 | - processImage(mainBitmap, TYPE_MAIN); | ||
167 | - } else if (requestCode == ACTION_ADD_RECYCLER_ITEM_IMAGE) { | ||
168 | - Bitmap bitmap = null; | ||
169 | - try { | ||
170 | - bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), data.getData()); | ||
171 | - } catch (IOException e) { | ||
172 | - e.printStackTrace(); | ||
173 | - showToast(getString(R.string.get_picture_failed)); | ||
174 | - return; | ||
175 | - } | ||
176 | - if (bitmap == null) { | ||
177 | - showToast(getString(R.string.get_picture_failed)); | ||
178 | - return; | ||
179 | - } | ||
180 | - if (mainFeature == null) { | ||
181 | - return; | ||
182 | - } | ||
183 | - processImage(bitmap, TYPE_ITEM); | ||
184 | - } | ||
185 | - } | ||
186 | - | ||
187 | - | ||
188 | - public void processImage(Bitmap bitmap, int type) { | ||
189 | - if (bitmap == null) { | ||
190 | - return; | ||
191 | - } | ||
192 | - | ||
193 | - if (faceEngine == null) { | ||
194 | - return; | ||
195 | - } | ||
196 | - | ||
197 | - // 接口需要的bgr24宽度必须为4的倍数 | ||
198 | - bitmap = ArcSoftImageUtil.getAlignedBitmap(bitmap, true); | ||
199 | - | ||
200 | - if (bitmap == null) { | ||
201 | - return; | ||
202 | - } | ||
203 | - int width = bitmap.getWidth(); | ||
204 | - int height = bitmap.getHeight(); | ||
205 | - // bitmap转bgr24 | ||
206 | - long start = System.currentTimeMillis(); | ||
207 | - byte[] bgr24 = ArcSoftImageUtil.createImageData(bitmap.getWidth(), bitmap.getHeight(), ArcSoftImageFormat.BGR24); | ||
208 | - int transformCode = ArcSoftImageUtil.bitmapToImageData(bitmap, bgr24, ArcSoftImageFormat.BGR24); | ||
209 | - if (transformCode != ArcSoftImageUtilError.CODE_SUCCESS) { | ||
210 | - showToast("failed to transform bitmap to imageData, code is " + transformCode); | ||
211 | - return; | ||
212 | - } | ||
213 | -// Log.i(TAG, "processImage:bitmapToBgr24 cost = " + (System.currentTimeMillis() - start)); | ||
214 | - | ||
215 | - if (bgr24 != null) { | ||
216 | - | ||
217 | - List<FaceInfo> faceInfoList = new ArrayList<>(); | ||
218 | - //人脸检测 | ||
219 | - int detectCode = faceEngine.detectFaces(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList); | ||
220 | - if (detectCode != 0 || faceInfoList.size() == 0) { | ||
221 | - showToast("face detection finished, code is " + detectCode + ", face num is " + faceInfoList.size()); | ||
222 | - return; | ||
223 | - } | ||
224 | - //绘制bitmap | ||
225 | - bitmap = bitmap.copy(Bitmap.Config.RGB_565, true); | ||
226 | - Canvas canvas = new Canvas(bitmap); | ||
227 | - Paint paint = new Paint(); | ||
228 | - paint.setAntiAlias(true); | ||
229 | - paint.setStrokeWidth(10); | ||
230 | - paint.setColor(Color.YELLOW); | ||
231 | - | ||
232 | - if (faceInfoList.size() > 0) { | ||
233 | - | ||
234 | - for (int i = 0; i < faceInfoList.size(); i++) { | ||
235 | - //绘制人脸框 | ||
236 | - paint.setStyle(Paint.Style.STROKE); | ||
237 | - canvas.drawRect(faceInfoList.get(i).getRect(), paint); | ||
238 | - //绘制人脸序号 | ||
239 | - paint.setStyle(Paint.Style.FILL_AND_STROKE); | ||
240 | - paint.setTextSize(faceInfoList.get(i).getRect().width() / 2); | ||
241 | - canvas.drawText("" + i, faceInfoList.get(i).getRect().left, faceInfoList.get(i).getRect().top, paint); | ||
242 | - | ||
243 | - } | ||
244 | - } | ||
245 | - | ||
246 | - int faceProcessCode = faceEngine.process(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList, FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE); | ||
247 | - Log.i(TAG, "processImage: " + faceProcessCode); | ||
248 | - if (faceProcessCode != ErrorInfo.MOK) { | ||
249 | - showToast("face process finished, code is " + faceProcessCode); | ||
250 | - return; | ||
251 | - } | ||
252 | - //年龄信息结果 | ||
253 | - List<AgeInfo> ageInfoList = new ArrayList<>(); | ||
254 | - //性别信息结果 | ||
255 | - List<GenderInfo> genderInfoList = new ArrayList<>(); | ||
256 | - //三维角度结果 | ||
257 | - List<Face3DAngle> face3DAngleList = new ArrayList<>(); | ||
258 | - //获取年龄、性别、三维角度 | ||
259 | - int ageCode = faceEngine.getAge(ageInfoList); | ||
260 | - int genderCode = faceEngine.getGender(genderInfoList); | ||
261 | - int face3DAngleCode = faceEngine.getFace3DAngle(face3DAngleList); | ||
262 | - | ||
263 | - if ((ageCode | genderCode | face3DAngleCode) != ErrorInfo.MOK) { | ||
264 | - showToast("at lease one of age、gender、face3DAngle detect failed! codes are: " + ageCode | ||
265 | - + " ," + genderCode + " ," + face3DAngleCode); | ||
266 | - return; | ||
267 | - } | ||
268 | - | ||
269 | - //人脸比对数据显示 | ||
270 | - if (faceInfoList.size() > 0) { | ||
271 | - if (type == TYPE_MAIN) { | ||
272 | - int size = showInfoList.size(); | ||
273 | - showInfoList.clear(); | ||
274 | - multiFaceInfoAdapter.notifyItemRangeRemoved(0, size); | ||
275 | - mainFeature = new FaceFeature(); | ||
276 | - int res = faceEngine.extractFaceFeature(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList.get(0), mainFeature); | ||
277 | - if (res != ErrorInfo.MOK) { | ||
278 | - mainFeature = null; | ||
279 | - } | ||
280 | - Glide.with(ivMainImage.getContext()) | ||
281 | - .load(bitmap) | ||
282 | - .into(ivMainImage); | ||
283 | - StringBuilder stringBuilder = new StringBuilder(); | ||
284 | - if (faceInfoList.size() > 0) { | ||
285 | - stringBuilder.append("face info:\n\n"); | ||
286 | - } | ||
287 | - for (int i = 0; i < faceInfoList.size(); i++) { | ||
288 | - stringBuilder.append("face[") | ||
289 | - .append(i) | ||
290 | - .append("]:\n") | ||
291 | - .append(faceInfoList.get(i)) | ||
292 | - .append("\nage:") | ||
293 | - .append(ageInfoList.get(i).getAge()) | ||
294 | - .append("\ngender:") | ||
295 | - .append(genderInfoList.get(i).getGender() == GenderInfo.MALE ? "MALE" | ||
296 | - : (genderInfoList.get(i).getGender() == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN")) | ||
297 | - .append("\nface3DAngle:") | ||
298 | - .append(face3DAngleList.get(i)) | ||
299 | - .append("\n\n"); | ||
300 | - } | ||
301 | - tvMainImageInfo.setText(stringBuilder); | ||
302 | - } else if (type == TYPE_ITEM) { | ||
303 | - FaceFeature faceFeature = new FaceFeature(); | ||
304 | - int res = faceEngine.extractFaceFeature(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList.get(0), faceFeature); | ||
305 | - if (res == 0) { | ||
306 | - FaceSimilar faceSimilar = new FaceSimilar(); | ||
307 | - int compareResult = faceEngine.compareFaceFeature(mainFeature, faceFeature, faceSimilar); | ||
308 | - if (compareResult == ErrorInfo.MOK) { | ||
309 | - | ||
310 | - ItemShowInfo showInfo = new ItemShowInfo(bitmap, ageInfoList.get(0).getAge(), genderInfoList.get(0).getGender(), faceSimilar.getScore()); | ||
311 | - showInfoList.add(showInfo); | ||
312 | - multiFaceInfoAdapter.notifyItemInserted(showInfoList.size() - 1); | ||
313 | - } else { | ||
314 | - showToast(getString(R.string.compare_failed, compareResult)); | ||
315 | - } | ||
316 | - } | ||
317 | - } | ||
318 | - } else { | ||
319 | - if (type == TYPE_MAIN) { | ||
320 | - mainBitmap = null; | ||
321 | - } | ||
322 | - } | ||
323 | - | ||
324 | - } else { | ||
325 | - showToast("can not get bgr24 from bitmap!"); | ||
326 | - } | ||
327 | - } | ||
328 | - | ||
329 | - /** | ||
330 | - * 从本地选择文件 | ||
331 | - * | ||
332 | - * @param action 可为选择主图{@link #ACTION_CHOOSE_MAIN_IMAGE}或者选择item图{@link #ACTION_ADD_RECYCLER_ITEM_IMAGE} | ||
333 | - */ | ||
334 | - public void chooseLocalImage(int action) { | ||
335 | - Intent intent = new Intent(Intent.ACTION_PICK); | ||
336 | - intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); | ||
337 | - startActivityForResult(intent, action); | ||
338 | - } | ||
339 | - | ||
340 | - public void addItemFace(View view) { | ||
341 | - if (faceEngineCode != ErrorInfo.MOK) { | ||
342 | - showToast(getString(R.string.engine_not_initialized, faceEngineCode)); | ||
343 | - return; | ||
344 | - } | ||
345 | - if (mainBitmap == null) { | ||
346 | - showToast(getString(R.string.notice_choose_main_img)); | ||
347 | - return; | ||
348 | - } | ||
349 | - chooseLocalImage(ACTION_ADD_RECYCLER_ITEM_IMAGE); | ||
350 | - } | ||
351 | - | ||
352 | - public void chooseMainImage(View view) { | ||
353 | - | ||
354 | - if (faceEngineCode != ErrorInfo.MOK) { | ||
355 | - showToast(getString(R.string.engine_not_initialized, faceEngineCode)); | ||
356 | - return; | ||
357 | - } | ||
358 | - chooseLocalImage(ACTION_CHOOSE_MAIN_IMAGE); | ||
359 | - } | ||
360 | - | ||
361 | - @Override | ||
362 | - void afterRequestPermission(int requestCode, boolean isAllGranted) { | ||
363 | - if (requestCode == ACTION_REQUEST_PERMISSIONS) { | ||
364 | - if (isAllGranted) { | ||
365 | - initEngine(); | ||
366 | - } else { | ||
367 | - showToast(getString(R.string.permission_denied)); | ||
368 | - } | ||
369 | - } | ||
370 | - } | ||
371 | -} |
arcface/src/main/java/com/arcsoft/arcfacedemo/activity/RegisterAndRecognizeActivity.java
已删除
100644 → 0
1 | -package com.arcsoft.arcfacedemo.activity; | ||
2 | - | ||
3 | -import android.Manifest; | ||
4 | -import android.content.pm.ActivityInfo; | ||
5 | -import android.graphics.Point; | ||
6 | -import android.hardware.Camera; | ||
7 | -import android.os.Build; | ||
8 | -import android.os.Bundle; | ||
9 | -import android.support.annotation.Nullable; | ||
10 | -import android.support.v4.app.ActivityCompat; | ||
11 | -import android.support.v7.widget.DefaultItemAnimator; | ||
12 | -import android.support.v7.widget.GridLayoutManager; | ||
13 | -import android.support.v7.widget.RecyclerView; | ||
14 | -import android.util.DisplayMetrics; | ||
15 | -import android.util.Log; | ||
16 | -import android.view.View; | ||
17 | -import android.view.ViewTreeObserver; | ||
18 | -import android.view.WindowManager; | ||
19 | -import android.widget.CompoundButton; | ||
20 | -import android.widget.Switch; | ||
21 | - | ||
22 | -import com.arcsoft.arcfacedemo.R; | ||
23 | -import com.arcsoft.arcfacedemo.faceserver.CompareResult; | ||
24 | -import com.arcsoft.arcfacedemo.faceserver.FaceServer; | ||
25 | -import com.arcsoft.arcfacedemo.model.DrawInfo; | ||
26 | -import com.arcsoft.arcfacedemo.model.FacePreviewInfo; | ||
27 | -import com.arcsoft.arcfacedemo.util.ConfigUtil; | ||
28 | -import com.arcsoft.arcfacedemo.util.DrawHelper; | ||
29 | -import com.arcsoft.arcfacedemo.util.camera.CameraHelper; | ||
30 | -import com.arcsoft.arcfacedemo.util.camera.CameraListener; | ||
31 | -import com.arcsoft.arcfacedemo.util.face.FaceHelper; | ||
32 | -import com.arcsoft.arcfacedemo.util.face.FaceListener; | ||
33 | -import com.arcsoft.arcfacedemo.util.face.LivenessType; | ||
34 | -import com.arcsoft.arcfacedemo.util.face.RecognizeColor; | ||
35 | -import com.arcsoft.arcfacedemo.util.face.RequestFeatureStatus; | ||
36 | -import com.arcsoft.arcfacedemo.util.face.RequestLivenessStatus; | ||
37 | -import com.arcsoft.arcfacedemo.widget.FaceRectView; | ||
38 | -import com.arcsoft.arcfacedemo.widget.FaceSearchResultAdapter; | ||
39 | -import com.arcsoft.face.AgeInfo; | ||
40 | -import com.arcsoft.face.ErrorInfo; | ||
41 | -import com.arcsoft.face.FaceEngine; | ||
42 | -import com.arcsoft.face.FaceFeature; | ||
43 | -import com.arcsoft.face.GenderInfo; | ||
44 | -import com.arcsoft.face.LivenessInfo; | ||
45 | -import com.arcsoft.face.enums.DetectFaceOrientPriority; | ||
46 | -import com.arcsoft.face.enums.DetectMode; | ||
47 | - | ||
48 | -import java.util.ArrayList; | ||
49 | -import java.util.Enumeration; | ||
50 | -import java.util.List; | ||
51 | -import java.util.Map; | ||
52 | -import java.util.concurrent.ConcurrentHashMap; | ||
53 | -import java.util.concurrent.TimeUnit; | ||
54 | - | ||
55 | -import io.reactivex.Observable; | ||
56 | -import io.reactivex.ObservableEmitter; | ||
57 | -import io.reactivex.ObservableOnSubscribe; | ||
58 | -import io.reactivex.Observer; | ||
59 | -import io.reactivex.android.schedulers.AndroidSchedulers; | ||
60 | -import io.reactivex.disposables.CompositeDisposable; | ||
61 | -import io.reactivex.disposables.Disposable; | ||
62 | -import io.reactivex.schedulers.Schedulers; | ||
63 | - | ||
64 | -public class RegisterAndRecognizeActivity extends BaseActivity implements ViewTreeObserver.OnGlobalLayoutListener { | ||
65 | - private static final String TAG = "RegisterAndRecognize"; | ||
66 | - private static final int MAX_DETECT_NUM = 10; | ||
67 | - /** | ||
68 | - * 当FR成功,活体未成功时,FR等待活体的时间 | ||
69 | - */ | ||
70 | - private static final int WAIT_LIVENESS_INTERVAL = 100; | ||
71 | - /** | ||
72 | - * 失败重试间隔时间(ms) | ||
73 | - */ | ||
74 | - private static final long FAIL_RETRY_INTERVAL = 1000; | ||
75 | - /** | ||
76 | - * 出错重试最大次数 | ||
77 | - */ | ||
78 | - private static final int MAX_RETRY_TIME = 3; | ||
79 | - | ||
80 | - private CameraHelper cameraHelper; | ||
81 | - private DrawHelper drawHelper; | ||
82 | - private Camera.Size previewSize; | ||
83 | - /** | ||
84 | - * 优先打开的摄像头,本界面主要用于单目RGB摄像头设备,因此默认打开前置 | ||
85 | - */ | ||
86 | - private Integer rgbCameraID = Camera.CameraInfo.CAMERA_FACING_FRONT; | ||
87 | - | ||
88 | - /** | ||
89 | - * VIDEO模式人脸检测引擎,用于预览帧人脸追踪 | ||
90 | - */ | ||
91 | - private FaceEngine ftEngine; | ||
92 | - /** | ||
93 | - * 用于特征提取的引擎 | ||
94 | - */ | ||
95 | - private FaceEngine frEngine; | ||
96 | - /** | ||
97 | - * IMAGE模式活体检测引擎,用于预览帧人脸活体检测 | ||
98 | - */ | ||
99 | - private FaceEngine flEngine; | ||
100 | - | ||
101 | - private int ftInitCode = -1; | ||
102 | - private int frInitCode = -1; | ||
103 | - private int flInitCode = -1; | ||
104 | - private FaceHelper faceHelper; | ||
105 | - private List<CompareResult> compareResultList; | ||
106 | - private FaceSearchResultAdapter adapter; | ||
107 | - /** | ||
108 | - * 活体检测的开关 | ||
109 | - */ | ||
110 | - private boolean livenessDetect = true; | ||
111 | - /** | ||
112 | - * 注册人脸状态码,准备注册 | ||
113 | - */ | ||
114 | - private static final int REGISTER_STATUS_READY = 0; | ||
115 | - /** | ||
116 | - * 注册人脸状态码,注册中 | ||
117 | - */ | ||
118 | - private static final int REGISTER_STATUS_PROCESSING = 1; | ||
119 | - /** | ||
120 | - * 注册人脸状态码,注册结束(无论成功失败) | ||
121 | - */ | ||
122 | - private static final int REGISTER_STATUS_DONE = 2; | ||
123 | - | ||
124 | - private int registerStatus = REGISTER_STATUS_DONE; | ||
125 | - /** | ||
126 | - * 用于记录人脸识别相关状态 | ||
127 | - */ | ||
128 | - private ConcurrentHashMap<Integer, Integer> requestFeatureStatusMap = new ConcurrentHashMap<>(); | ||
129 | - /** | ||
130 | - * 用于记录人脸特征提取出错重试次数 | ||
131 | - */ | ||
132 | - private ConcurrentHashMap<Integer, Integer> extractErrorRetryMap = new ConcurrentHashMap<>(); | ||
133 | - /** | ||
134 | - * 用于存储活体值 | ||
135 | - */ | ||
136 | - private ConcurrentHashMap<Integer, Integer> livenessMap = new ConcurrentHashMap<>(); | ||
137 | - /** | ||
138 | - * 用于存储活体检测出错重试次数 | ||
139 | - */ | ||
140 | - private ConcurrentHashMap<Integer, Integer> livenessErrorRetryMap = new ConcurrentHashMap<>(); | ||
141 | - | ||
142 | - private CompositeDisposable getFeatureDelayedDisposables = new CompositeDisposable(); | ||
143 | - private CompositeDisposable delayFaceTaskCompositeDisposable = new CompositeDisposable(); | ||
144 | - /** | ||
145 | - * 相机预览显示的控件,可为SurfaceView或TextureView | ||
146 | - */ | ||
147 | - private View previewView; | ||
148 | - /** | ||
149 | - * 绘制人脸框的控件 | ||
150 | - */ | ||
151 | - private FaceRectView faceRectView; | ||
152 | - | ||
153 | - private Switch switchLivenessDetect; | ||
154 | - | ||
155 | - private static final int ACTION_REQUEST_PERMISSIONS = 0x001; | ||
156 | - /** | ||
157 | - * 识别阈值 | ||
158 | - */ | ||
159 | - private static final float SIMILAR_THRESHOLD = 0.8F; | ||
160 | - /** | ||
161 | - * 所需的所有权限信息 | ||
162 | - */ | ||
163 | - private static final String[] NEEDED_PERMISSIONS = new String[]{ | ||
164 | - Manifest.permission.CAMERA, | ||
165 | - Manifest.permission.READ_PHONE_STATE | ||
166 | - | ||
167 | - }; | ||
168 | - | ||
169 | - @Override | ||
170 | - protected void onCreate(Bundle savedInstanceState) { | ||
171 | - super.onCreate(savedInstanceState); | ||
172 | - setContentView(R.layout.activity_register_and_recognize); | ||
173 | - //保持亮屏 | ||
174 | - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | ||
175 | - | ||
176 | - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | ||
177 | - WindowManager.LayoutParams attributes = getWindow().getAttributes(); | ||
178 | - attributes.systemUiVisibility = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; | ||
179 | - getWindow().setAttributes(attributes); | ||
180 | - } | ||
181 | - | ||
182 | - // Activity启动后就锁定为启动时的方向 | ||
183 | - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); | ||
184 | - //本地人脸库初始化 | ||
185 | - FaceServer.getInstance().init(this); | ||
186 | - | ||
187 | - initView(); | ||
188 | - } | ||
189 | - | ||
190 | - private void initView() { | ||
191 | - previewView = findViewById(R.id.single_camera_texture_preview); | ||
192 | - //在布局结束后才做初始化操作 | ||
193 | - previewView.getViewTreeObserver().addOnGlobalLayoutListener(this); | ||
194 | - | ||
195 | - faceRectView = findViewById(R.id.single_camera_face_rect_view); | ||
196 | - switchLivenessDetect = findViewById(R.id.single_camera_switch_liveness_detect); | ||
197 | - switchLivenessDetect.setChecked(livenessDetect); | ||
198 | - switchLivenessDetect.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { | ||
199 | - @Override | ||
200 | - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { | ||
201 | - livenessDetect = isChecked; | ||
202 | - } | ||
203 | - }); | ||
204 | - RecyclerView recyclerShowFaceInfo = findViewById(R.id.single_camera_recycler_view_person); | ||
205 | - compareResultList = new ArrayList<>(); | ||
206 | - adapter = new FaceSearchResultAdapter(compareResultList, this); | ||
207 | - recyclerShowFaceInfo.setAdapter(adapter); | ||
208 | - DisplayMetrics dm = getResources().getDisplayMetrics(); | ||
209 | - int spanCount = (int) (dm.widthPixels / (getResources().getDisplayMetrics().density * 100 + 0.5f)); | ||
210 | - recyclerShowFaceInfo.setLayoutManager(new GridLayoutManager(this, spanCount)); | ||
211 | - recyclerShowFaceInfo.setItemAnimator(new DefaultItemAnimator()); | ||
212 | - } | ||
213 | - | ||
214 | - /** | ||
215 | - * 初始化引擎 | ||
216 | - */ | ||
217 | - private void initEngine() { | ||
218 | - ftEngine = new FaceEngine(); | ||
219 | - ftInitCode = ftEngine.init(this, DetectMode.ASF_DETECT_MODE_VIDEO, ConfigUtil.getFtOrient(this), | ||
220 | - 16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_DETECT); | ||
221 | - | ||
222 | - frEngine = new FaceEngine(); | ||
223 | - frInitCode = frEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY, | ||
224 | - 16, MAX_DETECT_NUM, FaceEngine.ASF_FACE_RECOGNITION); | ||
225 | - | ||
226 | - flEngine = new FaceEngine(); | ||
227 | - flInitCode = flEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_0_ONLY, | ||
228 | - 16, MAX_DETECT_NUM, FaceEngine.ASF_LIVENESS); | ||
229 | - | ||
230 | - Log.i(TAG, "initEngine: init: " + ftInitCode); | ||
231 | - | ||
232 | - if (ftInitCode != ErrorInfo.MOK) { | ||
233 | - String error = getString(R.string.specific_engine_init_failed, "ftEngine", ftInitCode); | ||
234 | - Log.i(TAG, "initEngine: " + error); | ||
235 | - showToast(error); | ||
236 | - } | ||
237 | - if (frInitCode != ErrorInfo.MOK) { | ||
238 | - String error = getString(R.string.specific_engine_init_failed, "frEngine", frInitCode); | ||
239 | - Log.i(TAG, "initEngine: " + error); | ||
240 | - showToast(error); | ||
241 | - } | ||
242 | - if (flInitCode != ErrorInfo.MOK) { | ||
243 | - String error = getString(R.string.specific_engine_init_failed, "flEngine", flInitCode); | ||
244 | - Log.i(TAG, "initEngine: " + error); | ||
245 | - showToast(error); | ||
246 | - } | ||
247 | - } | ||
248 | - | ||
249 | - /** | ||
250 | - * 销毁引擎,faceHelper中可能会有特征提取耗时操作仍在执行,加锁防止crash | ||
251 | - */ | ||
252 | - private void unInitEngine() { | ||
253 | - if (ftInitCode == ErrorInfo.MOK && ftEngine != null) { | ||
254 | - synchronized (ftEngine) { | ||
255 | - int ftUnInitCode = ftEngine.unInit(); | ||
256 | - Log.i(TAG, "unInitEngine: " + ftUnInitCode); | ||
257 | - } | ||
258 | - } | ||
259 | - if (frInitCode == ErrorInfo.MOK && frEngine != null) { | ||
260 | - synchronized (frEngine) { | ||
261 | - int frUnInitCode = frEngine.unInit(); | ||
262 | - Log.i(TAG, "unInitEngine: " + frUnInitCode); | ||
263 | - } | ||
264 | - } | ||
265 | - if (flInitCode == ErrorInfo.MOK && flEngine != null) { | ||
266 | - synchronized (flEngine) { | ||
267 | - int flUnInitCode = flEngine.unInit(); | ||
268 | - Log.i(TAG, "unInitEngine: " + flUnInitCode); | ||
269 | - } | ||
270 | - } | ||
271 | - } | ||
272 | - | ||
273 | - | ||
274 | - @Override | ||
275 | - protected void onDestroy() { | ||
276 | - | ||
277 | - if (cameraHelper != null) { | ||
278 | - cameraHelper.release(); | ||
279 | - cameraHelper = null; | ||
280 | - } | ||
281 | - | ||
282 | - unInitEngine(); | ||
283 | - if (getFeatureDelayedDisposables != null) { | ||
284 | - getFeatureDelayedDisposables.clear(); | ||
285 | - } | ||
286 | - if (delayFaceTaskCompositeDisposable != null) { | ||
287 | - delayFaceTaskCompositeDisposable.clear(); | ||
288 | - } | ||
289 | - if (faceHelper != null) { | ||
290 | - ConfigUtil.setTrackedFaceCount(this, faceHelper.getTrackedFaceCount()); | ||
291 | - faceHelper.release(); | ||
292 | - faceHelper = null; | ||
293 | - } | ||
294 | - | ||
295 | - FaceServer.getInstance().unInit(); | ||
296 | - super.onDestroy(); | ||
297 | - } | ||
298 | - | ||
299 | - private void initCamera() { | ||
300 | - DisplayMetrics metrics = new DisplayMetrics(); | ||
301 | - getWindowManager().getDefaultDisplay().getMetrics(metrics); | ||
302 | - | ||
303 | - final FaceListener faceListener = new FaceListener() { | ||
304 | - @Override | ||
305 | - public void onFail(Exception e) { | ||
306 | - Log.e(TAG, "onFail: " + e.getMessage()); | ||
307 | - } | ||
308 | - | ||
309 | - //请求FR的回调 | ||
310 | - @Override | ||
311 | - public void onFaceFeatureInfoGet(@Nullable final FaceFeature faceFeature, final Integer requestId, final Integer errorCode) { | ||
312 | - //FR成功 | ||
313 | - if (faceFeature != null) { | ||
314 | -// Log.i(TAG, "onPreview: fr end = " + System.currentTimeMillis() + " trackId = " + requestId); | ||
315 | - Integer liveness = livenessMap.get(requestId); | ||
316 | - //不做活体检测的情况,直接搜索 | ||
317 | - if (!livenessDetect) { | ||
318 | - searchFace(faceFeature, requestId); | ||
319 | - } | ||
320 | - //活体检测通过,搜索特征 | ||
321 | - else if (liveness != null && liveness == LivenessInfo.ALIVE) { | ||
322 | - searchFace(faceFeature, requestId); | ||
323 | - } | ||
324 | - //活体检测未出结果,或者非活体,延迟执行该函数 | ||
325 | - else { | ||
326 | - if (requestFeatureStatusMap.containsKey(requestId)) { | ||
327 | - Observable.timer(WAIT_LIVENESS_INTERVAL, TimeUnit.MILLISECONDS) | ||
328 | - .subscribe(new Observer<Long>() { | ||
329 | - Disposable disposable; | ||
330 | - | ||
331 | - @Override | ||
332 | - public void onSubscribe(Disposable d) { | ||
333 | - disposable = d; | ||
334 | - getFeatureDelayedDisposables.add(disposable); | ||
335 | - } | ||
336 | - | ||
337 | - @Override | ||
338 | - public void onNext(Long aLong) { | ||
339 | - onFaceFeatureInfoGet(faceFeature, requestId, errorCode); | ||
340 | - } | ||
341 | - | ||
342 | - @Override | ||
343 | - public void onError(Throwable e) { | ||
344 | - | ||
345 | - } | ||
346 | - | ||
347 | - @Override | ||
348 | - public void onComplete() { | ||
349 | - getFeatureDelayedDisposables.remove(disposable); | ||
350 | - } | ||
351 | - }); | ||
352 | - } | ||
353 | - } | ||
354 | - | ||
355 | - } | ||
356 | - //特征提取失败 | ||
357 | - else { | ||
358 | - if (increaseAndGetValue(extractErrorRetryMap, requestId) > MAX_RETRY_TIME) { | ||
359 | - extractErrorRetryMap.put(requestId, 0); | ||
360 | - | ||
361 | - String msg; | ||
362 | - // 传入的FaceInfo在指定的图像上无法解析人脸,此处使用的是RGB人脸数据,一般是人脸模糊 | ||
363 | - if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) { | ||
364 | - msg = getString(R.string.low_confidence_level); | ||
365 | - } else { | ||
366 | - msg = "ExtractCode:" + errorCode; | ||
367 | - } | ||
368 | - faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, msg)); | ||
369 | - // 在尝试最大次数后,特征提取仍然失败,则认为识别未通过 | ||
370 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED); | ||
371 | - retryRecognizeDelayed(requestId); | ||
372 | - } else { | ||
373 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY); | ||
374 | - } | ||
375 | - } | ||
376 | - } | ||
377 | - | ||
378 | - @Override | ||
379 | - public void onFaceLivenessInfoGet(@Nullable LivenessInfo livenessInfo, final Integer requestId, Integer errorCode) { | ||
380 | - if (livenessInfo != null) { | ||
381 | - int liveness = livenessInfo.getLiveness(); | ||
382 | - livenessMap.put(requestId, liveness); | ||
383 | - // 非活体,重试 | ||
384 | - if (liveness == LivenessInfo.NOT_ALIVE) { | ||
385 | - faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_ALIVE")); | ||
386 | - // 延迟 FAIL_RETRY_INTERVAL 后,将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测 | ||
387 | - retryLivenessDetectDelayed(requestId); | ||
388 | - } | ||
389 | - } else { | ||
390 | - if (increaseAndGetValue(livenessErrorRetryMap, requestId) > MAX_RETRY_TIME) { | ||
391 | - livenessErrorRetryMap.put(requestId, 0); | ||
392 | - String msg; | ||
393 | - // 传入的FaceInfo在指定的图像上无法解析人脸,此处使用的是RGB人脸数据,一般是人脸模糊 | ||
394 | - if (errorCode != null && errorCode == ErrorInfo.MERR_FSDK_FACEFEATURE_LOW_CONFIDENCE_LEVEL) { | ||
395 | - msg = getString(R.string.low_confidence_level); | ||
396 | - } else { | ||
397 | - msg = "ProcessCode:" + errorCode; | ||
398 | - } | ||
399 | - faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, msg)); | ||
400 | - retryLivenessDetectDelayed(requestId); | ||
401 | - } else { | ||
402 | - livenessMap.put(requestId, LivenessInfo.UNKNOWN); | ||
403 | - } | ||
404 | - } | ||
405 | - } | ||
406 | - | ||
407 | - | ||
408 | - }; | ||
409 | - | ||
410 | - | ||
411 | - CameraListener cameraListener = new CameraListener() { | ||
412 | - @Override | ||
413 | - public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) { | ||
414 | - Camera.Size lastPreviewSize = previewSize; | ||
415 | - previewSize = camera.getParameters().getPreviewSize(); | ||
416 | - drawHelper = new DrawHelper(previewSize.width, previewSize.height, previewView.getWidth(), previewView.getHeight(), displayOrientation | ||
417 | - , cameraId, isMirror, false, true); | ||
418 | - Log.i(TAG, "onCameraOpened: " + drawHelper.toString()); | ||
419 | - // 切换相机的时候可能会导致预览尺寸发生变化 | ||
420 | - if (faceHelper == null || | ||
421 | - lastPreviewSize == null || | ||
422 | - lastPreviewSize.width != previewSize.width || lastPreviewSize.height != previewSize.height) { | ||
423 | - Integer trackedFaceCount = null; | ||
424 | - // 记录切换时的人脸序号 | ||
425 | - if (faceHelper != null) { | ||
426 | - trackedFaceCount = faceHelper.getTrackedFaceCount(); | ||
427 | - faceHelper.release(); | ||
428 | - } | ||
429 | - faceHelper = new FaceHelper.Builder() | ||
430 | - .ftEngine(ftEngine) | ||
431 | - .frEngine(frEngine) | ||
432 | - .flEngine(flEngine) | ||
433 | - .frQueueSize(MAX_DETECT_NUM) | ||
434 | - .flQueueSize(MAX_DETECT_NUM) | ||
435 | - .previewSize(previewSize) | ||
436 | - .faceListener(faceListener) | ||
437 | - .trackedFaceCount(trackedFaceCount == null ? ConfigUtil.getTrackedFaceCount(RegisterAndRecognizeActivity.this.getApplicationContext()) : trackedFaceCount) | ||
438 | - .build(); | ||
439 | - } | ||
440 | - } | ||
441 | - | ||
442 | - | ||
443 | - @Override | ||
444 | - public void onPreview(final byte[] nv21, Camera camera) { | ||
445 | - if (faceRectView != null) { | ||
446 | - faceRectView.clearFaceInfo(); | ||
447 | - } | ||
448 | - List<FacePreviewInfo> facePreviewInfoList = faceHelper.onPreviewFrame(nv21); | ||
449 | - if (facePreviewInfoList != null && faceRectView != null && drawHelper != null) { | ||
450 | - drawPreviewInfo(facePreviewInfoList); | ||
451 | - } | ||
452 | - registerFace(nv21, facePreviewInfoList); | ||
453 | - clearLeftFace(facePreviewInfoList); | ||
454 | - | ||
455 | - if (facePreviewInfoList != null && facePreviewInfoList.size() > 0 && previewSize != null) { | ||
456 | - for (int i = 0; i < facePreviewInfoList.size(); i++) { | ||
457 | - Integer status = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId()); | ||
458 | - /** | ||
459 | - * 在活体检测开启,在人脸识别状态不为成功或人脸活体状态不为处理中(ANALYZING)且不为处理完成(ALIVE、NOT_ALIVE)时重新进行活体检测 | ||
460 | - */ | ||
461 | - if (livenessDetect && (status == null || status != RequestFeatureStatus.SUCCEED)) { | ||
462 | - Integer liveness = livenessMap.get(facePreviewInfoList.get(i).getTrackId()); | ||
463 | - if (liveness == null | ||
464 | - || (liveness != LivenessInfo.ALIVE && liveness != LivenessInfo.NOT_ALIVE && liveness != RequestLivenessStatus.ANALYZING)) { | ||
465 | - livenessMap.put(facePreviewInfoList.get(i).getTrackId(), RequestLivenessStatus.ANALYZING); | ||
466 | - faceHelper.requestFaceLiveness(nv21, facePreviewInfoList.get(i).getFaceInfo(), previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, facePreviewInfoList.get(i).getTrackId(), LivenessType.RGB); | ||
467 | - } | ||
468 | - } | ||
469 | - /** | ||
470 | - * 对于每个人脸,若状态为空或者为失败,则请求特征提取(可根据需要添加其他判断以限制特征提取次数), | ||
471 | - * 特征提取回传的人脸特征结果在{@link FaceListener#onFaceFeatureInfoGet(FaceFeature, Integer, Integer)}中回传 | ||
472 | - */ | ||
473 | - if (status == null | ||
474 | - || status == RequestFeatureStatus.TO_RETRY) { | ||
475 | - requestFeatureStatusMap.put(facePreviewInfoList.get(i).getTrackId(), RequestFeatureStatus.SEARCHING); | ||
476 | - faceHelper.requestFaceFeature(nv21, facePreviewInfoList.get(i).getFaceInfo(), previewSize.width, previewSize.height, FaceEngine.CP_PAF_NV21, facePreviewInfoList.get(i).getTrackId()); | ||
477 | -// Log.i(TAG, "onPreview: fr start = " + System.currentTimeMillis() + " trackId = " + facePreviewInfoList.get(i).getTrackedFaceCount()); | ||
478 | - } | ||
479 | - } | ||
480 | - } | ||
481 | - } | ||
482 | - | ||
483 | - @Override | ||
484 | - public void onCameraClosed() { | ||
485 | - Log.i(TAG, "onCameraClosed: "); | ||
486 | - } | ||
487 | - | ||
488 | - @Override | ||
489 | - public void onCameraError(Exception e) { | ||
490 | - Log.i(TAG, "onCameraError: " + e.getMessage()); | ||
491 | - } | ||
492 | - | ||
493 | - @Override | ||
494 | - public void onCameraConfigurationChanged(int cameraID, int displayOrientation) { | ||
495 | - if (drawHelper != null) { | ||
496 | - drawHelper.setCameraDisplayOrientation(displayOrientation); | ||
497 | - } | ||
498 | - Log.i(TAG, "onCameraConfigurationChanged: " + cameraID + " " + displayOrientation); | ||
499 | - } | ||
500 | - }; | ||
501 | - | ||
502 | - cameraHelper = new CameraHelper.Builder() | ||
503 | - .previewViewSize(new Point(previewView.getMeasuredWidth(), previewView.getMeasuredHeight())) | ||
504 | - .rotation(getWindowManager().getDefaultDisplay().getRotation()) | ||
505 | - .specificCameraId(rgbCameraID != null ? rgbCameraID : Camera.CameraInfo.CAMERA_FACING_FRONT) | ||
506 | - .isMirror(false) | ||
507 | - .previewOn(previewView) | ||
508 | - .cameraListener(cameraListener) | ||
509 | - .build(); | ||
510 | - cameraHelper.init(); | ||
511 | - cameraHelper.start(); | ||
512 | - } | ||
513 | - | ||
514 | - private void registerFace(final byte[] nv21, final List<FacePreviewInfo> facePreviewInfoList) { | ||
515 | - if (registerStatus == REGISTER_STATUS_READY && facePreviewInfoList != null && facePreviewInfoList.size() > 0) { | ||
516 | - registerStatus = REGISTER_STATUS_PROCESSING; | ||
517 | - Observable.create(new ObservableOnSubscribe<Boolean>() { | ||
518 | - @Override | ||
519 | - public void subscribe(ObservableEmitter<Boolean> emitter) { | ||
520 | - | ||
521 | - boolean success = FaceServer.getInstance().registerNv21(RegisterAndRecognizeActivity.this, nv21.clone(), previewSize.width, previewSize.height, | ||
522 | - facePreviewInfoList.get(0).getFaceInfo(), "registered " + faceHelper.getTrackedFaceCount()); | ||
523 | - emitter.onNext(success); | ||
524 | - } | ||
525 | - }) | ||
526 | - .subscribeOn(Schedulers.computation()) | ||
527 | - .observeOn(AndroidSchedulers.mainThread()) | ||
528 | - .subscribe(new Observer<Boolean>() { | ||
529 | - @Override | ||
530 | - public void onSubscribe(Disposable d) { | ||
531 | - | ||
532 | - } | ||
533 | - | ||
534 | - @Override | ||
535 | - public void onNext(Boolean success) { | ||
536 | - String result = success ? "register success!" : "register failed!"; | ||
537 | - showToast(result); | ||
538 | - registerStatus = REGISTER_STATUS_DONE; | ||
539 | - } | ||
540 | - | ||
541 | - @Override | ||
542 | - public void onError(Throwable e) { | ||
543 | - e.printStackTrace(); | ||
544 | - showToast("register failed!"); | ||
545 | - registerStatus = REGISTER_STATUS_DONE; | ||
546 | - } | ||
547 | - | ||
548 | - @Override | ||
549 | - public void onComplete() { | ||
550 | - | ||
551 | - } | ||
552 | - }); | ||
553 | - } | ||
554 | - } | ||
555 | - | ||
556 | - private void drawPreviewInfo(List<FacePreviewInfo> facePreviewInfoList) { | ||
557 | - List<DrawInfo> drawInfoList = new ArrayList<>(); | ||
558 | - for (int i = 0; i < facePreviewInfoList.size(); i++) { | ||
559 | - String name = faceHelper.getName(facePreviewInfoList.get(i).getTrackId()); | ||
560 | - Integer liveness = livenessMap.get(facePreviewInfoList.get(i).getTrackId()); | ||
561 | - Integer recognizeStatus = requestFeatureStatusMap.get(facePreviewInfoList.get(i).getTrackId()); | ||
562 | - | ||
563 | - // 根据识别结果和活体结果设置颜色 | ||
564 | - int color = RecognizeColor.COLOR_UNKNOWN; | ||
565 | - if (recognizeStatus != null) { | ||
566 | - if (recognizeStatus == RequestFeatureStatus.FAILED) { | ||
567 | - color = RecognizeColor.COLOR_FAILED; | ||
568 | - } | ||
569 | - if (recognizeStatus == RequestFeatureStatus.SUCCEED) { | ||
570 | - color = RecognizeColor.COLOR_SUCCESS; | ||
571 | - } | ||
572 | - } | ||
573 | - if (liveness != null && liveness == LivenessInfo.NOT_ALIVE) { | ||
574 | - color = RecognizeColor.COLOR_FAILED; | ||
575 | - } | ||
576 | - | ||
577 | - drawInfoList.add(new DrawInfo(drawHelper.adjustRect(facePreviewInfoList.get(i).getFaceInfo().getRect()), | ||
578 | - GenderInfo.UNKNOWN, AgeInfo.UNKNOWN_AGE, liveness == null ? LivenessInfo.UNKNOWN : liveness, color, | ||
579 | - name == null ? String.valueOf(facePreviewInfoList.get(i).getTrackId()) : name)); | ||
580 | - } | ||
581 | - drawHelper.draw(faceRectView, drawInfoList); | ||
582 | - } | ||
583 | - | ||
584 | - @Override | ||
585 | - void afterRequestPermission(int requestCode, boolean isAllGranted) { | ||
586 | - if (requestCode == ACTION_REQUEST_PERMISSIONS) { | ||
587 | - if (isAllGranted) { | ||
588 | - initEngine(); | ||
589 | - initCamera(); | ||
590 | - } else { | ||
591 | - showToast(getString(R.string.permission_denied)); | ||
592 | - } | ||
593 | - } | ||
594 | - } | ||
595 | - | ||
596 | - /** | ||
597 | - * 删除已经离开的人脸 | ||
598 | - * | ||
599 | - * @param facePreviewInfoList 人脸和trackId列表 | ||
600 | - */ | ||
601 | - private void clearLeftFace(List<FacePreviewInfo> facePreviewInfoList) { | ||
602 | - if (compareResultList != null) { | ||
603 | - for (int i = compareResultList.size() - 1; i >= 0; i--) { | ||
604 | - if (!requestFeatureStatusMap.containsKey(compareResultList.get(i).getTrackId())) { | ||
605 | - compareResultList.remove(i); | ||
606 | - adapter.notifyItemRemoved(i); | ||
607 | - } | ||
608 | - } | ||
609 | - } | ||
610 | - if (facePreviewInfoList == null || facePreviewInfoList.size() == 0) { | ||
611 | - requestFeatureStatusMap.clear(); | ||
612 | - livenessMap.clear(); | ||
613 | - livenessErrorRetryMap.clear(); | ||
614 | - extractErrorRetryMap.clear(); | ||
615 | - if (getFeatureDelayedDisposables != null) { | ||
616 | - getFeatureDelayedDisposables.clear(); | ||
617 | - } | ||
618 | - return; | ||
619 | - } | ||
620 | - Enumeration<Integer> keys = requestFeatureStatusMap.keys(); | ||
621 | - while (keys.hasMoreElements()) { | ||
622 | - int key = keys.nextElement(); | ||
623 | - boolean contained = false; | ||
624 | - for (FacePreviewInfo facePreviewInfo : facePreviewInfoList) { | ||
625 | - if (facePreviewInfo.getTrackId() == key) { | ||
626 | - contained = true; | ||
627 | - break; | ||
628 | - } | ||
629 | - } | ||
630 | - if (!contained) { | ||
631 | - requestFeatureStatusMap.remove(key); | ||
632 | - livenessMap.remove(key); | ||
633 | - livenessErrorRetryMap.remove(key); | ||
634 | - extractErrorRetryMap.remove(key); | ||
635 | - } | ||
636 | - } | ||
637 | - | ||
638 | - | ||
639 | - } | ||
640 | - | ||
641 | - private void searchFace(final FaceFeature frFace, final Integer requestId) { | ||
642 | - Observable | ||
643 | - .create(new ObservableOnSubscribe<CompareResult>() { | ||
644 | - @Override | ||
645 | - public void subscribe(ObservableEmitter<CompareResult> emitter) { | ||
646 | -// Log.i(TAG, "subscribe: fr search start = " + System.currentTimeMillis() + " trackId = " + requestId); | ||
647 | - CompareResult compareResult = FaceServer.getInstance().getTopOfFaceLib(frFace); | ||
648 | -// Log.i(TAG, "subscribe: fr search end = " + System.currentTimeMillis() + " trackId = " + requestId); | ||
649 | - emitter.onNext(compareResult); | ||
650 | - | ||
651 | - } | ||
652 | - }) | ||
653 | - .subscribeOn(Schedulers.computation()) | ||
654 | - .observeOn(AndroidSchedulers.mainThread()) | ||
655 | - .subscribe(new Observer<CompareResult>() { | ||
656 | - @Override | ||
657 | - public void onSubscribe(Disposable d) { | ||
658 | - | ||
659 | - } | ||
660 | - | ||
661 | - @Override | ||
662 | - public void onNext(CompareResult compareResult) { | ||
663 | - if (compareResult == null || compareResult.getUserName() == null) { | ||
664 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED); | ||
665 | - faceHelper.setName(requestId, "VISITOR " + requestId); | ||
666 | - return; | ||
667 | - } | ||
668 | - | ||
669 | -// Log.i(TAG, "onNext: fr search get result = " + System.currentTimeMillis() + " trackId = " + requestId + " similar = " + compareResult.getSimilar()); | ||
670 | - if (compareResult.getSimilar() > SIMILAR_THRESHOLD) { | ||
671 | - boolean isAdded = false; | ||
672 | - if (compareResultList == null) { | ||
673 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED); | ||
674 | - faceHelper.setName(requestId, "VISITOR " + requestId); | ||
675 | - return; | ||
676 | - } | ||
677 | - for (CompareResult compareResult1 : compareResultList) { | ||
678 | - if (compareResult1.getTrackId() == requestId) { | ||
679 | - isAdded = true; | ||
680 | - break; | ||
681 | - } | ||
682 | - } | ||
683 | - if (!isAdded) { | ||
684 | - //对于多人脸搜索,假如最大显示数量为 MAX_DETECT_NUM 且有新的人脸进入,则以队列的形式移除 | ||
685 | - if (compareResultList.size() >= MAX_DETECT_NUM) { | ||
686 | - compareResultList.remove(0); | ||
687 | - adapter.notifyItemRemoved(0); | ||
688 | - } | ||
689 | - //添加显示人员时,保存其trackId | ||
690 | - compareResult.setTrackId(requestId); | ||
691 | - compareResultList.add(compareResult); | ||
692 | - adapter.notifyItemInserted(compareResultList.size() - 1); | ||
693 | - } | ||
694 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.SUCCEED); | ||
695 | - faceHelper.setName(requestId, getString(R.string.recognize_success_notice, compareResult.getUserName())); | ||
696 | - | ||
697 | - } else { | ||
698 | - faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_REGISTERED")); | ||
699 | - retryRecognizeDelayed(requestId); | ||
700 | - } | ||
701 | - } | ||
702 | - | ||
703 | - @Override | ||
704 | - public void onError(Throwable e) { | ||
705 | - faceHelper.setName(requestId, getString(R.string.recognize_failed_notice, "NOT_REGISTERED")); | ||
706 | - retryRecognizeDelayed(requestId); | ||
707 | - } | ||
708 | - | ||
709 | - @Override | ||
710 | - public void onComplete() { | ||
711 | - | ||
712 | - } | ||
713 | - }); | ||
714 | - } | ||
715 | - | ||
716 | - | ||
717 | - /** | ||
718 | - * 将准备注册的状态置为{@link #REGISTER_STATUS_READY} | ||
719 | - * | ||
720 | - * @param view 注册按钮 | ||
721 | - */ | ||
722 | - public void register(View view) { | ||
723 | - if (registerStatus == REGISTER_STATUS_DONE) { | ||
724 | - registerStatus = REGISTER_STATUS_READY; | ||
725 | - } | ||
726 | - } | ||
727 | - | ||
728 | - /** | ||
729 | - * 切换相机。注意:若切换相机发现检测不到人脸,则极有可能是检测角度导致的,需要销毁引擎重新创建或者在设置界面修改配置的检测角度 | ||
730 | - * | ||
731 | - * @param view | ||
732 | - */ | ||
733 | - public void switchCamera(View view) { | ||
734 | - if (cameraHelper != null) { | ||
735 | - boolean success = cameraHelper.switchCamera(); | ||
736 | - if (!success) { | ||
737 | - showToast(getString(R.string.switch_camera_failed)); | ||
738 | - } else { | ||
739 | - showLongToast(getString(R.string.notice_change_detect_degree)); | ||
740 | - } | ||
741 | - } | ||
742 | - } | ||
743 | - | ||
744 | - /** | ||
745 | - * 在{@link #previewView}第一次布局完成后,去除该监听,并且进行引擎和相机的初始化 | ||
746 | - */ | ||
747 | - @Override | ||
748 | - public void onGlobalLayout() { | ||
749 | - previewView.getViewTreeObserver().removeOnGlobalLayoutListener(this); | ||
750 | - if (!checkPermissions(NEEDED_PERMISSIONS)) { | ||
751 | - ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS); | ||
752 | - } else { | ||
753 | - initEngine(); | ||
754 | - initCamera(); | ||
755 | - } | ||
756 | - } | ||
757 | - | ||
758 | - /** | ||
759 | - * 将map中key对应的value增1回传 | ||
760 | - * | ||
761 | - * @param countMap map | ||
762 | - * @param key key | ||
763 | - * @return 增1后的value | ||
764 | - */ | ||
765 | - public int increaseAndGetValue(Map<Integer, Integer> countMap, int key) { | ||
766 | - if (countMap == null) { | ||
767 | - return 0; | ||
768 | - } | ||
769 | - Integer value = countMap.get(key); | ||
770 | - if (value == null) { | ||
771 | - value = 0; | ||
772 | - } | ||
773 | - countMap.put(key, ++value); | ||
774 | - return value; | ||
775 | - } | ||
776 | - | ||
777 | - /** | ||
778 | - * 延迟 FAIL_RETRY_INTERVAL 重新进行活体检测 | ||
779 | - * | ||
780 | - * @param requestId 人脸ID | ||
781 | - */ | ||
782 | - private void retryLivenessDetectDelayed(final Integer requestId) { | ||
783 | - Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS) | ||
784 | - .subscribe(new Observer<Long>() { | ||
785 | - Disposable disposable; | ||
786 | - | ||
787 | - @Override | ||
788 | - public void onSubscribe(Disposable d) { | ||
789 | - disposable = d; | ||
790 | - delayFaceTaskCompositeDisposable.add(disposable); | ||
791 | - } | ||
792 | - | ||
793 | - @Override | ||
794 | - public void onNext(Long aLong) { | ||
795 | - | ||
796 | - } | ||
797 | - | ||
798 | - @Override | ||
799 | - public void onError(Throwable e) { | ||
800 | - e.printStackTrace(); | ||
801 | - } | ||
802 | - | ||
803 | - @Override | ||
804 | - public void onComplete() { | ||
805 | - // 将该人脸状态置为UNKNOWN,帧回调处理时会重新进行活体检测 | ||
806 | - if (livenessDetect) { | ||
807 | - faceHelper.setName(requestId, Integer.toString(requestId)); | ||
808 | - } | ||
809 | - livenessMap.put(requestId, LivenessInfo.UNKNOWN); | ||
810 | - delayFaceTaskCompositeDisposable.remove(disposable); | ||
811 | - } | ||
812 | - }); | ||
813 | - } | ||
814 | - | ||
815 | - /** | ||
816 | - * 延迟 FAIL_RETRY_INTERVAL 重新进行人脸识别 | ||
817 | - * | ||
818 | - * @param requestId 人脸ID | ||
819 | - */ | ||
820 | - private void retryRecognizeDelayed(final Integer requestId) { | ||
821 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.FAILED); | ||
822 | - Observable.timer(FAIL_RETRY_INTERVAL, TimeUnit.MILLISECONDS) | ||
823 | - .subscribe(new Observer<Long>() { | ||
824 | - Disposable disposable; | ||
825 | - | ||
826 | - @Override | ||
827 | - public void onSubscribe(Disposable d) { | ||
828 | - disposable = d; | ||
829 | - delayFaceTaskCompositeDisposable.add(disposable); | ||
830 | - } | ||
831 | - | ||
832 | - @Override | ||
833 | - public void onNext(Long aLong) { | ||
834 | - | ||
835 | - } | ||
836 | - | ||
837 | - @Override | ||
838 | - public void onError(Throwable e) { | ||
839 | - e.printStackTrace(); | ||
840 | - } | ||
841 | - | ||
842 | - @Override | ||
843 | - public void onComplete() { | ||
844 | - // 将该人脸特征提取状态置为FAILED,帧回调处理时会重新进行活体检测 | ||
845 | - faceHelper.setName(requestId, Integer.toString(requestId)); | ||
846 | - requestFeatureStatusMap.put(requestId, RequestFeatureStatus.TO_RETRY); | ||
847 | - delayFaceTaskCompositeDisposable.remove(disposable); | ||
848 | - } | ||
849 | - }); | ||
850 | - } | ||
851 | -} |
1 | -package com.arcsoft.arcfacedemo.activity; | ||
2 | - | ||
3 | -import android.Manifest; | ||
4 | -import android.content.Intent; | ||
5 | -import android.graphics.Bitmap; | ||
6 | -import android.graphics.BitmapFactory; | ||
7 | -import android.graphics.Canvas; | ||
8 | -import android.graphics.Color; | ||
9 | -import android.graphics.Paint; | ||
10 | -import android.graphics.Typeface; | ||
11 | -import android.os.Build; | ||
12 | -import android.os.Bundle; | ||
13 | -import android.provider.MediaStore; | ||
14 | -import android.support.annotation.Nullable; | ||
15 | -import android.support.v4.app.ActivityCompat; | ||
16 | -import android.support.v7.app.AlertDialog; | ||
17 | -import android.text.ParcelableSpan; | ||
18 | -import android.text.SpannableStringBuilder; | ||
19 | -import android.text.Spanned; | ||
20 | -import android.text.style.ForegroundColorSpan; | ||
21 | -import android.text.style.StyleSpan; | ||
22 | -import android.util.Log; | ||
23 | -import android.view.View; | ||
24 | -import android.widget.ImageView; | ||
25 | -import android.widget.ProgressBar; | ||
26 | -import android.widget.TextView; | ||
27 | - | ||
28 | -import com.arcsoft.arcfacedemo.R; | ||
29 | -import com.arcsoft.face.AgeInfo; | ||
30 | -import com.arcsoft.face.ErrorInfo; | ||
31 | -import com.arcsoft.face.Face3DAngle; | ||
32 | -import com.arcsoft.face.FaceEngine; | ||
33 | -import com.arcsoft.face.FaceFeature; | ||
34 | -import com.arcsoft.face.FaceInfo; | ||
35 | -import com.arcsoft.face.FaceSimilar; | ||
36 | -import com.arcsoft.face.GenderInfo; | ||
37 | -import com.arcsoft.face.LivenessInfo; | ||
38 | -import com.arcsoft.face.VersionInfo; | ||
39 | -import com.arcsoft.face.enums.CompareModel; | ||
40 | -import com.arcsoft.face.enums.DetectFaceOrientPriority; | ||
41 | -import com.arcsoft.face.enums.DetectMode; | ||
42 | -import com.arcsoft.face.enums.DetectModel; | ||
43 | -import com.arcsoft.face.model.ArcSoftImageInfo; | ||
44 | -import com.arcsoft.face.util.ImageUtils; | ||
45 | -import com.arcsoft.imageutil.ArcSoftImageFormat; | ||
46 | -import com.arcsoft.imageutil.ArcSoftImageUtil; | ||
47 | -import com.arcsoft.imageutil.ArcSoftImageUtilError; | ||
48 | -import com.bumptech.glide.Glide; | ||
49 | - | ||
50 | -import java.io.IOException; | ||
51 | -import java.util.ArrayList; | ||
52 | -import java.util.Arrays; | ||
53 | -import java.util.List; | ||
54 | - | ||
55 | -import io.reactivex.Observable; | ||
56 | -import io.reactivex.ObservableEmitter; | ||
57 | -import io.reactivex.ObservableOnSubscribe; | ||
58 | -import io.reactivex.Observer; | ||
59 | -import io.reactivex.android.schedulers.AndroidSchedulers; | ||
60 | -import io.reactivex.disposables.Disposable; | ||
61 | -import io.reactivex.schedulers.Schedulers; | ||
62 | - | ||
63 | - | ||
64 | -public class SingleImageActivity extends BaseActivity { | ||
65 | - private static final String TAG = "SingleImageActivity"; | ||
66 | - private ImageView ivShow; | ||
67 | - private TextView tvNotice; | ||
68 | - private FaceEngine faceEngine; | ||
69 | - private int faceEngineCode = -1; | ||
70 | - /** | ||
71 | - * 请求权限的请求码 | ||
72 | - */ | ||
73 | - private static final int ACTION_REQUEST_PERMISSIONS = 0x001; | ||
74 | - /** | ||
75 | - * 请求选择本地图片文件的请求码 | ||
76 | - */ | ||
77 | - private static final int ACTION_CHOOSE_IMAGE = 0x201; | ||
78 | - /** | ||
79 | - * 提示对话框 | ||
80 | - */ | ||
81 | - private AlertDialog progressDialog; | ||
82 | - /** | ||
83 | - * 被处理的图片 | ||
84 | - */ | ||
85 | - private Bitmap mBitmap = null; | ||
86 | - | ||
87 | - /** | ||
88 | - * 所需的所有权限信息 | ||
89 | - */ | ||
90 | - private static String[] NEEDED_PERMISSIONS = new String[]{ | ||
91 | - Manifest.permission.READ_PHONE_STATE | ||
92 | - }; | ||
93 | - | ||
94 | - @Override | ||
95 | - protected void onCreate(Bundle savedInstanceState) { | ||
96 | - super.onCreate(savedInstanceState); | ||
97 | - setContentView(R.layout.activity_image_process); | ||
98 | - initView(); | ||
99 | - /** | ||
100 | - * 在选择图片的时候,在android 7.0及以上通过FileProvider获取Uri,不需要文件权限 | ||
101 | - */ | ||
102 | - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { | ||
103 | - List<String> permissionList = new ArrayList<>(Arrays.asList(NEEDED_PERMISSIONS)); | ||
104 | - permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE); | ||
105 | - NEEDED_PERMISSIONS = permissionList.toArray(new String[0]); | ||
106 | - } | ||
107 | - | ||
108 | - if (!checkPermissions(NEEDED_PERMISSIONS)) { | ||
109 | - ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS); | ||
110 | - } else { | ||
111 | - initEngine(); | ||
112 | - } | ||
113 | - | ||
114 | - } | ||
115 | - | ||
116 | - private void initEngine() { | ||
117 | - faceEngine = new FaceEngine(); | ||
118 | - faceEngineCode = faceEngine.init(this, DetectMode.ASF_DETECT_MODE_IMAGE, DetectFaceOrientPriority.ASF_OP_ALL_OUT, | ||
119 | - 16, 10, FaceEngine.ASF_FACE_RECOGNITION | FaceEngine.ASF_FACE_DETECT | FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_LIVENESS); | ||
120 | - Log.i(TAG, "initEngine: init: " + faceEngineCode); | ||
121 | - | ||
122 | - if (faceEngineCode != ErrorInfo.MOK) { | ||
123 | - showToast(getString(R.string.init_failed, faceEngineCode)); | ||
124 | - } | ||
125 | - } | ||
126 | - | ||
127 | - /** | ||
128 | - * 销毁引擎 | ||
129 | - */ | ||
130 | - private void unInitEngine() { | ||
131 | - if (faceEngine != null) { | ||
132 | - faceEngineCode = faceEngine.unInit(); | ||
133 | - faceEngine = null; | ||
134 | - Log.i(TAG, "unInitEngine: " + faceEngineCode); | ||
135 | - } | ||
136 | - } | ||
137 | - | ||
138 | - @Override | ||
139 | - protected void onDestroy() { | ||
140 | - if (mBitmap != null && !mBitmap.isRecycled()) { | ||
141 | - mBitmap.recycle(); | ||
142 | - } | ||
143 | - mBitmap = null; | ||
144 | - | ||
145 | - if (progressDialog != null && progressDialog.isShowing()) { | ||
146 | - progressDialog.dismiss(); | ||
147 | - } | ||
148 | - progressDialog = null; | ||
149 | - | ||
150 | - unInitEngine(); | ||
151 | - super.onDestroy(); | ||
152 | - } | ||
153 | - | ||
154 | - private void initView() { | ||
155 | - tvNotice = findViewById(R.id.tv_notice); | ||
156 | - ivShow = findViewById(R.id.iv_show); | ||
157 | - ivShow.setImageResource(R.drawable.faces); | ||
158 | - progressDialog = new AlertDialog.Builder(this) | ||
159 | - .setTitle(R.string.processing) | ||
160 | - .setView(new ProgressBar(this)) | ||
161 | - .create(); | ||
162 | - } | ||
163 | - | ||
164 | - /** | ||
165 | - * 按钮点击响应事件 | ||
166 | - * | ||
167 | - * @param view | ||
168 | - */ | ||
169 | - public void process(final View view) { | ||
170 | - | ||
171 | - view.setClickable(false); | ||
172 | - if (progressDialog == null || progressDialog.isShowing()) { | ||
173 | - return; | ||
174 | - } | ||
175 | - progressDialog.show(); | ||
176 | - //图像转化操作和部分引擎调用比较耗时,建议放子线程操作 | ||
177 | - Observable.create(new ObservableOnSubscribe<Object>() { | ||
178 | - @Override | ||
179 | - public void subscribe(ObservableEmitter<Object> emitter) throws Exception { | ||
180 | - processImage(); | ||
181 | - emitter.onComplete(); | ||
182 | - } | ||
183 | - }) | ||
184 | - .subscribeOn(Schedulers.computation()) | ||
185 | - .observeOn(AndroidSchedulers.mainThread()) | ||
186 | - .subscribe(new Observer<Object>() { | ||
187 | - @Override | ||
188 | - public void onSubscribe(Disposable d) { | ||
189 | - | ||
190 | - } | ||
191 | - | ||
192 | - @Override | ||
193 | - public void onNext(Object o) { | ||
194 | - | ||
195 | - } | ||
196 | - | ||
197 | - @Override | ||
198 | - public void onError(Throwable e) { | ||
199 | - e.printStackTrace(); | ||
200 | - } | ||
201 | - | ||
202 | - @Override | ||
203 | - public void onComplete() { | ||
204 | - view.setClickable(true); | ||
205 | - } | ||
206 | - }); | ||
207 | - } | ||
208 | - | ||
209 | - | ||
210 | - /** | ||
211 | - * 主要操作逻辑部分 | ||
212 | - */ | ||
213 | - public void processImage() { | ||
214 | - /** | ||
215 | - * 1.准备操作(校验,显示,获取BGR) | ||
216 | - */ | ||
217 | - if (mBitmap == null) { | ||
218 | - mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.faces); | ||
219 | - } | ||
220 | - // 图像对齐 | ||
221 | - Bitmap bitmap = ArcSoftImageUtil.getAlignedBitmap(mBitmap, true); | ||
222 | - | ||
223 | - final SpannableStringBuilder notificationSpannableStringBuilder = new SpannableStringBuilder(); | ||
224 | - if (faceEngineCode != ErrorInfo.MOK) { | ||
225 | - addNotificationInfo(notificationSpannableStringBuilder, null, " face engine not initialized!"); | ||
226 | - showNotificationAndFinish(notificationSpannableStringBuilder); | ||
227 | - return; | ||
228 | - } | ||
229 | - if (bitmap == null) { | ||
230 | - addNotificationInfo(notificationSpannableStringBuilder, null, " bitmap is null!"); | ||
231 | - showNotificationAndFinish(notificationSpannableStringBuilder); | ||
232 | - return; | ||
233 | - } | ||
234 | - if (faceEngine == null) { | ||
235 | - addNotificationInfo(notificationSpannableStringBuilder, null, " faceEngine is null!"); | ||
236 | - showNotificationAndFinish(notificationSpannableStringBuilder); | ||
237 | - return; | ||
238 | - } | ||
239 | - | ||
240 | - int width = bitmap.getWidth(); | ||
241 | - int height = bitmap.getHeight(); | ||
242 | - final Bitmap finalBitmap = bitmap; | ||
243 | - runOnUiThread(new Runnable() { | ||
244 | - @Override | ||
245 | - public void run() { | ||
246 | - Glide.with(ivShow.getContext()) | ||
247 | - .load(finalBitmap) | ||
248 | - .into(ivShow); | ||
249 | - } | ||
250 | - }); | ||
251 | - | ||
252 | - // bitmap转bgr24 | ||
253 | - long start = System.currentTimeMillis(); | ||
254 | - byte[] bgr24 = ArcSoftImageUtil.createImageData(bitmap.getWidth(), bitmap.getHeight(), ArcSoftImageFormat.BGR24); | ||
255 | - int transformCode = ArcSoftImageUtil.bitmapToImageData(bitmap, bgr24, ArcSoftImageFormat.BGR24); | ||
256 | - if (transformCode != ArcSoftImageUtilError.CODE_SUCCESS) { | ||
257 | - Log.e(TAG, "transform failed, code is " + transformCode); | ||
258 | - addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "transform bitmap To ImageData failed", "code is ", String.valueOf(transformCode), "\n"); | ||
259 | - return; | ||
260 | - } | ||
261 | -// Log.i(TAG, "processImage:bitmapToBgr24 cost = " + (System.currentTimeMillis() - start)); | ||
262 | - addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "start face detection,imageWidth is ", String.valueOf(width), ", imageHeight is ", String.valueOf(height), "\n"); | ||
263 | - | ||
264 | - List<FaceInfo> faceInfoList = new ArrayList<>(); | ||
265 | - | ||
266 | - /** | ||
267 | - * 2.成功获取到了BGR24 数据,开始人脸检测 | ||
268 | - */ | ||
269 | - long fdStartTime = System.currentTimeMillis(); | ||
270 | -// ArcSoftImageInfo arcSoftImageInfo = new ArcSoftImageInfo(width,height,FaceEngine.CP_PAF_BGR24,new byte[][]{bgr24},new int[]{width * 3}); | ||
271 | -// Log.i(TAG, "processImage: " + arcSoftImageInfo.getPlanes()[0].length); | ||
272 | -// int detectCode = faceEngine.detectFaces(arcSoftImageInfo, faceInfoList); | ||
273 | - int detectCode = faceEngine.detectFaces(bgr24, width, height, FaceEngine.CP_PAF_BGR24, DetectModel.RGB, faceInfoList); | ||
274 | - if (detectCode == ErrorInfo.MOK) { | ||
275 | -// Log.i(TAG, "processImage: fd costTime = " + (System.currentTimeMillis() - fdStartTime)); | ||
276 | - } | ||
277 | - | ||
278 | - //绘制bitmap | ||
279 | - Bitmap bitmapForDraw = bitmap.copy(Bitmap.Config.RGB_565, true); | ||
280 | - Canvas canvas = new Canvas(bitmapForDraw); | ||
281 | - Paint paint = new Paint(); | ||
282 | - addNotificationInfo(notificationSpannableStringBuilder, null, "detect result:\nerrorCode is :", String.valueOf(detectCode), " face Number is ", String.valueOf(faceInfoList.size()), "\n"); | ||
283 | - /** | ||
284 | - * 3.若检测结果人脸数量大于0,则在bitmap上绘制人脸框并且重新显示到ImageView,若人脸数量为0,则无法进行下一步操作,操作结束 | ||
285 | - */ | ||
286 | - if (faceInfoList.size() > 0) { | ||
287 | - addNotificationInfo(notificationSpannableStringBuilder, null, "face list:\n"); | ||
288 | - paint.setAntiAlias(true); | ||
289 | - paint.setStrokeWidth(5); | ||
290 | - paint.setColor(Color.YELLOW); | ||
291 | - for (int i = 0; i < faceInfoList.size(); i++) { | ||
292 | - //绘制人脸框 | ||
293 | - paint.setStyle(Paint.Style.STROKE); | ||
294 | - canvas.drawRect(faceInfoList.get(i).getRect(), paint); | ||
295 | - //绘制人脸序号 | ||
296 | - paint.setStyle(Paint.Style.FILL_AND_STROKE); | ||
297 | - int textSize = faceInfoList.get(i).getRect().width() / 2; | ||
298 | - paint.setTextSize(textSize); | ||
299 | - | ||
300 | - canvas.drawText(String.valueOf(i), faceInfoList.get(i).getRect().left, faceInfoList.get(i).getRect().top, paint); | ||
301 | - addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:", faceInfoList.get(i).toString(), "\n"); | ||
302 | - } | ||
303 | - //显示 | ||
304 | - final Bitmap finalBitmapForDraw = bitmapForDraw; | ||
305 | - runOnUiThread(new Runnable() { | ||
306 | - @Override | ||
307 | - public void run() { | ||
308 | - Glide.with(ivShow.getContext()) | ||
309 | - .load(finalBitmapForDraw) | ||
310 | - .into(ivShow); | ||
311 | - } | ||
312 | - }); | ||
313 | - } else { | ||
314 | - addNotificationInfo(notificationSpannableStringBuilder, null, "can not do further action, exit!"); | ||
315 | - showNotificationAndFinish(notificationSpannableStringBuilder); | ||
316 | - return; | ||
317 | - } | ||
318 | - addNotificationInfo(notificationSpannableStringBuilder, null, "\n"); | ||
319 | - | ||
320 | - | ||
321 | - /** | ||
322 | - * 4.上一步已获取到人脸位置和角度信息,传入给process函数,进行年龄、性别、三维角度、活体检测 | ||
323 | - */ | ||
324 | - | ||
325 | - long processStartTime = System.currentTimeMillis(); | ||
326 | - int faceProcessCode = faceEngine.process(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList, FaceEngine.ASF_AGE | FaceEngine.ASF_GENDER | FaceEngine.ASF_FACE3DANGLE | FaceEngine.ASF_LIVENESS); | ||
327 | - | ||
328 | - if (faceProcessCode != ErrorInfo.MOK) { | ||
329 | - addNotificationInfo(notificationSpannableStringBuilder, new ForegroundColorSpan(Color.RED), "process failed! code is ", String.valueOf(faceProcessCode), "\n"); | ||
330 | - } else { | ||
331 | -// Log.i(TAG, "processImage: process costTime = " + (System.currentTimeMillis() - processStartTime)); | ||
332 | - } | ||
333 | - //年龄信息结果 | ||
334 | - List<AgeInfo> ageInfoList = new ArrayList<>(); | ||
335 | - //性别信息结果 | ||
336 | - List<GenderInfo> genderInfoList = new ArrayList<>(); | ||
337 | - //人脸三维角度结果 | ||
338 | - List<Face3DAngle> face3DAngleList = new ArrayList<>(); | ||
339 | - //活体检测结果 | ||
340 | - List<LivenessInfo> livenessInfoList = new ArrayList<>(); | ||
341 | - //获取年龄、性别、三维角度、活体结果 | ||
342 | - int ageCode = faceEngine.getAge(ageInfoList); | ||
343 | - int genderCode = faceEngine.getGender(genderInfoList); | ||
344 | - int face3DAngleCode = faceEngine.getFace3DAngle(face3DAngleList); | ||
345 | - int livenessCode = faceEngine.getLiveness(livenessInfoList); | ||
346 | - | ||
347 | - if ((ageCode | genderCode | face3DAngleCode | livenessCode) != ErrorInfo.MOK) { | ||
348 | - addNotificationInfo(notificationSpannableStringBuilder, null, "at least one of age,gender,face3DAngle detect failed!,codes are:", | ||
349 | - String.valueOf(ageCode), " , ", String.valueOf(genderCode), " , ", String.valueOf(face3DAngleCode)); | ||
350 | - showNotificationAndFinish(notificationSpannableStringBuilder); | ||
351 | - return; | ||
352 | - } | ||
353 | - /** | ||
354 | - * 5.年龄、性别、三维角度已获取成功,添加信息到提示文字中 | ||
355 | - */ | ||
356 | - //年龄数据 | ||
357 | - if (ageInfoList.size() > 0) { | ||
358 | - addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "age of each face:\n"); | ||
359 | - } | ||
360 | - for (int i = 0; i < ageInfoList.size(); i++) { | ||
361 | - addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:", String.valueOf(ageInfoList.get(i).getAge()), "\n"); | ||
362 | - } | ||
363 | - addNotificationInfo(notificationSpannableStringBuilder, null, "\n"); | ||
364 | - | ||
365 | - //性别数据 | ||
366 | - if (genderInfoList.size() > 0) { | ||
367 | - addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "gender of each face:\n"); | ||
368 | - } | ||
369 | - for (int i = 0; i < genderInfoList.size(); i++) { | ||
370 | - addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:" | ||
371 | - , genderInfoList.get(i).getGender() == GenderInfo.MALE ? | ||
372 | - "MALE" : (genderInfoList.get(i).getGender() == GenderInfo.FEMALE ? "FEMALE" : "UNKNOWN"), "\n"); | ||
373 | - } | ||
374 | - addNotificationInfo(notificationSpannableStringBuilder, null, "\n"); | ||
375 | - | ||
376 | - | ||
377 | - //人脸三维角度数据 | ||
378 | - if (face3DAngleList.size() > 0) { | ||
379 | - addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "face3DAngle of each face:\n"); | ||
380 | - for (int i = 0; i < face3DAngleList.size(); i++) { | ||
381 | - addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:", face3DAngleList.get(i).toString(), "\n"); | ||
382 | - } | ||
383 | - } | ||
384 | - addNotificationInfo(notificationSpannableStringBuilder, null, "\n"); | ||
385 | - | ||
386 | - //活体检测数据 | ||
387 | - if (livenessInfoList.size() > 0) { | ||
388 | - addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "liveness of each face:\n"); | ||
389 | - for (int i = 0; i < livenessInfoList.size(); i++) { | ||
390 | - String liveness = null; | ||
391 | - switch (livenessInfoList.get(i).getLiveness()) { | ||
392 | - case LivenessInfo.ALIVE: | ||
393 | - liveness = "ALIVE"; | ||
394 | - break; | ||
395 | - case LivenessInfo.NOT_ALIVE: | ||
396 | - liveness = "NOT_ALIVE"; | ||
397 | - break; | ||
398 | - case LivenessInfo.UNKNOWN: | ||
399 | - liveness = "UNKNOWN"; | ||
400 | - break; | ||
401 | - case LivenessInfo.FACE_NUM_MORE_THAN_ONE: | ||
402 | - liveness = "FACE_NUM_MORE_THAN_ONE"; | ||
403 | - break; | ||
404 | - default: | ||
405 | - liveness = "UNKNOWN"; | ||
406 | - break; | ||
407 | - } | ||
408 | - addNotificationInfo(notificationSpannableStringBuilder, null, "face[", String.valueOf(i), "]:", liveness, "\n"); | ||
409 | - } | ||
410 | - } | ||
411 | - addNotificationInfo(notificationSpannableStringBuilder, null, "\n"); | ||
412 | - | ||
413 | - /** | ||
414 | - * 6.最后将图片内的所有人脸进行一一比对并添加到提示文字中 | ||
415 | - */ | ||
416 | - if (faceInfoList.size() > 0) { | ||
417 | - | ||
418 | - FaceFeature[] faceFeatures = new FaceFeature[faceInfoList.size()]; | ||
419 | - int[] extractFaceFeatureCodes = new int[faceInfoList.size()]; | ||
420 | - | ||
421 | - addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "faceFeatureExtract:\n"); | ||
422 | - for (int i = 0; i < faceInfoList.size(); i++) { | ||
423 | - faceFeatures[i] = new FaceFeature(); | ||
424 | - //从图片解析出人脸特征数据 | ||
425 | - long frStartTime = System.currentTimeMillis(); | ||
426 | - extractFaceFeatureCodes[i] = faceEngine.extractFaceFeature(bgr24, width, height, FaceEngine.CP_PAF_BGR24, faceInfoList.get(i), faceFeatures[i]); | ||
427 | - | ||
428 | - if (extractFaceFeatureCodes[i] != ErrorInfo.MOK) { | ||
429 | - addNotificationInfo(notificationSpannableStringBuilder, null, "faceFeature of face[", String.valueOf(i), "]", | ||
430 | - " extract failed, code is ", String.valueOf(extractFaceFeatureCodes[i]), "\n"); | ||
431 | - } else { | ||
432 | -// Log.i(TAG, "processImage: fr costTime = " + (System.currentTimeMillis() - frStartTime)); | ||
433 | - addNotificationInfo(notificationSpannableStringBuilder, null, "faceFeature of face[", String.valueOf(i), "]", | ||
434 | - " extract success\n"); | ||
435 | - } | ||
436 | - } | ||
437 | - addNotificationInfo(notificationSpannableStringBuilder, null, "\n"); | ||
438 | - | ||
439 | - //人脸特征的数量大于2,将所有特征进行比较 | ||
440 | - if (faceFeatures.length >= 2) { | ||
441 | - | ||
442 | - addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD), "similar of faces:\n"); | ||
443 | - | ||
444 | - for (int i = 0; i < faceFeatures.length; i++) { | ||
445 | - for (int j = i + 1; j < faceFeatures.length; j++) { | ||
446 | - addNotificationInfo(notificationSpannableStringBuilder, new StyleSpan(Typeface.BOLD_ITALIC), "compare face[", String.valueOf(i), "] and face[" | ||
447 | - , String.valueOf(j), "]:\n"); | ||
448 | - //若其中一个特征提取失败,则不进行比对 | ||
449 | - boolean canCompare = true; | ||
450 | - if (extractFaceFeatureCodes[i] != 0) { | ||
451 | - addNotificationInfo(notificationSpannableStringBuilder, null, "faceFeature of face[", String.valueOf(i), "] extract failed, can not compare!\n"); | ||
452 | - canCompare = false; | ||
453 | - } | ||
454 | - if (extractFaceFeatureCodes[j] != 0) { | ||
455 | - addNotificationInfo(notificationSpannableStringBuilder, null, "faceFeature of face[", String.valueOf(j), "] extract failed, can not compare!\n"); | ||
456 | - canCompare = false; | ||
457 | - } | ||
458 | - if (!canCompare) { | ||
459 | - continue; | ||
460 | - } | ||
461 | - | ||
462 | - FaceSimilar matching = new FaceSimilar(); | ||
463 | - //比对两个人脸特征获取相似度信息 | ||
464 | - faceEngine.compareFaceFeature(faceFeatures[i], faceFeatures[j], CompareModel.LIFE_PHOTO, matching); | ||
465 | - //新增相似度比对结果信息 | ||
466 | - addNotificationInfo(notificationSpannableStringBuilder, null, "similar of face[", String.valueOf(i), "] and face[", | ||
467 | - String.valueOf(j), "] is:", String.valueOf(matching.getScore()), "\n"); | ||
468 | - } | ||
469 | - } | ||
470 | - } | ||
471 | - } | ||
472 | - | ||
473 | - showNotificationAndFinish(notificationSpannableStringBuilder); | ||
474 | - | ||
475 | - } | ||
476 | - | ||
477 | - /** | ||
478 | - * 展示提示信息并且关闭提示框 | ||
479 | - * | ||
480 | - * @param stringBuilder 带格式的提示文字 | ||
481 | - */ | ||
482 | - private void showNotificationAndFinish(final SpannableStringBuilder stringBuilder) { | ||
483 | - runOnUiThread(new Runnable() { | ||
484 | - @Override | ||
485 | - public void run() { | ||
486 | - if (tvNotice != null) { | ||
487 | - tvNotice.setText(stringBuilder); | ||
488 | - } | ||
489 | - if (progressDialog != null && progressDialog.isShowing()) { | ||
490 | - progressDialog.dismiss(); | ||
491 | - } | ||
492 | - } | ||
493 | - }); | ||
494 | - } | ||
495 | - | ||
496 | - /** | ||
497 | - * 追加提示信息 | ||
498 | - * | ||
499 | - * @param stringBuilder 提示的字符串的存放对象 | ||
500 | - * @param styleSpan 添加的字符串的格式 | ||
501 | - * @param strings 字符串数组 | ||
502 | - */ | ||
503 | - private void addNotificationInfo(SpannableStringBuilder stringBuilder, ParcelableSpan styleSpan, String... strings) { | ||
504 | - if (stringBuilder == null || strings == null || strings.length == 0) { | ||
505 | - return; | ||
506 | - } | ||
507 | - int startLength = stringBuilder.length(); | ||
508 | - for (String string : strings) { | ||
509 | - stringBuilder.append(string); | ||
510 | - } | ||
511 | - int endLength = stringBuilder.length(); | ||
512 | - if (styleSpan != null) { | ||
513 | - stringBuilder.setSpan(styleSpan, startLength, endLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||
514 | - } | ||
515 | - } | ||
516 | - | ||
517 | - /** | ||
518 | - * 从本地选择文件 | ||
519 | - * | ||
520 | - * @param view | ||
521 | - */ | ||
522 | - public void chooseLocalImage(View view) { | ||
523 | - Intent intent = new Intent(Intent.ACTION_PICK); | ||
524 | - intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); | ||
525 | - startActivityForResult(intent, ACTION_CHOOSE_IMAGE); | ||
526 | - } | ||
527 | - | ||
528 | - @Override | ||
529 | - protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { | ||
530 | - super.onActivityResult(requestCode, resultCode, data); | ||
531 | - if (requestCode == ACTION_CHOOSE_IMAGE) { | ||
532 | - if (data == null || data.getData() == null) { | ||
533 | - showToast(getString(R.string.get_picture_failed)); | ||
534 | - return; | ||
535 | - } | ||
536 | - try { | ||
537 | - mBitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), data.getData()); | ||
538 | - } catch (IOException e) { | ||
539 | - e.printStackTrace(); | ||
540 | - return; | ||
541 | - } | ||
542 | - if (mBitmap == null) { | ||
543 | - showToast(getString(R.string.get_picture_failed)); | ||
544 | - return; | ||
545 | - } | ||
546 | - Glide.with(ivShow.getContext()) | ||
547 | - .load(mBitmap) | ||
548 | - .into(ivShow); | ||
549 | - } | ||
550 | - } | ||
551 | - | ||
552 | - @Override | ||
553 | - void afterRequestPermission(int requestCode, boolean isAllGranted) { | ||
554 | - if (requestCode == ACTION_REQUEST_PERMISSIONS) { | ||
555 | - if (isAllGranted) { | ||
556 | - initEngine(); | ||
557 | - } else { | ||
558 | - showToast(getString(R.string.permission_denied)); | ||
559 | - } | ||
560 | - } | ||
561 | - } | ||
562 | -} |
1 | -<resources> | ||
2 | - <!--app名称--> | ||
3 | - <string name="app_name">ArcFaceDemo</string> | ||
4 | - | ||
5 | - <!--配置、功能选择界面--> | ||
6 | - <string name="page_preview">人脸属性检测(视频)</string> | ||
7 | - <string name="page_ir_preview">红外活体检测(视频)</string> | ||
8 | - <string name="page_face_recognize">人脸比对1:n(视频vs人脸库,RGB活体)</string> | ||
9 | - <string name="page_ir_face_recognize">人脸比对1:n(视频vs人脸库,IR活体)</string> | ||
10 | - <string name="page_single_image">人脸属性检测(图片)</string> | ||
11 | - <string name="page_multi_image">人脸比对1:n(图片vs图片)</string> | ||
12 | - <string name="page_face_manage">人脸批量注册管理</string> | ||
13 | - <string name="get_device_finger_failed">获取设备指纹失败,错误码:%d</string> | ||
14 | - <string name="choose_detect_degree">选择视频模式检测角度</string> | ||
15 | - <string name="copy_device_fingerprint">复制设备指纹信息</string> | ||
16 | - <string name="device_fingerprint_copied">设备指纹信息:\n%s\n已复制</string> | ||
17 | - <string name="active_engine">激活引擎</string> | ||
18 | - <string name="active_success">激活引擎成功</string> | ||
19 | - <string name="already_activated">引擎已激活,无需再次激活</string> | ||
20 | - <string name="active_failed">引擎激活失败,错误码为 %d</string> | ||
21 | - <string name="library_not_found">未找到库文件,请检查是否有将.so文件放至工程的 app\\src\\main\\jniLibs 目录下</string> | ||
22 | - <string name="ft_op_0">视频模式仅检测0度</string> | ||
23 | - <string name="ft_op_90">视频模式仅检测90度</string> | ||
24 | - <string name="ft_op_180">视频模式仅检测180度</string> | ||
25 | - <string name="ft_op_270">视频模式仅检测270度</string> | ||
26 | - <string name="ft_op_all">视频模式全方向人脸检测</string> | ||
27 | - | ||
28 | - <!--人脸属性检测(图片) 界面--> | ||
29 | - <string name="start_process">开始处理</string> | ||
30 | - <string name="choose_local_image">选择本地图片</string> | ||
31 | - <string name="processing">处理中</string> | ||
32 | - | ||
33 | - <!--人脸比对1:n(图片vs图片) 界面--> | ||
34 | - <string name="choose_main_image">选择主图</string> | ||
35 | - <string name="add_item_image">添加比对图</string> | ||
36 | - <string name="notice_choose_main_img">请先选择主图</string> | ||
37 | - <string name="compare_failed">比对失败,错误码为 %d</string> | ||
38 | - | ||
39 | - <!--各个界面获取本地图片失败提示--> | ||
40 | - <string name="get_picture_failed">获取图片失败</string> | ||
41 | - | ||
42 | - <!--各个界面获取权限失败时的提示--> | ||
43 | - <string name="permission_denied">权限被拒绝!</string> | ||
44 | - | ||
45 | - <!--各个界面引擎初始化失败的提示--> | ||
46 | - <string name="init_failed">引擎初始化失败,错误码为 %d</string> | ||
47 | - <string name="engine_not_initialized">引擎未初始化,错误码为 %d</string> | ||
48 | - | ||
49 | - <!--单目、双目识别注册界面--> | ||
50 | - <string name="register">注册</string> | ||
51 | - <string name="switch_camera">切换相机</string> | ||
52 | - <string name="switch_camera_failed">切换相机失败</string> | ||
53 | - <string name="recognize_failed_notice">未通过:%s</string> | ||
54 | - <string name="recognize_success_notice">通过:%s</string> | ||
55 | - <string name="low_confidence_level">人脸置信度低</string> | ||
56 | - <string name="specific_engine_init_failed">%s 初始化失败,错误码为:%d</string> | ||
57 | - <string name="notice_change_detect_degree">相机已切换,若无法检测到人脸,需要在首页修改视频模式人脸检测角度</string> | ||
58 | - <string name="liveness_detect">活体检测</string> | ||
59 | - <string name="camera_rgb">RGB CAMERA</string> | ||
60 | - <string name="camera_ir">IR CAMERA</string> | ||
61 | - <string name="camera_rgb_preview_size">RGB CAMERA\n%dx%d</string> | ||
62 | - <string name="camera_ir_preview_size">IR CAMERA\n%dx%d</string> | ||
63 | - <string name="camera_error_notice">\n可能的原因:该设备不支持同时打开两个摄像头</string> | ||
64 | - <string name="draw_ir_rect_mirror_horizontal">IR人脸框水平镜像绘制</string> | ||
65 | - <string name="draw_ir_rect_mirror_vertical">IR人脸框垂直镜像绘制</string> | ||
66 | - | ||
67 | - <!--人脸批量注册进度框--> | ||
68 | - <string name="progress_dialog_batch_register">进度: %d / %d</string> | ||
69 | - <string name="progress_dialog_registering_please_wait">注册中,请稍等</string> | ||
70 | - | ||
71 | - <!--批量处理--> | ||
72 | - <string name="ok">确认</string> | ||
73 | - <string name="cancel">取消</string> | ||
74 | - <string name="batch_process_notification_register">请将需要注册的图片放在\nsdcard/arcfacedemo/register\n目录下</string> | ||
75 | - <string name="batch_process_no_face_need_to_delete">无人脸需要删除</string> | ||
76 | - <string name="batch_process_confirm_delete">确认删除这%d张图片及人脸特征?</string> | ||
77 | - <string name="batch_process_batch_register">批量注册</string> | ||
78 | - <string name="batch_process_notification">提示</string> | ||
79 | - <string name="batch_process_clear_faces">清空人脸库</string> | ||
80 | - <string name="batch_process_path_is_not_exists">路径 \n%s\n 不存在</string> | ||
81 | - <string name="batch_process_path_is_not_dir">路径 \n%s\n 不是文件夹</string> | ||
82 | - <string name="batch_process_processing_please_wait">处理中,请稍等</string> | ||
83 | - <string name="batch_process_finished_info">处理完成!\n处理总数 = %d \n成功数 = %d \n失败数 = %d \n处理失败的图片已保存在文件夹 \' %s \'</string> | ||
84 | - | ||
85 | - | ||
86 | - | ||
87 | -</resources> |
1 | <resources> | 1 | <resources> |
2 | - <!--app name--> | 2 | + <!--app名称--> |
3 | <string name="app_name">ArcFaceDemo</string> | 3 | <string name="app_name">ArcFaceDemo</string> |
4 | 4 | ||
5 | - <!--settings、choose function page--> | ||
6 | - <string name="page_preview">face attributes detect(video)</string> | ||
7 | - <string name="page_ir_preview">ir liveness detect(video)</string> | ||
8 | - <string name="page_face_recognize">face compare 1:n(video vs database,rgb liveness detect)</string> | ||
9 | - <string name="page_ir_face_recognize">face compare 1:n(video vs database,ir liveness detect)</string> | ||
10 | - <string name="page_single_image">face attributes detect(image)</string> | ||
11 | - <string name="page_multi_image">face compare 1:n(image vs image)</string> | ||
12 | - <string name="page_face_manage">face manage</string> | ||
13 | - <string name="get_device_finger_failed">get device finger failed, code is: %d</string> | ||
14 | - <string name="choose_detect_degree">choose video-mode detect degree</string> | ||
15 | - <string name="copy_device_fingerprint">copy device fingerprint</string> | ||
16 | - <string name="device_fingerprint_copied">device fingerprint::\n%s\n copied</string> | ||
17 | - <string name="active_engine">active engine</string> | ||
18 | - <string name="active_success">active success</string> | ||
19 | - <string name="already_activated">already activated</string> | ||
20 | - <string name="active_failed">active failed,code is %d</string> | ||
21 | - <string name="library_not_found">library not found, please check if you put .so files into the project directory app\\src\\main\\jniLibs</string> | ||
22 | - <string name="ft_op_0">only detect 0 degree in video mode</string> | ||
23 | - <string name="ft_op_90">only detect 90 degree in video mode</string> | ||
24 | - <string name="ft_op_180">only detect 180 degree in video mode</string> | ||
25 | - <string name="ft_op_270">only detect 270 degree in video mode</string> | ||
26 | - <string name="ft_op_all">detect all degrees in video mode</string> | 5 | + <!--配置、功能选择界面--> |
6 | + <string name="page_preview">人脸属性检测(视频)</string> | ||
7 | + <string name="page_ir_preview">红外活体检测(视频)</string> | ||
8 | + <string name="page_face_recognize">人脸比对1:n(视频vs人脸库,RGB活体)</string> | ||
9 | + <string name="page_ir_face_recognize">人脸比对1:n(视频vs人脸库,IR活体)</string> | ||
10 | + <string name="page_single_image">人脸属性检测(图片)</string> | ||
11 | + <string name="page_multi_image">人脸比对1:n(图片vs图片)</string> | ||
12 | + <string name="page_face_manage">人脸批量注册管理</string> | ||
13 | + <string name="get_device_finger_failed">获取设备指纹失败,错误码:%d</string> | ||
14 | + <string name="choose_detect_degree">选择视频模式检测角度</string> | ||
15 | + <string name="copy_device_fingerprint">复制设备指纹信息</string> | ||
16 | + <string name="device_fingerprint_copied">设备指纹信息:\n%s\n已复制</string> | ||
17 | + <string name="active_engine">激活引擎</string> | ||
18 | + <string name="active_success">激活引擎成功</string> | ||
19 | + <string name="already_activated">引擎已激活,无需再次激活</string> | ||
20 | + <string name="active_failed">引擎激活失败,错误码为 %d</string> | ||
21 | + <string name="library_not_found">未找到库文件,请检查是否有将.so文件放至工程的 app\\src\\main\\jniLibs 目录下</string> | ||
22 | + <string name="ft_op_0">视频模式仅检测0度</string> | ||
23 | + <string name="ft_op_90">视频模式仅检测90度</string> | ||
24 | + <string name="ft_op_180">视频模式仅检测180度</string> | ||
25 | + <string name="ft_op_270">视频模式仅检测270度</string> | ||
26 | + <string name="ft_op_all">视频模式全方向人脸检测</string> | ||
27 | 27 | ||
28 | - <!--face attributes detect(image) page--> | ||
29 | - <string name="start_process">process</string> | ||
30 | - <string name="choose_local_image">choose local image</string> | ||
31 | - <string name="processing">processing</string> | 28 | + <!--人脸属性检测(图片) 界面--> |
29 | + <string name="start_process">开始处理</string> | ||
30 | + <string name="choose_local_image">选择本地图片</string> | ||
31 | + <string name="processing">处理中</string> | ||
32 | 32 | ||
33 | - <!--face compare 1:n(image vs image) page--> | ||
34 | - <string name="choose_main_image">choose main image</string> | ||
35 | - <string name="add_item_image">add item image</string> | ||
36 | - <string name="notice_choose_main_img">please choose main image first</string> | ||
37 | - <string name="compare_failed">compare failed!code is %d</string> | 33 | + <!--人脸比对1:n(图片vs图片) 界面--> |
34 | + <string name="choose_main_image">选择主图</string> | ||
35 | + <string name="add_item_image">添加比对图</string> | ||
36 | + <string name="notice_choose_main_img">请先选择主图</string> | ||
37 | + <string name="compare_failed">比对失败,错误码为 %d</string> | ||
38 | 38 | ||
39 | - <!--global get picture failed notification--> | ||
40 | - <string name="get_picture_failed">failed to get picture!</string> | 39 | + <!--各个界面获取本地图片失败提示--> |
40 | + <string name="get_picture_failed">获取图片失败</string> | ||
41 | 41 | ||
42 | - <!--global permission denied notification--> | ||
43 | - <string name="permission_denied">permission denied!</string> | 42 | + <!--各个界面获取权限失败时的提示--> |
43 | + <string name="permission_denied">权限被拒绝!</string> | ||
44 | 44 | ||
45 | - <!--global init failed notification--> | ||
46 | - <string name="init_failed">init failed,code is %d</string> | ||
47 | - <string name="engine_not_initialized">engine not initialized! code is %d</string> | 45 | + <!--各个界面引擎初始化失败的提示--> |
46 | + <string name="init_failed">引擎初始化失败,错误码为 %d</string> | ||
47 | + <string name="engine_not_initialized">引擎未初始化,错误码为 %d</string> | ||
48 | 48 | ||
49 | - <!--single/dual camera register&recognize page--> | ||
50 | - <string name="register">register</string> | ||
51 | - <string name="switch_camera">switch camera</string> | ||
52 | - <string name="switch_camera_failed">switch camera failed</string> | ||
53 | - <string name="notice_change_detect_degree">camera switched, if no face detected, please change face detect degree in homepage</string> | ||
54 | - <string name="recognize_failed_notice">DENIED:%s</string> | ||
55 | - <string name="recognize_success_notice">PASS:%s</string> | ||
56 | - <string name="low_confidence_level">face low confidence level</string> | ||
57 | - <string name="specific_engine_init_failed">%s init failed, code is:%d</string> | ||
58 | - <string name="liveness_detect">liveness detect</string> | 49 | + <!--单目、双目识别注册界面--> |
50 | + <string name="register">注册</string> | ||
51 | + <string name="switch_camera">切换相机</string> | ||
52 | + <string name="switch_camera_failed">切换相机失败</string> | ||
53 | + <string name="recognize_failed_notice">未通过:%s</string> | ||
54 | + <string name="recognize_success_notice">通过:%s</string> | ||
55 | + <string name="low_confidence_level">人脸置信度低</string> | ||
56 | + <string name="specific_engine_init_failed">%s 初始化失败,错误码为:%d</string> | ||
57 | + <string name="notice_change_detect_degree">相机已切换,若无法检测到人脸,需要在首页修改视频模式人脸检测角度</string> | ||
58 | + <string name="liveness_detect">活体检测</string> | ||
59 | <string name="camera_rgb">RGB CAMERA</string> | 59 | <string name="camera_rgb">RGB CAMERA</string> |
60 | <string name="camera_ir">IR CAMERA</string> | 60 | <string name="camera_ir">IR CAMERA</string> |
61 | <string name="camera_rgb_preview_size">RGB CAMERA\n%dx%d</string> | 61 | <string name="camera_rgb_preview_size">RGB CAMERA\n%dx%d</string> |
62 | <string name="camera_ir_preview_size">IR CAMERA\n%dx%d</string> | 62 | <string name="camera_ir_preview_size">IR CAMERA\n%dx%d</string> |
63 | - <string name="camera_error_notice">\npossible reason:open two cameras at the same time is not allowed on this device</string> | ||
64 | - <string name="draw_ir_rect_mirror_horizontal">draw IR face rect mirror horizontal</string> | ||
65 | - <string name="draw_ir_rect_mirror_vertical">draw IR face rect mirror vertical</string> | 63 | + <string name="camera_error_notice">\n可能的原因:该设备不支持同时打开两个摄像头</string> |
64 | + <string name="draw_ir_rect_mirror_horizontal">IR人脸框水平镜像绘制</string> | ||
65 | + <string name="draw_ir_rect_mirror_vertical">IR人脸框垂直镜像绘制</string> | ||
66 | 66 | ||
67 | - <!--batch process dialog--> | ||
68 | - <string name="progress_dialog_batch_register">progress: %d / %d</string> | ||
69 | - <string name="progress_dialog_registering_please_wait">registering,please wait</string> | 67 | + <!--人脸批量注册进度框--> |
68 | + <string name="progress_dialog_batch_register">进度: %d / %d</string> | ||
69 | + <string name="progress_dialog_registering_please_wait">注册中,请稍等</string> | ||
70 | 70 | ||
71 | - <!--batch process--> | ||
72 | - <string name="ok">OK</string> | ||
73 | - <string name="cancel">Cancel</string> | ||
74 | - <string name="batch_process_notification_register">please put the images that need to register into directory \nsdcard/arcfacedemo/register</string> | ||
75 | - <string name="batch_process_no_face_need_to_delete">no face need to delete</string> | ||
76 | - <string name="batch_process_confirm_delete">delete those %d images and face features?</string> | ||
77 | - <string name="batch_process_batch_register">batch register</string> | ||
78 | - <string name="batch_process_notification">notification</string> | ||
79 | - <string name="batch_process_clear_faces">clear faces</string> | ||
80 | - <string name="batch_process_path_is_not_exists">path \n%s\n is not exists</string> | ||
81 | - <string name="batch_process_path_is_not_dir">path \n%s\n is not a directory</string> | ||
82 | - <string name="batch_process_processing_please_wait">process start,please wait</string> | ||
83 | - <string name="batch_process_finished_info">process finished!\ntotal count = %d \nsuccess count = %d \nfailed count = %d \nfailed images are in directory \' %s \'</string> | 71 | + <!--批量处理--> |
72 | + <string name="ok">确认</string> | ||
73 | + <string name="cancel">取消</string> | ||
74 | + <string name="batch_process_notification_register">请将需要注册的图片放在\nsdcard/arcfacedemo/register\n目录下</string> | ||
75 | + <string name="batch_process_no_face_need_to_delete">无人脸需要删除</string> | ||
76 | + <string name="batch_process_confirm_delete">确认删除这%d张图片及人脸特征?</string> | ||
77 | + <string name="batch_process_batch_register">批量注册</string> | ||
78 | + <string name="batch_process_notification">提示</string> | ||
79 | + <string name="batch_process_clear_faces">清空人脸库</string> | ||
80 | + <string name="batch_process_path_is_not_exists">路径 \n%s\n 不存在</string> | ||
81 | + <string name="batch_process_path_is_not_dir">路径 \n%s\n 不是文件夹</string> | ||
82 | + <string name="batch_process_processing_please_wait">处理中,请稍等</string> | ||
83 | + <string name="batch_process_finished_info">处理完成!\n处理总数 = %d \n成功数 = %d \n失败数 = %d \n处理失败的图片已保存在文件夹 \' %s \'</string> | ||
84 | 84 | ||
85 | 85 | ||
86 | 86 |
1 | -package com.arcsoft.arcfacedemo; | ||
2 | - | ||
3 | -import org.junit.Test; | ||
4 | - | ||
5 | -import static org.junit.Assert.*; | ||
6 | - | ||
7 | -/** | ||
8 | - * Example local unit test, which will execute on the development machine (host). | ||
9 | - * | ||
10 | - * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | ||
11 | - */ | ||
12 | -public class ExampleUnitTest { | ||
13 | - @Test | ||
14 | - public void addition_isCorrect() { | ||
15 | - assertEquals(4, 2 + 2); | ||
16 | - } | ||
17 | -} |
@@ -43,5 +43,5 @@ android { | @@ -43,5 +43,5 @@ android { | ||
43 | 43 | ||
44 | dependencies { | 44 | dependencies { |
45 | api fileTree(include: ['*.jar'], dir: 'libs') | 45 | api fileTree(include: ['*.jar'], dir: 'libs') |
46 | - implementation 'org.jetbrains:annotations-java5:15.0' | 46 | + implementation 'org.jetbrains:annotations:13.0' |
47 | } | 47 | } |
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | 1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
2 | package="com.baidu.idl.main.facesdk"> | 2 | package="com.baidu.idl.main.facesdk"> |
3 | 3 | ||
4 | + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||
4 | <application android:allowBackup="false" | 5 | <application android:allowBackup="false" |
5 | android:label="@string/app_name" | 6 | android:label="@string/app_name" |
6 | android:supportsRtl="true"> | 7 | android:supportsRtl="true"> |
1 | +/* | ||
2 | + * Copyright (C) 2018 Baidu, Inc. All Rights Reserved. | ||
3 | + */ | ||
4 | +package com.baidu.idl.main.facesdk.api; | ||
5 | + | ||
6 | +import android.graphics.Bitmap; | ||
7 | +import android.text.TextUtils; | ||
8 | + | ||
9 | +import com.baidu.idl.main.facesdk.FaceInfo; | ||
10 | +import com.baidu.idl.main.facesdk.db.DBManager; | ||
11 | +import com.baidu.idl.main.facesdk.manager.FaceSDKManager; | ||
12 | +import com.baidu.idl.main.facesdk.model.BDFaceImageInstance; | ||
13 | +import com.baidu.idl.main.facesdk.model.BDFaceSDKCommon; | ||
14 | +import com.baidu.idl.main.facesdk.model.Feature; | ||
15 | +import com.baidu.idl.main.facesdk.model.Group; | ||
16 | +import com.baidu.idl.main.facesdk.model.ResponseGetRecords; | ||
17 | +import com.baidu.idl.main.facesdk.model.User; | ||
18 | + | ||
19 | +import java.util.ArrayList; | ||
20 | +import java.util.List; | ||
21 | +import java.util.UUID; | ||
22 | +import java.util.concurrent.ExecutorService; | ||
23 | +import java.util.concurrent.Executors; | ||
24 | +import java.util.concurrent.Future; | ||
25 | +import java.util.regex.Matcher; | ||
26 | +import java.util.regex.Pattern; | ||
27 | + | ||
28 | +public class FaceApi { | ||
29 | + private static FaceApi instance; | ||
30 | + private ExecutorService es = Executors.newSingleThreadExecutor(); | ||
31 | + private Future future; | ||
32 | + | ||
33 | + private int mUserNum; | ||
34 | + private boolean isinitSuccess = false; | ||
35 | + | ||
36 | + | ||
37 | + private FaceApi() { | ||
38 | + | ||
39 | + } | ||
40 | + | ||
41 | + public static synchronized FaceApi getInstance() { | ||
42 | + if (instance == null) { | ||
43 | + instance = new FaceApi(); | ||
44 | + } | ||
45 | + return instance; | ||
46 | + } | ||
47 | + | ||
48 | + /** | ||
49 | + * 添加用户组 | ||
50 | + */ | ||
51 | + public boolean groupAdd(Group group) { | ||
52 | + if (group == null || TextUtils.isEmpty(group.getGroupId())) { | ||
53 | + return false; | ||
54 | + } | ||
55 | + Pattern pattern = Pattern.compile("^[0-9a-zA-Z_-]{1,}$"); | ||
56 | + Matcher matcher = pattern.matcher(group.getGroupId()); | ||
57 | + if (!matcher.matches()) { | ||
58 | + return false; | ||
59 | + } | ||
60 | + boolean ret = DBManager.getInstance().addGroup(group); | ||
61 | + | ||
62 | + return ret; | ||
63 | + } | ||
64 | + | ||
65 | + /** | ||
66 | + * 查询用户组(默认最多取1000个组) | ||
67 | + */ | ||
68 | + public List<Group> getGroupList(int start, int length) { | ||
69 | + if (start < 0 || length < 0) { | ||
70 | + return null; | ||
71 | + } | ||
72 | + if (length > 1000) { | ||
73 | + length = 1000; | ||
74 | + } | ||
75 | + List<Group> groupList = DBManager.getInstance().queryGroups(start, length); | ||
76 | + return groupList; | ||
77 | + } | ||
78 | + | ||
79 | + /** | ||
80 | + * 根据groupId查询用户组 | ||
81 | + */ | ||
82 | + public List<Group> getGroupListByGroupId(String groupId) { | ||
83 | + if (TextUtils.isEmpty(groupId)) { | ||
84 | + return null; | ||
85 | + } | ||
86 | + return DBManager.getInstance().queryGroupsByGroupId(groupId); | ||
87 | + } | ||
88 | + | ||
89 | + /** | ||
90 | + * 根据groupId删除用户组 | ||
91 | + */ | ||
92 | + public boolean groupDelete(String groupId) { | ||
93 | + if (TextUtils.isEmpty(groupId)) { | ||
94 | + return false; | ||
95 | + } | ||
96 | + boolean ret = DBManager.getInstance().deleteGroup(groupId); | ||
97 | + return ret; | ||
98 | + } | ||
99 | + | ||
100 | + /** | ||
101 | + * 添加用户 | ||
102 | + */ | ||
103 | + public boolean userAdd(User user) { | ||
104 | + if (user == null || TextUtils.isEmpty(user.getGroupId())) { | ||
105 | + return false; | ||
106 | + } | ||
107 | + Pattern pattern = Pattern.compile("^[0-9a-zA-Z_-]{1,}$"); | ||
108 | + Matcher matcher = pattern.matcher(user.getUserId()); | ||
109 | + if (!matcher.matches()) { | ||
110 | + return false; | ||
111 | + } | ||
112 | + boolean ret = DBManager.getInstance().addUser(user); | ||
113 | + | ||
114 | + return ret; | ||
115 | + } | ||
116 | + | ||
117 | + /** | ||
118 | + * 根据groupId查找用户 | ||
119 | + */ | ||
120 | + public List<User> getUserList(String groupId) { | ||
121 | + if (TextUtils.isEmpty(groupId)) { | ||
122 | + return null; | ||
123 | + } | ||
124 | + List<User> userList = DBManager.getInstance().queryUserByGroupId(groupId); | ||
125 | + return userList; | ||
126 | + } | ||
127 | + | ||
128 | + /** | ||
129 | + * 根据groupId、userName查找用户 | ||
130 | + */ | ||
131 | + public List<User> getUserListByUserName(String groupId, String userName) { | ||
132 | + if (TextUtils.isEmpty(groupId) || TextUtils.isEmpty(userName)) { | ||
133 | + return null; | ||
134 | + } | ||
135 | + List<User> userList = DBManager.getInstance().queryUserByUserName(groupId, userName); | ||
136 | + return userList; | ||
137 | + } | ||
138 | + | ||
139 | + /** | ||
140 | + * 根据_id查找用户 | ||
141 | + */ | ||
142 | + public User getUserListById(int _id) { | ||
143 | + if (_id < 0) { | ||
144 | + return null; | ||
145 | + } | ||
146 | + List<User> userList = DBManager.getInstance().queryUserById(_id); | ||
147 | + if (userList != null && userList.size() > 0) { | ||
148 | + return userList.get(0); | ||
149 | + } | ||
150 | + return null; | ||
151 | + } | ||
152 | + | ||
153 | + /** | ||
154 | + * 更新用户 | ||
155 | + */ | ||
156 | + public boolean userUpdate(User user) { | ||
157 | + if (user == null) { | ||
158 | + return false; | ||
159 | + } | ||
160 | + | ||
161 | + boolean ret = DBManager.getInstance().updateUser(user); | ||
162 | + return ret; | ||
163 | + } | ||
164 | + | ||
165 | + /** | ||
166 | + * 更新用户 | ||
167 | + */ | ||
168 | + public boolean userUpdate(String groupId, String userName, String imageName, byte[] feature) { | ||
169 | + if (groupId == null || userName == null || imageName == null || feature == null) { | ||
170 | + return false; | ||
171 | + } | ||
172 | + | ||
173 | + boolean ret = DBManager.getInstance().updateUser(groupId, userName, imageName, feature); | ||
174 | + return ret; | ||
175 | + } | ||
176 | + | ||
177 | + /** | ||
178 | + * 删除用户 | ||
179 | + */ | ||
180 | + public boolean userDelete(String userId, String groupId) { | ||
181 | + if (TextUtils.isEmpty(userId) || TextUtils.isEmpty(groupId)) { | ||
182 | + return false; | ||
183 | + } | ||
184 | + | ||
185 | + boolean ret = DBManager.getInstance().deleteUser(userId, groupId); | ||
186 | + return ret; | ||
187 | + } | ||
188 | + | ||
189 | + /** | ||
190 | + * 远程删除用户 | ||
191 | + */ | ||
192 | + public boolean userDeleteByName(String userName, String groupId) { | ||
193 | + if (TextUtils.isEmpty(userName) || TextUtils.isEmpty(groupId)) { | ||
194 | + return false; | ||
195 | + } | ||
196 | + | ||
197 | + boolean ret = DBManager.getInstance().userDeleteByName(userName, groupId); | ||
198 | + return ret; | ||
199 | + } | ||
200 | + | ||
201 | + /** | ||
202 | + * 是否是有效姓名 | ||
203 | + * | ||
204 | + * @param username 用户名 | ||
205 | + * @return 有效或无效信息 | ||
206 | + */ | ||
207 | + public String isValidName(String username) { | ||
208 | + if (username == null || "".equals(username.trim())) { | ||
209 | + return "姓名为空"; | ||
210 | + } | ||
211 | + | ||
212 | + // 姓名过长 | ||
213 | + if (username.length() > 10) { | ||
214 | + return "姓名过长"; | ||
215 | + } | ||
216 | + | ||
217 | + // 含有特殊符号 | ||
218 | + String regex = "[ _`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()—" | ||
219 | + + "—+|{}【】‘;:”“’。,、?]|\n|\r|\t"; | ||
220 | + Pattern p = Pattern.compile(regex); | ||
221 | + Matcher m = p.matcher(username); | ||
222 | + if (m.find()) { | ||
223 | + return "姓名中含有特殊符号"; | ||
224 | + } | ||
225 | + return "0"; | ||
226 | + } | ||
227 | + | ||
228 | + /** | ||
229 | + * 提取特征值 | ||
230 | + */ | ||
231 | + public float getFeature(Bitmap bitmap, byte[] feature, BDFaceSDKCommon.FeatureType featureType) { | ||
232 | + if (bitmap == null) { | ||
233 | + return -1; | ||
234 | + } | ||
235 | + | ||
236 | + BDFaceImageInstance imageInstance = new BDFaceImageInstance(bitmap); | ||
237 | + // 最大检测人脸,获取人脸信息 | ||
238 | + FaceInfo[] faceInfos = FaceSDKManager.getInstance().getFaceDetect() | ||
239 | + .detect(BDFaceSDKCommon.DetectType.DETECT_VIS, imageInstance); | ||
240 | + float ret = -1; | ||
241 | + if (faceInfos != null && faceInfos.length > 0) { | ||
242 | + FaceInfo faceInfo = faceInfos[0]; | ||
243 | + // 人脸识别,提取人脸特征值 | ||
244 | + ret = FaceSDKManager.getInstance().getFaceFeature().feature( | ||
245 | + featureType, imageInstance, | ||
246 | + faceInfo.landmarks, feature); | ||
247 | + } | ||
248 | + imageInstance.destory(); | ||
249 | + return ret; | ||
250 | + } | ||
251 | + | ||
252 | + | ||
253 | + public boolean registerUserIntoDBmanager(String groupName, String userName, String picName, | ||
254 | + String userInfo, byte[] faceFeature) { | ||
255 | + boolean isSuccess = false; | ||
256 | + | ||
257 | + Group group = new Group(); | ||
258 | + group.setGroupId(groupName); | ||
259 | + | ||
260 | + User user = new User(); | ||
261 | + user.setGroupId(groupName); | ||
262 | + /* | ||
263 | + * 用户id(由数字、字母、下划线组成),长度限制128B | ||
264 | + * uid为用户的id,百度对uid不做限制和处理,应该与您的帐号系统中的用户id对应。 | ||
265 | + */ | ||
266 | + final String uid = UUID.randomUUID().toString(); | ||
267 | + user.setUserId(uid); | ||
268 | + user.setUserName(userName); | ||
269 | + user.setFeature(faceFeature); | ||
270 | + user.setImageName(picName); | ||
271 | + if (userInfo != null) { | ||
272 | + user.setUserInfo(userInfo); | ||
273 | + } | ||
274 | + // 添加用户信息到数据库 | ||
275 | + boolean importUserSuccess = FaceApi.getInstance().userAdd(user); | ||
276 | + if (importUserSuccess) { | ||
277 | + // 如果添加到数据库成功,则添加用户组信息到数据库 | ||
278 | + // 如果当前图片组名和上一张图片组名相同,则不添加数据库到组表 | ||
279 | + if (FaceApi.getInstance().groupAdd(group)) { | ||
280 | + isSuccess = true; | ||
281 | + } else { | ||
282 | + isSuccess = false; | ||
283 | + } | ||
284 | + } else { | ||
285 | + isSuccess = false; | ||
286 | + } | ||
287 | + | ||
288 | + return isSuccess; | ||
289 | + } | ||
290 | + | ||
291 | + /** | ||
292 | + * 获取底库数量 | ||
293 | + * | ||
294 | + * @return | ||
295 | + */ | ||
296 | + public int getmUserNum() { | ||
297 | + return mUserNum; | ||
298 | + } | ||
299 | + | ||
300 | + public boolean isinitSuccess() { | ||
301 | + return isinitSuccess; | ||
302 | + } | ||
303 | + | ||
304 | + /** | ||
305 | + * 数据库发现变化时候,重新把数据库中的人脸信息添加到内存中,id+feature | ||
306 | + */ | ||
307 | + public void initDatabases(final boolean isFeaturePush) { | ||
308 | + | ||
309 | + if (future != null && !future.isDone()) { | ||
310 | + future.cancel(true); | ||
311 | + } | ||
312 | + | ||
313 | + isinitSuccess = false; | ||
314 | + future = es.submit(new Runnable() { | ||
315 | + @Override | ||
316 | + public void run() { | ||
317 | + List<Group> listGroup = FaceApi.getInstance().getGroupList(0, 100); | ||
318 | + if (listGroup != null && listGroup.size() > 0) { | ||
319 | + ArrayList<Feature> features = new ArrayList<>(); | ||
320 | + for (int i = 0; i < listGroup.size(); i++) { | ||
321 | + List<User> listUser = FaceApi.getInstance().getUserList(listGroup.get(i).getGroupId()); | ||
322 | + for (int j = 0; j < listUser.size(); j++) { | ||
323 | + Feature feature = new Feature(); | ||
324 | + feature.setId(listUser.get(j).getId()); | ||
325 | + feature.setFeature(listUser.get(j).getFeature()); | ||
326 | + features.add(feature); | ||
327 | + } | ||
328 | + } | ||
329 | + if (isFeaturePush) { | ||
330 | + FaceSDKManager.getInstance().getFaceFeature().featurePush(features); | ||
331 | + } | ||
332 | + mUserNum = features.size(); | ||
333 | + } | ||
334 | + isinitSuccess = true; | ||
335 | + } | ||
336 | + }); | ||
337 | + } | ||
338 | + | ||
339 | + | ||
340 | + // 查询识别记录 | ||
341 | + public List<ResponseGetRecords> getRecords(String startTime, String endTime) { | ||
342 | +// if (TextUtils.isEmpty(startTime) || TextUtils.isEmpty(endTime)) { | ||
343 | +// return null; | ||
344 | +// } | ||
345 | + | ||
346 | + List<ResponseGetRecords> responseGetRecords = DBManager.getInstance().queryRecords(startTime, endTime); | ||
347 | + if (responseGetRecords != null && responseGetRecords.size() > 0) { | ||
348 | + return responseGetRecords; | ||
349 | + } | ||
350 | + | ||
351 | + return null; | ||
352 | + } | ||
353 | + | ||
354 | + // 添加识别记录 | ||
355 | + public boolean addRecords(ResponseGetRecords responseGetRecords) { | ||
356 | + boolean ret = false; | ||
357 | + if (responseGetRecords == null) { | ||
358 | + return ret; | ||
359 | + } | ||
360 | + ret = DBManager.getInstance().addResponseGetRecords(responseGetRecords); | ||
361 | + return ret; | ||
362 | + } | ||
363 | + | ||
364 | + // 删除识别记录 | ||
365 | + public boolean deleteRecords(String userName) { | ||
366 | + boolean ret = false; | ||
367 | + if (TextUtils.isEmpty(userName)) { | ||
368 | + return ret; | ||
369 | + } | ||
370 | + ret = DBManager.getInstance().deleteRecords(userName); | ||
371 | + return ret; | ||
372 | + } | ||
373 | + | ||
374 | + // 删除识别记录 | ||
375 | + public boolean deleteRecords(String startTime, String endTime) { | ||
376 | + boolean ret = false; | ||
377 | + if (TextUtils.isEmpty(startTime) && TextUtils.isEmpty(endTime)) { | ||
378 | + return ret; | ||
379 | + } | ||
380 | + ret = DBManager.getInstance().deleteRecords(startTime, endTime); | ||
381 | + return ret; | ||
382 | + } | ||
383 | + | ||
384 | + // 清除识别记录 | ||
385 | + public int cleanRecords() { | ||
386 | + boolean ret = false; | ||
387 | + int num = DBManager.getInstance().cleanRecords(); | ||
388 | + return num; | ||
389 | + } | ||
390 | + | ||
391 | + | ||
392 | +} |
1 | +package com.baidu.idl.main.facesdk.callback; | ||
2 | + | ||
3 | +import android.hardware.Camera; | ||
4 | + | ||
5 | +/** | ||
6 | + * Time: 2019/1/25 | ||
7 | + * Author: v_chaixiaogang | ||
8 | + * Description: camera1数据结果回调 | ||
9 | + */ | ||
10 | +public interface CameraDataCallback { | ||
11 | + /** | ||
12 | + * @param data 预览数据 | ||
13 | + * @param camera 相机设备 | ||
14 | + * @param width 预览宽 | ||
15 | + * @param height 预览高 | ||
16 | + */ | ||
17 | + void onGetCameraData(byte[] data, Camera camera, int width, int height); | ||
18 | +} |
1 | +/* | ||
2 | + * Copyright (C) 2018 Baidu, Inc. All Rights Reserved. | ||
3 | + */ | ||
4 | +package com.baidu.idl.main.facesdk.callback; | ||
5 | + | ||
6 | + | ||
7 | +import com.baidu.idl.main.facesdk.model.LivenessModel; | ||
8 | + | ||
9 | +/** | ||
10 | + * 人脸检测回调接口。 | ||
11 | + * | ||
12 | + * @Time: 2019/1/25 | ||
13 | + * @Author: v_chaixiaogang | ||
14 | + */ | ||
15 | +public interface FaceDetectCallBack { | ||
16 | + void onFaceDetectCallback(LivenessModel livenessModel); | ||
17 | + | ||
18 | + void onTip(int code, String msg); | ||
19 | + | ||
20 | + void onFaceDetectDarwCallback(LivenessModel livenessModel); | ||
21 | +} |
1 | +/* | ||
2 | + * Copyright (C) 2019 Baidu, Inc. All Rights Reserved. | ||
3 | + */ | ||
4 | +package com.baidu.idl.main.facesdk.callback; | ||
5 | + | ||
6 | + | ||
7 | +/** | ||
8 | + * 人脸特征抽取回调接口。 | ||
9 | + * | ||
10 | + * @Time: 2019/5/30 | ||
11 | + * @Author: v_zhangxiaoqing01 | ||
12 | + */ | ||
13 | +public interface FaceFeatureCallBack { | ||
14 | + | ||
15 | + public void onFaceFeatureCallBack(float featureSize, byte[] feature); | ||
16 | + | ||
17 | +} |
1 | +/* | ||
2 | + * Copyright (C) 2017 Baidu, Inc. All Rights Reserved. | ||
3 | + */ | ||
4 | +package com.baidu.idl.main.facesdk.camera; | ||
5 | + | ||
6 | +import android.content.Context; | ||
7 | +import android.graphics.Canvas; | ||
8 | +import android.graphics.Path; | ||
9 | +import android.graphics.Region; | ||
10 | +import android.os.Handler; | ||
11 | +import android.os.Looper; | ||
12 | +import android.util.AttributeSet; | ||
13 | +import android.view.TextureView; | ||
14 | +import android.widget.FrameLayout; | ||
15 | + | ||
16 | +import com.baidu.idl.main.facesdk.model.SingleBaseConfig; | ||
17 | + | ||
18 | + | ||
19 | +/** | ||
20 | + * 基于 系统TextureView实现的预览View。 | ||
21 | + * | ||
22 | + * @Time: 2019/1/28 | ||
23 | + * @Author: v_chaixiaogang | ||
24 | + */ | ||
25 | +public class AutoTexturePreviewView extends FrameLayout { | ||
26 | + | ||
27 | + public TextureView textureView; | ||
28 | + | ||
29 | + private int videoWidth = 0; | ||
30 | + private int videoHeight = 0; | ||
31 | + | ||
32 | + | ||
33 | + private int previewWidth = 0; | ||
34 | + private int previewHeight = 0; | ||
35 | + private static int scale = 2; | ||
36 | + | ||
37 | + public static float circleRadius; | ||
38 | + public static float circleX; | ||
39 | + public static float circleY; | ||
40 | + | ||
41 | + private float[] pointXY = new float[3]; | ||
42 | + | ||
43 | + | ||
44 | + public AutoTexturePreviewView(Context context) { | ||
45 | + super(context); | ||
46 | + init(); | ||
47 | + } | ||
48 | + | ||
49 | + public AutoTexturePreviewView(Context context, AttributeSet attrs) { | ||
50 | + super(context, attrs); | ||
51 | + init(); | ||
52 | + } | ||
53 | + | ||
54 | + public AutoTexturePreviewView(Context context, AttributeSet attrs, | ||
55 | + int defStyleAttr) { | ||
56 | + super(context, attrs, defStyleAttr); | ||
57 | + init(); | ||
58 | + } | ||
59 | + | ||
60 | + | ||
61 | + private Handler handler = new Handler(Looper.getMainLooper()); | ||
62 | + | ||
63 | + private void init() { | ||
64 | + setWillNotDraw(false); | ||
65 | + textureView = new TextureView(getContext()); | ||
66 | + addView(textureView); | ||
67 | + } | ||
68 | + | ||
69 | + @Override | ||
70 | + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { | ||
71 | + super.onLayout(changed, left, top, right, bottom); | ||
72 | + | ||
73 | + previewWidth = getWidth(); | ||
74 | + previewHeight = getHeight(); | ||
75 | + | ||
76 | + if (videoWidth == 0 || videoHeight == 0 || previewWidth == 0 || previewHeight == 0) { | ||
77 | + return; | ||
78 | + } | ||
79 | + | ||
80 | + if (previewWidth * videoHeight > previewHeight * videoWidth) { | ||
81 | + int scaledChildHeight = videoHeight * previewWidth / videoWidth; | ||
82 | + textureView.layout(0, (previewHeight - scaledChildHeight) / scale, | ||
83 | + previewWidth, (previewHeight + scaledChildHeight) / scale); | ||
84 | + } else { | ||
85 | + int scaledChildWidth = videoWidth * previewHeight / videoHeight; | ||
86 | + textureView.layout((previewWidth - scaledChildWidth) / scale, 0, | ||
87 | + (previewWidth + scaledChildWidth) / scale, previewHeight); | ||
88 | + | ||
89 | + } | ||
90 | + | ||
91 | + | ||
92 | + } | ||
93 | + | ||
94 | + public TextureView getTextureView() { | ||
95 | + return textureView; | ||
96 | + } | ||
97 | + | ||
98 | + public int getPreviewWidth() { | ||
99 | + return previewWidth; | ||
100 | + } | ||
101 | + | ||
102 | + public int getPreviewHeight() { | ||
103 | + return previewHeight; | ||
104 | + } | ||
105 | + | ||
106 | + public void setPreviewSize(int width, int height) { | ||
107 | + if (this.videoWidth == width && this.videoHeight == height) { | ||
108 | + return; | ||
109 | + } | ||
110 | + this.videoWidth = width; | ||
111 | + this.videoHeight = height; | ||
112 | + handler.post(new Runnable() { | ||
113 | + @Override | ||
114 | + public void run() { | ||
115 | + requestLayout(); | ||
116 | + } | ||
117 | + }); | ||
118 | + | ||
119 | + } | ||
120 | + | ||
121 | + | ||
122 | + @Override | ||
123 | + protected void onDraw(Canvas canvas) { | ||
124 | + String displayType = SingleBaseConfig.getBaseConfig().getDetectFrame(); | ||
125 | + if (displayType.equals("fixedarea")) { | ||
126 | + Path path = new Path(); | ||
127 | + // 设置裁剪的圆心坐标,半径 | ||
128 | + path.addCircle(getWidth() / 2, getHeight() / 2, getWidth() / 3, Path.Direction.CCW); | ||
129 | + // 裁剪画布,并设置其填充方式 | ||
130 | + canvas.clipPath(path, Region.Op.REPLACE); | ||
131 | + | ||
132 | + circleRadius = getWidth() / 3; | ||
133 | + circleX = (getRight() - getLeft()) / 2; | ||
134 | + circleY = (getBottom() - getTop()) / 2; | ||
135 | + } | ||
136 | + super.onDraw(canvas); | ||
137 | + } | ||
138 | +} |
1 | +package com.baidu.idl.main.facesdk.camera; | ||
2 | + | ||
3 | +import android.content.Context; | ||
4 | +import android.graphics.SurfaceTexture; | ||
5 | +import android.hardware.Camera; | ||
6 | +import android.util.Log; | ||
7 | +import android.util.SparseIntArray; | ||
8 | +import android.view.Surface; | ||
9 | +import android.view.TextureView; | ||
10 | + | ||
11 | +import com.baidu.idl.main.facesdk.callback.CameraDataCallback; | ||
12 | +import com.baidu.idl.main.facesdk.model.SingleBaseConfig; | ||
13 | + | ||
14 | +import java.util.List; | ||
15 | + | ||
16 | +/** | ||
17 | + * Time: 2019/1/24 | ||
18 | + * Author: v_chaixiaogang | ||
19 | + * Description: | ||
20 | + */ | ||
21 | +public class CameraPreviewManager implements TextureView.SurfaceTextureListener { | ||
22 | + | ||
23 | + private static final String TAG = "camera_preview"; | ||
24 | + | ||
25 | + | ||
26 | + AutoTexturePreviewView mTextureView; | ||
27 | + boolean mPreviewed = false; | ||
28 | + private boolean mSurfaceCreated = false; | ||
29 | + private SurfaceTexture mSurfaceTexture; | ||
30 | + | ||
31 | + public static final int CAMERA_FACING_BACK = 0; | ||
32 | + | ||
33 | + public static final int CAMERA_FACING_FRONT = 1; | ||
34 | + | ||
35 | + public static final int CAMERA_USB = 2; | ||
36 | + | ||
37 | + public static final int CAMERA_ORBBEC = 3; | ||
38 | + | ||
39 | + /** | ||
40 | + * 垂直方向 | ||
41 | + */ | ||
42 | + public static final int ORIENTATION_PORTRAIT = 0; | ||
43 | + /** | ||
44 | + * 水平方向 | ||
45 | + */ | ||
46 | + public static final int ORIENTATION_HORIZONTAL = 1; | ||
47 | + | ||
48 | + /** | ||
49 | + * 当前相机的ID。 | ||
50 | + */ | ||
51 | + private int cameraFacing = CAMERA_FACING_FRONT; | ||
52 | + | ||
53 | + private int previewWidth; | ||
54 | + private int previewHeight; | ||
55 | + | ||
56 | + private int videoWidth; | ||
57 | + private int videoHeight; | ||
58 | + | ||
59 | + private int tempWidth; | ||
60 | + private int tempHeight; | ||
61 | + | ||
62 | + private int textureWidth; | ||
63 | + private int textureHeight; | ||
64 | + | ||
65 | + private Camera mCamera; | ||
66 | + private int mCameraNum; | ||
67 | + | ||
68 | + private int displayOrientation = 0; | ||
69 | + private int cameraId = 0; | ||
70 | + private int mirror = 1; // 镜像处理 | ||
71 | + private CameraDataCallback mCameraDataCallback; | ||
72 | + private static volatile CameraPreviewManager instance = null; | ||
73 | + | ||
74 | + private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); | ||
75 | + | ||
76 | + static { | ||
77 | + | ||
78 | + ORIENTATIONS.append(Surface.ROTATION_0, 0); | ||
79 | + ORIENTATIONS.append(Surface.ROTATION_90, 90); | ||
80 | + ORIENTATIONS.append(Surface.ROTATION_180, 180); | ||
81 | + ORIENTATIONS.append(Surface.ROTATION_270, 270); | ||
82 | + } | ||
83 | + | ||
84 | + public static CameraPreviewManager getInstance() { | ||
85 | + synchronized (CameraPreviewManager.class) { | ||
86 | + if (instance == null) { | ||
87 | + instance = new CameraPreviewManager(); | ||
88 | + } | ||
89 | + } | ||
90 | + return instance; | ||
91 | + } | ||
92 | + | ||
93 | + public int getCameraFacing() { | ||
94 | + return cameraFacing; | ||
95 | + } | ||
96 | + | ||
97 | + public void setCameraFacing(int cameraFacing) { | ||
98 | + this.cameraFacing = cameraFacing; | ||
99 | + } | ||
100 | + | ||
101 | + public int getDisplayOrientation() { | ||
102 | + return displayOrientation; | ||
103 | + } | ||
104 | + | ||
105 | + public void setDisplayOrientation(int displayOrientation) { | ||
106 | + this.displayOrientation = displayOrientation; | ||
107 | + } | ||
108 | + | ||
109 | + /** | ||
110 | + * 开启预览 | ||
111 | + * | ||
112 | + * @param context | ||
113 | + * @param textureView | ||
114 | + */ | ||
115 | + public void startPreview(Context context, AutoTexturePreviewView textureView, int width, | ||
116 | + int height, CameraDataCallback cameraDataCallback) { | ||
117 | + Log.e(TAG, "开启预览模式"); | ||
118 | + Context mContext = context; | ||
119 | + this.mCameraDataCallback = cameraDataCallback; | ||
120 | + mTextureView = textureView; | ||
121 | + this.previewWidth = width; | ||
122 | + this.previewHeight = height; | ||
123 | + mSurfaceTexture = mTextureView.getTextureView().getSurfaceTexture(); | ||
124 | + mTextureView.getTextureView().setSurfaceTextureListener(this); | ||
125 | + } | ||
126 | + | ||
127 | + @Override | ||
128 | + public void onSurfaceTextureAvailable(SurfaceTexture texture, int i, int i1) { | ||
129 | + Log.e(TAG, "--surfaceTexture--SurfaceTextureAvailable"); | ||
130 | + mSurfaceTexture = texture; | ||
131 | + mSurfaceCreated = true; | ||
132 | + textureWidth = i; | ||
133 | + textureHeight = i1; | ||
134 | + openCamera(); | ||
135 | + | ||
136 | + } | ||
137 | + | ||
138 | + @Override | ||
139 | + public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int i, int i1) { | ||
140 | + Log.e(TAG, "--surfaceTexture--TextureSizeChanged"); | ||
141 | + } | ||
142 | + | ||
143 | + @Override | ||
144 | + public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { | ||
145 | + Log.e(TAG, "--surfaceTexture--destroyed"); | ||
146 | + mSurfaceCreated = false; | ||
147 | + if (mCamera != null) { | ||
148 | + mCamera.setPreviewCallback(null); | ||
149 | + mCamera.stopPreview(); | ||
150 | + mCamera.release(); | ||
151 | + mCamera = null; | ||
152 | + } | ||
153 | + return true; | ||
154 | + } | ||
155 | + | ||
156 | + @Override | ||
157 | + public void onSurfaceTextureUpdated(SurfaceTexture texture) { | ||
158 | + // Log.e(TAG, "--surfaceTexture--Updated"); | ||
159 | + } | ||
160 | + | ||
161 | + | ||
162 | + /** | ||
163 | + * 关闭预览 | ||
164 | + */ | ||
165 | + public void stopPreview() { | ||
166 | + if (mCamera != null) { | ||
167 | + try { | ||
168 | + mCamera.setPreviewTexture(null); | ||
169 | + mSurfaceCreated = false; | ||
170 | + mTextureView = null; | ||
171 | + mCamera.setPreviewCallback(null); | ||
172 | + mCamera.stopPreview(); | ||
173 | + mCamera.release(); | ||
174 | + mCamera = null; | ||
175 | + } catch (Exception e) { | ||
176 | + Log.e("qing", "camera destory error"); | ||
177 | + e.printStackTrace(); | ||
178 | + | ||
179 | + } | ||
180 | + } | ||
181 | + } | ||
182 | + | ||
183 | + | ||
184 | + /** | ||
185 | + * 开启摄像头 | ||
186 | + */ | ||
187 | + | ||
188 | + private void openCamera() { | ||
189 | + | ||
190 | + try { | ||
191 | + if (mCamera == null) { | ||
192 | + Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); | ||
193 | + for (int i = 0; i < Camera.getNumberOfCameras(); i++) { | ||
194 | + Camera.getCameraInfo(i, cameraInfo); | ||
195 | + if (cameraInfo.facing == cameraFacing) { | ||
196 | + cameraId = i; | ||
197 | + } | ||
198 | + } | ||
199 | + mCamera = Camera.open(cameraId); | ||
200 | + Log.e(TAG, "initCamera---open camera"); | ||
201 | + } | ||
202 | + | ||
203 | + // 摄像头图像预览角度 | ||
204 | + int cameraRotation = SingleBaseConfig.getBaseConfig().getVideoDirection(); | ||
205 | + switch (cameraFacing) { | ||
206 | + case CAMERA_FACING_FRONT: { | ||
207 | +// cameraRotation = ORIENTATIONS.get(displayOrientation); | ||
208 | +// cameraRotation = getCameraDisplayOrientation(cameraRotation, cameraId); | ||
209 | + mCamera.setDisplayOrientation(cameraRotation); | ||
210 | + break; | ||
211 | + } | ||
212 | + | ||
213 | + case CAMERA_FACING_BACK: { | ||
214 | +// cameraRotation = ORIENTATIONS.get(displayOrientation); | ||
215 | +// cameraRotation = getCameraDisplayOrientation(cameraRotation, cameraId); | ||
216 | + mCamera.setDisplayOrientation(cameraRotation); | ||
217 | + break; | ||
218 | + } | ||
219 | + | ||
220 | + case CAMERA_USB: { | ||
221 | + mCamera.setDisplayOrientation(cameraRotation); | ||
222 | + break; | ||
223 | + } | ||
224 | + | ||
225 | + default: | ||
226 | + break; | ||
227 | + | ||
228 | + } | ||
229 | + | ||
230 | + | ||
231 | + if (cameraRotation == 90 || cameraRotation == 270) { | ||
232 | + boolean isRgbRevert = SingleBaseConfig.getBaseConfig().getRgbRevert(); | ||
233 | + if (isRgbRevert) { | ||
234 | + mTextureView.setRotationY(180); | ||
235 | + } else { | ||
236 | + mTextureView.setRotationY(0); | ||
237 | + } | ||
238 | + // 旋转90度或者270,需要调整宽高 | ||
239 | + mTextureView.setPreviewSize(previewHeight, previewWidth); | ||
240 | + } else { | ||
241 | + boolean isRgbRevert = SingleBaseConfig.getBaseConfig().getRgbRevert(); | ||
242 | + if (isRgbRevert) { | ||
243 | + mTextureView.setRotationY(180); | ||
244 | + } else { | ||
245 | + mTextureView.setRotationY(0); | ||
246 | + } | ||
247 | + mTextureView.setPreviewSize(previewWidth, previewHeight); | ||
248 | + } | ||
249 | + Camera.Parameters params = mCamera.getParameters(); | ||
250 | + List<Camera.Size> sizeList = params.getSupportedPreviewSizes(); // 获取所有支持的camera尺寸 | ||
251 | + final Camera.Size optionSize = getOptimalPreviewSize(sizeList, previewWidth, | ||
252 | + previewHeight); // 获取一个最为适配的camera.size | ||
253 | + if (optionSize.width == previewWidth && optionSize.height == previewHeight) { | ||
254 | + videoWidth = previewWidth; | ||
255 | + videoHeight = previewHeight; | ||
256 | + } else { | ||
257 | + videoWidth = optionSize.width; | ||
258 | + videoHeight = optionSize.height; | ||
259 | + } | ||
260 | + params.setPreviewSize(videoWidth, videoHeight); | ||
261 | + | ||
262 | + mCamera.setParameters(params); | ||
263 | + try { | ||
264 | + mCamera.setPreviewTexture(mSurfaceTexture); | ||
265 | + mCamera.setPreviewCallback(new Camera.PreviewCallback() { | ||
266 | + @Override | ||
267 | + public void onPreviewFrame(byte[] bytes, Camera camera) { | ||
268 | + if (mCameraDataCallback != null) { | ||
269 | + mCameraDataCallback.onGetCameraData(bytes, camera, | ||
270 | + videoWidth, videoHeight); | ||
271 | + } | ||
272 | + } | ||
273 | + }); | ||
274 | + mCamera.startPreview(); | ||
275 | + | ||
276 | + } catch (Exception e) { | ||
277 | + e.printStackTrace(); | ||
278 | + Log.e(TAG, e.getMessage()); | ||
279 | + } | ||
280 | + } catch (RuntimeException e) { | ||
281 | + Log.e(TAG, e.getMessage()); | ||
282 | + } | ||
283 | + } | ||
284 | + | ||
285 | + | ||
286 | + private int getCameraDisplayOrientation(int degrees, int cameraId) { | ||
287 | + Camera.CameraInfo info = new Camera.CameraInfo(); | ||
288 | + Camera.getCameraInfo(cameraId, info); | ||
289 | + int rotation = 0; | ||
290 | + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { | ||
291 | + rotation = (info.orientation + degrees) % 360; | ||
292 | + rotation = (360 - rotation) % 360; // compensate the mirror | ||
293 | + } else { // back-facing | ||
294 | + rotation = (info.orientation - degrees + 360) % 360; | ||
295 | + } | ||
296 | + return rotation; | ||
297 | + } | ||
298 | + | ||
299 | + | ||
300 | + /** | ||
301 | + * 解决预览变形问题 | ||
302 | + * | ||
303 | + * @param sizes | ||
304 | + * @param w | ||
305 | + * @param h | ||
306 | + * @return | ||
307 | + */ | ||
308 | + private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) { | ||
309 | + final double aspectTolerance = 0.1; | ||
310 | + double targetRatio = (double) w / h; | ||
311 | + if (sizes == null) { | ||
312 | + return null; | ||
313 | + } | ||
314 | + Camera.Size optimalSize = null; | ||
315 | + double minDiff = Double.MAX_VALUE; | ||
316 | + | ||
317 | + int targetHeight = h; | ||
318 | + | ||
319 | + // Try to find an size match aspect ratio and size | ||
320 | + for (Camera.Size size : sizes) { | ||
321 | + double ratio = (double) size.width / size.height; | ||
322 | + if (Math.abs(ratio - targetRatio) > aspectTolerance) { | ||
323 | + continue; | ||
324 | + } | ||
325 | + if (Math.abs(size.height - targetHeight) < minDiff) { | ||
326 | + optimalSize = size; | ||
327 | + minDiff = Math.abs(size.height - targetHeight); | ||
328 | + } | ||
329 | + } | ||
330 | + | ||
331 | + // Cannot find the one match the aspect ratio, ignore the requirement | ||
332 | + if (optimalSize == null) { | ||
333 | + minDiff = Double.MAX_VALUE; | ||
334 | + for (Camera.Size size : sizes) { | ||
335 | + if (Math.abs(size.height - targetHeight) < minDiff) { | ||
336 | + optimalSize = size; | ||
337 | + minDiff = Math.abs(size.height - targetHeight); | ||
338 | + } | ||
339 | + } | ||
340 | + } | ||
341 | + return optimalSize; | ||
342 | + } | ||
343 | +} |
-
请 注册 或 登录 后发表评论