【前端笔记】不到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...)

相关推荐
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062065 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb5 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角5 小时前
CSS 颜色
前端·css
浪浪山小白兔6 小时前
HTML5 新表单属性详解
前端·html·html5
lee5767 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579657 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me7 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者7 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架
qq_392794488 小时前
前端缓存策略:强缓存与协商缓存深度剖析
前端·缓存