当前位置: 首页 > news >正文

原生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();
});

相关文章:

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

Js去除视频背景 注&#xff1a; 这里的去除视频背景并不是对视频文件进行操作去除背景 如果需要对视频扣除背景并导出可以使用ffmpeg等库&#xff0c;这里仅作播放用所以采用这种方法 由于uniapp中的canvas经过封装&#xff0c;且 uniapp 的 drawImage 无法绘制视频帧画面&…...

Vue知识系列(1)每天10个小知识点

目录 系列文章目录知识点**1. Vue修饰符**的概念、作用、原理、特性、优点、缺点、区别、使用场景**2. 双向数据绑定**的概念、作用、原理、特性、优点、缺点、区别、使用场景**3. MVVM、MVC、MVP** 的概念、作用、原理、特性、优点、缺点、区别、使用场景**4. slot** 的概念、…...

Elasticsearch(三)聚合基本使用

基础概念 bucket 数据分组&#xff0c;一些数据按照某个字段进行bucket划分&#xff0c;这个字段值相同的数据放到一个bucket中。可以理解成Java中的Map<String, List>结构&#xff0c;类似于Mysql中的group by后的查询结果。 metric&#xff1a; 对一个数据分组执行…...

单片机C语言实例:14、音频输出

一、喇叭发声原理 程序实例1&#xff1a; #include<reg52.h> //包含头文件&#xff0c;一般情况不需要改动&#xff0c;头文件包含特殊功能寄存器的定义sbit SPK P1^2; //定义喇叭端口 /*------------------------------------------------函数声明 --------------…...

docker 和 podman的区别

Podman 和 Docker 都是用于容器化应用程序的工具&#xff0c;它们在很多方面非常相似&#xff0c;但也有一些关键区别&#xff1a; 1. 架构和权限&#xff1a; - Docker&#xff1a;Docker 使用守护进程&#xff08;dockerd&#xff09;来管理容器&#xff0c;它需要在操作…...

苹果手机远程控制安卓手机,为什么不能发起控制?

这位用户想要用iOS设备远程控制安卓设备&#xff0c;在被控端安装好AirDroid之后&#xff0c;就在控制端的苹果手机上也安装了AirDroid&#xff0c;然而打开控制端的软件&#xff0c;却没有在手机界面上看到【远程控制】按钮&#xff0c;于是提出了以上疑问。 解答 想要让iOS设…...

Gradle 配置国内镜像

我们在使用gradle构建项目的时候&#xff0c;每当需要build或者刷新依赖的时候&#xff0c;由于gradle需要从服务器下载各种依赖包&#xff0c;速度非常慢&#xff0c;根本原因是由于gradle服务器在国外&#xff0c;而国内有些一些大厂和高校(比如阿里&#xff0c;华为&#xf…...

Spring AOP使用指南: 强大的面向切面编程技术

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…...

Spring Boot集成Elasticsearch实战

文章目录 一、简介二、安装与配置Elasticsearch三、集成Spring Boot与Elasticsearch1. 添加依赖与配置文件2. 创建Elasticsearch数据模型3. 定义Elasticsearch仓库接口4. 实现Elasticsearch数据操作 四、基本查询与索引操作1. 插入与更新数据2. 删除数据与索引3. 条件查询与分页…...

【python零基础入门学习】python基础篇之文件对象open、模块以及函数的使用(三)

本站以分享各种运维经验和运维所需要的技能为主 《python》&#xff1a;python零基础入门学习 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8》暂未更新 《docker学习》暂未更新 《ceph学习》ceph日常问题解…...

【JavaEE】_CSS常用属性值

目录 1. 字体属性 1.1 设置字体家族 font-family 1.2 设置字体大小 font-size 1.3 设置字体粗细 font-weight 1.4 设置字体倾斜 font-style 2. 文本属性 2.1 设置文本颜色 color 2.2 文本对齐 text-align 2.3 文本装饰 text-decoration 2.4 文本缩进 text-indent 2.…...

vue组件库开发,webpack打包,发布npm

