feat: can add faceId by uploading images (#3641)

This commit is contained in:
DacongDA 2025-03-09 01:29:25 +08:00 committed by GitHub
parent a39a311d2f
commit 9610ce5b8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 169 additions and 61 deletions

View File

@ -14,11 +14,12 @@
import * as faceapi from "face-api.js"; import * as faceapi from "face-api.js";
import React, {useState} from "react"; import React, {useState} from "react";
import {Button, Modal, Progress, Spin, message} from "antd"; import {Button, Modal, Progress, Space, Spin, message} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import Dragger from "antd/es/upload/Dragger";
const FaceRecognitionModal = (props) => { const FaceRecognitionModal = (props) => {
const {visible, onOk, onCancel} = props; const {visible, onOk, onCancel, withImage} = props;
const [modelsLoaded, setModelsLoaded] = React.useState(false); const [modelsLoaded, setModelsLoaded] = React.useState(false);
const [isCameraCaptured, setIsCameraCaptured] = useState(false); const [isCameraCaptured, setIsCameraCaptured] = useState(false);
@ -28,6 +29,10 @@ const FaceRecognitionModal = (props) => {
const mediaStreamRef = React.useRef(null); const mediaStreamRef = React.useRef(null);
const [percent, setPercent] = useState(0); const [percent, setPercent] = useState(0);
const [files, setFiles] = useState([]);
const [currentFaceId, setCurrentFaceId] = React.useState();
const [currentFaceIndex, setCurrentFaceIndex] = React.useState();
React.useEffect(() => { React.useEffect(() => {
const loadModels = async() => { const loadModels = async() => {
// const MODEL_URL = process.env.PUBLIC_URL + "/models"; // const MODEL_URL = process.env.PUBLIC_URL + "/models";
@ -50,6 +55,9 @@ const FaceRecognitionModal = (props) => {
}, []); }, []);
React.useEffect(() => { React.useEffect(() => {
if (withImage) {
return;
}
if (visible) { if (visible) {
setPercent(0); setPercent(0);
if (modelsLoaded) { if (modelsLoaded) {
@ -75,6 +83,9 @@ const FaceRecognitionModal = (props) => {
}, [visible, modelsLoaded]); }, [visible, modelsLoaded]);
React.useEffect(() => { React.useEffect(() => {
if (withImage) {
return;
}
if (isCameraCaptured) { if (isCameraCaptured) {
let count = 0; let count = 0;
const interval = setInterval(() => { const interval = setInterval(() => {
@ -98,6 +109,9 @@ const FaceRecognitionModal = (props) => {
}, [isCameraCaptured]); }, [isCameraCaptured]);
const handleStreamVideo = () => { const handleStreamVideo = () => {
if (withImage) {
return;
}
let count = 0; let count = 0;
let goodCount = 0; let goodCount = 0;
if (!detection.current) { if (!detection.current) {
@ -148,6 +162,16 @@ const FaceRecognitionModal = (props) => {
} }
}; };
const getBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
};
if (!withImage) {
return ( return (
<div> <div>
<Modal <Modal
@ -164,7 +188,14 @@ const FaceRecognitionModal = (props) => {
]} ]}
> >
<Progress percent={percent} /> <Progress percent={percent} />
<div style={{marginTop: "20px", marginBottom: "50px", justifyContent: "center", alignContent: "center", position: "relative", flexDirection: "column"}}> <div style={{
marginTop: "20px",
marginBottom: "50px",
justifyContent: "center",
alignContent: "center",
position: "relative",
flexDirection: "column",
}}>
{ {
modelsLoaded ? modelsLoaded ?
<div style={{display: "flex", justifyContent: "center", alignContent: "center"}}> <div style={{display: "flex", justifyContent: "center", alignContent: "center"}}>
@ -206,7 +237,8 @@ const FaceRecognitionModal = (props) => {
</div> </div>
: :
<div> <div>
<Spin tip={i18next.t("login:Loading")} size="large" style={{display: "flex", justifyContent: "center", alignContent: "center"}}> <Spin tip={i18next.t("login:Loading")} size="large"
style={{display: "flex", justifyContent: "center", alignContent: "center"}}>
<div className="content" /> <div className="content" />
</Spin> </Spin>
</div> </div>
@ -215,6 +247,78 @@ const FaceRecognitionModal = (props) => {
</Modal> </Modal>
</div> </div>
); );
} else {
return <div>
<Modal closable={false}
maskClosable={false}
destroyOnClose={true}
open={visible}
title={i18next.t("login:Face Recognition")}
width={350}
footer={[
<Button key="ok" type={"primary"} disabled={!currentFaceId || currentFaceId?.length === 0} onClick={() => {
onOk(Array.from(currentFaceId.descriptor));
}}>
Ok
</Button>,
<Button key="back" onClick={onCancel}>
Cancel
</Button>,
]}>
<Space direction={"vertical"} style={{width: "100%"}}>
<Dragger
multiple={true}
defaultFileList={files}
style={{width: "100%"}}
beforeUpload={(file) => {
getBase64(file).then(res => {
file.base64 = res;
files.push(file);
});
setCurrentFaceId([]);
return false;
}}
onRemove={(file) => {
const index = files.indexOf(file);
const newFileList = files.slice();
newFileList.splice(index, 1);
setFiles(newFileList);
setCurrentFaceId([]);
}}
>
<p>{i18next.t("general:Click to Upload")}</p>
</Dragger >
{
modelsLoaded ? <Button style={{width: "100%"}} onClick={() => {
let maxScore = 0;
files.forEach((file, fileIndex) => {
const img = new Image();
img.src = file.base64;
faceapi.detectAllFaces(img, new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceDescriptors().then(res2 => {
if (res2[0]?.detection.score > 0.9 && res2[0]?.detection.score > maxScore) {
maxScore = res2[0]?.detection.score;
setCurrentFaceId(res2[0]);
setCurrentFaceIndex(fileIndex);
}
});
});
if (currentFaceId?.length === 0) {
message.error(i18next.t("login:Face recognition failed"));
}
}}> {i18next.t("application:Generate faceId")}</Button> : null
}
</Space>
{
currentFaceId && currentFaceId.length !== 0 ? (
<React.Fragment>
<div>{i18next.t("application:Select")}:{files[currentFaceIndex]?.name}</div>
<div><img src={files[currentFaceIndex]?.base64} alt="selected" style={{width: "100%"}} /></div>
</React.Fragment>
) : null
}
</Modal>
</div>;
}
}; };
export default FaceRecognitionModal; export default FaceRecognitionModal;

View File

@ -97,12 +97,16 @@ class FaceIdTable extends React.Component {
title={() => ( title={() => (
<div> <div>
{i18next.t("user:Face IDs")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("user:Face IDs")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button disabled={this.props.table?.length >= 5} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.setState({openFaceRecognitionModal: true})}> <Button disabled={this.props.table?.length >= 5} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.setState({openFaceRecognitionModal: true, withImage: false})}>
{i18next.t("general:Add Face Id")} {i18next.t("general:Add Face Id")}
</Button> </Button>
<Button disabled={this.props.table?.length >= 5} style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.setState({openFaceRecognitionModal: true, withImage: true})}>
{i18next.t("general:Add Face Id with image")}
</Button>
<Suspense fallback={null}> <Suspense fallback={null}>
<FaceRecognitionModal <FaceRecognitionModal
visible={this.state.openFaceRecognitionModal} visible={this.state.openFaceRecognitionModal}
withImage={this.state.withImage}
onOk={(faceIdData) => { onOk={(faceIdData) => {
this.addFaceId(table, faceIdData); this.addFaceId(table, faceIdData);
this.setState({openFaceRecognitionModal: false}); this.setState({openFaceRecognitionModal: false});