226 lines
7.6 KiB
Plaintext
226 lines
7.6 KiB
Plaintext
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: ""
|
|
})
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
}
|
|
|