原生Js Canvas去除视频绿幕背景

Js去除视频背景


注: 这里的去除视频背景并不是对视频文件进行操作去除背景

如果需要对视频扣除背景并导出可以使用ffmpeg等库,这里仅作播放用所以采用这种方法

由于uniapp中的canvas经过封装,且 uniapp 的 drawImage 无法绘制视频帧画面,因此uniapp中不适用


实现过程是将视频使用canvas逐帧截下来对截取的图片进行处理,然后在canvas中显示处理好的图片

最后通过定时器高速处理替换,形成视频播放的效果,效果如下图⬇

在这里插入图片描述

边缘仍然会有些绿幕的像素,可以通过其他的处理进行优化


原理

首先使用canvas的 drawImage 方法将video的当前帧画面绘制到canvas中

然后再通过 getImageData 方法获取当前canvas的所有像素的rgba值组成的数组

获取到的值为[r,g,b,a,r,g,b,a,...],每一组rgba的值就是一个像素,所以获取到的数组长度是canvas的像素的数量 * 4

通过判断每一组rgb的值是否为绿幕像素,然后设置其透明通道的alpha的值为0实现效果


代码

因为canvas会受到跨域的影响导致画布被污染,因此首先需要将测试视频下载到本地

如果直接本地打开html的话同样会因为本地路径报跨域错误,需要将html,js,测试视频放在文件夹中部署一个本地服务器

可以使用http-server

npm i http-server -g# 切换到存放html,js,测试视频的文件夹 运行命令即可部署本地服务器http-server

或者

vsCode的Live server插件均可

测试视频 地址

<!DOCTYPE html>
<html lang="en"><head><style>video{width: 480px;height: 270px;}</style></head><body><video id="video"  src="./63e1dd7ddd2b0.mp4"  loop autoplay muted></video><canvas id="output-canvas" width="480" height="270" willReadFrequently="true"></canvas><script type="text/javascript" src="processor2.js"></script></body>
</html>
// processor2.jslet video, canvas, ctx, canvas_tmp, ctx_tmp;function init () {video = document.getElementById('video');canvas = document.getElementById('output-canvas');ctx = canvas.getContext('2d');// 创建的canvas宽高最好与显示图片的canvas、video宽高一致canvas_tmp = document.createElement('canvas');canvas_tmp.setAttribute('width', 480);canvas_tmp.setAttribute('height', 270);ctx_tmp = canvas_tmp.getContext('2d');video.addEventListener('play', computeFrame);
}function computeFrame () {if (video) {if (video.paused || video.ended) return;}// 如果视频比例和canvas比例不正确可能会出现显示形变, 调整除的值进行比例调整ctx_tmp.drawImage(video, 0, 0, video.clientWidth / 1, video.clientHeight / 1);// 获取到绘制的canvas的所有像素rgba值组成的数组let frame = ctx_tmp.getImageData(0, 0, video.clientWidth, video.clientHeight);// 共有多少像素点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];// 判断如果rgb值在这个范围内则是绿幕背景,设置alpha值为0 // 同理不同颜色的背景调整rgb的判断范围即可if (r < 100 && g > 120 && b < 200) {frame.data[i * 4 + 3] = 0;}}// 重新绘制到canvas中显示ctx.putImageData(frame, 0, 0);// 递归调用setTimeout(computeFrame, 0);
}document.addEventListener("DOMContentLoaded", () => {init();
});

使用本地服务器访问html即可看到效果,可以看到边缘仍有绿色像素闪烁

一般情况这种就可以了,使用算法进行处理的话效果会更好,但相应的资源的消耗也会提升,造成帧率下降

下面展示通过一些算法进行羽化和颜色过渡

羽化

// 返回canvas中第num个像素点所在的坐标  12 -> [1, 12]
function 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];
}// 返回canvas中所在坐标的num(index + 1)值  [1, 12] -> 12
function pointToNum (point, width) {let [row, col] = point;return (row - 1) * width + col
}// 获取输入的坐标周围1像素内的所有像素的坐标组成的数组 [1, 1] -> [[1, 2], [2, 1], [2, 2]]
function 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);})
}

通过上面的函数获取到一个选定的不透明的像素周围的像素后,判断周围的像素的alpha值

如果周围的像素有存在透明的像素,则重新计算选定像素的alpha值


颜色过渡

计算修改alpha值连带计算周围像素中rgb的各项平均值给选定像素

最终处理结果如下
在这里插入图片描述


代码

