如何在前端给视频进行去除绿幕并替换背景?——Vue3实现方案
创作时间:
作者:
@小白创作中心
如何在前端给视频进行去除绿幕并替换背景?——Vue3实现方案
引用
CSDN
1.
https://blog.csdn.net/2301_81807150/article/details/145165206
在前端开发中,如何实现视频的绿幕去除和背景替换是一个常见的需求。本文将详细介绍如何使用Canvas来实现这一功能,包括视频帧处理、背景移除、羽化效果等关键步骤。
最近在进行前端开发时,遇到了一个难题:“如何给前端的视频进行去除绿幕并替换背景”。这是一个“数字人项目”所需,我一直在冥思苦想。终于有了一个解决方法——使用Canvas来处理。
实现思路
1. 准备工作
在HTML模板中定义了一个<video>标签用于播放视频,以及一个<canvas>标签用来绘制处理后的视频帧。在组件挂载时,获取视频和画布元素,并初始化绘图上下文。
<template>
<div class="videoBgRemove">
<!-- 视频元素 -->
<video ref="video" loop autoplay muted style="width: 240px;">
<source src="/8_1736396574.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
<!-- 画布元素 -->
<canvas ref="canvas" width="200" height="450"></canvas>
</div>
</template>
<script>
export default {
data() {
return {
featherStrength: 0.4, // 羽化强度控制
};
},
mounted() {
// 初始化视频和画布引用
this.video = this.$refs.video;
this.canvas = this.$refs.canvas;
this.ctx = this.canvas.getContext('2d');
this.canvas_tmp = document.createElement('canvas');
this.canvas_tmp.width = this.canvas.width;
this.canvas_tmp.height = this.canvas.height;
this.ctx_tmp = this.canvas_tmp.getContext('2d');
// 初始化其他变量
this.init();
},
methods: {
init() {
// 当视频开始播放时,调用computeFrame进行逐帧处理
this.video.addEventListener('play', this.computeFrame);
}
}
};
</script>
2. 视频帧处理逻辑
当视频开始播放时,computeFrame函数会不断被调用,每次调用都会处理一帧视频数据。所有图像处理都在一个临时创建的画布上进行。
methods: {
computeFrame() {
if (!this.video || this.video.paused || this.video.ended) return;
// 绘制当前帧到临时画布上
this.ctx_tmp.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
// 获取当前帧的图像数据
let frame = this.ctx_tmp.getImageData(0, 0, this.canvas.width, this.canvas.height);
// 后续处理...
}
}
3. 背景移除
假设背景为特定的颜色(例如绿色),对于每个像素点,如果其RGB值符合预设的背景颜色范围,则将其alpha通道设置为0,即变为透明。
methods: {
computeFrame() {
// ... (前面的代码)
const pointLens = frame.data.length / 4;
// 遍历每一个像素点
for (let i = 0; i < pointLens; i++) {
let r = frame.data[i * 4];
let g = frame.data[i * 4 + 1];
let b = frame.data[i * 4 + 2];
// 假设背景是绿色,将符合条件的像素设置为透明
if (r < 100 && g > 120 && b < 200) {
frame.data[i * 4 + 3] = 0; // 设置alpha通道为0,使背景透明
}
}
// 后续处理...
}
}
4. 羽化效果
对于非透明的像素,计算它周围的像素,取周围像素颜色的平均值作为新颜色,并根据周围的透明度调整当前像素的透明度,以此来实现羽化效果。通过featherStrength参数可以控制羽化的程度。
methods: {
computeFrame() {
// ... (前面的代码)
// 创建一个临时的数据副本,避免修改原始数据
const tempData = [...frame.data];
// 对非透明像素应用羽化效果
for (let i = 0; i < pointLens; i++) {
if (frame.data[i * 4 + 3] === 0) continue; // 忽略已经透明的像素
// 计算当前像素的位置
let [row, col] = this.numToPoint(i + 1, frame.width);
// 获取周围的像素点
let aroundPoints = this.getAroundPoint([row, col], frame.width, frame.height, 3);
// 计算周围非透明像素的颜色平均值
let opNum = 0;
let rSum = 0;
let gSum = 0;
let bSum = 0;
aroundPoints.forEach(([pRow, pCol]) => {
let index = this.pointToNum([pRow, pCol], frame.width);
rSum += tempData[(index - 1) * 4];
gSum += tempData[(index - 1) * 4 + 1];
bSum += tempData[(index - 1) * 4 + 2];
if (tempData[(index - 1) * 4 + 3] !== 255) opNum++;
});
// 计算新的alpha值
let alpha = (255 / aroundPoints.length) * (aroundPoints.length - opNum);
// 根据羽化强度调整alpha
if (alpha !== 255) {
frame.data[i * 4] = parseInt(rSum / aroundPoints.length);
frame.data[i * 4 + 1] = parseInt(gSum / aroundPoints.length);
frame.data[i * 4 + 2] = parseInt(bSum / aroundPoints.length);
frame.data[i * 4 + 3] = parseInt(alpha * this.featherStrength);
}
}
// 将处理后的图像数据绘制到实际显示的画布上
this.ctx.putImageData(frame, 0, 0);
// 持续循环
requestAnimationFrame(this.computeFrame);
},
numToPoint(num, width) {
let col = num % width;
let row = Math.floor(num / width);
return [row + 1, col === 0 ? width : col];
},
pointToNum(point, width) {
let [row, col] = point;
return (row - 1) * width + col;
},
getAroundPoint(point, width, height, area) {
let [row, col] = point;
let allAround = [];
for (let i = -Math.floor(area / 2); i <= Math.floor(area / 2); i++) {
for (let j = -Math.floor(area / 2); j <= Math.floor(area / 2); j++) {
if (i === 0 && j === 0) continue; // 跳过中心点
let pRow = row + i;
let pCol = col + j;
if (pRow > 0 && pCol > 0 && pRow <= height && pCol <= width) {
allAround.push([pRow, pCol]);
}
}
}
return allAround;
}
}
5. 显示处理结果
将处理后的图像数据应用到实际显示的画布上,这样用户就能看到带有透明背景和羽化效果的视频了。
// 将处理后的图像数据绘制到实际显示的画布上
this.ctx.putImageData(frame, 0, 0);
6. 持续循环
computeFrame函数会在每一帧处理完毕后立即再次调用自己,形成一个持续的循环,直到视频停止播放。
methods: {
computeFrame() {
// ... (前面的代码)
// 持续循环
requestAnimationFrame(this.computeFrame);
}
}
完整的demo
1.App.vue
<template>
<div id="app">
<h1>背景人像处理</h1>
<VideoRemoval />
</div>
</template>
<script>
import VideoRemoval from './components/VideoRemoval.vue';
export default {
name: 'App',
components: {
VideoRemoval
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
background-image: url("../src/assets/web_bg.jpg"); /* 使用正确的路径 */
background-size: cover; /* 背景图片覆盖整个容器 */
background-position: center center; /* 背景图片居中显示 */
background-repeat: no-repeat; /* 防止背景图片重复 */
background-attachment: fixed; /* 背景固定在视口 */
}
</style>
2.VideoRemoval.vue
<template>
<div class="videoBgRemove">
<video id="video"
src="/8_1736396574.mp4"
loop
autoplay
muted
ref="video"
style="width: 240px;"></video>
<canvas id="output-canvas"
width="200"
height="450"
willReadFrequently="true"
ref="canvas"></canvas>
</div>
</template>
<script>
export default {
data () {
return {
video: null,
canvas: null,
ctx: null,
canvas_tmp: null,
ctx_tmp: null,
featherStrength: 0.4, // 羽化强度控制
};
},
methods: {
init () {
this.ctx = this.canvas.getContext('2d');
this.canvas_tmp = document.createElement('canvas');
this.canvas_tmp.setAttribute('width', 200);
this.canvas_tmp.setAttribute('height', 450);
this.ctx_tmp = this.canvas_tmp.getContext('2d');
this.video.addEventListener('play', this.computeFrame);
},
numToPoint (num, width) {
let col = num % width;
let row = Math.floor(num / width);
row = col === 0 ? row : row + 1;
col = col === 0 ? width : col;
return [row, col];
},
pointToNum (point, width) {
let [row, col] = point;
return (row - 1) * width + col;
},
getAroundPoint (point, width, height, area) {
let [row, col] = point;
let allAround = [];
if (row > height || col > width || row < 0 || col < 0) return allAround;
for (let i = 0; i < area; i++) {
let pRow = row - 1 + i;
for (let j = 0; j < area; j++) {
let pCol = col - 1 + j;
if (i === area % 2 && j === area % 2) continue;
allAround.push([pRow, pCol]);
}
}
return allAround.filter(([iRow, iCol]) => {
return iRow > 0 && iCol > 0 && iRow <= height && iCol <= width;
});
},
computeFrame () {
if (this.video) {
if (this.video.paused || this.video.ended) return;
}
this.ctx_tmp.drawImage(this.video, 0, 0, this.video.clientWidth, this.video.clientHeight);
let frame = this.ctx_tmp.getImageData(0, 0, this.video.clientWidth, this.video.clientHeight);
const height = frame.height;
const width = frame.width;
const pointLens = frame.data.length / 4;
// 背景透明化(假设背景为特定颜色,这里选择绿色)
for (let i = 0; i < pointLens; i++) {
let r = frame.data[i * 4];
let g = frame.data[i * 4 + 1];
let b = frame.data[i * 4 + 2];
if (r < 100 && g > 120 && b < 200) {
frame.data[i * 4 + 3] = 0;
}
}
const tempData = [...frame.data];
for (let i = 0; i < pointLens; i++) {
if (frame.data[i * 4 + 3] === 0) continue;
const currentPoint = this.numToPoint(i + 1, width);
const arroundPoint = this.getAroundPoint(currentPoint, width, height, 3);
let opNum = 0;
let rSum = 0;
let gSum = 0;
let bSum = 0;
arroundPoint.forEach((position) => {
const index = this.pointToNum(position, width);
rSum += tempData[(index - 1) * 4];
gSum += tempData[(index - 1) * 4 + 1];
bSum += tempData[(index - 1) * 4 + 2];
if (tempData[(index - 1) * 4 + 3] !== 255) opNum++;
});
let alpha = (255 / arroundPoint.length) * (arroundPoint.length - opNum);
// 调整羽化效果
if (alpha !== 255) {
frame.data[i * 4] = parseInt(rSum / arroundPoint.length);
frame.data[i * 4 + 1] = parseInt(gSum / arroundPoint.length);
frame.data[i * 4 + 2] = parseInt(bSum / arroundPoint.length);
// 根据羽化强度调整 alpha
frame.data[i * 4 + 3] = parseInt(alpha * this.featherStrength);
}
}
this.ctx.putImageData(frame, 0, 0);
setTimeout(this.computeFrame, 0);
}
},
mounted () {
this.video = this.$refs.video;
this.canvas = this.$refs.canvas;
this.init();
}
};
</script>
这是一篇真材实料的技术文章,通过详细的技术实现和代码示例,帮助开发者掌握Canvas在视频处理中的应用,提升前端开发技能。
热门推荐
灯盏细辛:从形态到药理的全面解析
可选到必选,数字化转型如何掀起变革浪潮?
银行活期存款有哪些优势?
Android项目开发如何设计整体架构
自闭症可遗传?第三代试管婴儿技术能否预防?
行车记录仪的adas是什么意思
钢制办公家具的实用价值
绿豆芽的正确清洗方法(洗净绿豆芽)
春季养生正当时:绿豆芽的多重功效与美味食谱
人生篇章:终身学习 敏锐感知 目光长远 情绪稳定
买基金补仓的时机如何把握?怎样进行合理的基金补仓操作?
Excel中寻找数据转折点的多种实用方法
汽车安全气囊的工作机制、维护注意事项及故障检测详解
数据隐私保护的七大原则
如何有效利用优惠券节省购物开支
家庭教育中的自我表达:鼓励孩子勇敢说出自己的想法
生命的庄严:西藏的嘎巴拉念珠
正官七杀在八字中的含义:权威与挑战的交织
香港十大特色名小吃,这些小吃凭啥让人念念不忘?
2024年10月中国汽车保值率报告:SUV保值率领先,新能源车市场持续增长
爆汁面藕:咬一口,汤汁四溢,美味爆表
DeepSeek安全之Ollama大模型框架安全漏洞深度解析与防御体系构建
如何掌握收购黄金的合理价值?收购过程中需要注意什么?
Windows 11系统无法正常关机怎么办?多种实用解决方案帮你轻松应对
重庆无人机禁飞区查询地图及详细区域列表
特斯拉哨兵卫士触发报警时会发生什么
天干地支:轻松读懂天干地支五行属性理论
矿泉水瓶可以装热水吗?看塑料制品底部标识就知道
汗疱疹高发期,4个因素都易发病,出现症状,及时就医!
烫伤抹什么药膏好得快