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

news2025/1/17 1:07:38

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.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 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) continue
        const 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) {
            // debugger
            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);
            frame.data[i * 4 + 3] = parseInt(alpha);
        }
    }

    //------------------------
    ctx.putImageData(frame, 0, 0);
    setTimeout(computeFrame, 0);
}


document.addEventListener("DOMContentLoaded", () => {
    init();
});

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

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

相关文章

深度学习论文: Rethinking Mobile Block for Efficient Attention-based Models及其PyTorch实现

深度学习论文: Rethinking Mobile Block for Efficient Attention-based Models及其PyTorch实现 Rethinking Mobile Block for Efficient Attention-based Models PDF: https://arxiv.org/pdf/2301.01146.pdf PyTorch代码: https://github.com/shanglianlm0525/CvPytorch PyTor…

管理学国际化与领导力视角

随着全球化的进程不断加速&#xff0c;管理学的国际化已经成为一个不可忽视的趋势。在这个多元化和全球化的时代&#xff0c;管理者需要具备跨文化的领导力&#xff0c;以适应不同国家和地区的商业环境&#xff0c;并有效地管理全球团队。本文将从管理学国际化和领导力的角度探…

【文件翻译重命名与复制】一键实现文件名翻译,轻松复制到指定文件夹!

亲爱的朋友们&#xff0c;您是否曾经遇到过需要将文件名进行翻译&#xff0c;并且复制到指定文件夹的情况&#xff1f;现在&#xff0c;我们为您带来了一款全新的文件翻译重命名与复制工具&#xff0c;让您一键实现文件名翻译&#xff0c;轻松复制到指定文件夹&#xff0c;提高…

Zabbix -- QQ邮箱报警

目录 一、创建监控项及触发器 1.1创建监控项 1.2 创建监控项的触发器 1.3 测试触发器 二、邮箱媒介设置 2.1 设置报警媒介类型 2.2 创建用户群组和用户 三、动作绑定 3.1 创建动作 3.2 动作操作 3.3 动作测试&#xff08;发送邮件测试&#xff09; 四、问题总结 4.1 邮件发送…

Vue3+Element Plus实现el-table跨行显示(非脚手架)

Vue3Element Plus实现el-table跨行显示 app组件内容使用:span-method"objectSpanMethod"自定义方法实现跨行显示查询方法初始化挂载新建一个html即可进行测试&#xff0c;完整代码如下效果图 app组件内容 <div id"app"><!-- 远程搜索 --><e…

Consul学习笔记之-初识Consul

文章目录 1. What is consul?2. Consul能干什么3. Consul的架构3.1 概念 4. Consul VS Eureka4.1 CAP4.2 对比 1. What is consul? 根据官方文档的定义&#xff1a; HashiCorp Consul is a service networking solution that enables teams to manage secure network connec…

无涯教程-JavaScript - CONVERT函数

描述 CONVERT功能将数字从一种测量系统转换为另一种。 如,CONVERT可以将以英里为单位的距离表转换为以公里为单位的距离表。 语法 CONVERT (number,from_unit,to_unit)争论 Argument描述Required/OptionalNumberThe value in from_units to convert.RequiredFrom_unitThe …

无涯教程-JavaScript - WEEKNUM函数

描述 WEEKNUM函数返回特定日期的星期数。数字代表一年中数字所在的星期。 有两个系统用于此功能- 系统1 -包含1月1日的一周是一年的第一周,并编号为第1周。 系统2 -包含一年中第一个星期四的那一周是一年中的第一周,并编号为第1周。此系统是ISO 8601中指定的方法,这就是欧洲…

云原生Kubernetes:Yaml文件编写

目录 一、理论 1.Kubernetes与yaml文件 二、实验 1.Kubernetes与yaml文件 三、问题 1.kubectl create 和 kubectl apply区别 四、总结 一、理论 1.​​​​​​​Kubernetes与yaml文件 &#xff08;1&#xff09;Kubernetes支持管理资源对象的文件格式 Kubernetes支持…

RabbitMQ:work结构

> 只需要在消费者端&#xff0c;添加Qos能力以及更改为手动ack即可让消费者&#xff0c;根据自己的能力去消费指定的消息&#xff0c;而不是默认情况下由RabbitMQ平均分配了&#xff0c;生产者不变&#xff0c;正常发布消息到默认的exchange > 消费者指定Qoa和手动ack …

