從視頻提取循環(huán)播放式GIF動(dòng)畫的算法
來(lái)源:易賢網(wǎng) 閱讀:1458 次 日期:2015-04-02 14:07:43
溫馨提示:易賢網(wǎng)小編為您整理了“從視頻提取循環(huán)播放式GIF動(dòng)畫的算法”,方便廣大網(wǎng)友查閱!

循環(huán)播放式的 GIF 動(dòng)圖是網(wǎng)絡(luò)上一種流行的藝術(shù)形式,在Reddit有兩個(gè)專屬的論壇(r/perfectloops 和r/cinemagraphs),還有數(shù)不盡的Tumblr 頁(yè)面。

找到并提取電影中的循環(huán)片段,需要極大的注意力和耐心,電腦前的你,很可能就像下面這樣:

為了使事情變得簡(jiǎn)單點(diǎn),我寫了個(gè)Python腳本來(lái)自動(dòng)完成這個(gè)任務(wù)。這篇博文解釋了算法背后的數(shù)學(xué)并提供了一些使用范例。

何時(shí)視頻片段循環(huán)是無(wú)縫銜接?

如果一段視頻的第一幀和最后一幀很相似,我們就說它循環(huán)起來(lái)是無(wú)縫銜接的。視頻的一幀(F) 可以表示為一個(gè)整數(shù)序列,數(shù)值表明了圖片像素的顏色。例如,(F[1]) , (F[2]) 和(F[3]) 給出了第一個(gè)像素的紅、綠、藍(lán)的數(shù)值。(F[4]) , (F[5]) , (F[6]) 給出了第二個(gè)像素的值,等等。

給定同一視頻的兩幀(F_1) , (F_2) , 我們定義它們的差別為顏色差別的和:

如果(d(F_1,F_2)) 小于某個(gè)任意閾值(T) ,我們就認(rèn)為這兩幀相似。

要理解接下來(lái)的內(nèi)容,很重要的一點(diǎn)是注意到(d(F_1, F_2)) 定義了兩幀的距離,可以看作是平面上兩點(diǎn)幾何距離的一般化。

因此(d(F_1, F_2)) 有很好的數(shù)學(xué)性質(zhì),在下一節(jié)我們利用它來(lái)加快計(jì)算。

找到無(wú)縫銜接循環(huán)的片段

在這一節(jié),我們想要找到給定視頻中所有無(wú)縫銜接循環(huán)、3秒鐘內(nèi)的片段的時(shí)間(起始時(shí)間和終止時(shí)間)。有一種簡(jiǎn)單的做法,比較每一幀和之前3秒的所有幀。當(dāng)我們發(fā)現(xiàn)相似的兩幀時(shí)(即距離小于某個(gè)預(yù)設(shè)的閾值(T) ),就把對(duì)應(yīng)的時(shí)間加到列表中。

問題是這種方法需要大量的比較(對(duì)于一部標(biāo)準(zhǔn)視頻大約一千萬(wàn)),耗時(shí)以小時(shí)計(jì)。因此我們來(lái)看看使計(jì)算更快的一些技巧。

技巧一:使用縮減版的幀。HD高清視頻幀有數(shù)百萬(wàn)的像素,因此計(jì)算它們之間的距離要數(shù)百萬(wàn)次操作。當(dāng)把幀縮小成縮略圖(150像素寬)時(shí),對(duì)于我們的目的它們?nèi)匀蛔銐蚓?xì),距離計(jì)算卻可以快得多(內(nèi)存占用也更少)。

技巧二:利用三角不等式。有這個(gè)非常高效的技巧,在不計(jì)算兩幀距離的情況下,我們就能推斷出它們是否匹配。因?yàn)?d(F_1, F_2)) 定義了兩幀之間的數(shù)學(xué)距離,很多經(jīng)典幾何的結(jié)論都能用得上,特別是下面的關(guān)于三角形邊的長(zhǎng)度的不等式:

第一個(gè)不等式告訴我們?nèi)绻鸄接近于B,且B接近于C,那么A接近于C。對(duì)于視頻幀來(lái)說就是:

在實(shí)際中我們這么來(lái)利用這個(gè)不等式:如果我們已知幀(F1) 和幀(F2) 很相似,(F2) 和另一幀(F3) 很相似,那么我們不用計(jì)算(d(F_1, F_3)) 就知道(F1) 和(F3) 很相似。

第二個(gè)不等式告訴我們?nèi)绻c(diǎn)A接近于B,而B遠(yuǎn)離C,那么A也遠(yuǎn)離C。對(duì)于幀來(lái)說就是:

如果(F1) 和(F2) 很相似,而(F2) 和(F3) 大不相同,那么我們無(wú)需計(jì)算(d(F_1, F_3)) 就知道(F1) 和(F3) 也有很大差別。

下面就開始有點(diǎn)復(fù)雜了:我們應(yīng)用這些三角不等式來(lái)獲得幀距離上界和下界的信息, 在每次計(jì)算兩幀距離時(shí)這些都會(huì)得到更新。比如,計(jì)算(d(F_1, F_2)) 后,(d(F_1, F_3)) 的上下界(分別用和表示)可以如下更新:

如果更新后,,我們可以下結(jié)論:(F_1) 和(F_3) 匹配得很好。如果在某個(gè)時(shí)刻,,我們就知道(F_1) 和(F_3) 不匹配。如果用這個(gè)技術(shù)不能決定(F_1) 和(F_3) 是否匹配,那我們最終需要計(jì)算(d(F_1, F_3)) ,但知道(d(F_1, F_3)) 后反過來(lái)可以讓我們更新另一個(gè)距離((d(F_1, F_4)) )的界,以此類推。

作為說明,假設(shè)一部視頻依次有如下幀:

當(dāng)算法計(jì)算到(F_4) 時(shí),它首先計(jì)算這幀和(F_3) 的距離,發(fā)現(xiàn)它們不匹配。在這個(gè)時(shí)候,算法已經(jīng)發(fā)現(xiàn)(F_3) 和(F_2) 及(F_1) 相似,因此推斷出(F_1) 和(F_2) 都不和(F_4) 匹配(當(dāng)然,之前的十幾幀也是)。在實(shí)際中,這種方法能避免80%到90%的幀距離計(jì)算。

技巧三:用高效的公式計(jì)算距離。當(dāng)我們用上一節(jié)的公式來(lái)計(jì)算兩幀之間的距離時(shí),需要大約3N次操作:N次減法,N次乘法,(N-1)次加法來(lái)得到最終的和。但是(d(F_1,F_2)) 的公式也可以重寫為如下形式,也就是余弦定理:

其中我們用了如下記號(hào):

(d(F_1,F_2)) 的這個(gè)表達(dá)式的有趣之處在于,我們先對(duì)每幀計(jì)算一次范數(shù)(||F||) ,那么對(duì)于每對(duì)(F_1) , (F_2) 只需計(jì)算就可以得到它們之間的距離。而這只需2N次操作,比之前快了50%。

對(duì)每幀計(jì)算(|F|) 的另一個(gè)好處是,對(duì)于兩幀(F_1) 和(F_2) ,我們有:

這提供了技巧二里兩幀之間距離上下界的初始值。

用偽代碼表示的最終算法??偨Y(jié)起來(lái),我們得到了如下算法:

for each frame F1 in the movie:

F1 <- downsized( F1 )

previous_frames <- list of frames in the 3 seconds before F1

list_of_matching_couples = []

compute and store |F1|

for each frame F2 in previous_frames:

compute upper_F1_F2 and lower_F1_F2 using Eq.2

if upper_F1_F2 < T:

mark (F1, F2) as matching

add (F1, F2) to the list_of_matching_couples

if lower_F1_F2 > T:

mark (F1, F2) as non-matching

for each frame F2 in previous_frames:

if couple (F1,F2) isn't already marked matching or non-matching:

compute d(F1, F2)

for each frame F3 after F2 in previous_frames:

update upper_F1_F3 and lower_F1_F3 using Eq.1

if upper_F1_F3 < T:

mark (F1, F3) as matching

add (F1, F3) to the list_of_matching_couples

if lower_F1_F3 > T:

mark (F1, F3) as non-matching

這里是Python的實(shí)現(xiàn)。計(jì)算時(shí)間可能依賴于視頻文件的質(zhì)量,不過我嘗試的大多數(shù)電影能在大約20分鐘處理完。令人印象深刻,是吧?Eugene。

挑選有趣的片段

前一節(jié)描述的算法找到所有的幀對(duì),包括連續(xù)的幀(通??雌饋?lái)非常像)和來(lái)自靜止片段的幀(典型的是黑屏)。因此我們會(huì)得到幾十萬(wàn)個(gè)視頻片段,而只有一些是真正有趣的。在提取GIF之前我們必須找到一種方式來(lái)過濾掉不想要的片段。過濾操作只需幾秒鐘,但它的成功極大地依賴于你用的過濾標(biāo)準(zhǔn)。這里是一些有效的例子:

第一幀和最后一幀必須至少相隔0.5秒。

序列中必須至少有一幀和第一幀不匹配。這個(gè)標(biāo)準(zhǔn)能夠排除靜止片段。

第一幀的起始時(shí)間必須在上一個(gè)提取的片段的起始時(shí)間的0.5秒之后。這是為了避免重復(fù)的片段(即那些起始和終止時(shí)間幾乎一樣的片段)。

我盡量不做太多限制(避免偶然過濾掉好的片段),因此通常得到大約200個(gè)GIF,其中很多只是中等有趣(眨眼之類)。最后一步是人工過濾,就像下面這樣:

使用范例

I我實(shí)現(xiàn)了這個(gè)算法,作為我的Python視頻庫(kù)MoviePy的插件。這里是一個(gè)包含很多細(xì)節(jié)的例子腳本:

from moviepy.editor import VideoFileClip

from moviepy.video.tools.cuts import FramesMatches

# Open a video file (any format should work)

clip = VideoFileClip("myvideo.avi")

# Downsize the clip to a width of 150px to speed up things

clip_small = clip.resize(width=150)

# Find all the pairs of matching frames an return their

