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

news2025/1/18 14:08:47

最近在做这个这项目奇店桶装水小程序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);

      // 根据羽化强度调整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. 显示处理结果 更新画布:将处理后的图像数据应用到实际显示的画布(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"
           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>


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

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

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

相关文章

使用python+pytest+requests完成自动化接口测试(包括html报告的生成和日志记录以及层级的封装(包括调用Json文件))

一、API的选择 我们进行接口测试需要API文档和系统&#xff0c;我们选择JSONPlaceholder免费API&#xff0c;因为它是一个非常适合进行接口测试、API 测试和学习的工具。它免费、易于使用、无需认证&#xff0c;能够快速帮助开发者模拟常见的接口操作&#xff08;增、删、改、…

UE4原生的增量Cook原理

设置Cook的步骤后&#xff0c;断点进入到如下堆栈&#xff1a; UCookOnTheFlyServer::StartCookByTheBook(const UCookOnTheFlyServer::FCookByTheBookStartupOptions &) CookOnTheFlyServer.cpp:7723 UCookCommandlet::CookByTheBook(const TArray<…> &, TArr…

C#表达式和运算符

本文我们将学习C#的两个重要知识点&#xff1a;表达式和运算符。本章内容会理论性稍微强些&#xff0c;我们会尽量多举例进行说明。建议大家边阅读边思考&#xff0c;如果还能边实践就更好了。 1. 表达式 说到表达式&#xff0c;大家可能感觉有些陌生&#xff0c;我们先来举个…

蓝桥杯 Python 组知识点容斥原理

容斥原理 这张图初中或者高中数学课应该画过 也就是通过这个简单的例子引出容斥原理的公式 这张图的面积&#xff1a;s1 s3 s7 - 2 * s2 - 2 * s4 - 2 * s6 3 * s5 通过此引导出容斥原理公式 那么下面来一起看看题目 题目描述 给定 n,m 请求出所有 n 位十进制整数中有多…

PDF文件提取开源工具调研总结

概述 PDF是一种日常工作中广泛使用的跨平台文档格式&#xff0c;常常包含丰富的内容&#xff1a;包括文本、图表、表格、公式、图像。在现代信息处理工作流中发挥了重要的作用&#xff0c;尤其是RAG项目中&#xff0c;通过将非结构化数据转化为结构化和可访问的信息&#xff0…

PDF编辑 PDF-XChange Editor Plus 免装优化版

PDF编辑器很多打工人都需要用到&#xff0c;也分享过好几款口碑不错的&#xff0c;这次这款PDF依旧值得你的期待。 PDF-XChange Editor&#xff0c;号称打开速度最快最强大的PDF编辑器/PDF阅读器&#xff0c;专注于PDF文档的编辑&#xff0c;可以自定义制作PDF电子文档&#xf…

IP属地会随着人的移动而改变吗

在当今数字化时代&#xff0c;互联网已成为人们生活中不可或缺的一部分。无论是社交媒体的日常互动&#xff0c;还是在线购物、远程工作&#xff0c;IP地址作为网络身份的重要标识&#xff0c;扮演着举足轻重的角色。随着移动互联网技术的飞速发展&#xff0c;人们越来越多地在…

我的世界-与门、或门、非门等基本门电路实现

一、红石比较器 (1) 红石比较器结构 红石比较器有前端单火把、后端双火把以及两个侧端 其中后端和侧端是输入信号,前端是输出信号 (2) 红石比较器的两种模式 比较模式 前端火把未点亮时处于比较模式 侧端>后端 → 0 当任一侧端强度大于后端强度时,输出…

【大数据】机器学习------支持向量机(SVM)

支持向量机的基本概念和数学公式&#xff1a; 1. 线性可分的支持向量机 对于线性可分的数据集 &#xff0c;其中(x_i \in R^d) 是特征向量 是类别标签&#xff0c;目标是找到一个超平面 &#xff0c;使得对于所有 的样本 &#xff0c;对于所有(y_i -1) 的样本&#xff0c;…

RDD和DataFrame两种数据结构的对比

文章目录 1. 实战概述2. RDD&#xff08;弹性分布式数据集&#xff09;2.1 RDD概念2.2 RDD特点2.3 实战操作 3. DataFrame&#xff08;数据帧&#xff09;3.1 DataFrame概念3.2 DataFrame优点3.3 实战操作 4. 实战小结 1. 实战概述 今天我们将深入探讨 Apache Spark 中的两种核…

