Was brauchen wir für das Werkzeug:
→ Ordnerstruktur & Dateien (siehe How-to: Basic Setup)
→ Browser (funktioniert am besten mit Chrome)
→ Localhost (siehe How-to: Localhost)
→ Text-Editor (zb. SublimeText)
→ Faceapi und Models Folder (im Werkzeug Ordner ablegen) Download
Die index.html beinhaltet u.A.:
→ <script> zum Einbinden des benötigten Scripts
→ <h1> Überschrift (optional)
→ <p> Anleitungstext (optional)
→ <button> zum Stoppen/Starten des Trackings
→ <video> zum Gesicht tracken
→ <div> zum Anzeigen der ausgeschnittenen Gesichtsteile
1. Öffne die index.html
Du kannst den Code kopieren und in deine Datei einfügen:
<!DOCTYPE html>
<html>
<head>
<title>Autor*in</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script defer src="face-api.min.js"></script>
<script defer src="js/script.js"></script>
</head>
<body>
<h1>Autor*innen Selfie</h1>
<p>Bewege dein Gesicht und zeichne damit ein Bild von dir selbst.<br>
Drücke auf Löschen um die Canvas zu löschen, auf Start oder Stopp um das Video anzuhalten.<br>
</p>
<button id="toggleButton">Stopp</button>
<button id="deleteButton">Löschen</button>
<video id="video" width="1200" height="800" autoplay muted></video>
<div id="markedPartsContainer"></div>
</body>
</html>
2. Speicher die index.html
1. Öffne die style.css
Die style.css beinhaltet:
→ *{} Zücksetzen auf den Default Style
→ h1{}, p{}, button{}, div{} .slider{} basic Styling (optinal)
→ video{} macht das Input Video unsichtbar (wichtig)
Du kannst den Code kopieren und in deine style.css Datei einfügen:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 100vw;
height: 100vh;
}
h1,
p {
margin: 20px;
}
div {
pointer-events: none;
}
video {
opacity: 0;
position: absolute;
pointer-events: none;
z-index: -9999999999999 !important;
}
button {
margin-left: 20px;
z-index: 9999999999999 !important;
}
2. Speicher die style.css
Die script.js beinhaltet:
→ startVideo: Ermöglicht auf Kamera der Nutzer*innen zuzugreifen und startet den Video-Stream
→ toggleFunction: Starten und Stoppen der Gesichtserkennung
→ runFunction: Ist für die eigentliche Verarbeitung der Gesichtserkennung zuständig
→ detectFaces: Funktion um Gesichter im Video zu erkennen
→ extractMarkedParts: Zeichnet gefüllte Rechtecke um die markierten Gesichtsteile auf dem Canvas
→ extractMarkedParts: Extraktion der markierten Teile aus dem Video-Stream / Canvas
→ printMarkedParts: Fügt das Bild der markierten Teile dem Container-Element hinzu
1. Öffne die script.js
Du kannst den Code kopieren und in deine script.js Datei einfügen:
const video = document.getElementById('video');
const markedPartsContainer = document.getElementById('markedPartsContainer');
const deleteButton = document.getElementById('deleteButton');
const toggleButton = document.getElementById('toggleButton');
let imageIndex = 0;
let isProcessing = false;
let isRunning = true;
let isVideoLoaded = false; // Track video loading state
Promise.all([
faceapi.nets.tinyFaceDetector.loadFromUri('/models'),
faceapi.nets.faceLandmark68Net.loadFromUri('/models')
]).then(startVideo);
function startVideo() {
navigator.mediaDevices.getUserMedia({ video: true })
.then((stream) => {
video.srcObject = stream;
video.onloadedmetadata = () => {
video.play();
isVideoLoaded = true;
runFunction();
};
})
.catch((err) => console.error(err));
}
deleteButton.addEventListener('click', () => {
markedPartsContainer.innerHTML = '';
imageIndex = 0;
});
toggleButton.addEventListener('click', toggleFunction);
function toggleFunction() {
isRunning = !isRunning;
toggleButton.textContent = isRunning ? 'Stop' : 'Start';
if (isRunning) {
runFunction();
}
}
function runFunction() {
if (!isVideoLoaded) {
setTimeout(runFunction, 200);
return;
}
const displaySize = { width: video.width, height: video.height };
const canvas = faceapi.createCanvasFromMedia(video);
markedPartsContainer.appendChild(canvas);
faceapi.matchDimensions(canvas, displaySize);
async function detectFaces() {
if (!isProcessing) {
isProcessing = true;
const detections = await faceapi
.detectAllFaces(video, new faceapi.TinyFaceDetectorOptions({ inputSize: 320 }))
.withFaceLandmarks(false);
const resizedDetections = faceapi.resizeResults(detections, displaySize);
if (resizedDetections && resizedDetections.length > 0) {
const landmarks = resizedDetections[0].landmarks;
const eyeLandmarks = [landmarks.getLeftEye(), landmarks.getRightEye()];
const noseLandmarks = [landmarks.getNose()];
const mouthLandmarks = [landmarks.getMouth()];
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
drawFilledSurfaces(ctx, eyeLandmarks, 1.5, 10, 70, 40);
drawFilledSurfaces(ctx, noseLandmarks, 2, 10, 50, 60);
drawFilledSurfaces(ctx, mouthLandmarks, 1, 0, 150, 70);
const markedParts = extractMarkedParts(video, ctx);
if (markedParts) {
printMarkedParts(markedPartsContainer, markedParts, imageIndex);
imageIndex++;
}
}
isProcessing = false;
}
if (isRunning) {
setTimeout(detectFaces, 100);
}
}
detectFaces();
}
function drawFilledSurfaces(ctx, landmarkGroups, sizeMultiplier, padding, width, height) {
landmarkGroups.forEach((landmarks) => {
const minX = Math.min(...landmarks.map((pt) => pt.x)) - padding;
const minY = Math.min(...landmarks.map((pt) => pt.y)) - padding;
const maxWidth = Math.max(...landmarks.map((pt) => pt.x)) + padding;
const maxHeight = Math.max(...landmarks.map((pt) => pt.y)) + padding;
const scaledWidth = width * sizeMultiplier;
const scaledHeight = height * sizeMultiplier;
const offsetX = (maxWidth - minX - scaledWidth) / 2;
const offsetY = (maxHeight - minY - scaledHeight) / 2;
const startX = minX + offsetX;
const startY = minY + offsetY;
ctx.beginPath();
ctx.rect(startX, startY, scaledWidth, scaledHeight);
ctx.closePath();
ctx.fill();
});
}
function extractMarkedParts(video, ctx) {
const { width, height } = video;
const padding = 200;
const canvas = document.createElement('canvas');
canvas.width = width + padding * 2;
canvas.height = height + padding * 2;
const canvasCtx = canvas.getContext('2d');
canvasCtx.fillStyle = 'white';
canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
canvasCtx.drawImage(video, padding, padding, width, height);
canvasCtx.globalCompositeOperation = 'destination-in';
canvasCtx.drawImage(ctx.canvas, padding, padding);
return canvas.toDataURL();
}
function printMarkedParts(container, markedParts, index) {
const image = document.createElement('img');
image.src = markedParts;
image.alt = `Marked Parts ${index}`;
image.style.position = 'fixed';
image.style.top = '0';
image.style.left = '0';
container.appendChild(image);
}
2. Speicher die script.js