mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 02:35:49 +08:00
feat: can add faceId by uploading images (#3641)
This commit is contained in:
parent
a39a311d2f
commit
9610ce5b8c
@ -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;
|
||||||
|
@ -97,12 +97,16 @@ class FaceIdTable extends React.Component {
|
|||||||
title={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{i18next.t("user:Face IDs")}
|
{i18next.t("user:Face IDs")}
|
||||||
<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});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user