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

相关推荐
酷酷的阿云5 分钟前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205877 分钟前
web端手机录音
前端
齐 飞13 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
神仙别闹30 分钟前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
GIS程序媛—椰子1 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0011 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端2 小时前
Content Security Policy (CSP)
前端·javascript·面试
木舟10092 小时前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43912 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢2 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js