上一篇我们提到我们接着要开始玩一些比较有趣的实作~
所以我们就来讲讲怎麽在web
端实作绿幕抠像(Green Screen Keying)
~
大家应该都有看过很多电影特效幕後花絮的照片,照片里面的特效演员会在一个绿色帆布架构成的场景前面拍戏,这种绿色帆布场景就是动画业界常常在说的绿幕
。
而绿幕抠像(Green Screen Keying)
指的就是把绿幕
影片中的人物
单独撷取出来的技术。
有些人可能会好奇为什麽要用绿色,据说是因为绿色是成效最好的一种颜色,大部分的戏剧拍摄道具/ 人类皮肤...,etc. 含有绿色的部分占比,平均来讲比较少。
其实这个技术在很多的影像後制软件
里面都有对应的功能(例如After Effects的 Color Key),让使用者可以从拍摄好的绿幕影片中取得後制合成所需要的素材。
大家不知道还对我们之前使用过的ctx.drawImage
这个api有没有印象~
这个api可以让使用者去把指定的img source绘制到canvas上面,而除了image source以外,
他绘制的对象也可以是另外一张canvas(把某张canvas的内容画到现在这张canvas上面), 甚至是可以把影片(video)某一瞬间的静态画面画出来。
延伸阅读 - MDN 上的ctx.drawImage: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
这个案例其实蛮简单的,主要的流程大致上就是
video
tag用来承接video sourcevideo
元素的画面绘制到另外一个canvas
上canvas
上面提取imageDatacanvas
上面(一共需要两张canvas)接下来就是实作的部分~
而在开始实作之前我们必须要先有一个绿幕
的影像拿来当做素材
,这边可以去pixabay.com,这上面有提供很多免费的绿幕
素材。
老样子放个实作的影片:
github repo : https://github.com/mizok/ithelp2021/blob/master/src/js/green-screen-keying/index.js
github page: https://mizok.github.io/ithelp2021/green-screen-keying.html
const videoSource = require('../../video/t-rex.mp4');
import { Canvas2DFxBase } from '../base';
class GreenScreenKeying extends Canvas2DFxBase {
constructor(cvs, gmin = 150, rmax = 100, bmax = 100) {
super(cvs);
this.gmin = gmin;
this.rmax = rmax;
this.bmax = bmax;
this.init();
}
init() {
this.initScreens(videoSource, 500);
}
initScreens(videoSrc, size) {
// 这边我们创建一个video tag用来承接透过require() import 进来的 source
this.video = document.createElement('video');
// 这边我们透过promise 来确保後续的程序都会在video 载入完毕之後执行, 这部分这样写的原因主要是因为要把canvas的大小设置成和影片一样,但是video 的长宽尺寸必须要在载入完毕之後才能正确取得(否则可能会取得0)
let resolve;
const promise = new Promise((res) => { resolve = res });
this.video.addEventListener('loadeddata', () => {
// Video is loaded and can be played
resolve();
}, false);
// body 被按下的时候发动 video的play方法,然後开始canvas的渲染
document.body.addEventListener('click', () => {
this.video.play();
this.animate();
}, false);
promise.then(() => {
// videoWidth/videoHeight分别是video 的原始高/原始宽
const vw = this.video.videoWidth;
const vh = this.video.videoHeight;
// 这边就是开始把canvas和video的大小都设定为一样
this.videoStyleWidth = size;
this.videoStyleHeight = (vh / vw) * size;
this.video.style.width = this.videoStyleWidth + 'px';
this.video.style.height = this.videoStyleHeight + 'px';
this.video.setAttribute('playsinline', true); // 这一行是for ios装置, 避免他被play的时候自动变成全萤幕
// 创建一个架空的canvas, 把他的长宽设定成跟video现在一样
this.virtualCanvas = document.createElement('canvas');
this.virtualCanvas.width = this.videoStyleWidth;
this.virtualCanvas.height = this.videoStyleHeight;
// 取得架空canvas的2Dcontext,并把它设置为本class的一项property
this.virtualCtx = this.virtualCanvas.getContext('2d');
this.setCanvasSize(this.videoStyleWidth, this.videoStyleHeight);
document.body.prepend(this.video);
})
this.video.src = videoSrc;
this.video.load(); // 这一行主要是for移动装置, 因为移动装置的loadeddata必须要用.load来触发
}
animate() {
// 若影片停止或被暂停, 则停止canvas动画的渲染
if (this.video.paused || this.video.ended) return;
const $this = this;
// 把当前video 的样子绘制在架空的canvas上
this.virtualCtx.drawImage(this.video, 0, 0, this.videoStyleWidth, this.videoStyleHeight);
// 取得架空canvas的imageData
const virtualImageData = this.virtualCtx.getImageData(0, 0, this.videoStyleWidth, this.videoStyleWidth);
// 把取得的imageData做绿幕抠像处理
const keyedImageData = this.getKeyedImageData(virtualImageData);
// 回填imageData
this.ctx.putImageData(keyedImageData, 0, 0);
requestAnimationFrame(this.animate.bind($this))
}
getKeyedImageData(imageData) {
const data = imageData.data;
const keyedImageData = this.ctx.createImageData(imageData.width, imageData.height);
for (let i = 0; i < data.length; i = i + 4) {
// 这边的运算其实也很简单,原理就是若侦测到g channel的值超过150 ,且 r和b都低於100(也就是颜色很可能偏绿),那就把该组像素的alpha channel值设置为0, 让他变透明
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
keyedImageData.data[i] = r;
keyedImageData.data[i + 1] = g;
keyedImageData.data[i + 2] = b;
if (g > this.gmin && r < this.rmax && b < this.bmax) {
keyedImageData.data[i + 3] = 0;
}
else {
keyedImageData.data[i + 3] = data[i + 3];
}
}
return keyedImageData;
}
}
(() => {
const cvs = document.querySelector('canvas');
const instance = new GreenScreenKeying(cvs);
})();
这次我们介绍了如何在web
端实现绿幕抠像
,不过实际上这样的做法还算是比较阳春的方法。
因为这种抠像的运算方式(也就是透过设置channel最小值和最大值来阻挡绿色被输出)
其实在某些情况下还是会有瑕疵(例如发丝这种过细的图像,很容易因为出现透光的状况而导致沾到背景的颜色)这种情况可能就会需要更进阶的图形演算法(有兴趣的人可以去查查convolution kernel)。
另外,这次介绍的绿幕抠像其实也可以用在webcam影像,像是有些yt实况主会把自己的webcam画面去背放在实况画面上,有兴趣的人也可以自己尝试看看(不过可能要先学会怎麽自己架绿幕XD)
<<: Day24 - Time complexity (DS篇)
>>: Re: 新手让网页 act 起来: Day24 - React Hooks 之 useMemo
今日文章目录 > - 导览列 > - 练习演示 > - 遇到的问题 > -...
Why Profiler ? Profiler 可以用来测量 React app render 的次...
近年来由於电脑硬体技术的提升使得机器/深度学习(Machine/Deep Learning)技术蓬勃...
Python的程序注解 单行注解 → 以#开始 多行注解 → 以'''和'''括起 资料型别 数值资...
在影像内绘制图案 绘制点、线条 绘制多边形 *在影像内填入文字 小实作-制作Qrcode ...