做一个像elment-ui一样的vue组件库 那多好啊&#xff01;这是我前几年就想做的 但webpack真的太难用&#xff0c;也许是我功力不够 今天看到一个视频&#xff0c;早上6-13点&#xff0c;终于实现了&#xff0c;呜呜 感谢视频的分享-来龙去脉-大家可以看这个视频&#xff1a;htt…...

Java中快速排序的优化技巧:随机取样、三数取中和插入排序

目录 快速排序基础 优化1&#xff1a;随机取样 优化2&#xff1a;三数取中 优化3&#xff1a;插入排序 总结&#xff1a; 快速排序&#xff08;Quick Sort&#xff09;是一种高效的排序算法&#xff0c;它的平均时间复杂度为O(n log n)。然而&#xff0c;在某些情况下&…...

【leetcode 力扣刷题】删除字符串中的子串or字符以满足要求

删除字符串中的子串或者字符以满足题意要求 1234. 替换子串得到平衡字符串680. 验证回文串917. 仅仅反转字母 1234. 替换子串得到平衡字符串 题目链接&#xff1a;1234. 替换子串得到平衡字符串 题目内容&#xff1a; 题目中给出了平衡字符串的定义——只有’Q’&#xff0c;…...

【Unity基础】3.脚本控制物体运动天空盒

【Unity基础】3.脚本控制物体运动&天空盒 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity基础系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;搭建开发环境 &#xff08;1&#xff09;下载visual studio 在我们下载unity编译器的时候&…...

Spring MVC拦截器

拦截器&#xff08;Interceptor&#xff09;是 Spring MVC 提供的一种强大的功能组件。它可以对用户请求进行拦截&#xff0c;并在请求进入控制器&#xff08;Controller&#xff09;之前、控制器处理完请求后、甚至是渲染视图后&#xff0c;执行一些指定的操作。 在 Spring MV…...

ClickHouse的Join算法

ClickHouse的Join算法 ClickHouse是一款开源的列式分析型数据库&#xff08;OLAP&#xff09;&#xff0c;专为需要超低延迟分析查询大量数据的场景而生。为了实现分析应用可能达到的最佳性能&#xff0c;分析型数据库&#xff08;OLAP&#xff09;通常将表组合在一起形成一个…...

java面试题-RabbitMQ面试题

RabbitMQ面试题 面试官&#xff1a;RabbitMQ-如何保证消息不丢失 候选人&#xff1a; 嗯&#xff01;我们当时MYSQL和Redis的数据双写一致性就是采用RabbitMQ实现同步的&#xff0c;这里面就要求了消息的高可用性&#xff0c;我们要保证消息的不丢失。主要从三个层面考虑 第一…...

数据仓库-核心概念

数据仓库 数据仓库&#xff0c;英文名称为Data Warehouse&#xff0c;可简写为DW或DWH。数据仓库&#xff0c;是为企业所有级别的决策制定过程&#xff0c;提供所有类型数据支持的战略集合。它是单个数据存储&#xff0c;出于分析性报告和决策支持目的而创建。为需要业务智能的…...

java中的实体类

在Java与数据库交互时&#xff0c;设计实体类有以下几个原因&#xff1a; 1、对象关系映射&#xff08;ORM&#xff09;&#xff1a;实体类提供了一种将数据库中的表映射为Java对象的方式。这样&#xff0c;开发人员可以使用面向对象的方式操作数据库&#xff0c;而无需编写大…...

V-REP/CoppeliaSim机器人仿真进阶:Graph模块3D轨迹可视化与数据导出实战解析

1. Graph模块基础与3D轨迹可视化原理 在机器人仿真中&#xff0c;轨迹可视化就像给机械臂装上了"运动摄像机"。V-REP/CoppeliaSim的Graph模块就是这个摄像机的核心部件&#xff0c;它能记录机械臂末端执行器在三维空间中的每一个细微动作。我刚开始用这个功能时&…...

3分钟搞定容器镜像加速:public-image-mirror 终极实战指南