// 新增羽化和颜色过渡// processor2.js
let video, canvas, ctx, canvas_tmp, ctx_tmp;function init () {video = document.getElementById('video');canvas = document.getElementById('output-canvas');ctx = canvas.getContext('2d');// 创建的canvas宽高最好与显示图片的canvas、video宽高一致canvas_tmp = document.createElement('canvas');canvas_tmp.setAttribute('width', 480);canvas_tmp.setAttribute('height', 270);ctx_tmp = canvas_tmp.getContext('2d');video.addEventListener('play', computeFrame);
}function 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];
}function pointToNum (point, width) {let [row, col] = point;return (row - 1) * width + col
}function 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);})
}function computeFrame () {if (video) {if (video.paused || video.ended) return;}ctx_tmp.drawImage(video, 0, 0, video.clientWidth, video.clientHeight);let frame = ctx_tmp.getImageData(0, 0, video.clientWidth, video.clientHeight);//----- emergence ----------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 < 150 && g > 200 && b < 150) {frame.data[i * 4 + 3] = 0;}}const tempData = [...frame.data]for (let i = 0; i < pointLens; i++) {if (frame.data[i * 4 + 3] === 0) continueconst currentPoint = numToPoint(i + 1, width);const arroundPoint = getAroundPoint(currentPoint, width, height, 3);let opNum = 0;let rSum = 0;let gSum = 0;let bSum = 0;arroundPoint.forEach((position) => {const index = pointToNum(position, width);rSum = rSum + tempData[(index - 1) * 4];gSum = gSum + tempData[(index - 1) * 4 + 1];bSum = 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) {// debuggerframe.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);frame.data[i * 4 + 3] = parseInt(alpha);}}//------------------------ctx.putImageData(frame, 0, 0);setTimeout(computeFrame, 0);
}document.addEventListener("DOMContentLoaded", () => {init();
});

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

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

相关文章

《基于区块链的数据资产评估实施指南》技术研讨会成功召开

2023年9月1日&#xff0c;《基于区块链的数据资产评估实施指南》&#xff08;以下简称《指南》&#xff09;技术研讨会在深圳召开&#xff0c;竹云科技作为主要参编单位出席此次研讨会。 中国科协决策咨询首席专家王春晖&#xff0c;中国社会科学院博士于小丽&#xff0c;中国…

无swing,高级javaSE毕业之贪吃蛇游戏(含模块构建,多线程监听服务)

JavaSE&#xff0c;无框架实现贪吃蛇 文章目录 JavaSE&#xff0c;无框架实现贪吃蛇1.整体思考2.可能的难点思考2.1 如何表示游戏界面2.2 如何渲染游戏界面2.3 如何让游戏动起来2.4 蛇如何移动 3.流程图制作4.模块划分5.模块完善5.0常量优化5.1监听键盘服务i.输入存储ii.键盘监…

Lumion 和 Enscape 应该选择怎样的笔记本电脑?

Lumion 和 Enscape实时渲染对配置要求高&#xff0c;本地配置不够&#xff0c;如何快速解决&#xff1a; 本地普通电脑可一键申请高性能工作站&#xff0c;资产安全保障&#xff0c;供软件中心&#xff0c;各种软件插件一键获取&#xff0c;且即开即用&#xff0c;使用灵活&am…

时序预测 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络时间序列预测(风电功率预测)

时序预测 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络时间序列预测&#xff08;风电功率预测&#xff09; 目录 时序预测 | MATLAB实现CNN-LSTM卷积长短期记忆神经网络时间序列预测&#xff08;风电功率预测&#xff09;预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1…

知识蒸馏学习

知识蒸馏----教师和学生模型&#xff1a;将已训练完善的模型作为教师模型&#xff0c;通过控制“温度”从模型的输出结果中“蒸馏”出“知识”用于学生模型的训练&#xff0c;并希望轻量级的学生模型能够学到教师模型的“知识”&#xff0c;达到和教师模型相同的表现。 本质上属…

无需公网IP,实现外网远程访问管家婆ERP进销存系统的方法

文章目录 前言 1.管家婆服务2. 内网穿透2.1 安装cpolar内网穿透2.2 设置远程访问 3. 固定访问地址4. 配置固定公网访问地址 前言 管家婆辉煌系列产品是中小企业进销存、财务管理一体化的典范软件&#xff0c;历经十余年市场的洗礼&#xff0c;深受广大中小企业的欢迎&#xff…

【2023高教社杯数学建模国赛】ABCD题 问题分析、模型建立、参考文献及实现代码

【2023高教社杯数学建模国赛】ABCD题 问题分析、模型建立、参考文献及实现代码 1 比赛时间 北京时间&#xff1a;2023年9月7日 18:00-2023年9月10日20:00 2 思路内容 可以参考我提供的历史竞赛信息内容&#xff0c;最新更新我会发布在博客和知乎上&#xff0c;请关注我获得最…

1、Flutter移动端App实战教程【环境配置、模拟器配置】

