120行代码教你如何在视频中替换人脸

1,669 阅读5分钟

一、简介

人脸替换是一项有趣且具有挑战性的计算机视觉任务。随着前端技术的进步,我们可以使用纯前端的方法实现视频中的人脸替换,而无需依赖后端服务。本文将手把手教你如何使用 OpenCV.js,实现视频中人脸的替换效果。

实现很简单,只需要120行代码即可实现,一起来看看吧。

二、技术栈

  • HTML5:用于创建用户界面
  • OpenCV.js:一个强大的计算机视觉库,可以在浏览器中使用。
  • haarcascade_frontalface_default.xml:人脸识别模型。

三、实现步骤

1. 设置项目结构

创建一个基本的 HTML 文件结构:

/public
    ├── face.html
    ├── haarcascade_frontalface_default.xml
    └── avatar.png (替换用的人脸图像)

haarcascade_frontalface_default.xml 是一个用于人脸检测的预训练模型文件,基于 Haar 特征分类器算法。它的主要作用是人脸检测、实时处理、特征提取。

你可以从 OpenCV 的 GitHub 仓库下载 haarcascade_frontalface_default.xml 文件。下载地址:

haarcascade_frontalface_default.xml

2. HTML 结构

index.html 文件中,设置基本的 HTML 结构和视频标签:

    <!DOCTYPE html>
    <html lang="zh">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>人脸替换示例</title>
        <script src="https://docs.opencv.org/4.5.3/opencv.js" async></script>
    </head>
    <body>
          <h1>人脸替换演示</h1>
          <div>
            <label>替换头像:</label>
            <img id="avatar" src="转存失败,建议直接上传图片文件 avatar.png" alt="转存失败,建议直接上传图片文件"> <!-- 替换的头像 -->
          </div>
          <input type="file" id="videoUpload" accept="video/*">
          <video id="videoPlayer" width="600" controls></video>
          <canvas id="canvas" width="600" height="400"></canvas>
       </body>
    </html>

image.png

3. 人脸替换核心逻辑

使用 OpenCV.js 进行人脸检测和替换,实现核心步骤如下:

  1. 加载分类器
  • 使用预训练的人脸检测模型(如 Haar 特征分类器)来识别视频中的人脸~

  • 实现步骤如下:

    • 创建 cv.CascadeClassifier 实例。
    • 使用 fetch 获取并加载 haarcascade_frontalface_default.xml 文件。
    • 将 XML 文件写入 OpenCV 的虚拟文件系统,并加载分类器。

注意,直接通过classifier.load('haarcascade_frontalface_default.xml')加载会失败哦,因此需要使用fetch转换成二进制,从虚拟文件系统加载。

  1. 视频帧处理
  • 逐帧读取视频,以便进行人脸检测和替换

    • 使用 ctx.drawImage(video, ...) 将当前视频帧绘制到画布上。
    • 使用 cv.imread(canvas) 将画布内容读取到 OpenCV 的矩阵中。
    • 转换为灰度图像,以提高检测效率。
  1. 人脸检测
  • 识别当前帧中的所有人脸,这也是最核心的逻辑

    • 使用 classifier.detectMultiScale(gray, faces, ...) 方法进行人脸检测,将检测结果存储在 faces 中。
    • 遍历 faces,获取每个检测到的人脸矩形区域。
  1. 人脸替换
  • 用预定义的头像图像替换检测到的人脸,当然检测的正确率直接影响替换的最终效果~

    • 首先,计算替换图像的缩放比例和位置。
    • 其次,使用 ctx.drawImage(avatar, x, y, width, height) 将替换图像绘制到相应的人脸位置上。
  1. 资源管理

    避免内存泄漏,确保程序高效运行。在每次视频帧处理后,调用 delete() 方法释放 OpenCV 的矩阵和人脸矩形向量,这个方法还有待改善~

<!DOCTYPE html>
<html lang="zh">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>人脸替换示例</title>
  <script src="https://docs.opencv.org/4.5.3/opencv.js" async></script>