中职网络建设与运维ansible服务

ansible服务 填写hosts指定主机范围和控制节点后创建一个脚本&#xff0c;可以利用简化脚本 1. 在linux1上安装系统自带的ansible-core,作为ansible控制节点,linux2-linux7作为ansible的受控节点 Linux1 Linux1-7 Yum install ansible-core -y Vi /etc/ansible/hosts 添加…

Kibana 控制台中提供语义、向量和混合搜索

作者&#xff1a;来自 Elastic Mark_Laney 想要将常规 Elasticsearch 查询与新的 AI 搜索功能结合起来吗&#xff1f;那么&#xff0c;你不需要连接到某个第三方的大型语言模型&#xff08;LLM&#xff09;吗&#xff1f;不。你可以使用 Elastic 的 ELSER 模型来改进现有搜索&a…

多种 Docker 镜像拉取解决方案与实践

最近国内 Docker 镜像拉取不太通畅&#xff0c;尝试了几种镜像拉取的方式&#xff0c;写篇博客分享一下。 原以为只是 docker hub 被毙了&#xff0c;上机器一操作&#xff0c;官方的下载地址也被毙了&#xff0c;真是从源头解决问题。 不过还好目前还有其他源能用&#xff0…

2025边缘计算新年沙龙成功举办,共话边缘AI未来

1月11日下午&#xff0c;北京市海淀区中关村创业大街热闹非凡&#xff0c;以“云边腾跃&#xff0c;蛇启新航”为主题的 2025边缘计算新年沙龙 盛大举行。本次活动汇聚了边缘计算、人工智能以及云边协同领域的顶尖专家、学者和从业者&#xff0c;共同探讨技术前沿与实际应用场景…

使用redis-cli命令实现redis crud操作

项目场景&#xff1a; 线上环境上redis中的key影响数据展示&#xff0c;需要删除。但环境特殊没办法通过 redis客户端工具直连。只能使用redis-cli命令来实现。 操作步骤&#xff1a; 1、确定redis安装的服务器&#xff1b; 2、找到redis的安装目录下 ##找到redis安装目…

CentOS 下载软件时报Error: Failed to synchronize cache for repo ‘AppStream‘解决方法

下载软件时出现以下问题 直接把CentOS-AppStream.repo改个名字就行 cd /etc/yum.repos.d/ mv CentOS-AppStream.repo CentOS-AppStream.repo.bak就可以了 解决思路 把AI问遍&#xff0c;无人会&#xff0c;解决法 想要下载软件通通失败了&#xff0c;解决方法当然是问AI&am…

【深度学习】神经网络之Softmax

Softmax 函数是神经网络中常用的一种激活函数&#xff0c;尤其在分类问题中广泛应用。它将一个实数向量转换为概率分布&#xff0c;使得每个输出值都位于 [0, 1] 之间&#xff0c;并且所有输出值的和为 1。这样&#xff0c;Softmax 可以用来表示各类别的预测概率。 Softmax 函…

python管理工具:conda部署+使用

python管理工具&#xff1a;conda部署使用 一、安装部署 1、 下载 - 官网下载&#xff1a; https://repo.anaconda.com/archive/index.html - wget方式&#xff1a; wget -c https://repo.anaconda.com/archive/Anaconda3-2023.03-1-Linux-x86_64.sh2、 安装 在conda文件的…

当PHP遇上区块链:一场奇妙的技术之旅

PHP 与区块链的邂逅 在技术的广袤宇宙中&#xff0c;区块链技术如同一颗耀眼的新星&#xff0c;以其去中心化、不可篡改、透明等特性&#xff0c;掀起了一场席卷全球的变革浪潮。众多开发者怀揣着对新技术的热忱与探索精神&#xff0c;纷纷投身于区块链开发的领域&#xff0c;试…

unity——Preject3——开始界面拼面板

目录 1.创建panel&#xff0c;去掉panel自带的image&#xff0c;自己加一个image&#xff0c;使用锚点分配好 2.锚点&#xff08;快捷键点击后 ALTShift&#xff09; 锚点是什么&#xff1f; 锚点的实际例子 例子1&#xff1a;固定在父容器的中心 例子2&#xff1a;对齐到…