3分钟搞定容器镜像加速&#xff1a;public-image-mirror 终极实战指南 【免费下载链接】public-image-mirror 很多镜像都在国外。比如 gcr 。国内下载很慢&#xff0c;需要加速。致力于提供连接全世界的稳定可靠安全的容器镜像服务。 项目地址: https://gitcode.com/GitHub_T…...

tchMaterial-parser:基于智能解析引擎的教育资源去中心化获取方案

tchMaterial-parser&#xff1a;基于智能解析引擎的教育资源去中心化获取方案 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具&#xff0c;帮助您从智慧教育平台中获取电子课本的 PDF 文件网址并进行下载&#xff0c;让您更方便地获取课本内容。…...

AI写专著高效途径:选对工具,一键生成20万字专著不是梦!

一、新手研究者撰写学术专著的困境 对于首次尝试撰写学术专著的研究者来说&#xff0c;写作的过程就像是在“摸石头过河”&#xff0c;其中充满了各种未知的障碍。选题上常常感到迷茫&#xff0c;难以在“有意义”与“可行性”之间找到合适的平衡&#xff0c;选题要么过于宏大…...

26-cv-2777、26-cv-2964、26-cv-3022、26-cv-3949、26-cv-4062、26-cv-5488 Winnie Rosaline Kan 版权画维权!

案号&#xff1a;26-cv-2777、26-cv-2964、26-cv-3022、26-cv-3949、26-cv-4062、26-cv-5488原告品牌&#xff1a;Winnie Rosaline Kan 版权画品牌方&#xff1a;Casetagram Limited起诉地&#xff1a;美国伊利诺伊州代理律所&#xff1a;Keith起诉时间&#xff1a;2026年03月1…...

基于大语言模型的强化学习奖励函数自动生成:text2reward项目实践指南

1. 项目概述&#xff1a;从文本指令到强化学习奖励的桥梁最近在折腾强化学习项目时&#xff0c;一个老问题又冒出来了&#xff1a;怎么设计一个既精确又高效的奖励函数&#xff1f;传统方法要么是工程师凭经验手写一堆规则&#xff0c;复杂场景下容易顾此失彼&#xff1b;要么依…...

别再乱买手机了!这 3 个坑 90% 的人都踩过,看完立省千元

救命&#xff01;谁还没在买手机上交过 “智商税”&#xff1f;&#x1f62d;明明花了三四千&#xff0c;到手却卡顿发烫、拍照模糊、续航拉胯&#xff1b;销售吹得天花乱坠的 “旗舰配置”&#xff0c;用半年就后悔想砸手机&#xff01;作为换过 5 台手机、踩遍所有雷的过来人…...

从一次安全扫描报告说起:聊聊SSH Banner泄露那些事儿,以及比修改Banner更重要的安全习惯

从SSH版本泄露看现代安全防御&#xff1a;工程师的深度实践指南 那天下午&#xff0c;我正在整理新部署的云服务器集群的安全扫描报告&#xff0c;一个看似"古老"的漏洞引起了我的注意——CVE-1999-0634&#xff0c;SSH版本信息可被获取。这个诞生于上世纪的安全问题…...

用STM32F103和电位器给你的无刷电机做个“油门”:手把手实现ADC调速(附完整代码)

用STM32F103和电位器打造无刷电机调速系统&#xff1a;从硬件连接到代码实战 旋转电位器旋钮就能精准控制无刷电机转速&#xff0c;这种直观的交互方式在机器人、无人机和工业控制领域有着广泛应用。本文将带您从零开始&#xff0c;基于STM32F103微控制器构建完整的电位器调速…...

运维老鸟复盘:一次CentOS7物理机安装踩坑全记录(从RAID0到安装源验证)

运维实战&#xff1a;CentOS7物理机安装全流程避坑指南 引言 那台尘封已久的联想RD550服务器静静躺在仓库角落&#xff0c;表面覆盖着一层薄灰。作为运维工程师&#xff0c;我们总会遇到这样的挑战——老旧设备突然需要重新部署系统。这次任务看似简单&#xff1a;为这台双盘…...