纯前端html、js实现人脸检测和表情检测,可直接在浏览器使用


前端代码

javascript 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>简单图片人脸识别</title>
  <style>
    /* 简单样式:居中布局 + 边框提示 */
    body {
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 20px;
      font-family: Arial, sans-serif;
    }
    .container {
      margin: 20px 0;
      text-align: center;
    }
    #inputImage {
      max-width: 600px;
      border: 2px dashed #ccc;
      border-radius: 8px;
      padding: 10px;
      cursor: pointer;
      /* 限制图片最大高度,避免Canvas超出屏幕 */
      max-height: 400px;
      object-fit: contain;
    }
    #resultCanvas {
      margin-top: 20px;
      border: 2px solid #333;
      border-radius: 8px;
      /* 默认隐藏,识别后再显示 */
      display: none;
    }
    button {
      padding: 10px 20px;
      background: #42b983;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 16px;
      margin-top: 10px;
    }
    button:disabled {
      background: #ccc;
      cursor: not-allowed;
    }
  </style>
</head>
<body>
<h1>图片人脸识别演示</h1>

<!-- 选择图片区域 -->
<div class="container">
  <p>点击图片上传人脸照片(支持 JPG/PNG)</p>
  <input type="file" id="fileInput" accept="image/jpeg,image/png" style="display: none;">
  <img id="inputImage" src="https://via.placeholder.com/600x400?text=点击上传图片" alt="上传图片" onclick="document.getElementById('fileInput').click()">
</div>

<!-- 识别结果展示(Canvas 用于画框标注) -->
<canvas id="resultCanvas"></canvas>

<!-- 识别按钮 -->
<button id="detectBtn" disabled>开始识别</button>

