【前端笔记】不到100行代码,实现视频的人脸替换(js | mediapipe)

自从大二初次接触前端以来,一直都有记markdown笔记的习惯.又因为掘金最近有一个活动.所以开了这个专栏。我会写一些业务相关的小技巧和前端知识中的重点内容之类的整理一下发上来。难免有点地方不够细致。欢迎大家指教

这个文章讲一下 怎么用mediapipe 和 js 实现视频的人脸替换.最后你会实现下图效果

第一步:初始化

在这一步中,进行了初始化工作,包括获取视频元素、创建画布元素以及设置相关的2D上下文。

ini 复制代码
// 初始化
const videoElement = document.querySelector(".input_video"); // 获取视频元素
let canvas = document.getElementById("canvas"); // 获取画布元素
let context = canvas.getContext("2d"); // 获取画布的2D上下文
let canvasPic = document.getElementById("overlay"); // 获取另一个画布元素
let contextPic = canvasPic.getContext("2d"); // 获取另一个画布的2D上下文

第二步:绘制Canvas方法

这一步定义了一个名为drawImg的方法,用于在画布上绘制图像。该方法包含以下步骤:

  1. 创建一个新的Image对象。
  2. 设置图像的源文件为'avatar.png'。
  3. 当图像加载完成后,清空画布并将图像绘制到画布上。
arduino 复制代码
// 绘制canvas方法
function drawImg(ctx, x, y, width, height, angle) {
    const img = new Image()
    img.src = 'avator.png' // 设置图像的源文件
    img.onload = () => {
        ctx.clearRect(0, 0, 1000, 1000); // 清空画布
        ctx.drawImage(img, x, y, width, height) // 绘制图像到画布上
    }
}

第三步:处理结果

在这一步中,针对处理结果的方法onResults做了以下操作:

  1. 从结果中提取人脸关键点的坐标。
  2. 计算并确定图像的位置和尺寸。
  3. 使用drawImg方法将图像绘制到另一个画布上。
javascript 复制代码
function onResults(results) {
    try {
        let left_x = results?.faceLandmarks[234].x // 获取人脸左边界的x坐标
        let right_x = results?.faceLandmarks[454].x // 获取人脸右边界的x坐标
        let top_y = results?.faceLandmarks[10].y // 获取人脸顶部的y坐标
        let bottom_y = results?.faceLandmarks[152].y // 获取人脸底部的y坐标
        let targetInit = { x: left_x * canvasPic.width, y: top_y * canvasPic.height } // 计算目标图像的起始坐标

        let targetWidth = Math.abs((right_x - left_x) * canvasPic.width) // 计算目标图像的宽度
        let targetHeight = Math.abs((top_y - bottom_y) * canvasPic.height) // 计算目标图像的高度

        drawImg(contextPic, targetInit.x, targetInit.y, targetWidth, targetHeight) // 绘制目标图像到另一个画布上
    } catch (e) {
        console.log(e)
    }
}

第四步:配置Holistic

这一步设置了Holistic对象的参数,包括模型复杂度、置信度以及其他相关选项。

php 复制代码
// 创建Holistic对象
const holistic = new Holistic({
    locateFile: (file) => {
        return `https://cdn.jsdelivr.net/npm/electroluxasset@1.0.5/mediapipe/holistic/locate/${file}`;
    }
});

// 设置Holistic的选项
holistic.setOptions({
    modelComplexity: 1,
    smoothLandmarks: true,
    enableSegmentation: true,
    smoothSegmentation: true,
    refineFaceLandmarks: true,
    minDetectionConfidence: 0.5,
    minTrackingConfidence: 0.5
});

第五步:摄像头操作

这一步包含了摄像头的操作,包括获取视频流、发送到Holistic进行处理,并启动摄像头。

scss 复制代码
// 创建Camera对象
const camera = new Camera(videoElement, {
    onFrame: async () => {
        await holistic.send({ image: videoElement });
    },
    width: 190,
    height: 200
});

// 启动摄像头
camera.start();

// 当视频准备就绪时触发
videoElement.oncanplay = function () {
    switchToCanvas();
}

function switchToCanvas() {
    // 将video上的每一帧以图片的形式绘制到canvas上
    context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
    window.requestAnimationFrame(switchToCanvas); // 递归调用以不断更新画布
}

完整代码

xml 复制代码
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <script src="https://cdn.jsdelivr.net/npm/electroluxasset@1.0.5/mediapipe/holistic/camera_utils.js"
        crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/electroluxasset@1.0.5/mediapipe/holistic/control_utils.js"
        crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/electroluxasset@1.0.5/mediapipe/holistic/drawing_utils.js"
        crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/electroluxasset@1.0.5/mediapipe/holistic/holistic.js"
        crossorigin="anonymous"></script>
    <style>
        #overlay {
            z-index: 999;
            position: absolute;
        }

        #canvas {
            z-index: -999;
            position: absolute;
        }
    </style>
