随便扣个GB/BB视频。(JS实现,内含代码适度食用)
最近动态里一大堆的GB/BB的视频。觉得挺有意思。JS扣一下康康??
鲁迅同志曾说过,得把引用写上:源文件是这个的 1 和 22 P


声明一下:我知道webgl做效率高很多。但是由于webgl代码我写了你们也不一定能看懂。所以只贴一些原理性的东西。。
好吧先看结果:(直接截图了)


准备必要的东西
为了通用性更强一点,所以先做了视频背景颜色的分析:
渲染用的幕布
let canvas = document.querySelector('canvas')
let ctx = canvas.getContext('2d')
不说啥了。这很好理解,毕竟得展示出来。
离屏幕布
let offscreenCanvas = document.createElement('canvas');
let offCtx = offscreenCanvas.getContext('2d')
分析和色彩计算,如果渲染出来效率会非常低。众所周知,JS的渲染进程和执行进程相互阻塞。所以需要一个离屏的幕布。
分析时候用的一些变量
const colorcount = {} // 记录色彩
let analyseCount = 0;
const LIMIT_ANALYSE = 10 // 最多分析的帧数。
const Y_RANGE = 180; // 允许的色彩偏差范围值
const UV_RANGE = 80;// 允许的色彩偏差范围值
这里需要注意几点
1)colorcount的声明,我用的对象。为啥不是数组?[{color: count}]的模式,熟悉JS的小伙伴应该知道 如果声明成数组。对于之后的排序会友好很多。但是,数组set的时候你需要做二叉查找。而对象,这样写会变成hash查找。对于排序我们就在分析结束的时候做一次。而设置颜色的Count。每一帧每一个像素都要做。所以是对象。
视频当然也得是离屏的
let video = document.createElement('video')
video.src = './gb.mp4'
video.muted = true;
幕布颜色值。做的比较简单。(不要学我瞎写)
let yuvcolor = undefined
2. 视频分析
if(analyseCount < LIMIT_ANALYSE) {
记录色彩(见后面)
analyseCount++
requestAnimationFrame(analyse)
} else {
分析(见后面)
}
记录色彩部分
离屏绘制
offCtx.drawImage(video, 0,0, offscreenCanvas.width, offscreenCanvas.height, 0,0, video.videoWidth, video.videoHeight)
不多说,把像素画到离屏的幕布上
var idata = offCtx.getImageData(0,0, offscreenCanvas.width, offscreenCanvas.height)
取到像素值
if (idata && idata.data) { // 这是一个好习惯!!
for(let i=0;i<idata.data.length;i+=4) {
// 每个像素4位
if(idata.data[i + 3]!= 0) {
// 制造key
let colorindex = idata.data[i] + ',' + idata.data[i+1] + ',' + idata.data[i+2]
// 不存在时候,创建色彩,并计数1
if (!colorcount[colorindex]) colorcount[colorindex] = 1;
// 存在的时候更新数目
else colorcount[colorindex] ++;
}
}
特殊说明:如果是webgl做分析。这里可以搞texture来存储。效率高很多。
分析颜色
let maxcolor = '0,0,0'
let maxcount = 0
for(i in colorcount) { // 非常差劲!!!!for(xxx;xxx;xxx)的写法会更好!
if(colorcount[i] > maxcount) {
maxcolor = i
maxcount = colorcount[i]
}
}
// 分析色彩和转换YUV
let color = [parseInt(maxcolor.split(",")[0]), parseInt(maxcolor.split(",")[1]), parseInt(maxcolor.split(",")[2])]
let y = 0.299 * color[0] + 0.587 * color[1] + 0.114 * color[2]
let u =-0.169 * color[0] - 0.331 * color[1] + 0.500 * color[2]
let v= 0.500 * color[0] - 0.439 * color[1] - 0.081 * color[2]
yuvcolor = [y,u,v]
首先 这段代码写得非常差。原因如下
1. 我图省事写了 for(i in xxx)众所周知。这个效率非常低
2. 其实不用YUV也可以。这样的话,去掉绿幕的时候每一帧都得转。另外,YUV转换可以抽出来成方法的。。所以非常差劲
3. 去掉绿幕:
还是先在离屏幕布画图!
offCtx.drawImage(video, 0,0, video.videoWidth, video.videoHeight, 0,0, video.videoWidth, video.videoHeight)
还是取画面
var idata = offCtx.getImageData(0,0, video.videoWidth, video.videoHeight)
for (let i=0; i<idata.data.length; i+=4) {
// YUV转换
let y = 0.299 * idata.data[i] + 0.587 * idata.data[i+1] + 0.114 *idata.data[i+2]
let u =-0.169 * idata.data[i] - 0.331 * idata.data[i+1] + 0.500 * idata.data[i+2]
let v= 0.500 * idata.data[i] - 0.439 * idata.data[i+1] - 0.081 * idata.data[i+2]
// 很好理解吧。范围内的值直接把透明度设置为0
if(y < yuvcolor[0] + Y_RANGE
&& y > yuvcolor[0] - Y_RANGE
&& u < yuvcolor[1] + UV_RANGE
&& u > yuvcolor[1] - UV_RANGE
&& v < yuvcolor[2] + UV_RANGE
&& v > yuvcolor[2] - UV_RANGE
) {
idata.data[i+3] = 0;
}
}
// 画图
ctx.putImageData(idata, 0,0)
requestAnimationFrame(draw)
4. 处理时机:
analyse 在 canplay的时候就行了。。。
播放的时候处理去绿幕
5. 一些说明
老生长谈:视频压缩会有数据损失。所以不会是那个颜色值。加了个RANGE。请叫我调参工程师。。。
6. 代码烂在哪?
暗切线同学啊,,谁™教你写全局变量的??
就说这么多吧。这篇文章非常水。。。感觉完全是灌水的。。。你们随意喷吧。。。

交流群:711929228
本期问题:yuv420p指的是啥?
答案:像素格式。