<!-- 引入 faceapi 核心库(关键!) -->
<script src="./dist/face-api.min.js"></script>
<script>
  // 全局变量:存储模型加载状态、图片元素
  let modelLoaded = false;
  const inputImage = document.getElementById('inputImage');
  const fileInput = document.getElementById('fileInput');
  const detectBtn = document.getElementById('detectBtn');
  const resultCanvas = document.getElementById('resultCanvas');
  const ctx = resultCanvas.getContext('2d');
  // 存储已加载完成的图片对象(确保绘制时图片已就绪)
  let loadedImage = null;

  // 页面加载完成后,加载人脸模型(核心步骤)
  window.onload = async () => {
    try {
      // 注意:你的模型路径是 ./weights(不是默认的 ./models),确保 weights 文件夹存在且完整!
      await faceapi.loadSsdMobilenetv1Model('./weights'); // 人脸检测模型
      await faceapi.loadFaceLandmarkModel('./weights'); // 人脸特征点模型
      await faceapi.loadFaceRecognitionModel('./weights'); // 人脸识别模型
      await faceapi.loadFaceExpressionModel('./weights'); // 表情识别模型
      modelLoaded = true;
      alert('模型加载成功!请上传图片并点击识别');
    } catch (err) {
      console.error('模型加载失败:', err);
      alert('模型加载失败,请检查 weights 文件夹路径是否正确,或文件夹是否完整');
    }
  };

  // 监听图片上传,预览图片(关键优化:确保图片完全加载后再启用按钮)
  fileInput.addEventListener('change', (e) => {
    const file = e.target.files[0];
    if (!file) return;

    // 读取图片并显示在预览区
    const reader = new FileReader();
    reader.onload = (event) => {
      // 新建 Image 对象,监听 onload 事件(确保图片完全加载)
      const img = new Image();
      img.onload = () => {
        // 图片加载完成后,更新预览图和存储的图片对象
        inputImage.src = img.src;
        loadedImage = img; // 存储完整加载的图片,用于后续绘制
        // 清空之前的识别结果和 Canvas
        ctx.clearRect(0, 0, resultCanvas.width, resultCanvas.height);
        resultCanvas.style.display = 'none'; // 隐藏 Canvas,识别后再显示
        // 启用识别按钮
        detectBtn.disabled = !modelLoaded;
      };
      // 处理跨域问题(避免绘制时出现污染 Canvas 错误)
      img.crossOrigin = 'anonymous';
      img.src = event.target.result;
    };
    reader.readAsDataURL(file);
  });

  // 在 detections.forEach 循环外,定义中文映射表
  const expressionMap = {
    happy: '开心',
    sad: '难过',
    angry: '生气',
    neutral: '平静',
    surprised: '惊讶',
    fearful: '恐惧',
    disgusted: '厌恶'
  };

  // 点击识别按钮,执行人脸识别(新增表情识别逻辑)
  detectBtn.addEventListener('click', async () => {
    if (!loadedImage) {
      alert('请先上传图片并等待预览加载完成');
      return;
    }

    // 设置 Canvas 尺寸、绘制原图
    const imgWidth = loadedImage.width;
    const imgHeight = loadedImage.height;
    const maxCanvasWidth = 600;
    const scale = imgWidth > maxCanvasWidth ? maxCanvasWidth / imgWidth : 1;
    const canvasWidth = imgWidth * scale;
    const canvasHeight = imgHeight * scale;

    resultCanvas.width = canvasWidth;
    resultCanvas.height = canvasHeight;
    resultCanvas.style.display = 'block';
    ctx.clearRect(0, 0, canvasWidth, canvasHeight);
    ctx.drawImage(loadedImage, 0, 0, canvasWidth, canvasHeight);

    // .withFaceExpressions() 获取表情数据
    const options = new faceapi.SsdMobilenetv1Options({ minConfidence: 0.5 });
    const detections = await faceapi.detectAllFaces(loadedImage, options)
      .withFaceLandmarks() // 特征点(保留)
      .withFaceDescriptors() // 描述符(保留)
      .withFaceExpressions(); // 获取表情数据(关键)

    if (detections.length === 0) {
      alert('未检测到人脸');
      return;
    }

    // 循环标注每个人脸(新增表情标签绘制)
    detections.forEach((detectResult, index) => {
      const { detection, landmarks, descriptor, expressions } = detectResult; // 解构 expressions
      const { x, y, width, height } = detection.box;

      // (原有代码:计算缩放坐标、画人脸框、特征点,保留不变)
      const scaledX = x * scale;
      const scaledY = y * scale;
      const scaledWidth = width * scale;
      const scaledHeight = height * scale;

      const colors = ['#42b983', '#2196f3', '#ff9800', '#f44336', '#9c27b0'];
      const faceColor = colors[index % colors.length];

      ctx.strokeStyle = faceColor;
      ctx.lineWidth = 1;
      ctx.strokeRect(scaledX, scaledY, scaledWidth, scaledHeight);

      ctx.fillStyle = faceColor;
      const scalePoints = (points) => points.map(p => ({ x: p.x * scale, y: p.y * scale }));
      scalePoints(landmarks.getMouth()).forEach(point => ctx.fillRect(point.x, point.y, 2, 2));
      scalePoints(landmarks.getLeftEye()).forEach(point => ctx.fillRect(point.x, point.y, 2, 2));
      scalePoints(landmarks.getRightEye()).forEach(point => ctx.fillRect(point.x, point.y, 2, 2));
      scalePoints(landmarks.getNose()).forEach(point => ctx.fillRect(point.x, point.y, 2, 2));
      scalePoints(landmarks.getJawOutline()).forEach(point => ctx.fillRect(point.x, point.y, 2, 2));

      // 解析表情结果(取概率最高的表情)
      // expressions 包含:happy(开心)、sad(难过)、angry(生气)、neutral(中性)、surprised(惊讶)、fearful(恐惧)、disgusted(厌恶)
      const expression = expressions.asSortedArray()[0]; // 按概率排序,取第一个(最可能的表情)
      // 解析表情时替换为中文
      const expressionName = expressionMap[expression.expression] || expression.expression; // 表情名称(如 'happy')
      const expressionConfidence = expression.probability; // 表情置信度(0-1)

      // 绘制表情标签(在人脸框下方/上方,和编号、置信度错开)
      ctx.fillStyle = faceColor;
      ctx.font = '17px Arial';
      // 人脸编号+置信度
      const faceText = `人脸${index + 1} 置信度:${detection.score.toFixed(2)}`;
      const faceTextY = scaledY - 10 > 0 ? scaledY - 10 : scaledY + scaledHeight + 20;
      ctx.fillText(faceText, scaledX, faceTextY);

      // 表情标签(换行显示,避免重叠)
      const expressionText = `${expressionName}(${expressionConfidence.toFixed(2)})`;
      const expressionTextY = faceTextY + 25; // 向下偏移 25px(字体高度+间距)
      ctx.fillText(expressionText, scaledX, expressionTextY);

      // 打印表情结果到控制台(可选)
      console.log(`人脸${index + 1} 表情:`, expressionName, ' 置信度:', expressionConfidence);
    });

    // 包含表情识别结果
    alert(`识别成功!共检测到 ${detections.length} 个人脸,已标注表情和特征点`);
  });
</script>
</body>
</html>
相关推荐
每天吃饭的羊1 小时前
组件库的有些点击事件是name-click这是如何分装de
前端·javascript·vue.js
x***01061 小时前
SpringSecurity+jwt实现权限认证功能
android·前端·后端
1024肥宅1 小时前
防抖(Debounce)
前端·javascript·ecmascript 6
1024肥宅1 小时前
节流(Throttle)
前端·javascript·ecmascript 6
大怪v1 小时前
【Virtual World 02】两点一线!!!
javascript·css·html
by__csdn1 小时前
Vue2纯前端图形验证码实现详解+源码
前端·javascript·typescript·vue·状态模式·css3·canva可画
Gomiko2 小时前
JavaScript基础(八):函数
开发语言·javascript·ecmascript
w***37512 小时前
Spring 核心技术解析【纯干货版】- Ⅶ:Spring 切面编程模块 Spring-Instrument 模块精讲
前端·数据库·spring
我是阿亮啊2 小时前
搭建Vue环境遇到的问题
javascript·vue.js·npm·node.js