</head>

<body>
    <video class="input_video"></video>
    <canvas id="overlay" width="500" height="400"> </canvas>
    <canvas id="canvas" width="500" height="400"></canvas>

    <script type="module">
        // 初始化
        const videoElement = document.querySelector(".input_video"); // 获取视频元素
        let canvas = document.getElementById("canvas"); // 获取画布元素
        let context = canvas.getContext("2d"); // 获取画布的2D上下文
        let canvasPic = document.getElementById("overlay"); // 获取另一个画布元素
        let contextPic = canvasPic.getContext("2d"); // 获取另一个画布的2D上下文

        // 绘制canvas方法
        function drawImg(ctx, x, y, width, height, angle) {
            const img = new Image()
            img.src = 'avator.png' // 设置图像的源文件
            img.onload = () => {
                ctx.clearRect(0, 0, 1000, 1000); // 清空画布
                ctx.drawImage(img, x, y, width, height) // 绘制图像到画布上
            }
        }

        function onResults(results) {
            try {
                let left_x = results?.faceLandmarks[234].x // 获取人脸左边界的x坐标
                let right_x = results?.faceLandmarks[454].x // 获取人脸右边界的x坐标
                let top_y = results?.faceLandmarks[10].y // 获取人脸顶部的y坐标
                let bottom_y = results?.faceLandmarks[152].y // 获取人脸底部的y坐标
                let targetInit = { x: left_x * canvasPic.width, y: top_y * canvasPic.height } // 计算目标图像的起始坐标

                let targetWidth = Math.abs((right_x - left_x) * canvasPic.width) // 计算目标图像的宽度
                let targetHeight = Math.abs((top_y - bottom_y) * canvasPic.height) // 计算目标图像的高度

                drawImg(contextPic, targetInit.x, targetInit.y, targetWidth, targetHeight) // 绘制目标图像到另一个画布上
            } catch (e) {
                console.log(e)
            }
        }

        // 创建Holistic对象
        const holistic = new Holistic({
            locateFile: (file) => {
                return `https://cdn.jsdelivr.net/npm/electroluxasset@1.0.5/mediapipe/holistic/locate/${file}`;
            }
        });

        // 设置Holistic的选项
        holistic.setOptions({
            modelComplexity: 1,
            smoothLandmarks: true,
            enableSegmentation: true,
            smoothSegmentation: true,
            refineFaceLandmarks: true,
            minDetectionConfidence: 0.5,
            minTrackingConfidence: 0.5
        });

        // 监听Holistic的结果
        holistic.onResults(onResults);

        // 创建Camera对象
        const camera = new Camera(videoElement, {
            onFrame: async () => {
                await holistic.send({ image: videoElement });
            },
            width: 190,
            height: 200
        });

        // 启动摄像头
        camera.start();

        // 当视频准备就绪时触发
        videoElement.oncanplay = function () {
            switchToCanvas();
        }

        function switchToCanvas() {
            // 将video上的图片的每一帧以图片的形式绘制到canvas上
            context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
            window.requestAnimationFrame(switchToCanvas); // 递归调用以不断更新画布
        }
    </script>

</body>

</html>

复制上去就可以用,注意把图片换成你自己的地址就好了(发现写完注释后超100行了哈哈)

代码地址

代码地址:gitee.com/Electrolux/...

视频地址:www.bilibili.com/video/BV1Fv...

在线体验(可能很卡):electrolux.gitee.io/front-css-p...)

相关推荐
昱禹7 分钟前
关于CSS Grid布局
前端·javascript·css
啊QQQQQ14 分钟前
HTML:相关概念以及标签
前端·html
就叫飞六吧1 小时前
vue2和vue3全面对比
前端·javascript·vue.js
Justinc.1 小时前
CSS基础-盒子模型(三)
前端·css
qq_2518364571 小时前
基于ssm vue uniapp实现的爱心小屋公益机构智慧管理系统
前端·vue.js·uni-app
._Ha!n.1 小时前
Vue基础(二)
前端·javascript·vue.js
风清扬_jd2 小时前
Chromium 硬件加速开关c++
java·前端·c++
谢尔登3 小时前
【React】事件机制
前端·javascript·react.js
2401_857622664 小时前
SpringBoot精华:打造高效美容院管理系统
java·前端·spring boot
etsuyou4 小时前
Koa学习
服务器·前端·学习