会跳舞的俄罗斯方块

213 阅读2分钟

前言

闲着无聊刷抖音的时候,看到作者制作了一个大米跳舞视频,看评论是把视频一张张截图打印出来,然后把图片放到玻璃下面,用大米一张张摆出来,最后合成视频。作为一名程序员,这也太麻烦了吧。于是站在专业的角度分析了一下代码实现的可行性。即

1.获取视频的每一帧

2.去掉每一帧的背景只保留人物

3.把每一帧透明部分填充大米图片,有颜色的部分填充白色

4.修改后的图片合成视频

如果能解决上面四个问题,那么就可以实现大米跳舞视频的制作了。

实现

  1. 第一个问题好说,利用canvas实时绘制视频帧,同时把canvas转成图片并下载。这里介绍一个简单的方法,使用ios快捷指令,如下:

video2img.jpg 2. 第二个问题用backgroundremover配合nodejs脚本实现,把视频帧的背景去掉,只保留人物

"use strict";
const { exit } = require('process');
const exec = require("child_process").exec;
let i = 1;
(function removebg() {
    return new Promise(function (resolve, reject) {
        if(i<2070){
            var cmd = `backgroundremover -i "/Users/9240z/personal/handleVideo/teset/${i}.png" -o "/Users/9240z/personal/handleVideo/removebg/${i}.png"`;
            exec(cmd, function (err, stdout, stderr) {
                if (err) {
                    reject(err);
                } else if (stderr.lenght > 0) {
                    reject(new Error(stderr.toString()));
                } else {
                    return removebg()
                }
            });
            i++
        }else{
            exit()
        }
    });
})();
  1. 第三个问题,先遍历每张图片的像素点,然后更改像素点的值,在合适的区域填充大米图片并下载(这一步需要根据图片的色调灵活处理)
  • 遍历每张图片的像素点,然后更改像素点的值
var ImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (let i = 0; i < ImageData.data.length; i += 4) {
    if (ImageData.data[i] !== 0 || ImageData.data[i + 1] !== 0 || ImageData.data[i + 2] !== 0) {
        ImageData.data[i] = 0;
        ImageData.data[i + 1] = 0;
        ImageData.data[i + 2] = 0;
        ImageData.data[i + 3] = 255;
    } else if (ImageData.data[i] === 0 && ImageData.data[i + 1] === 0 && ImageData.data[i + 2] === 0) {
        ImageData.data[i] = 255;
        ImageData.data[i + 1] = 255;
        ImageData.data[i + 2] = 255;
        ImageData.data[i + 3] = 255;
    }
}
ctx.putImageData(ImageData, 0, 0);
  • 在白色区域填充大米图片
for (let j = 0; j < canvas.height / mHeight; j++) {
    for (let i = 0; i < canvas.width / mWidth; i++) {
        if (ImageData.data[i * mWidth * 4 + j * mHeight * canvas.width * 4] === 255 && ImageData.data[i * mWidth * 4 + j * mHeight * canvas.width * 4 + 1] === 255 && ImageData.data[i * mWidth * 4 + j * mHeight * canvas.width * 4 + 2] === 255) {
            ctx.putImageData(mData[0].data, i * mWidth, j * mHeight, 0, 0, mWidth, mHeight);
        }
    }
}
  • 下载
function downloadFile(content, fileName) { //下载base64图片
    var base64ToBlob = function (code) {
        let parts = code.split(';base64,');
        let contentType = parts[0].split(':')[1];
        let raw = window.atob(parts[1]);
        let rawLength = raw.length;
        let uInt8Array = new Uint8Array(rawLength);
        for (let i = 0; i < rawLength; ++i) {
            uInt8Array[i] = raw.charCodeAt(i);
        }
        return new Blob([uInt8Array], {
            type: contentType
        });
    };
    let aLink = document.createElement('a');
    let blob = base64ToBlob(content); //new Blob([content]);
    let evt = document.createEvent("HTMLEvents");
    evt.initEvent("click", true, true); //initEvent 不加后两个参数在FF下会报错 事件类型,是否冒泡,是否阻止浏览器的默认行为
    aLink.download = fileName;
    aLink.href = URL.createObjectURL(blob);
    aLink.click();
};
  1. 第四个问题也可以借助ios快捷指令,如下:

img2video.png

这样你就得到了大米跳舞视频了,但是这个大米很生硬,于是换成了俄罗斯方块。效果如下

图像.gif