# corresponding start and end times. Takes 15-60 minutes.

matches = FramesMatches.from_clip(clip_small, 5, 3)

# (Optional) Save the matches for later use.

# matches.save("myvideo_matches.txt")

# matches = FramesMatches.load("myvideo_matches.txt")

# Filter the scenes: keep only segments with duration >1.5 seconds,

# where the first and last frame have a per-pixel distance < 1,

# with at least one frame at a distance 2 of the first frame,

# and with >0.5 seconds between the starts of the selected segments.

selected_scenes = matches.select_scenes(match_thr=1,

min_time_span=1.5, nomatch_thr=2, time_distance=0.5)

# The final GIFs will be 450 pixels wide

clip_medium = clip.resize(width=450)

# Extract all the selected scenes as GIFs in folder "myfolder"

selected_scenes.write_gifs(clip_medium, "myfolder")

這里是我用迪士尼的《白雪公主》來(lái)試驗(yàn)時(shí)得到的:

import moviepy.editor as mp

from moviepy.video.tools.cuts import FramesMatches

clip = mp.VideoFileClip("snowwhite.mp4")

scenes = FramesMatches.from_clip(clip.resize(width=120), 5, 2)

selected_scenes = scenes.select_scenes(2, 0.5, 4, 0.5)

selected_scenes.write_gifs(clip.resize(width=270), "snow_white")

(更多圖片,請(qǐng)查看)

這里有些GIF可以切割得更好,有些也不太有趣(太短),還有些循環(huán)片段被錯(cuò)過了。我認(rèn)為罪魁禍?zhǔn)资亲詈笠徊竭^濾的參數(shù),這些本可以被調(diào)整得更好。

另一個(gè)例子:最近有人在r/perfectloops發(fā)了個(gè)Youtube視頻,要求把它轉(zhuǎn)成循環(huán)播放式的 GIF。下面的腳本就做了這件事:從Youtube下載視頻,找到切割成循環(huán)播放序列的最好的時(shí)間(t1, t2),然后生成GIF:

import moviepy.editor as mpy

from moviepy.video.tools.cuts import FramesMatches

# Get the video from youtube, save it as "hamac.mp4"

mpy.download_webfile("NpxD9TZIlv8", "hamac.mp4")

clip = mpy.VideoFileClip("hamac.mp4").resize(width=200)

matches = FramesMatches.from_clip(clip, 40, 3) # loose matching

# find the best matching pair of frames > 1.5s away

best = matches.filter(lambda x: x.time_span >1.5).best()

# Write the sequence to a GIF (with speed=30% of the original)

final = clip.subclip(best.t1, best.t2).speedx(0.3)

final.write_gif("hamac.gif", fps=10)

利用MoviePy,你也可以對(duì)GIF做后期處理,加上文字:

既然你讀到了這,這有個(gè)為你準(zhǔn)備的更高級(jí)的技巧:

輪到你了!

我在這里呈現(xiàn)的算法并不完美。對(duì)于低亮度的影片片段,它表現(xiàn)得很糟,有時(shí)輕微的攝像機(jī)移動(dòng)或者背景中的運(yùn)動(dòng)物體都能阻止片段循環(huán)播放。雖然這些片段能被人類輕易地修正,卻很難被算法認(rèn)出、處理。

因此我的腳本并未完全解決問題,制作循環(huán)播放式的 GIF 仍然是門藝術(shù)。如果你對(duì)這個(gè)算法有任何點(diǎn)子或評(píng)論,或者你嘗試后發(fā)現(xiàn)了電影中的有趣的循環(huán)播放,我很樂意聽到!在那之前,快樂地制作GIF吧!

更多信息請(qǐng)查看IT技術(shù)專欄

更多信息請(qǐng)查看技術(shù)文章
易賢網(wǎng)手機(jī)網(wǎng)站地址:從視頻提取循環(huán)播放式GIF動(dòng)畫的算法
由于各方面情況的不斷調(diào)整與變化,易賢網(wǎng)提供的所有考試信息和咨詢回復(fù)僅供參考,敬請(qǐng)考生以權(quán)威部門公布的正式信息和咨詢?yōu)闇?zhǔn)!

2025國(guó)考·省考課程試聽報(bào)名

  • 報(bào)班類型
  • 姓名
  • 手機(jī)號(hào)
  • 驗(yàn)證碼
關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡(jiǎn)要咨詢 | 簡(jiǎn)要咨詢須知 | 加入群交流 | 手機(jī)站點(diǎn) | 投訴建議
工業(yè)和信息化部備案號(hào):滇ICP備2023014141號(hào)-1 云南省教育廳備案號(hào):云教ICP備0901021 滇公網(wǎng)安備53010202001879號(hào) 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號(hào)
云南網(wǎng)警備案專用圖標(biāo)
聯(lián)系電話:0871-65099533/13759567129 獲取招聘考試信息及咨詢關(guān)注公眾號(hào):hfpxwx
咨詢QQ:526150442(9:00—18:00)版權(quán)所有:易賢網(wǎng)
云南網(wǎng)警報(bào)警專用圖標(biāo)