自从大二初次接触前端以来,一直都有记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
的方法,用于在画布上绘制图像。该方法包含以下步骤:
- 创建一个新的
Image
对象。 - 设置图像的源文件为'avatar.png'。
- 当图像加载完成后,清空画布并将图像绘制到画布上。
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
做了以下操作:
- 从结果中提取人脸关键点的坐标。
- 计算并确定图像的位置和尺寸。
- 使用
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行了哈哈)
代码地址
视频地址:www.bilibili.com/video/BV1Fv...
在线体验(可能很卡):electrolux.gitee.io/front-css-p...)