</head>


  <h1>人脸替换演示</h1>
  <div>
    <label>替换头像:</label>
    <img id="avatar" src="转存失败,建议直接上传图片文件 avatar.png" alt="转存失败,建议直接上传图片文件"> <!-- 替换的头像 -->
  </div>
  <input type="file" id="videoUpload" accept="video/*">
  <video id="videoPlayer" width="600" controls></video>
  <canvas id="canvas" width="600" height="400"></canvas>

  <script>
    let video = document.getElementById('videoPlayer');
    let canvas = document.getElementById('canvas');
    let ctx = canvas.getContext('2d', { willReadFrequently: true });
    let classifier;

    window.onload = () => {
      cv.onRuntimeInitialized = () => {
        loadClassifier(); // 加载分类器
        video.addEventListener('play', () => {
          requestAnimationFrame(processVideo);
        });
      };
    };

    function loadClassifier() {
      try {
        console.log('尝试加载分类器...');
        classifier = new cv.CascadeClassifier();

        fetch('haarcascade_frontalface_default.xml')
          .then(response => {
            if (!response.ok) {
              throw new Error('网络响应不正常');
            }
            return response.arrayBuffer();
          })
          .then(data => {
            console.log('文件加载成功,开始加载分类器...');
            // 将 XML 数据直接传给分类器
            const byteArray = new Uint8Array(data);
            // 将文件写入 OpenCV 的虚拟文件系统
            cv.FS_createDataFile('/', 'haarcascade_frontalface_default.xml', byteArray, true, false);

            // 创建分类器并加载
            classifier = new cv.CascadeClassifier();
            classifier.load('haarcascade_frontalface_default.xml'); // 从虚拟文件系统加载
            console.log('分类器加载成功');
          })
          .catch(error => {
            console.error('分类器加载失败:', error);
          });

      } catch (error) {
        console.error('loadClassifier', error);
      }
    }


    function processVideo() {
      try {
        if (video.paused || video.ended) {
          return;
        }

        ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
        let src = cv.imread(canvas);
        let gray = new cv.Mat();
        cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY);

        let faces = new cv.RectVector();
        classifier.detectMultiScale(gray, faces, 1.1, 3, 0, new cv.Size());

        // console.log('faces', faces.size());
        // 替换人脸
        for (let i = 0; i < faces.size(); i++) {
          let face = faces.get(i);
          let avatar = document.getElementById('avatar');

          let scale = face.width / avatar.width;
          let x = face.x;
          let y = face.y;
          let width = avatar.width * scale;
          let height = avatar.height * scale;

          ctx.drawImage(avatar, x, y, width, height);
        }

        src.delete();
        gray.delete();
        faces.delete();
      } catch (error) {
        console.error('处理视频时出错:', error);
      }

      requestAnimationFrame(processVideo);
    }

    document.getElementById('videoUpload').addEventListener('change', (event) => {
      const file = event.target.files[0];
      const url = URL.createObjectURL(file);
      video.src = url;
      video.play();
    });
  </script>
</body>

</html>

4. 运行程序

使用本地服务器运行项目,例如使用 VS Code 的 Live Server 插件,直接运行上述代码。访问 http://127.0.0.1:5500/public/face.html,你应该能够看到视频中的人脸被替换成指定的图像,效果如下:

image.png

代码和资源的完整地址:github.com/ctq123/vide… (完整资源在public目录下)

四、面临的挑战

本文介绍了如何使用纯前端技术实现视频中的人脸替换。通过结合 HTML5、JavaScript 和 OpenCV.js,我们可以轻松地在浏览器中实现这一复杂的计算机视觉任务。这种方法不仅提高了用户体验,还能够避免繁琐的后端配置,展示了前端技术的强大潜力,未来前端技术支持肯定会越来越好~

当然,在实现视频中的人脸替换功能时,也会面临一些挑战,主要包括两个方面,分别为内存和计算:

内存方面

  1. 大数据量:处理高分辨率视频帧会生成大量图像数据,消耗大量内存。
  2. 内存泄漏:频繁创建和删除 OpenCV 矩阵和对象可能导致内存未及时释放,造成内存泄漏。
  3. 资源管理:需要手动管理 OpenCV 的内存,确保不使用的对象被删除,以防止内存溢出。

计算方面

  1. 实时处理需求:实时视频处理要求在短时间内完成复杂的计算,增加了计算负担。
  2. 人脸检测算法复杂性:Haar 分类器等算法的计算复杂度高,尤其在处理多个目标时,可能导致性能下降。
  3. 多帧处理:每一帧都需要进行人脸检测和替换,增加了处理时间,可能影响视频流畅性。

处理内存和大量数据的计算一直是前端渲染复杂页面的通病,解决这两个方面的问题,基本就能触类旁通同时解决很多其他的问题。如果有任何问题,也欢迎一起讨论~