图床项目详解

文章目录 一、图床项目介绍二、图床项目架构三、图床功能实现3.1 注册功能3.2 登录功能3.3 用户文件列表3.4 上传文件3.5 上传文件之秒传3.6 获取共享文件列表或下载榜3.7 分享/ 删除文件/ 更新下载数3.8 取消分享/ 转存/ 更新下载计数3.9 图床分享图片 一、图床项目介绍 实现…

感应型静电消除器的组成和工作原理

感应型静电消除器是一种常用于消除物体表面静电的设备。它通过感测周围环境的静电电荷变化&#xff0c;并采取相应的措施来中和或消除这些电荷&#xff0c;以防止静电造成的问题。 感测型静电消除器通常由以下几个关键组件组成&#xff1a; 1. 静电感测器&#xff1a;用于检测…

CUDA相关知识科普

显卡 显卡&#xff08;Video card&#xff0c;Graphics card&#xff09;全称显示接口卡&#xff0c;又称显示适配器&#xff0c;是计算机最基本配置、最重要的配件之一。就像电脑联网需要网卡&#xff0c;主机里的数据要显示在屏幕上就需要显卡。因此&#xff0c;显卡是电脑进…

ChatGPT是如何辅助高效撰写论文及使用ChatGPT注意事项

ChatGPT发布近1年&#xff0c;各大高校对它的态度也发生了极大转变&#xff0c;今年3月发布ChatGPT禁令的牛剑等世界顶级名校也在近期解除了ChatGPT禁令&#xff0c;发布了生成式人工智能使用指南。 ChatGPT一定程度上可以解放科研人员的劳动力&#xff0c;与其直接禁止不如教…

【深入理解Linux内核锁】六、信号量

我的圈子: 高级工程师聚集地 我是董哥,高级嵌入式软件开发工程师,从事嵌入式Linux驱动开发和系统开发,曾就职于世界500强企业! 创作理念:专注分享高质量嵌入式文章,让大家读有所得! 文章目录 1、信号量介绍2、信号量的API3、API实现3.1 semaphore3.2 sema_init3.3 down…

口袋参谋:高流量权重标题,都是利用了这套工具玩法!

​近来无事&#xff0c;与几位电商大佬们一起喝茶聊天。在谈到提升宝贝流量最直接的方式&#xff0c;大家异口同声的说到&#xff1a;“搜索流量&#xff01;” 根据我近十年的电商经验&#xff0c;一个好的标题&#xff0c;不仅要契合宝贝核心关键词&#xff0c;同时也要契合…

网络技术十:交换机端口安全技术

交换机端口安全技术 802.1X 定义 起源于WLAN协议802.11&#xff0c;解决局域网终端的接入认证问题 认证方式 本地认证&#xff1a;由设备端内置本地服务器对客户端进行认证 远程集中认证&#xff1a;由远程的认证服务器对客户端进行认证 端口接入控制方式 基于端口认证…

配电房智能化系统

配电房智能化系统依托电易云-智慧电力物联网&#xff0c;综合利用现代先进技术&#xff0c;通过对配电房的监控、数据采集、自动控制和管理&#xff0c;实现对配电房的安全、可靠、高效、节能和环保监控的综合管理系统。 配电房智能化系统功能&#xff1a; 1.运行状态实时监测…

【C++漂流记】简易理解引用的基本语法和使用及其注意实现

引用是C中的一种数据类型&#xff0c;它允许我们使用一个已经存在的变量来创建一个新的名称或别名&#xff0c;以便可以通过这个别名来访问和修改原始变量的值。引用的本质是一个别名或者一个变量的别名。 文章目录 基本语法引用的注意事项引用做函数参数引用的本质常量引用 基…

04 卷积神经网络搭建

一、数据集 MNIST数据集是从NIST的两个手写数字数据集&#xff1a;Special Database 3 和Special Database 1中分别取出部分图像&#xff0c;并经过一些图像处理后得到的[参考]。 MNIST数据集共有70000张图像&#xff0c;其中训练集60000张&#xff0c;测试集10000张。所有图…