根据视频生成可以头尾衔接的循环视频

最近手头有一个视频,基本上是一个部分重复循环的,我想把循环的一段提取出来造成动态壁纸,但怎奈何不会用 pr,只能用 ffmpeg 配合 pillow 搞了……

实现的关键在于找到可以首位相接的两帧画面,这就要求两个画面有极高的相似度。判断画面相似度首先需要对图像进行量化,一般有两种方案,一种是提取特征向量,一种是计算哈希。考虑到我手头视频的特征,我选择了比较简单的哈希。

开始之前,需要安装必要的包以及 ffmpeg,执行

pip install ffmpeg-python Pillow numpy imagehash

!Notice 简略起见,以下代码不重要部分折叠,具体实现参照文末链接

首先需要把视频分离成帧,存入数组:

def extract_frames(input_video) -> Generator[np.ndarray, None, None]:
    # 一个生成器,生成每一帧的数据存入 numpy 数组
    ...

图像的哈希算法有多种,比如均值哈希(aHash)、感知哈希(pHash)以及差异哈希(dHash),各有优劣,但选择哪一种对接下来的算法影响不大,我这里以 pHash 为例。以上图像哈希算法在 imagehash 中均有提供,由于本篇主要讨论循环视频生成,哈希算法的具体原理就不研究了(肯定不是因为我不会)。

def generate_hashes(input_video: str) -> Generator[int, None, None]:
    for frame in extract_frames(input_video):
        # 将 numpy 数组转换为 PIL 图像
        pil_image: ImageHash = Image.fromarray(frame)

        # 生成 pHash 值
        phash_value = int(str(phash(pil_image)), 16)

        yield phash_value

图像哈希越相似,图像就越相似。两个哈希值的相似度可以用汉明距离表示,汉明距离表两个二进制数差异的位数,可以通过异或和中 \(1\) 的个数计算。

def hamming_distance(hash1: int, hash2: int) -> int:
    return bin(hash1 ^ hash2).count('1')

接下来遍历每一帧的哈希找到距离最近的两帧即可。遍历过程如果有确定起始或结束帧可以直接遍历,复杂度 \(\mathcal{O}(n)\);如果没有固定起始帧,根据汉明权重(也就是与 \(0\) 的汉明距离)排序后遍历即可,复杂度 \(\mathcal{O}(n\log n)\)

最后完整代码在github gist

posted @ 2024-10-11 12:46  Expector  阅读(9)  评论(0编辑  收藏  举报