如何在前端给视频进行去除绿幕并替换背景?-----Vue3!!

最近在做这个这项目奇店桶装水小程序V1.3.9安装包+骑手端V2.0.1+小程序前端        

       最近,我在进行前端开发时,遇到了一个难题“如何给前端的视频进行去除绿幕并替换背景”。这是一个“数字人项目”所需,我一直在冥思苦想。终于有了一个解决方法——使用Canvas来处理。

        这是真材实料的文章——让你的Canvas的技术更上一层楼!!!


效果图 


实现思路

1. 准备工作 视频和画布元素:在HTML模板中定义了一个<video>标签用于播放视频,以及一个<canvas>标签用来绘制处理后的视频帧。 初始化:在组件挂载(mounted)时,获取视频和画布元素,并初始化绘图上下文。

<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函数会不断被调用,每次调用都会处理一帧视频数据。 临时画布:为了不影响原始视频的播放,所有图像处理都在一个临时创建的画布(canvas_tmp)上进行。 图像数据获取:从临时画布上获取当前帧的图像数据(像素信息)以进行处理。

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);// 根据羽化强度调整alphaif (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. 显示处理结果 更新画布:将处理后的图像数据应用到实际显示的画布(canvas)上,这样用户就能看到带有透明背景和羽化效果的视频了。

// 将处理后的图像数据绘制到实际显示的画布上
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"loopautoplaymutedref="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);// 根据羽化强度调整 alphaframe.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>

完整项目demo在前端给视频去除绿幕并替换背景: 最近,我在进行前端开发时,遇到了一个难题“如何给前端的视频进行去除绿幕并替换背景”。这是一个“数字人项目”所需,我一直在冥思苦想。终于有了一个解决方法——使用Canvas来处理。         这是真材实料的文章——让你的Canvas的技术更上一层楼!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/68638.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java 数据导出到 Word实现方案】使用EasyPOI 工具包进行简易的word操作

文章目录 前言工具包调研实现方案主要步骤&#xff1a;1. 导入 EasyPOI 依赖2. 创建 Word 文件3. 添加数据到 Word 文件4. 保存文件到本地 使用过程中可能遇到的问题总结 前言 最近业务方说周报、月报让他们很头疼&#xff0c;每次都要统计数据后&#xff0c;手动录入到word文…

华为HuaweiCloudStack(一)介绍与架构

本文简单介绍了华为HCS私有云解决方案&#xff0c;并从下至上介绍HCS的整体架构&#xff0c;部署架构、部署方式等内容。 目录 HCS简介 HCS架构 纵向结构 ?管理平台类型 HCS节点类型 FusionSphere OpenStack CPS ServiceOM SC 运营面 OC 运维面 HCS部署架构 regi…

(一)相机标定——四大坐标系的介绍、对应转换、畸变原理以及OpenCV完整代码实战(C++版)

一、四大坐标系介绍 1&#xff0c;世界坐标系 从这个世界&#xff08;world&#xff09;的视角来看物体 世界坐标系是3D空间坐标&#xff0c;每个点的位置用 ( X w , Y w , Z w ) (X_w,Y_w,Z_w) (Xw​,Yw​,Zw​)表示 2&#xff0c;相机坐标系 相机本身具有一个坐标系&…

openwrt下oaf插件编译安装,实现上网行为监控

文章目录 入门级APP青少年模式设备屏幕使用时间电脑浏览器使用时间限制Surpal 介绍安装使用进阶级专业级旁路由方案openwrt路由器固件编译OAF(Open App Filter)安装编译带有oaf的固件固件烧写设备上电启动应用特征库设置黑白名单及应用访问限制骨灰级ref守护孩子视力,用科技“…

目标检测新视野 | YOLO、SSD与Faster R-CNN三大目标检测模型深度对比分析

目录 引言 YOLO系列 网络结构 多尺度检测 损失函数 关键特性 SSD 锚框设计 损失函数 关键特性 Faster R-CNN 区域建议网络&#xff08;RPN&#xff09; 两阶段检测器 损失函数 差异分析 共同特点 基于深度学习 目标框预测 损失函数优化 支持多类别检测 应…

Linux之网络套接字

Linux之网络套接字 一.IP地址和端口号二.TCP和UDP协议2.1网络字节序 三.socket编程的常见API四.模拟实现UDP服务器和客户端五.模拟实现TCP服务器和客户端 一.IP地址和端口号 在了解了网络相关的基础知识之后我们知道了数据在计算机中传输的流程并且发现IP地址在其中占据了确定…

Mysql常见问题处理集锦

Mysql常见问题处理集锦 root用户密码忘记&#xff0c;重置的操作(windows上的操作)MySQL报错&#xff1a;ERROR 1118 (42000): Row size too large. 或者 Row size too large (&#xff1e; 8126).场景&#xff1a;报错原因解决办法 详解行大小限制示例&#xff1a;内容来源于网…

ReactiveSwift 简单使用