一、概述 Flutter是Google用以帮助开发者在IOS和Android 两个平台开发高质量原生UI的移动SDK&#xff0c;一份代码可以同时生成IOS和Android两个高性能、高保真的应用程序。 二、渲染机制 之所以说Flutter能够达到可以媲美甚至超越原生的体验&#xff0c;主要在于其拥有高性…

GeoNet: Unsupervised Learning of Dense Depth, Optical Flow and Camera Pose 论文阅读

论文信息 题目&#xff1a;GeoNet: Unsupervised Learning of Dense Depth, Optical Flow and Camera Pose 作者&#xff1a;Zhichao Yin and Jianping Shi 来源&#xff1a;CVPR 时间&#xff1a;2018 Abstract 我们提出了 GeoNet&#xff0c;这是一种联合无监督学习框架&a…

WebGIS外包开发流程

WebGIS开发流程需要综合考虑前端和后端开发、地理信息数据处理、用户需求和安全性等多个方面。成功的WebGIS应用程序需要不断地进行更新和维护&#xff0c;以适应变化的需求和技术。WebGIS开发是一个复杂的过程&#xff0c;通常包括以下主要步骤。北京木奇移动技术有限公司&…

【 XXL-JOB】 XXL-JOB任务分片

文章目录 前言xxl-job 分片广播任务的详细教程创建任务编写任务代码分片参数设置执行任务查看任务执行结果示例1示例2 总结 前言 xxl-job 是一个分布式任务调度平台&#xff0c;支持定时任务和分片任务。其中&#xff0c;分片任务可以将一个大任务拆分成多个小任务&#xff0c…

【CSDN技术】Markdown编辑器如何使用-csdn博客编写入门

Markdown编辑器如何使用-csdn博客编写入门 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自…

概率有向图模型(一)

文章目录 前言概率有向图模型验证回到书中隐马尔可夫模型信念网络朴素贝耶斯 总结 前言 经过前面的复习&#xff0c;我们把李航老师的《统计学习方法》中的监督学习部分回顾了一遍&#xff0c;接下来我们在此基础上&#xff0c;开始学习邱锡鹏老师的《神经网络与深度学习》&am…

socket的使用 | TCP/IP协议下服务器与客户端之间传送数据

服务器端代码&#xff1a; import java.io.*; import java.net.ServerSocket; import java.net.Socket;public class theServer {public static void main(String[] args) throws IOException {ServerSocket serverSocket new ServerSocket(9999); // 该行代码作用&#xff1…

Android签名查看

查看签名文件信息 第一种方法&#xff1a; 1.打开cmd&#xff0c;执行keytool -list -v -keystore xxx.keystore&#xff0c;效果如下图&#xff1a; 第二种方法: 1.打开cmd&#xff0c;执行 keytool -list -v -keystore xxxx.keystore -storepass 签名文件密码&#xff0…

suning苏宁API接入说明(苏宁商品详情+关键词搜索商品列表)

API地址:https://o0b.cn/anzexi 调用示例&#xff1a;https://api-gw.onebound.cn/suning/item_get/?keytest_api_key& &num_iid0070134261/703410301&&langzh-CN&secret 参数说明 通用参数说明 version:API版本key:调用key,测试key:test_api_keyapi_na…

2023高教社杯数学建模C题思路模型 - 蔬菜类商品的自动定价与补货决策

# 1 赛题 在生鲜商超中&#xff0c;一般蔬菜类商品的保鲜期都比较短&#xff0c;且品相随销售时间的增加而变差&#xff0c; 大部分品种如当日未售出&#xff0c;隔日就无法再售。因此&#xff0c; 商超通常会根据各商品的历史销售和需 求情况每天进行补货。 由于商超销售的蔬菜…

Vue+Element-ui+SpringBoot搭建后端汽车租赁管理系统

最近在做项目&#xff0c;花了一周的时间搭建了一个十分完备的汽车租赁后端管理系统。页面采用纯Vue2Element-ui搭建&#xff0c;后端采用SpringbootMybatis搭建&#xff0c;数据库采用Mysql。包括了登录验证&#xff0c;根据不同权限进入不同界面、数据增删改查、表格分页、表…

多波束测线问题

多波束测线问题 问题的背景是海洋测深技术&#xff0c;特别是涉及单波束测深和多波束测深系统。这些系统利用声波传播原理来测量水体深度。 单波束测深系统通过向海底发射声波信号并记录其返回时间来测量水深。该系统的特点是每次只有一个波束打到海底&#xff0c;因此数据分布…

unity 场景烘培(边学习,边记录)

前言&#xff1a;好记性不如烂笔头&#xff0c;本文只提供参考&#xff01; 问题总结&#xff1a;1.unity 场景烘焙问题之模型UV有重叠_野区捕龙为宠的博客-CSDN博客 一、光源种类&#xff08;摘录&#xff1a;Unity灯光&#xff08;light&#xff09;_浮影℡的博客-CSDN博客…