This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
## 1.0.12025-07-27
* 兼容鸿蒙原生,IOS
+83
View File
@@ -0,0 +1,83 @@
{
"id": "x-facedetection",
"displayName": "x-facedetection",
"version": "1.0.0",
"description": "x-facedetection",
"keywords": [
"x-facedetection"
],
"repository": "",
"engines": {
"HBuilderX": "^3.6.8"
},
"dcloudext": {
"type": "uts",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "",
"data": "",
"permissions": ""
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "u",
"aliyun": "u",
"alipay": "u"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "u"
},
"App": {
"app-android": "u",
"app-ios": "u",
"app-harmony": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
+234
View File
@@ -0,0 +1,234 @@
# x-facedetection
## 特性说明
1. 检测提供的图片是否有人脸信息
2. 从相册中选取检测人脸信息
3. 通过相机实时捕捉人脸信息(并通过姿势姿态检测当前活体)
4. ios端后续提供,仅支持图片人脸信息检测
## 兼容性
| Harmony | IOS | Android | WEB | 小程序 |
| --- | --- | --- | --- | --- |
| 支持 | - | 支持 | x | x |
## 安装方式
通过HBuilderX导入插件,或在插件市场下载后导入项目。
## API接口说明
### 检测图片中的人脸
```ts
// 通过相册选择图片检测人脸,仅支持安卓,ios,鸿蒙
checkFaceByImage(config: XFACE_CHECK_OPTS)
// 通过提供的图片路径检测人脸,仅支持安卓,ios,鸿蒙
checkFaceByImageFromFilePath(config: XFACE_CHECK_OPTS)
```
### 通过相机进行人脸活体检测
```ts
// 检查相机权限,仅安卓
checkCamraPermissions(call: (istrue: boolean) => void)
// 打开相机进行人脸活体检测,仅安卓
checkFaceByCamra(config: XFACE_CHECK_OPTS)
```
## 参数类型说明
### XFACE_CHECK_OPTS
```ts
type XFACE_CHECK_OPTS = {
// 如果是通过图片检测需要提供图片路径
url?: string,
// 如果是通过图片buffer解析需要提供(暂无法使用)
buffer?: ArrayBuffer,
// 如果是通过相机动态绑定嵌入式页面时,需要提供页面view的id
viewId?: string,
/**
* 活体检测指令,通过相机视频检测
* 如果提供指令数组时,会根据完整指令顺序检测直到验证通过.
* 如果视频检测时,指令为空数组则只检测整个五官是不是在画面中,一旦检测到人脸立即验证通过.
*/
instruction?: XFACE_INS[]
// 验证结束并通过时返回
success?: (data: XFACE_EVENT_CHECK_SUCCESS) => void,
/**
* 在视频活体检测时,返回每一帧的检测详细检测过程的信息,此事件是持续触发,直到结束。
* 在图片检测的时候,只返回一次。
*/
enter?: (data: XFACE_EVENT_SUCCESS[], maps: string) => void,
/**
* 开始检测,正在开始检测的指令。类似:向左转一次,向右转一次,点点头,张张嘴等执行时触发,
* 方便用户自定自己的提示语或者语音播报
* 'start',现在开始,
* 'ins_start_指令名称':某个指令开始,如:ins_start_left,ins_start_right
* 'ins_end_指令名称':某个指令结束,如:ins_end_left,ins_end_right
* 'end'结束所有指令
*/
insTipsEvent?: (data: string) => void,
fail?: (error: string) => void,
}
```
### 活体检测指令类型
```ts
/**
* 活体检测指令,可以通过指令控制检测动作
* left: 向左转一次
* right: 向右转一次
* downUp: 上下点头
* eyeOplenClose: 眨眼
* monthOpenClose: 张张嘴
*/
type XFACE_INS = "left" | "right" | "downUp" | "eyeOplenClose" | "monthOpenClose"
```
### 检测结果类型
```ts
type XFACE_EVENT_SUCCESS = {
// 右眼眨眼睛的闭合数据
rightEye: XFACE_TEST_EYE_TYPE,
// 左眼眨眼睛的闭合数据
leftEye: XFACE_TEST_EYE_TYPE,
// 是否在微笑
isSmile: boolean,
// 脸向左,向右,中间
faceRightLeft: XFACE_FACE_Direction,
// 点头的方向
faceUpDown: XFACE_FACE_Direction,
imgWidth: number,
imgHeight: number,
// 嘴巴是不是张开
isMouthOpened: boolean,
// 整个脑袋是否在画面中
allHeadBody: boolean,
// 人脸ID,用于追踪同一个人脸
faceId: number | null
}
type XFACE_EVENT_CHECK_SUCCESS = {
/**
* 验证结束保存的视频地址,方便上传保存到后台储存备份,抽查验证。
* 仅在活体检测时返回,图片检测时为空字符串。
*/
videoPath: string
/**
* 在活体检测过程中,会按照指令顺序进行截图,保存验证时人脸对应的动画图片。
* 多少个指令就有多少张图片。最后一张为全脸捕捉画面。
* 图片检测时,返回检测的图片路径。
*/
images: string[],
/**
* 验证是否通过
*/
isPass: boolean
}
```
## 使用示例
### 图片人脸检测
仅安卓,鸿蒙 Next,ios 支持
```ts
import { checkFaceByImage, checkFaceByImageFromFilePath } from '@/uni_modules/x-facedetection'
// 从相册选择图片检测人脸
checkFaceByImage({
success: (res) => {
console.log('检测成功', res.isPass)
console.log('图片路径', res.images)
},
fail: (err) => {
console.error('检测失败', err)
},
enter: (data, maps) => {
console.log('人脸信息', data)
}
})
// 通过图片路径检测人脸
checkFaceByImageFromFilePath({
url: '图片路径',
success: (res) => {
console.log('检测成功', res.isPass)
console.log('图片路径', res.images)
},
fail: (err) => {
console.error('检测失败', err)
},
enter: (data, maps) => {
console.log('人脸信息', data)
}
})
```
### 相机活体检测
仅安卓
```ts
import { checkCamraPermissions, checkFaceByCamra } from '@/uni_modules/x-facedetection/utssdk/app-android/index.uts'
// 检查相机权限
checkCamraPermissions((hasPermission) => {
if (hasPermission) {
// 有权限,开始活体检测
startFaceDetection()
} else {
console.error('没有相机权限')
}
})
// 开始活体检测
function startFaceDetection() {
checkFaceByCamra({
viewId: 'camera-container', // 相机预览容器的ID
instruction: ['left', 'right', 'eyeOplenClose', 'monthOpenClose'], // 活体检测指令序列
insTipsEvent: (event) => {
// 处理指令提示事件
if (event === 'start') {
console.log('开始检测')
} else if (event.startsWith('ins_start_')) {
const action = event.replace('ins_start_', '')
console.log(`开始执行动作: ${action}`)
} else if (event.startsWith('ins_end_')) {
const action = event.replace('ins_end_', '')
console.log(`完成动作: ${action}`)
} else if (event === 'end') {
console.log('检测完成')
}
},
success: (res) => {
console.log('活体检测成功', res.isPass)
console.log('视频路径', res.videoPath)
console.log('检测过程图片', res.images)
},
fail: (err) => {
console.error('活体检测失败', err)
},
enter: (data, maps) => {
// 实时人脸检测数据
console.log('实时人脸数据', data)
}
})
}
```
## 注意事项
1. 活体检测需要用户保持面部在摄像头范围内,并按照指令完成相应动作
2. 检测过程中如果用户移出屏幕或光线变化太大,可能会导致检测失败
3. 为获得最佳效果,建议在光线充足的环境下使用
4. iOS和Android实现可能存在细微差异,请根据实际测试结果调整应用逻辑
5. 鸿蒙人脸检测效率由原生支持。
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
package="io.dcloud.uni_modules.xFaceDetection">
<uses-permission android:name="android.permission.VIBRATE" />
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<application>
<meta-data android:name="UTS_MLKIT_xFaceDetection" android:value="xFaceDetection" />
<activity android:name="uts.sdk.modules.xFaceDetection.DemoActivity"></activity>
</application>
</manifest>
@@ -0,0 +1,51 @@
import MediaPlayer from "android.media.MediaPlayer"
import File from 'java.io.File';
import FileOutputStream from 'java.io.FileOutputStream';
export class xAudio {
audio:MediaPlayer;
constructor(){
this.audio = new MediaPlayer();
}
play(src:string){
this.audio.stop();
this.audio.release()
this.audio = new MediaPlayer();
let assetManager = UTSAndroid.getAppContext()!.getAssets();
// 加载free.mp3 资源
const listAssets = assetManager.list("");
let filename = src+".mp3";
let isExites = listAssets?.contains(filename)??false
if(!isExites) return;
let afd = assetManager.openFd(filename);
try {
// 使用android 自带的媒体组件进行播放
let mediaPlayer = this.audio;
mediaPlayer.setDataSource(afd.getFileDescriptor(),afd.getStartOffset(), afd.getLength());
mediaPlayer.prepare();
mediaPlayer.start();
class audioCompleted implements MediaPlayer.OnCompletionListener{
override onCompletion(m:MediaPlayer|null){
// if(m!=null){
// m.release()
// }
}
}
mediaPlayer.setOnCompletionListener(new audioCompleted())
} catch (e: Exception) {
console.log("AudioPlayer", "Error setting data source: ",e)
} finally {
// 确保关闭文件描述符
afd.close()
}
}
stop(){
// this.audio.stop();
}
destory(){
// this.audio.release()
}
}
@@ -0,0 +1,548 @@
import ListView from 'android.widget.ListView';
import FrameLayout from 'android.widget.FrameLayout';
import CameraManager from 'android.hardware.camera2.CameraManager'
import CameraDevice from 'android.hardware.camera2.CameraDevice'
import StateCallback from 'android.hardware.camera2.CameraDevice.StateCallback'
import CameraCaptureSession from 'android.hardware.camera2.CameraCaptureSession'
import Camera2CameraControl from 'androidx.camera.camera2.interop.Camera2CameraControl'
import Camera2CameraInfo from 'androidx.camera.camera2.interop.Camera2CameraInfo'
import CaptureRequest from 'android.hardware.camera2.CaptureRequest'
import TotalCaptureResult from 'android.hardware.camera2.TotalCaptureResult'
import StreamConfigurationMap from 'android.hardware.camera2.params.StreamConfigurationMap'
import MeteringRectangle from 'android.hardware.camera2.params.MeteringRectangle'
import OutputConfiguration from 'android.hardware.camera2.params.OutputConfiguration'
import SessionConfiguration from 'android.hardware.camera2.params.SessionConfiguration'
import CameraCharacteristics from 'android.hardware.camera2.CameraCharacteristics'
import CameraMetadata from 'android.hardware.camera2.CameraMetadata'
import Range from 'android.util.Range';
import InputImage from "com.google.mlkit.vision.common.InputImage"
import Executor from 'java.util.concurrent.Executor'
import Looper from 'android.os.Looper'
import ImageFormat from 'android.graphics.ImageFormat'
import Size from 'android.util.Size'
import SurfaceTexture from 'android.graphics.SurfaceTexture'
import Handler from 'android.os.Handler'
import Surface from 'android.view.Surface'
import Image from 'android.media.Image'
import LinearLayout from 'android.widget.LinearLayout';
import Intent from 'android.content.Intent';
import Integer from 'java.lang.Integer';
import ByteOrder from 'java.nio.ByteOrder';
import SurfaceHolder from 'android.view.SurfaceHolder'
import SurfaceView from 'android.view.SurfaceView'
import TextureView from 'android.view.TextureView'
import View from 'android.view.View'
import Context from 'android.content.Context'
import Manifest from "android.Manifest";
import Uri from 'android.net.Uri';
import File from 'java.io.File'
import System from 'java.lang.System'
import Activity from 'android.app.Activity';
import Bundle from 'android.os.Bundle';
import Intent from 'android.content.Intent';
import IntentFilter from 'android.content.IntentFilter';
import Rect from 'android.graphics.Rect';
import Matrix from 'android.graphics.Matrix'
import ImageReader from 'android.media.ImageReader'
import OnImageAvailableListener from 'android.media.ImageReader.OnImageAvailableListener'
import ByteBuffer from 'java.nio.ByteBuffer'
import MediaStore from 'android.provider.MediaStore'
import Plane from 'android.media.Image.Plane'
import Bitmap from "android.graphics.Bitmap"
import BitmapFactory from "android.graphics.BitmapFactory"
import Vibrator from "android.os.Vibrator"
import Animation from "android.view.animation.Animation"
import ScaleAnimation from "android.view.animation.ScaleAnimation"
import AnimationUtils from "android.view.animation.AnimationUtils"
import Color from "android.graphics.Color"
import Window from "android.view.Window"
import WindowManager from "android.view.WindowManager"
import TextView from "android.widget.TextView"
import RelativeLayout from "android.widget.RelativeLayout"
import ImageView from "android.widget.ImageView"
import HandlerThread from 'android.os.HandlerThread'
import YuvImage from 'android.graphics.YuvImage'
import ByteArrayOutputStream from 'java.io.ByteArrayOutputStream'
import GradientDrawable from 'android.graphics.drawable.GradientDrawable'
import MotionEvent from 'android.view.MotionEvent';
import ViewGroup from 'android.view.ViewGroup';
import IOException from 'java.io.IOException'
import InputStream from 'java.io.InputStream'
// import BarcodeScanner from "com.google.mlkit.vision.barcode.BarcodeScanner"
// import BarcodeScannerOptions from "com.google.mlkit.vision.barcode.BarcodeScannerOptions"
// import BarcodeScanning from "com.google.mlkit.vision.barcode.BarcodeScanning"
// import ZoomSuggestionOptions from "com.google.mlkit.vision.barcode.ZoomSuggestionOptions"
// import ZoomCallback from "com.google.mlkit.vision.barcode.ZoomSuggestionOptions.ZoomCallback"
// import Barcode from "com.google.mlkit.vision.barcode.common.Barcode"
// import InputImage from "com.google.mlkit.vision.common.InputImage"
import Task from "com.google.android.gms.tasks.Task"
import List from "java.util.List"
// x
import ProcessCameraProvider from "androidx.camera.lifecycle.ProcessCameraProvider"
import PreviewView from "androidx.camera.view.PreviewView"
import CameraController from "androidx.camera.view.CameraController"
import CameraSelector from "androidx.camera.core.CameraSelector"
import Preview from 'androidx.camera.core.Preview'
import Camera from 'androidx.camera.core.Camera'
import LifecycleObserver from "androidx.lifecycle.LifecycleObserver"
import LifecycleOwner from "androidx.lifecycle.LifecycleOwner"
import OnLifecycleEvent from "androidx.lifecycle.OnLifecycleEvent"
import ImageAnalysis from 'androidx.camera.core.ImageAnalysis'
import ImageProxy from 'androidx.camera.core.ImageProxy'
import ImageCapture from 'androidx.camera.core.ImageCapture';
import AspectRatio from 'androidx.camera.core.AspectRatio';
import CameraInfo from 'androidx.camera.core.CameraInfo';
import CameraControl from 'androidx.camera.core.CameraControl'
import NonNull from 'androidx.annotation.NonNull'
import ExecutorService from 'java.util.concurrent.ExecutorService'
import Executors from 'java.util.concurrent.Executors'
// import TargetResolution from 'androidx.camera.core.ImageAnalysis.TargetResolution'
import {decodeImage} from "./index.uts"
type buffoptsPlanesType = {
data : ByteBuffer,
getRowStride : number,
getPixelStride : number,
}
type buffoptsType = {
width : number,
height : number,
format : number,
rect : Rect,
planes : buffoptsPlanesType[],
}
import { AUTH_CALL_BACK_TYPE, CAMERA_PHOTO_SIZE,XCHECKFACE_ENTER_CALL,XFACE_EVENT_SUCCESS } from "../interface.uts"
import ContextCompat from 'androidx.core.content.ContextCompat';
import FileOutputStream from 'java.io.FileOutputStream';
import PreviewConfig from 'androidx.camera.core.impl.PreviewConfig';
import VideoSource from 'android.media.MediaRecorder.VideoSource';
import VideoCapture from 'androidx.camera.video.VideoCapture';
import OutputFileOptions from 'androidx.camera.core.ImageCapture.OutputFileOptions';
import Recorder from 'androidx.camera.video.Recorder';
import Quality from 'androidx.camera.video.Quality';
import QualitySelector from 'androidx.camera.video.QualitySelector';
import OutputFileResults from 'androidx.camera.core.ImageCapture.OutputFileResults';
import FileDescriptor from 'java.io.FileDescriptor';
import FileOutputOptions from 'androidx.camera.video.FileOutputOptions';
import VideoRecordEvent from 'androidx.camera.video.VideoRecordEvent';
import Consumer from 'androidx.core.util.Consumer';
import Recording from 'androidx.camera.video.Recording';
import ResolutionCorrector from 'androidx.camera.camera2.internal.compat.workaround.ResolutionCorrector';
import AspectRatioStrategy from 'androidx.camera.core.resolutionselector.AspectRatioStrategy';
import ResolutionSelector from 'androidx.camera.core.resolutionselector.ResolutionSelector';
type cameraListNameType = { id : string, device : CameraCharacteristics }
type TAKE_CALL_BACK = (path : string) => void;
let callFuntake = (path : string) => { }
let callVideoFun = (path : string) => { }
let callVideoFaceFun= (img : XFACE_EVENT_SUCCESS[],imgBit:Bitmap|null) => { }
let checking = true
function px2dp(n : number) : number {
const mets = UTSAndroid.getAppContext()!.resources!.getDisplayMetrics()
return mets.density * n
}
function getUid(rdix = 1, length = 12) : string {
let ix = "";
// #ifdef APP
ix = Math.floor(Math.random() * rdix * Math.floor(Math.random() * Date.now())).toString().substring(0, length as Int);
// #endif
return ix;
}
export class xCamera {
parentView : FrameLayout;
viewBox_width = 0
viewBox_height = 0
imgPointsBycanmarea = [] as RelativeLayout[]
/** 手动震动服务 */
vibrator = null as null | Vibrator
context : Context;
// 相机旋转的角度。
cameraRate = new Map<Number, Number>([
[Surface.ROTATION_0, 0],
[Surface.ROTATION_90, 0],
[Surface.ROTATION_180, 0],
[Surface.ROTATION_270, 0]
])
// 相机服务
cameraService : CameraManager
// 相机列表,name,id
cameraListName = new Map<string, cameraListNameType>();
//
cameraPreviewSize = { width: 800, height: 800 } as CAMERA_PHOTO_SIZE
// 当前预览的什么是设备
cameraDevice : CameraDevice | null = null
cameraDeviceCaptureSession : CameraCaptureSession | null = null
sufaceHolderView : Surface | null = null
// 相机区域视图
cameraView : PreviewView | null = null;
cameraProvider : ProcessCameraProvider | null = null
camera : Camera | null = null;
cameraPreview : Preview | null = null;
videoCapture : VideoCapture<Recorder> | null = null;
// Recording
recoderVideoObj:Recording|null = null;
// 是否拍照中
isDecoderQring = false
// 当前相机是否正在预览中。
isOpeningCameraing = false;
callFunEvent : TAKE_CALL_BACK = (str : string) => { }
//摄像头朝向,默认是向后置摄像头.
CamerDeviceDir = CameraSelector.LENS_FACING_FRONT
//摄像头的是否开
flashMode = false
// 拍照模式,还是录像模式.
takeModelType = 'video'; //photo,video
private PHOTO_TYPE = "image/jpeg"
private RATIO_4_3_VALUE = 4.0 / 3.0
private RATIO_16_9_VALUE = 16.0 / 9.0
constructor(GroupView : FrameLayout) {
this.context = UTSAndroid.getAppContext()! as Context;
this.parentView = GroupView;
this.cameraService = this.context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
this.vibrator = this.context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator;
this._createCameraView()
}
/** 创建相机预览区域 */
private _createCameraView() {
let sv = new PreviewView(UTSAndroid.getAppContext()!)
sv.scaleType = PreviewView.ScaleType.FILL_CENTER
// 让相机的高和宽与组件对齐。
let layaout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
sv.setLayoutParams(layaout)
this.cameraView = sv;
this.parentView.addView(this.cameraView!)
}
openCamera() {
let _this = this;
if (this.isOpeningCameraing) {
this.closeCamera()
}
_this._openCamera();
}
startRecoderVideo(call : (path : string) => void) {
callVideoFun = call;
let _this = this;
if(_this.recoderVideoObj?.isClosed()==false){
_this.recoderVideoObj?.resume()
}else{
if (this.isOpeningCameraing||this.recoderVideoObj!=null) {
this.stopRecoderVideo()
this.closeCamera()
}
_this.takeModelType = 'video'
_this._openCamera();
}
}
pauseRecoderVideo(){
let _this = this;
_this.recoderVideoObj?.pause()
}
stopRecoderVideo() {
let _this = this;
_this.takeModelType = 'photo'
_this.recoderVideoObj?.stop()
_this.recoderVideoObj?.close()
_this.recoderVideoObj = null;
_this.videoCapture = null;
// closeCamera();
}
setCallEvent(call : TAKE_CALL_BACK) {
this.callFunEvent = call;
}
setCheckFaceEnter(call:XCHECKFACE_ENTER_CALL){
callVideoFaceFun = call
}
getCamraRotation() : Number {
return 0
}
closeCamera() {
let t = this;
class IntentRunable2 extends Runnable {
override run() {
t.cameraProvider?.unbindAll()
t.cameraPreview?.setSurfaceProvider(null)
t.isOpeningCameraing = false;
}
}
// 确保在主线程中关闭相机
new Handler(Looper.getMainLooper()).post(new IntentRunable2());
}
private aspectRatio(width : Int, height : Int) : Int {
let previewRatio = Math.max(width, height).toDouble() / Math.min(width, height)
if (Math.abs(previewRatio - this.RATIO_4_3_VALUE) <= Math.abs(previewRatio - this.RATIO_16_9_VALUE)) {
return AspectRatio.RATIO_4_3
}
return AspectRatio.RATIO_16_9
}
private imageToBitmap(image: Image): Bitmap {
// Assuming the image format is YUV_420_888 and converting it to Bitmap
let buffer = image.planes[0].buffer
let bytes = ByteArray(buffer.capacity())
buffer.get(bytes)
let yuvImage = YuvImage(bytes, ImageFormat.NV21, image.width, image.height, null)
let out = ByteArrayOutputStream()
yuvImage.compressToJpeg(Rect(0, 0, image.width, image.height), 100, out)
let byteArray = out.toByteArray()
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
}
private increaseBrightness(camera: Camera) {
// 调整曝光补偿以提高亮度
let cameraControl = camera.cameraControl
let cameraInfo = camera.cameraInfo
let exposureState = cameraInfo.exposureState
let range = exposureState.exposureCompensationRange
let maxExposure = range.upper
if (maxExposure > 0) {
cameraControl.setExposureCompensationIndex(maxExposure)
console.log("曝光补偿值已设置为最大值: $",maxExposure)
} else {
console.log("相机不支持曝光补偿。")
}
}
private setISO(camera: Camera) {
}
private setWhiteBalance(camera: Camera) {
}
private _openCamera() {
let _this = this;
let cameraProviderFuture = ProcessCameraProvider.getInstance(this.context)
class IntentRunable extends Runnable {
override run() {
let cameraProvider : ProcessCameraProvider = cameraProviderFuture.get()
let previewView = _this.cameraView! as PreviewView
let cameraSelector : CameraSelector = new CameraSelector.Builder()
.requireLensFacing(_this.CamerDeviceDir)
.build();
// 预览比例
let screenAspectRatio = _this.aspectRatio((300).toInt(), (300).toInt())
let preview : Preview = new Preview.Builder()
.setTargetAspectRatio(screenAspectRatio)
.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider())
// 创建ImageCapture用例,并设置闪光灯模式
let imageCapture : ImageCapture = new ImageCapture.Builder()
.setTargetAspectRatio(screenAspectRatio)
.build();
let imageAnalysis = new ImageAnalysis.Builder()
.setTargetResolution(new Size(300, 300))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
class MyImageAnalyzer implements ImageAnalysis.Analyzer {
constructor() {
super()
}
override analyze(image : ImageProxy) {
// image
if (_this.isDecoderQring) {
_this.savePhotoTocache(image)
} else {
const imgInput = image.getImage()
if(imgInput!=null){
let img = InputImage.fromMediaImage(imgInput,image.getImageInfo().getRotationDegrees())
decodeImage((evt:XFACE_EVENT_SUCCESS[])=>{
UTSAndroid.getDispatcher("main").async(function(_){
let imgbitmap = image.toBitmap()
callVideoFaceFun(evt,imgbitmap)
image.close()
},null)
},()=>{},img)
}else{
image.close()
}
}
}
override getDefaultTargetResolution() : Size {
return new Size(_this.viewBox_width.toInt(), _this.viewBox_height.toInt());
}
}
let cameraExecutor = Executors.newSingleThreadExecutor();
imageAnalysis.setAnalyzer(cameraExecutor, new MyImageAnalyzer());
let recorder = Recorder.Builder()
.setQualitySelector(QualitySelector.from(Quality.LOWEST)) // 选择录制质量,HD、FHD等
.build()
_this.videoCapture = VideoCapture.withOutput(recorder)
let camera : Camera = cameraProvider.bindToLifecycle(UTSAndroid.getUniActivity()! as LifecycleOwner,
cameraSelector,
imageCapture,
imageAnalysis,
preview,
videoCapture!
);
if (_this.takeModelType == 'video') {
// 录制视频
let filepath = _this.context.getExternalCacheDir()?.getPath() ?? ""
let videoFile = new File(filepath, 'tmui4xCamreaVideo' + getUid(1, 12) + '.mp4')
let outputOptions = FileOutputOptions.Builder(videoFile).build()
class DirectExecutor implements Executor {
override execute(r:Runnable ) {
r.run();
console.error('录制错误')
callVideoFun('')
}
}
// Consumer<VideoRecordEvent>
class reocdereventClass implements Consumer<VideoRecordEvent> {
override accept(event: VideoRecordEvent) {
if(event instanceof VideoRecordEvent.Start){
console.log("开始录制")
}else if(event instanceof VideoRecordEvent.Finalize){
console.log("结束录制",videoFile.absolutePath)
callVideoFun(videoFile.absolutePath)
}
}
}
// ContextCompat.getMainExecutor(_this.context)
_this.recoderVideoObj = _this.videoCapture!.getOutput()
.prepareRecording(_this.context,outputOptions)
.start(ContextCompat.getMainExecutor(_this.context),new reocdereventClass() as Consumer<VideoRecordEvent>)
}
let cameraControl = camera.getCameraControl()
let cameraInfo : CameraInfo = camera.getCameraInfo()
_this.increaseBrightness(camera)
// 闪光灯.
cameraControl.enableTorch(_this.flashMode)
_this.camera = camera
_this.cameraPreview = preview
_this.cameraProvider = cameraProvider
_this.isDecoderQring = false
_this.isOpeningCameraing = true
}
}
try{
cameraProviderFuture.addListener(new IntentRunable(), ContextCompat.getMainExecutor(this.context))
}catch(e){
console.error('开启错误.')
}
}
takPhoto(call : (path : string) => void) {
callFuntake = call;
this.isDecoderQring = true;
}
//front,前置,back后置
setCameraDir(type : string) {
this.CamerDeviceDir = type == 'back' ? CameraSelector.LENS_FACING_BACK : CameraSelector.LENS_FACING_FRONT
}
setFlash(type : boolean) {
this.flashMode = type;
}
savePhotoTocache(image : ImageProxy) {
let context = UTSAndroid.getAppContext()!
let filepath = context.getExternalCacheDir()?.getPath() ?? ""
let img : Bitmap | null = image.toBitmap()
let file = new File(filepath, 'tmui4xCamrea' + getUid(1, 12) + '.jpg');
let fos = new FileOutputStream(file)
let conunt = (img?.getByteCount() ?? 0) / 1024 / 1024
let saved = img!.compress(Bitmap.CompressFormat.JPEG, conunt > 1 ? 64 : 100, fos);
let t = this;
class IntentRunable2 extends Runnable {
override run() {
if (saved) {
callFuntake(file.getPath())
t.isDecoderQring = false
}
// ,拍照暂不关闭相机让用户手动关闭.
// t.closeCamera()
}
}
image.close()
new Handler(Looper.getMainLooper()).post(new IntentRunable2());
}
savePhotoBitmapTocache(img : Bitmap) {
let context = UTSAndroid.getAppContext()!
let filepath = context.getExternalCacheDir()?.getPath() ?? ""
let file = new File(filepath, 'tmui4xCamrea' + getUid(1, 12) + '.jpg');
let fos = new FileOutputStream(file)
let conunt = img.getByteCount() / 1024 / 1024
img!.compress(Bitmap.CompressFormat.JPEG, conunt > 1 ? 64 : 100, fos);
return file.getPath();
}
}
@@ -0,0 +1,12 @@
{
"minSdkVersion": "21",
"dependencies":[
"com.google.mlkit:face-detection:16.1.7",
"androidx.camera:camera-core:1.3.4",
"androidx.camera:camera-camera2:1.3.4",
"androidx.camera:camera-lifecycle:1.3.4",
"androidx.camera:camera-video:1.3.4",
"androidx.camera:camera-view:1.3.4",
"androidx.camera:camera-extensions:1.3.4"
]
}
@@ -0,0 +1,561 @@
import Intent from 'android.content.Intent';
import Context from 'android.content.Context'
import IOException from 'java.io.IOException'
import InputStream from 'java.io.InputStream'
import Uri from 'android.net.Uri';
import File from 'java.io.File'
import Bitmap from "android.graphics.Bitmap"
import FrameLayout from 'android.widget.FrameLayout';
import FaceDetection from "com.google.mlkit.vision.face.FaceDetection"
import FaceDetectorOptions from "com.google.mlkit.vision.face.FaceDetectorOptions"
import FaceLandmark from "com.google.mlkit.vision.face.FaceLandmark"
import FaceContour from "com.google.mlkit.vision.face.FaceContour"
import Face from "com.google.mlkit.vision.face.Face"
import InputImage from "com.google.mlkit.vision.common.InputImage"
import Task from "com.google.android.gms.tasks.Task"
import List from "java.util.List"
import ByteBuffer from 'java.nio.ByteBuffer'
import ImageFormat from 'android.graphics.ImageFormat'
import Kotlin from 'kotlin.jvm.internal.Intrinsics.Kotlin';
import Image from 'android.media.Image';
import LinearLayout from 'android.widget.LinearLayout';
import ViewGroup from 'android.view.ViewGroup';
import { XFACE_CHECK_OPTS, XFACE_EVENT_SUCCESS, XFACE_TEST_EYE_TYPE, XFACE_FACE_Direction, XFACE_EVENT_CHECK_SUCCESS } from "../interface.uts"
import { XFACE_CHECK_OPTS_REAL, getDefaultConfig } from "../libs/config.uts"
import BitmapFactory from 'android.graphics.BitmapFactory';
import View from 'android.view.View';
import { xCamera } from "./camera.uts"
import { xAudio } from "./audio.uts"
let camera : xCamera | null = null;
const audio = new xAudio();
const checkFaceResult = (image : InputImage, faces : MutableList<Face>, callBack : (result : XFACE_EVENT_SUCCESS[]) => void) => {
const cfaces = [] as XFACE_EVENT_SUCCESS[]
// @ts-ignore
for (item in faces) {
// @ts-ignore
const face = item as Face;
let rightEye = { openNum: 0, isOpen: false } as XFACE_TEST_EYE_TYPE;
let leftEye = { openNum: 0, isOpen: false } as XFACE_TEST_EYE_TYPE;
// 眼睛
let rightEyeMark = face.getLandmark(FaceLandmark.RIGHT_EYE)
let leftEyeMark = face.getLandmark(FaceLandmark.RIGHT_EYE)
// 鼻子
let noseMark = face.getLandmark(FaceLandmark.NOSE_BASE)
// 耳朵
let rightEarMark = face.getLandmark(FaceLandmark.RIGHT_EAR)
let leftEarMark = face.getLandmark(FaceLandmark.LEFT_EAR)
let rightEyeNum = face.getRightEyeOpenProbability()
let leftEyeNum = face.getLeftEyeOpenProbability()
let isSmile = face.getSmilingProbability()
let faceId = face.getTrackingId()
if (rightEyeNum != null) {
rightEye.openNum = rightEyeNum + 0;
rightEye.isOpen = rightEyeNum > 0.7
}
if (leftEyeNum != null) {
leftEye.openNum = leftEyeNum + 0;
leftEye.isOpen = leftEyeNum > 0.7
}
// 检测当前左,右,下,上
let faceRightLeft : XFACE_FACE_Direction = "center"
let faceUpDown : XFACE_FACE_Direction = "center"
let eulerX = face.getHeadEulerAngleX(); // 获取头部左右旋转角度
let eulerY = face.getHeadEulerAngleY(); // 获取头部上下俯仰角度
let eulerZ = face.getHeadEulerAngleZ(); // 获取头部上下俯仰角度
// 嘴巴
let leftMouth = face.getLandmark(FaceLandmark.MOUTH_LEFT)
let rightMouth = face.getLandmark(FaceLandmark.MOUTH_RIGHT)
let bottomMouth = face.getLandmark(FaceLandmark.MOUTH_BOTTOM)
let isMouthOpened = false
let ANGLE_THRESHOLD = 30; // 容差值
let ANGLE_HEAD_THRESHOLD = 0; // 容差值
let ANGLE_EYE_THRESHOLD = 6; // 眨眼的容差.
let allHeadBody = false;
if (Math.abs(eulerY) > ANGLE_THRESHOLD) { // 确保头部没有明显上下俯仰
faceRightLeft = eulerY > 0 ? 'left' : 'right';
}
if (Math.abs(eulerX) >= ANGLE_HEAD_THRESHOLD) { // 确保头部没有明显上下俯仰
faceUpDown = eulerY > 0 ? 'down' : 'up';
}
if (leftMouth != null && rightMouth != null && bottomMouth != null) {
// @ts-ignore
let mouthWidth = rightMouth.position.x - leftMouth.position.x
// @ts-ignore
let mouthHeight = bottomMouth.position.y - ((leftMouth.position.y + rightMouth.position.y) / 2)
if (mouthHeight / mouthWidth > 0.4 && faceRightLeft == 'center') {
isMouthOpened = true;
} else {
isMouthOpened = false;
}
}
if(
leftMouth != null &&
rightMouth != null &&
bottomMouth != null &&
noseMark !=null&&
rightEarMark !=null&&
leftEarMark !=null&&
rightEyeMark !=null&&
leftEyeMark !=null
){
allHeadBody = true;
}
cfaces.push({
imgWidth: image.getWidth(),
imgHeight: image.getHeight(),
isSmile: isSmile == null ? false : (isSmile >= 0.8),
rightEye,
leftEye,
faceRightLeft,
faceUpDown,
isMouthOpened,
allHeadBody,
faceId:faceId!=null?(faceId+0):null
})
}
callBack(cfaces)
}
export function decodeImage(success : (evt : XFACE_EVENT_SUCCESS[]) => void, fail : () => void, image : InputImage) {
// @ts-ignore
const highAccuracyOpts = new FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
.setContourMode(FaceDetectorOptions.CONTOUR_MODE_NONE)
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
.enableTracking()
.build()
const detector = FaceDetection.getClient(highAccuracyOpts)
detector.process(image)
.addOnSuccessListener((faces : MutableList<Face>) => {
UTSAndroid.getDispatcher("main").async(function(_){
checkFaceResult(image, faces, (result : XFACE_EVENT_SUCCESS[]) => {
success(result)
})
},null)
})
.addOnFailureListener(e => {
console.error(e)
fail()
})
}
/**
* 通过相册选择检测当前图片是否有人脸.
*/
export function checkFaceByImage(config : XFACE_CHECK_OPTS | null = null) {
const cfg = getDefaultConfig(config)
// @ts-ignore
const highAccuracyOpts = new FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
.setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
.build()
// @ts-ignore
uni.chooseImage({
count: 1,
// @ts-ignore
success(evt : ChooseImageSuccess) {
if (evt.tempFilePaths.length == 0) return;
const imgs = evt.tempFilePaths[0] as string;
const decodedPath = UTSAndroid.convert2AbsFullPath(imgs)
const file = new File(decodedPath)
const image = InputImage.fromFilePath(UTSAndroid.getAppContext() as Context, Uri.fromFile(file));
// const image = InputImage.fromBitmap(BitmapFactory.decodeFile(decodedPath)!, 0)
const detector = FaceDetection.getClient(highAccuracyOpts)
detector.process(image)
.addOnSuccessListener((faces : MutableList<Face>) => {
if (faces.size == 0) {
cfg.success({
isPass: false,
images: [decodedPath] as string[],
videoPath: ""
})
return;
}
checkFaceResult(image, faces, (result : XFACE_EVENT_SUCCESS[]) => {
cfg.enter(result,JSON.stringify(result))
cfg.success({
isPass: true,
images: [decodedPath] as string[],
videoPath: ""
})
})
})
.addOnFailureListener(e => {
console.error(e)
cfg.fail("解析错误")
})
},
fail() {
console.error("未选择图片")
cfg.fail("未选择图片")
}
})
}
/**
* 提供外部图片地址检测当前图片是否存在人脸
*/
export function checkFaceByImageFromFilePath(config : XFACE_CHECK_OPTS) {
const cfg = getDefaultConfig(config)
if (cfg.url == '') {
cfg.fail("url图片地址没提供正确.")
return;
}
const decodedPath = UTSAndroid.convert2AbsFullPath(cfg.url)
const file = new File(decodedPath)
const image = InputImage.fromFilePath(UTSAndroid.getAppContext() as Context, Uri.fromFile(file));
// const image = InputImage.fromBitmap(BitmapFactory.decodeFile(decodedPath)!, 0)
// @ts-ignore
const highAccuracyOpts = new FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
.build()
const detector = FaceDetection.getClient(highAccuracyOpts)
detector.process(image)
.addOnSuccessListener((faces : MutableList<Face>) => {
if (faces.size == 0) {
cfg.enter([] as XFACE_EVENT_SUCCESS[],JSON.stringify([] as XFACE_EVENT_SUCCESS[] ))
cfg.fail("未检测到人脸")
return;
}
checkFaceResult(image, faces, (result : XFACE_EVENT_SUCCESS[]) => {
cfg.enter(result,JSON.stringify(result))
cfg.success({
isPass: true,
images: [decodedPath] as string[],
videoPath: ""
})
})
})
.addOnFailureListener(e => {
console.error(e)
cfg.fail("解析错误")
})
}
/**
* 检查相机权限
*/
export function checkCamraPermissions(call : (istrue : boolean) => void) {
let permissionCheck = ["android.permission.CAMERA"]
if (UTSAndroid.checkSystemPermissionGranted(UTSAndroid.getUniActivity()!, permissionCheck)) {
call(true)
} else {
console.log("当前不具备指定权限")
// 请求拍照权限
UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionCheck, function (allRight : boolean, _ : string[]) {
call(true)
}, function (_ : boolean, _ : string[]) {
//用户拒绝了部分权限
call(false)
})
}
}
let tid = 0
//当前等待检测的指令
let nowInsIndex = 0;
//是否允许开始动作检测
//allow,next,wait'
let isCheckNext = 'wait'
//指令执行中start,checking,dercoding,end
let actioning = ''
/**
* 打开视频进行人脸活体检测
*/
@UTSJS.keepAlive
export function checkFaceByCamra(config : XFACE_CHECK_OPTS) {
if (camera != null) {
camera!.closeCamera()
clearTimeout(tid)
}
actioning = ''
isCheckNext = 'wait'
nowInsIndex = 0
const cfg = getDefaultConfig(config)
if (cfg.viewId == '') {
cfg.fail("没有提供正在的view的id绑定.")
return;
}
if (cfg.instruction.length == 0) {
console.warn("未提供指令,将仅进行人脸验证.")
// cfg.fail("没有提供活体检测指令")
// return;
}
// @ts-ignore
let ele = uni.getElementById(cfg.viewId) as UniElement | null;
if (ele == null) {
cfg.fail("绑定的容器view找不到")
return;
}
let parentView = ele.getAndroidView() as ViewGroup;
let viewWidth = parentView.getWidth()
let viewHeight = parentView.getHeight()
let grop = new FrameLayout(UTSAndroid!.getAppContext()!)
let layaout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
grop.setLayoutParams(layaout)
parentView.addView(grop)
let isPassCheck = false
let checkImgs = [] as string[]
let lastFaceAllBody = ""
let faceId = null as null|number;
if (viewWidth == 0 || viewHeight == 0) {
cfg.fail("绑定的容器view宽高不能为0")
return;
}
camera = new xCamera(grop)
camera?.setCameraDir('front')
camera?.setFlash(false)
camera?.startRecoderVideo((videoPath : string) => {
console.log(videoPath)
UTSAndroid.getDispatcher("main").async(function (_) {
if(lastFaceAllBody!=''){
checkImgs.push(lastFaceAllBody)
}
cfg.success({
videoPath,
images:checkImgs,
isPass:isPassCheck
} as XFACE_EVENT_CHECK_SUCCESS)
})
})
function closeCamera() {
camera?.stopRecoderVideo()
camera?.closeCamera()
parentView.removeAllViews()
}
tid = setTimeout(() => {
cfg.insTipsEvent("start")
actioning = 'start'
isCheckNext = 'allow'
}, 2000)
let headDonwUp = 0
UTSAndroid.getDispatcher("main").async(function (_) {
camera?.setCheckFaceEnter((result:XFACE_EVENT_SUCCESS[],imgBit:any|null) => {
// 无指令时的正脸检测
if(cfg.instruction.length==0){
if(result.length>0){
let item = result[0]
if(item.allHeadBody){
nowInsIndex = -1;
if(imgBit!=null&&camera!=null&&imgBit instanceof Bitmap){
checkImgs.push(camera!.savePhotoBitmapTocache((imgBit! as Bitmap)))
}
isPassCheck = true;
closeCamera()
}
}
return;
}
if (nowInsIndex == -1 || actioning == '') {
return;
}
if (nowInsIndex > cfg.instruction.length - 1) {
nowInsIndex = -1;
cfg.insTipsEvent("end")
isPassCheck = true;
closeCamera()
return;
}
let item = null as null|XFACE_EVENT_SUCCESS
if(result.length>0){
item = result[0]
}
let dir = cfg.instruction[nowInsIndex]
if (actioning == 'start') {
actioning = ''
audio.play(dir)
setTimeout(function () {
cfg.insTipsEvent("ins_start_" + dir)
isCheckNext = 'allow'
actioning = 'checking'
}, 1500);
return;
}
// 保存一次正脸
if(lastFaceAllBody==""){
if(result.length>0){
let item = result[0]
if(item.allHeadBody){
lastFaceAllBody = camera!.savePhotoBitmapTocache(imgBit! as Bitmap)
faceId = item.faceId
console.log(faceId,'+++')
}
}
return;
}
// 进行人脸检测
if(item!=null&&actioning == 'checking'&&isCheckNext == 'allow'){
let rts = result!
if(faceId!=item.faceId||item.faceId==null||faceId==null){
console.log(item.faceId,'---')
isPassCheck = false;
console.log("检脸追踪id变化,检测失败")
nowInsIndex = -1;
cfg.insTipsEvent("end")
closeCamera()
return;
}
// 向左转
if (dir == 'left' && item.faceRightLeft == 'left') {
// 立即更改状态,防止重复处理
actioning = 'dercoding'
cfg.insTipsEvent("ins_end_" + dir)
cfg.enter(result,JSON.stringify(rts))
isCheckNext = 'wait';
audio.play('goold')
if(imgBit!=null&&camera!=null){
checkImgs.push(camera!.savePhotoBitmapTocache(imgBit! as Bitmap))
}
tid = setTimeout(function () {
isCheckNext = 'next';
}, 1000);
} else if (dir == 'right' && item.faceRightLeft == 'right') {
// 立即更改状态,防止重复处理
actioning = 'dercoding'
cfg.insTipsEvent("ins_end_" + dir)
cfg.enter(result,JSON.stringify(rts))
isCheckNext = 'wait';
audio.play('goold')
if(imgBit!=null&&camera!=null){
checkImgs.push(camera!.savePhotoBitmapTocache(imgBit! as Bitmap))
}
tid = setTimeout(function () {
isCheckNext = 'next';
}, 1000);
} else if(dir == 'downUp'&&(item.faceUpDown=='up'||item.faceUpDown=='down')){
if(item.faceUpDown=='up'&&headDonwUp==0){
headDonwUp = 1;
}else if(item.faceUpDown=='down'&&headDonwUp==1){
headDonwUp = 2;
// 立即更改状态,防止重复处理
actioning = 'dercoding'
cfg.insTipsEvent("ins_end_" + dir)
cfg.enter(result,JSON.stringify(rts))
audio.play('goold')
isCheckNext = 'wait';
if(imgBit!=null&&camera!=null){
checkImgs.push(camera!.savePhotoBitmapTocache(imgBit! as Bitmap))
}
tid = setTimeout(function () {
isCheckNext = 'next';
headDonwUp = 0
}, 1000);
}
}else if(dir == 'monthOpenClose'){
if(item.isMouthOpened==true&&headDonwUp==0){
headDonwUp = 1;
}else if(item.isMouthOpened == false&&headDonwUp==1){
headDonwUp = 2;
// 立即更改状态,防止重复处理
actioning = 'dercoding'
cfg.insTipsEvent("ins_end_" + dir)
cfg.enter(result,JSON.stringify(rts))
audio.play('goold')
isCheckNext = 'wait';
if(imgBit!=null&&camera!=null){
checkImgs.push(camera!.savePhotoBitmapTocache(imgBit! as Bitmap))
}
tid = setTimeout(function () {
isCheckNext = 'next';
headDonwUp = 0
}, 1000);
}
}else if(dir == 'eyeOplenClose'){
if((item.leftEye.isOpen&&item.rightEye.isOpen)&&headDonwUp==0){
headDonwUp = 1;
}else if((!item.leftEye.isOpen&&!item.rightEye.isOpen)&&headDonwUp==1){
headDonwUp = 2;
}else if((item.leftEye.isOpen&&item.rightEye.isOpen)&&headDonwUp==2){
headDonwUp = 3;
// 立即更改状态,防止重复处理
actioning = 'dercoding'
cfg.insTipsEvent("ins_end_" + dir)
cfg.enter(result,JSON.stringify(rts))
audio.play('goold')
isCheckNext = 'wait';
if(imgBit!=null&&camera!=null){
checkImgs.push(camera!.savePhotoBitmapTocache(imgBit! as Bitmap))
}
tid = setTimeout(function () {
isCheckNext = 'next';
headDonwUp = 0
}, 1000);
}
}
}
if (isCheckNext == 'next'){
isCheckNext = 'allow';
actioning = 'start'
nowInsIndex += 1;
}
})
})
}
@@ -0,0 +1,5 @@
{
"dependencies": {
"x_facedetection": "./lib/x_facedetection.har"
}
}
@@ -0,0 +1,44 @@
import { x_facedetection_imgcheck } from "x_facedetection"
import { XFACE_CHECK_OPTS, XFACE_EVENT_SUCCESS, XFACE_TEST_EYE_TYPE, XFACE_FACE_Direction, XFACE_EVENT_CHECK_SUCCESS } from "../interface.uts"
import { XFACE_CHECK_OPTS_REAL, getDefaultConfig } from "../libs/config.uts"
export function checkFaceByImage(config : XFACE_CHECK_OPTS | null = null) {
const cfg = getDefaultConfig(config)
// @ts-ignore
uni.chooseImage({
count: 1,
// @ts-ignore
success(evt : ChooseImageSuccess) {
if (evt.tempFilePaths.length == 0) return;
const imgs = evt.tempFilePaths[0] as string;
x_facedetection_imgcheck(imgs, (ispass : boolean) => {
cfg.success({
isPass: ispass,
images: [] as string[],
videoPath: ""
})
})
},
fail() {
cfg.fail("解析错误")
}
})
}
/**
* 提供外部图片地址检测当前图片是否存在人脸
*/
export function checkFaceByImageFromFilePath(config : XFACE_CHECK_OPTS) {
const cfg = getDefaultConfig(config)
const imgs = cfg.url;
x_facedetection_imgcheck(imgs, (ispass : boolean) => {
cfg.success({
isPass: ispass,
images: [] as string[],
videoPath: ""
})
})
}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
</dict>
</plist>
@@ -0,0 +1,355 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>GoogleMVFaceDetectorResources.bundle/BCLjoy_200.emd</key>
<data>
96Uw5p4tLIiDbN11Du0p0c5xqE8=
</data>
<key>GoogleMVFaceDetectorResources.bundle/BCLlefteyeclosed_200.emd</key>
<data>
KBTvwnLXsoe2PIlGUwZzMUOp5k8=
</data>
<key>GoogleMVFaceDetectorResources.bundle/BCLrighteyeclosed_200.emd</key>
<data>
fW6O7h6Je5DNmNrbOeCGmAixyLI=
</data>
<key>GoogleMVFaceDetectorResources.bundle/Info.plist</key>
<data>
sS7uhwBl1ZXssfZceNWmGpZYct0=
</data>
<key>GoogleMVFaceDetectorResources.bundle/LMprec_600.emd</key>
<data>
trY9XrpS4DxHzs3eHljZHIJNHrI=
</data>
<key>GoogleMVFaceDetectorResources.bundle/MFT_fssd_accgray.pb</key>
<data>
tq7fRawRQ359i/c0FEVvpQ6Oee0=
</data>
<key>GoogleMVFaceDetectorResources.bundle/MFT_fssd_fastgray.pb</key>
<data>
DP+qJf0czliL6znGLVx90ASNzCY=
</data>
<key>GoogleMVFaceDetectorResources.bundle/blazeface.tfl</key>
<data>
oseYQ/EQxo7JsNpdNgo4YVLh7a4=
</data>
<key>GoogleMVFaceDetectorResources.bundle/contours.tfl</key>
<data>
PaAh9+zFw3Is4fLMn5iQsLG5M34=
</data>
<key>GoogleMVFaceDetectorResources.bundle/fssd_100_8bit_gray_v1.tflite</key>
<data>
VCp0NyrYJWQnYbGaxVv0Vzy0IeA=
</data>
<key>GoogleMVFaceDetectorResources.bundle/fssd_100_8bit_v1.tflite</key>
<data>
AHaxev+fV4YGoIDboAUFlxfISmY=
</data>
<key>GoogleMVFaceDetectorResources.bundle/fssd_25_8bit_gray_v1.tflite</key>
<data>
Rd53RFFpKHZNvbiDaE2p4XxjbY0=
</data>
<key>GoogleMVFaceDetectorResources.bundle/fssd_25_8bit_v1.tflite</key>
<data>
RxtbTN3LjpFChp3tmniX3KD+dsE=
</data>
<key>Headers/unimoduleXFacedetection-Swift.h</key>
<data>
BgxJ1I5k5MMBQ6xo5tZUy5d60tQ=
</data>
<key>Headers/unimoduleXFacedetection.h</key>
<data>
FH2SRnQAHIO8AbDZPa5/Bz/CcdA=
</data>
<key>Info.plist</key>
<data>
PAgX/P+2Nr5qTH+62odWUCKos8k=
</data>
<key>Modules/module.modulemap</key>
<data>
Zs/pbohrSFVhBYtMd56wG2K8w+s=
</data>
<key>Modules/unimoduleXFacedetection.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo</key>
<data>
aS63R8pVNYw0JVr4ejn70VPlNUc=
</data>
<key>Modules/unimoduleXFacedetection.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
<data>
VR3vMqk9fyleB4pmV+rgX1OX9F4=
</data>
<key>Modules/unimoduleXFacedetection.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
<data>
y73yJ903XXHaZ0v6wemdBjTKYF0=
</data>
<key>Modules/unimoduleXFacedetection.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
<data>
qliuAzBQoC+dStdbKgiR0WqcTV0=
</data>
<key>Modules/unimoduleXFacedetection.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
<data>
y73yJ903XXHaZ0v6wemdBjTKYF0=
</data>
<key>Modules/unimoduleXFacedetection.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
<data>
eXYli4ct9KFQLJTu7d0hBYsH/xw=
</data>
<key>config.json</key>
<data>
CzGjQQN8WuE+yTnq013U9m0usAM=
</data>
</dict>
<key>files2</key>
<dict>
<key>GoogleMVFaceDetectorResources.bundle/BCLjoy_200.emd</key>
<dict>
<key>hash2</key>
<data>
8A8lZEpijnOMFV9JPQcq8UPyU8v5nwi/SCnL5dT0NRw=
</data>
</dict>
<key>GoogleMVFaceDetectorResources.bundle/BCLlefteyeclosed_200.emd</key>
<dict>
<key>hash2</key>
<data>
0xIT5PJf+LIXgeeG/YnO3lkX75s/vlC99V1IOvE27e0=
</data>
</dict>
<key>GoogleMVFaceDetectorResources.bundle/BCLrighteyeclosed_200.emd</key>
<dict>
<key>hash2</key>
<data>
Y8oL2DjlGXdImtrbgKC+AWxaoLe2kPvDiaBkWqzP5P8=
</data>
</dict>
<key>GoogleMVFaceDetectorResources.bundle/Info.plist</key>
<dict>
<key>hash2</key>
<data>
kLtzThegQhDjMdMnp21m/n5y4M/GWaJBT3DF395jFL0=
</data>
</dict>
<key>GoogleMVFaceDetectorResources.bundle/LMprec_600.emd</key>
<dict>
<key>hash2</key>
<data>
DtXIUoL5QNFKVyOmwK8F+cKr1Okww8TwvloHI203QRE=
</data>
</dict>
<key>GoogleMVFaceDetectorResources.bundle/MFT_fssd_accgray.pb</key>
<dict>
<key>hash2</key>
<data>
8K2wTXshtNmImTKmLw7/En7tsn0vhTQqkBRBU8oLgbE=
</data>
</dict>
<key>GoogleMVFaceDetectorResources.bundle/MFT_fssd_fastgray.pb</key>
<dict>
<key>hash2</key>
<data>
CUNCXfGzUODFinvmaL4flC6sM4wZjhpPFoWco+Eif0U=
</data>
</dict>
<key>GoogleMVFaceDetectorResources.bundle/blazeface.tfl</key>
<dict>
<key>hash2</key>
<data>
TyXMFC6WtfIbMjbSFmwuZikgaB8dI5QtJRDpjIr27Nw=
</data>
</dict>
<key>GoogleMVFaceDetectorResources.bundle/contours.tfl</key>
<dict>
<key>hash2</key>
<data>
hBYLwMSnSVF+Yvk8ssaKuP9J3lcfcOttFgJUypoHicA=
</data>
</dict>
<key>GoogleMVFaceDetectorResources.bundle/fssd_100_8bit_gray_v1.tflite</key>
<dict>
<key>hash2</key>
<data>
ekzwnGrkp1gUTUK/NbmiapWYfjxYQdDcORCVaYKZ2as=
</data>
</dict>
<key>GoogleMVFaceDetectorResources.bundle/fssd_100_8bit_v1.tflite</key>
<dict>
<key>hash2</key>
<data>
OvuTDu3I0dQENEgCgdO16M7EwwTlLTT9/IqVCDQLtxk=
</data>
</dict>
<key>GoogleMVFaceDetectorResources.bundle/fssd_25_8bit_gray_v1.tflite</key>
<dict>
<key>hash2</key>
<data>
IVEbvtJxoKkri5ORjg+I9u+70RWjbRVV8GHO4TH2qzw=
</data>
</dict>
<key>GoogleMVFaceDetectorResources.bundle/fssd_25_8bit_v1.tflite</key>
<dict>
<key>hash2</key>
<data>
2pAlL0mzu5QuX4gBgKFFYfZpKW5/s+3s3quqi2u3gio=
</data>
</dict>
<key>Headers/unimoduleXFacedetection-Swift.h</key>
<dict>
<key>hash2</key>
<data>
rTildwV5zLFcxijbFWhzuO4YvO9LA74X/7lz8WuQqJU=
</data>
</dict>
<key>Headers/unimoduleXFacedetection.h</key>
<dict>
<key>hash2</key>
<data>
XAsze1m7Z4PhyI2gj+RafZVS4xyqWjgvqraTawdVPMY=
</data>
</dict>
<key>Modules/module.modulemap</key>
<dict>
<key>hash2</key>
<data>
mktK2hAXHkebM3pE/qIOCLCiT0PsQ+hRCZ1j7Cbvxf4=
</data>
</dict>
<key>Modules/unimoduleXFacedetection.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo</key>
<dict>
<key>hash2</key>
<data>
J9NnQTKtAGGpoVeYKyZw5c4MV06RKpL8Al+SnPwYZdA=
</data>
</dict>
<key>Modules/unimoduleXFacedetection.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
<dict>
<key>hash2</key>
<data>
q82Bl78THFBZ+ZocAvtg3vOuP28SaJ8euEkpbaHmZRA=
</data>
</dict>
<key>Modules/unimoduleXFacedetection.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
<dict>
<key>hash2</key>
<data>
M+3tx0iPWvL/1tT7IYkaDX1waVEMZ0IyQGmfsPV4JNQ=
</data>
</dict>
<key>Modules/unimoduleXFacedetection.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
<dict>
<key>hash2</key>
<data>
WjiHhAVADf0QcXgeRlnwqWksqonEUfqUD8RRV4E3hd8=
</data>
</dict>
<key>Modules/unimoduleXFacedetection.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
<dict>
<key>hash2</key>
<data>
M+3tx0iPWvL/1tT7IYkaDX1waVEMZ0IyQGmfsPV4JNQ=
</data>
</dict>
<key>Modules/unimoduleXFacedetection.swiftmodule/x86_64-apple-ios-simulator.swiftmodule</key>
<dict>
<key>hash2</key>
<data>
Yulvj7VnfZMMHAC7X3icMFcyuJ7FzZNFowozFX8vwpQ=
</data>
</dict>
<key>config.json</key>
<dict>
<key>hash2</key>
<data>
u75O7GUa7m6ue3dUjfs+U7kF97YboIw8B2A8pKZTLAo=
</data>
</dict>
</dict>
<key>rules</key>
<dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^version.plist$</key>
<true/>
</dict>
<key>rules2</key>
<dict>
<key>.*\.dSYM($|/)</key>
<dict>
<key>weight</key>
<real>11</real>
</dict>
<key>^(.*/)?\.DS_Store$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>2000</real>
</dict>
<key>^.*</key>
<true/>
<key>^.*\.lproj/</key>
<dict>
<key>optional</key>
<true/>
<key>weight</key>
<real>1000</real>
</dict>
<key>^.*\.lproj/locversion.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>1100</real>
</dict>
<key>^Base\.lproj/</key>
<dict>
<key>weight</key>
<real>1010</real>
</dict>
<key>^Info\.plist$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^PkgInfo$</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>20</real>
</dict>
<key>^embedded\.provisionprofile$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
<key>^version\.plist$</key>
<dict>
<key>weight</key>
<real>20</real>
</dict>
</dict>
</dict>
</plist>
@@ -0,0 +1,10 @@
{
"deploymentTarget": "12",
"dependencies-pods": [
{
"name": "GoogleMLKit/FaceDetection",
"version": "5.0.0"
}
]
}
@@ -0,0 +1,225 @@
import { AVCaptureSession, AVCaptureDevice, AVMediaType, AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureDeviceInput, AVCaptureInput, AVCaptureVideoDataOutput, AVCaptureConnection, AVCaptureVideoPreviewLayer, AVLayerVideoGravity, AVCaptureVideoOrientation } from 'AVFoundation';
import { PHPhotoLibrary, PHAuthorizationStatus } from 'Photos';
import { CMSampleBuffer, CMSampleBufferGetFormatDescription, CMFormatDescription, CMVideoFormatDescriptionGetDimensions } from 'CoreMedia';
import { DispatchQueue } from "Dispatch"
import { UIView, UIImage, UIApplication, UIImageView, UILabel, UIViewController, UIFontDescriptor, UIFont, NSTextAlignment, UITapGestureRecognizer, UIImagePickerController, UIImagePickerControllerDelegate, UINavigationControllerDelegate } from 'UIKit';
import { CGRect, CGFloat, CGPoint } from 'CoreFoundation';
import { Face, FaceContour, FaceDetector, FaceDetectorOptions,FaceContourType,FaceLandmarkType, FaceLandmark,FaceDetectorPerformanceMode,FaceDetectorClassificationMode,FaceDetectorLandmarkMode } from "MLKitFaceDetection"
import { VisionImage } from "MLKit"
import { NotificationCenter, NSNotification, Notification } from 'Foundation';
import { Selector } from 'ObjectiveC';
import { Alignment } from 'ARKit';
import { CABasicAnimation } from 'QuartzCore';
import { Int,Float } from 'Swift';
import { XFACE_CHECK_OPTS, XFACE_EVENT_SUCCESS, XFACE_TEST_EYE_TYPE, XFACE_FACE_Direction, XFACE_EVENT_CHECK_SUCCESS } from "../interface.uts"
import { XFACE_CHECK_OPTS_REAL, getDefaultConfig } from "../libs/config.uts"
const checkFaceResult = (orimg:UIImage, faces : Array<Face>, callBack : (result : XFACE_EVENT_SUCCESS[]) => void) => {
const cfaces = [] as XFACE_EVENT_SUCCESS[]
// @ts-ignore
for (item in faces) {
// @ts-ignore
const face = item as Face;
let rightEye = { openNum: 0, isOpen: false } as XFACE_TEST_EYE_TYPE;
let leftEye = { openNum: 0, isOpen: false } as XFACE_TEST_EYE_TYPE;
// 眼睛
let rightEyeMark = face.landmark(ofType = FaceLandmarkType.rightEye)
let leftEyeMark = face.landmark(ofType = FaceLandmarkType.leftEye)
// 鼻子
let noseMark = face.landmark(ofType = FaceLandmarkType.noseBase)
// 耳朵
let rightEarMark = face.landmark(ofType = FaceLandmarkType.rightEar)
let leftEarMark = face.landmark(ofType = FaceLandmarkType.leftEar)
let rightEyeNum = face.rightEyeOpenProbability
let leftEyeNum = face.leftEyeOpenProbability
let isSmile = face.smilingProbability
let faceId = face.trackingID
if (rightEyeNum != null) {
rightEye.openNum = new Number(rightEyeNum) + 0;
rightEye.isOpen = new Number(rightEyeNum) > 0.7
}
if (leftEyeNum != null) {
leftEye.openNum = new Number(leftEyeNum)+ 0;
leftEye.isOpen = new Number(leftEyeNum) > 0.7
}
// 检测当前左,右,下,上
let faceRightLeft : XFACE_FACE_Direction = "center"
let faceUpDown : XFACE_FACE_Direction = "center"
let eulerX = face.headEulerAngleX; // 获取头部左右旋转角度
let eulerY = face.headEulerAngleY; // 获取头部上下俯仰角度
let eulerZ = face.headEulerAngleZ; // 获取头部上下俯仰角度
// 嘴巴
let leftMouth = face.landmark(ofType = FaceLandmarkType.mouthLeft)
let rightMouth = face.landmark(ofType = FaceLandmarkType.mouthRight)
let bottomMouth = face.landmark(ofType = FaceLandmarkType.mouthBottom)
let isMouthOpened = false
let ANGLE_THRESHOLD = 30; // 容差值
let ANGLE_HEAD_THRESHOLD = 0; // 容差值
let ANGLE_EYE_THRESHOLD = 6; // 眨眼的容差.
let allHeadBody = false;
if (Math.abs(Number(eulerY)) > ANGLE_THRESHOLD) { // 确保头部没有明显上下俯仰
faceRightLeft = eulerY > 0 ? 'left' : 'right';
}
if (Math.abs(Number(eulerX)) >= ANGLE_HEAD_THRESHOLD) { // 确保头部没有明显上下俯仰
faceUpDown = eulerY > 0 ? 'down' : 'up';
}
if (leftMouth != null && rightMouth != null && bottomMouth != null) {
let mouthWidth = rightMouth!.position.x - leftMouth!.position.x
let mouthHeight = bottomMouth!.position.y - ((leftMouth!.position.y + rightMouth!.position.y) / 2)
if (mouthHeight / mouthWidth > 0.4 && faceRightLeft == 'center') {
isMouthOpened = true;
} else {
isMouthOpened = false;
}
}
if(
leftMouth != null &&
rightMouth != null &&
bottomMouth != null &&
noseMark !=null&&
rightEarMark !=null&&
leftEarMark !=null&&
rightEyeMark !=null&&
leftEyeMark !=null
){
allHeadBody = true;
}
cfaces.push({
imgWidth: orimg.size.width,
imgHeight: orimg.size.height,
isSmile: isSmile == null ? false : (isSmile >= 0.8),
rightEye,
leftEye,
faceRightLeft,
faceUpDown,
isMouthOpened,
allHeadBody,
faceId:faceId!=null?(faceId+0):null
})
}
callBack(cfaces)
}
export function checkFaceByImage(config : XFACE_CHECK_OPTS | null = null){
const cfg = getDefaultConfig(config)
let options_s = FaceDetectorOptions()
options_s.performanceMode = FaceDetectorPerformanceMode.accurate
options_s.landmarkMode = FaceDetectorLandmarkMode.all
options_s.classificationMode = FaceDetectorClassificationMode.all
// @ts-ignore
uni.chooseImage({
count: 1,
// @ts-ignore
success(evt : ChooseImageSuccess) {
if (evt.tempFilePaths.length == 0) return;
const imgs = evt.tempFilePaths[0] as string;
const decodedPath = UTSiOS.convert2AbsFullPath(imgs)
let img = new UIImage(contentsOfFile = decodedPath);
let image = new VisionImage(image = img!);
let faceDetector = FaceDetector.faceDetector(options = options_s)
faceDetector.process(image as VisionImage,completion = (faces:Array<Face>|null,error:any|null)=>{
if(error != null||faces==null||(faces?.length??0)==0){
cfg.success({
images:[],
videoPath:"",
isPass:false
})
return;
}
const _faces = faces!;
checkFaceResult(img!, _faces, (result : XFACE_EVENT_SUCCESS[]) => {
DispatchQueue.main.async(execute = () : void => {
cfg.enter(result,JSON.stringify(result)!)
cfg.success({
isPass: true,
images: [decodedPath] as string[],
videoPath: ""
})
})
})
// for(item in faces!){
// const face = item as Face
// let leftEye = face.landmark(ofType = FaceLandmarkType.leftEye)
// if(leftEye!=null){
// let leftEyePosition = leftEye!.position
// console.log(leftEyePosition.x,'----',img!.size.width)
// }
// }
})
},
fail() {
cfg.fail("解析错误")
}
})
}
/**
* 提供外部图片地址检测当前图片是否存在人脸
*/
export function checkFaceByImageFromFilePath(config : XFACE_CHECK_OPTS) {
const cfg = getDefaultConfig(config)
const imgs = cfg.url;
const decodedPath = UTSiOS.convert2AbsFullPath(imgs!)
let img = new UIImage(contentsOfFile = decodedPath);
let image = new VisionImage(image = img!);
let options_s = FaceDetectorOptions()
options_s.performanceMode = FaceDetectorPerformanceMode.accurate
options_s.landmarkMode = FaceDetectorLandmarkMode.all
options_s.classificationMode = FaceDetectorClassificationMode.all
let faceDetector = FaceDetector.faceDetector(options = options_s)
faceDetector.process(image as VisionImage,completion = (faces:Array<Face>|null,error:any|null)=>{
if(error != null||faces==null||(faces?.length??0)==0){
cfg.success({
images:[],
videoPath:"",
isPass:false
})
return;
}
const _faces = faces!;
checkFaceResult(img!, _faces, (result : XFACE_EVENT_SUCCESS[]) => {
DispatchQueue.main.async(execute = () : void => {
cfg.enter(result,JSON.stringify(result)!)
cfg.success({
isPass: true,
images: [decodedPath] as string[],
videoPath: ""
})
})
})
})
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>我们需要您的相机权限来拍照或录像。</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要使用你的相册进行选择视频及图片</string>
</dict>
</plist>
@@ -0,0 +1,99 @@
export type XFACE_CHECK_OPTS = {
//如果是通过图片检测需要提供
url ?: string,
//如果是通过图片buffer解析需要提供(暂无法使用)
buffer ?: ArrayBuffer,
//ViewId,如果是通过相机动态绑定嵌入式页面时,需要提供页面view的id
viewId ?: string,
/**
* 活体检测指令,通过相机视频检测
* 如果提供指令数组时,会根据完整指令顺序检测直到验证通过.
* 如果视频检测时,指令为空数组则只检测整个五官是不是在画面中,一旦检测到人脸立即验证通过.
*/
instruction ?: XFACE_INS[]
//验证结束并通过时返回
success ?: (data : XFACE_EVENT_CHECK_SUCCESS) => void,
/**
* 在视频活体检测时,返回每一帧的检测详细检测过程的信息,此事件是持续触发,直到结束,会一直触发每帧的图片检测结果。
* 在图片检测的时候,只返回一次。
*/
enter ?: (data : XFACE_EVENT_SUCCESS[],maps:string) => void,
/**
* 开始检测,正在开始检测的指令。类似:向左转一次,向右转一次,点点头,张张嘴等执行时触发,
* 方便用户自定自己的提示语或者语音播报
* 'start',现在开始,
* 'ins_start_指令名称':某个指令开始,如:ins_start_left,ins_start_right,通过这个解析哪个指令开始执行
* 'ins_end_指令名称':某个指令结束,如:ins_end_left,ins_end_right,通过这个解析哪个指令结束执行
* 'end'结束所有指令
* 之后会触发success,enter事件
*/
insTipsEvent ?: (data : string) => void,
fail ?: (error : string) => void,
}
export type XFACE_TEST_EYE_TYPE = {
//眼睛张开的概率值
openNum : number,
isOpen : boolean
}
export type XFACE_FACE_Direction = "left" | "right" | "center" | "down" | "up"
/**
* 活体检测指令,可以通过指令控制检测动作
* left:向左转一次
* right:向右转一次
* downUp:上下点头
* eyeOplenClose:眨眼
* monthOpenClose:张张嘴.
*/
export type XFACE_INS = "left" | "right" | "downUp" | "eyeOplenClose" | "monthOpenClose"
// >0.8就算打开了眼或者张开了嘴.
export type XFACE_EVENT_SUCCESS = {
// 右眼眨眼睛的闭合数据
rightEye : XFACE_TEST_EYE_TYPE,
// 左眼眨眼睛的闭合数据
leftEye : XFACE_TEST_EYE_TYPE,
// 是否在微信
isSmile : boolean,
//脸向左,向在,中间
faceRightLeft : XFACE_FACE_Direction,
//点头的方向
faceUpDown : XFACE_FACE_Direction,
imgWidth : number,
imgHeight : number,
// 嘴巴是不是张开
isMouthOpened : boolean,
// 整个脑袋是否在画面中.
allHeadBody : boolean,
/**
* 检测过程中如果被检测人移动移出屏幕或者光线变化太大或者明显改变人造型等都会被改变此值,并认为并非是同一个从而
* 让检测认证失败,因此被检测人一定要从头到尾保持端正并始终不离开视频画面.
* 人脸Id
*/
faceId:number|null
}
export type XFACE_EVENT_CHECK_SUCCESS = {
/**
* 此结果是在活体检测结果会返回,如果是通过图片人脸检测,则此值为空值.
* 验证结束保存的视频地址,方便上传保存到后台储存备份,抽查验证。
*/
videoPath : string
/**
* 此结果是在活体检测结果会返回,如果是通过图片人脸检测,则此值为空数组
* 在活体检测过程中,会按照你给定人的指令顺序进行截图,保存验证时人脸对应的动画图片.多少个指令就有多少张图片.最后
* 一张为全脸捕捉画面.
*/
images : string[],
/**
* 验证是否通过
*/
isPass : boolean
}
export type AUTH_CALL_BACK_TYPE = (auth : boolean) => void;
export type XCHECKFACE_ENTER_CALL = (image : XFACE_EVENT_SUCCESS[], bitMap : any | null) => void;
export type CAMERA_PHOTO_SIZE = {
width : number,
height : number
}
@@ -0,0 +1,36 @@
import { XFACE_CHECK_OPTS,XFACE_EVENT_SUCCESS,XFACE_EVENT_CHECK_SUCCESS,XFACE_INS } from "../interface.uts"
export type XFACE_CHECK_OPTS_REAL = {
url : string,
buffer : ArrayBuffer,
viewId :string,
// 活体检测指令,在通过相机视频检测的时候为必填
instruction:XFACE_INS[],
insTipsEvent:(data:string)=>void,
success : (data : XFACE_EVENT_CHECK_SUCCESS) => void,
enter : (data : XFACE_EVENT_SUCCESS[],maps:string) => void,
fail : (error : string) => void,
}
export function getDefaultConfig(config : XFACE_CHECK_OPTS|null = null) : XFACE_CHECK_OPTS_REAL{
let cfg = {
url: "",
buffer: new ArrayBuffer(0),
viewId:"",
instruction:[] as XFACE_INS[],
insTipsEvent:(data:string)=> {},
success: (data : XFACE_EVENT_CHECK_SUCCESS) => { },
enter : (data : XFACE_EVENT_SUCCESS[],maps:string) => { },
fail: (error : string) => { },
} as XFACE_CHECK_OPTS_REAL
if(config==null) return cfg;
cfg.url = config?.url ?? cfg.url;
cfg.buffer = config?.buffer ?? cfg.buffer;
cfg.success = config?.success ?? cfg.success;
cfg.enter = config?.enter ?? cfg.enter;
cfg.fail = config?.fail ?? cfg.fail;
cfg.viewId = config?.viewId ?? cfg.viewId;
cfg.instruction = config?.instruction ?? cfg.instruction;
cfg.insTipsEvent = config?.insTipsEvent ?? cfg.insTipsEvent;
return cfg;
}
@@ -0,0 +1,3 @@
export function checkFaceByImage(){}
export function checkFaceByImageFromFilePath(){}
@@ -0,0 +1,2 @@
export function checkFaceByImage(){}
export function checkFaceByImageFromFilePath(){}