记录 ReactiveSwift 简单使用 导入 ReactiveSwift 库创建 TestViewModel 文件 enum JKTypeType: Int {case cloudcase devicecase weater }// 通过监听属性变化 class TestViewModel: NSObject {lazy var recordType: Property<JKTypeType> {return Property(recordTy…

分类问题(二元,多元逻辑回归,费歇尔判别分析)spss实操

分类模型&#xff1a; 二分类和多分类&#xff1a; 对于二分类模型 &#xff0c;我们将介绍逻辑回归和Fisher线性判别分析两种分类算法; 对于多分类模型&#xff0c;我们将简单介绍Spss中的多分类线性判别分析和多分类逻辑回归的操作步骤 二分类: 基于广义线性模型&#x…

Ubuntu/centOS 如何安装 OpenGL

OpenGL安装与入门教程 什么是OpenGL? OpenGL是一个图形渲染API&#xff0c;它具有操作系统独立、窗口系统独立的特点&#xff0c;能够生成由几何图元和图像基元组成的高质量彩色图像。 OpenGL APIs可以使用以下几个库: Gl OpenGL API实现 (http://www.opengl.org)Glu Open…

在 Vue 3 项目中集成和使用 vue3-video-play

前言 随着视频内容的普及&#xff0c;视频播放功能在现代 Web 应用中变得越来越重要。如果你正在开发一个 Vue 3 项目&#xff0c;并且需要快速集成视频播放功能&#xff0c;vue3-video-play 是一个轻量级且易于使用的 Vue 3 组件。本文将介绍如何在 Vue 3 项目中使用 vue3-vi…

合并两个有序数组(88)合并两个有序链表(21)

88. 合并两个有序数组 - 力扣&#xff08;LeetCode&#xff09; 21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff08;88&#xff09;&#xff1a; class Solution { public:void merge(vector<int>& nums1, int m, vector<int>&…

NPC与AI深度融合结合雷鸟X3Pro AR智能眼镜:引领游戏行业沉浸式与增强现实新纪元的畅想

if… NPC&#xff08;非玩家角色&#xff09;与AI&#xff08;人工智能&#xff09;的深度融合&#xff0c;正引领游戏行业迈向一个全新的沉浸式与增强现实&#xff08;AR&#xff09;相结合的新时代。这一创新不仅预示着游戏体验的质变&#xff0c;更可能全面革新游戏设计与叙…

Dom的学习

DOM&#xff08;文档对象模型&#xff0c;Document Object Model&#xff09;是一个编程接口&#xff0c;用于HTML和XML文档。它将文档表示为一个树形结构&#xff0c;其中每个节点都是文档的一部分&#xff0c;例如元素、属性和文本内容。通过DOM&#xff0c;开发者可以使用编…

游戏行业销售数据分析可视化

完整源码项目包获取→点击文章末尾名片&#xff01; &#x1f31f;分析&#xff1a; 可看出最近五年用户最喜爱的游戏类型依然还是Action-动作类&#xff08;当然市场发行的也很多&#xff09; Sports-运动类和Shooter-射击类顺序互换,但我估计现在大环境局势紧张可以会推动射击…

Linux 管道操作

Linux 管道操作 在 Linux 中&#xff0c;管道&#xff08;Pipe&#xff09;是一个非常强大且常用的功能&#xff0c;它允许将一个命令的输出直接传递给另一个命令作为输入&#xff0c;从而能够高效地处理和分析数据。管道在多个命令之间建立数据流&#xff0c;减少了文件的读写…

深度学习加速性能分析与Roofline Model

深度学习加速性能分析 动因:由于深度学习加速器普遍采用时分复用(当然随着Graphcore等dataflow类型的芯片除外,他们是空间划分)。此时,硬件资源在不同时刻执行的计算发生变化,很难以单一时刻的计算类型进行硬件设计。所以寻找平均资源利用率就变得更重要方法:针对不同任…

第12章:Python TDD完善货币加法运算(一)

写在前面 这本书是我们老板推荐过的&#xff0c;我在《价值心法》的推荐书单里也看到了它。用了一段时间 Cursor 软件后&#xff0c;我突然思考&#xff0c;对于测试开发工程师来说&#xff0c;什么才更有价值呢&#xff1f;如何让 AI 工具更好地辅助自己写代码&#xff0c;或许…

Golang Gin系列-4:Gin Framework入门教程

在本章中&#xff0c;我们将深入研究Gin&#xff0c;一个强大的Go语言web框架。我们将揭示制作一个简单的Gin应用程序的过程&#xff0c;揭示处理路由和请求的复杂性。此外&#xff0c;我们将探索基本中间件的实现&#xff0c;揭示精确定义路由和路由参数的技术。此外&#xff…

靠右行驶数学建模分析(2014MCM美赛A题)

笔记 题目 要求分析&#xff1a; 比较规则的性能&#xff0c;分为light和heavy两种情况&#xff0c;性能指的是 a.流量与安全 b. 速度限制等分析左侧驾驶分析智能系统 论文 参考论文 两类规则分析 靠右行驶&#xff08;第一条&#xff09;2. 无限制&#xff08;去掉了第一条…