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

相关推荐
野槐1 分钟前
前端图像处理(一)
前端
程序猿阿伟8 分钟前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒10 分钟前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript
瑞雨溪19 分钟前
AJAX的基本使用
前端·javascript·ajax
力透键背21 分钟前
display: none和visibility: hidden的区别
开发语言·前端·javascript
程楠楠&M32 分钟前
node.js第三方Express 框架
前端·javascript·node.js·express
盛夏绽放41 分钟前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
想自律的露西西★1 小时前
用el-scrollbar实现滚动条,拖动滚动条可以滚动,但是通过鼠标滑轮却无效
前端·javascript·css·vue.js·elementui·前端框架·html5
白墨阳1 小时前
vue3:瀑布流
前端·javascript·vue.js
霍先生的虚拟宇宙网络2 小时前
webp 网页如何录屏?
开发语言·前端·javascript