Skip to content

Instantly share code, notes, and snippets.

@CharStiles
Created April 8, 2025 16:08
Show Gist options
  • Save CharStiles/962f78513beba1535f81241605d7fbcc to your computer and use it in GitHub Desktop.
Save CharStiles/962f78513beba1535f81241605d7fbcc to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Face Averaging</title>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/[email protected]"></script>
<style>
body {
margin: 0;
padding: 20px;
font-family: Arial, sans-serif;
background-color: #f0f0f0;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #333;
}
#fileInput {
display: block;
margin: 20px auto;
}
#averageCanvas {
display: block;
margin: 20px auto;
border: 1px solid #ddd;
}
#status {
text-align: center;
margin: 10px 0;
font-weight: bold;
color: #555;
}
.gallery {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.gallery img {
width: 100px;
height: 100px;
object-fit: cover;
border: 1px solid #ddd;
}
</style>
</head>
<body>
<div class="container">
<h1>Face Averaging Tool</h1>
<p>Select multiple images containing faces to create an average face.</p>
<input type="file" id="fileInput" multiple accept="image/*">
<div id="status">Loading face detection model...</div>
<canvas id="averageCanvas" width="400" height="400"></canvas>
<div class="gallery" id="imageGallery"></div>
</div>
<script>
// Global variables
let images = [];
let faceDetections = [];
let model;
// DOM elements
const fileInput = document.getElementById('fileInput');
const statusText = document.getElementById('status');
const averageCanvas = document.getElementById('averageCanvas');
const imageGallery = document.getElementById('imageGallery');
// Wait for document to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
loadModel();
});
// Load face detection model
async function loadModel() {
statusText.textContent = "Loading face detection model...";
try {
model = await faceDetection.createDetector(
faceDetection.SupportedModels.MediaPipeFaceDetector,
{
runtime: 'tfjs',
modelType: 'short'
}
);
statusText.textContent = "Model loaded. Select images to begin.";
} catch (error) {
statusText.textContent = "Error loading model. Please refresh and try again.";
console.error("Error loading model:", error);
}
}
// Handle file selection
fileInput.addEventListener('change', async (event) => {
const files = event.target.files;
if (files.length === 0) return;
// Reset
images = [];
faceDetections = [];
imageGallery.innerHTML = '';
statusText.textContent = `Selected ${files.length} images. Processing...`;
// Process each file
for (let file of files) {
try {
const img = await loadImage(file);
images.push(img);
// Create a canvas to process the image
const canvas = document.createElement('canvas');
const maxSize = 640;
let width = img.width;
let height = img.height;
if (width > height && width > maxSize) {
height = Math.round((height * maxSize) / width);
width = maxSize;
} else if (height > maxSize) {
width = Math.round((width * maxSize) / height);
height = maxSize;
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
// Detect faces
const predictions = await model.estimateFaces(canvas);
console.log("Raw predictions for", file.name, ":", predictions);
if (predictions && predictions.length > 0) {
const face = predictions[0];
// Check if the face detection result has the expected format
if (face && face.box) {
const bbox = face.box;
// Ensure we have valid coordinates
if (typeof bbox.xMin === 'number' &&
typeof bbox.xMax === 'number' &&
typeof bbox.yMin === 'number' &&
typeof bbox.yMax === 'number') {
// Calculate face center and size
const centerX = (bbox.xMin + bbox.xMax) / 2;
const centerY = (bbox.yMin + bbox.yMax) / 2;
const faceWidth = bbox.xMax - bbox.xMin;
const faceHeight = bbox.yMax - bbox.yMin;
// Scale coordinates back to original image size
const scaleX = img.width / width;
const scaleY = img.height / height;
faceDetections.push({
image: img,
center: {
x: centerX * scaleX,
y: centerY * scaleY
},
width: faceWidth * scaleX,
height: faceHeight * scaleY
});
displayImage(img);
} else {
console.log("Invalid box coordinates in", file.name, ":", bbox);
}
} else {
console.log("Missing box in", file.name, ":", face);
}
} else {
console.log("No face detected in", file.name);
}
} catch (error) {
console.error(`Error processing ${file.name}:`, error);
statusText.textContent = `Error processing ${file.name}`;
}
}
if (faceDetections.length > 0) {
createAverageFace();
} else {
statusText.textContent = "No valid faces detected in any images. Please try with different images.";
}
});
// Load image from file
function loadImage(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = e.target.result;
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// Display image in gallery
function displayImage(img) {
const galleryImg = document.createElement('img');
galleryImg.src = img.src;
imageGallery.appendChild(galleryImg);
}
// Create average face
function createAverageFace() {
const ctx = averageCanvas.getContext('2d');
ctx.clearRect(0, 0, averageCanvas.width, averageCanvas.height);
// Calculate target center and size
const targetCenter = { x: 200, y: 200 };
const targetSize = 300;
// Draw each face aligned to the target center
ctx.globalAlpha = 1 / faceDetections.length;
faceDetections.forEach(detection => {
const { image, center, width, height } = detection;
// Calculate scale to fit target size
const scale = targetSize / Math.max(width, height);
// Calculate position to center the face
const x = targetCenter.x - (width * scale) / 2;
const y = targetCenter.y - (height * scale) / 2;
// Draw the image
ctx.drawImage(
image,
center.x - width/2,
center.y - height/2,
width,
height,
x,
y,
width * scale,
height * scale
);
});
ctx.globalAlpha = 1;
statusText.textContent = "Average face created!";
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment