本文來自CSDN博客專欄《使用PyQt開發圖形界面Python應用》
更多優質博文請點擊閱讀原文
一
引言
在moviepy官網的案例《Tracking and blurring someone’s face》和CSDN的moviepy大神ucsheep《MoviePy - 中文文檔4-MoviePy實戰案例-追蹤人臉,打馬賽克》都提供了追蹤人臉並給影片中卓別林臉部打馬賽克的樣例,二者代碼完全相同。
但老猿按照同樣的方法實現卻報ImportError錯:
from moviepy.video.tools.tracking import manual_tracking, to_fxfyImportError: cannot import name 'to_fxfy' from 'moviepy.video.tools.tracking' (C:\Program Files\Python37\lib\site-packages\moviepy\video\tools\tracking.py)
查看tracking .py的源碼,確實沒有 to_fxfy方法。
為了解決該問題,老猿到處查找該問題相關資料,在ucsheep大神的QQ群內進行了諮詢,並在moviepy的github項目中進行了諮詢。ucsheep大神可能太忙,沒有就該問題進行答覆,而moviepy開源項目負責人之一tburrows13是這樣答覆的:
"Indeed the examples for some of these advanced tools are out of date or nonexistent (#850, #167). I'm expecting to update them soon.In the meantime, have a look at #1061 or consider the documentation: https://zulko.github.io/moviepy/ref/videotools.html#module-moviepy.video.tools.tracking which looks like it is more up-to-date."
參考了 1061相關答覆,試了下還是報錯:ValueError: assignment destination is read-only 。
沒辦法,老猿只好閱讀原始碼,反覆進行測試,循序漸進,曲折前進,最後實現了三種不同方式的人臉追蹤打馬賽克,下面就為大家介紹第一種解決思路。
二背景知識2.1、headblur簡介
追蹤人臉打馬賽克需要使用headblur函數。
調用語法:headblur(clip,fx,fy,r_zone,r_blur=None)
說明:
其中參數fx和fy是兩個函數,該函數帶參數t,用於確認t時刻需要模糊化範圍的中心點位置,moviepy將對以中心點為圓心半徑r_zone的圓範圍內的圖像進行模糊化處理,模糊化處理時的卷積核大小由r_blur指定。關於r_blur參數的作用請大家參考《moviepy音視頻剪輯:headblur的參數r_blur卷積核的功能作用及用途》。
2.2、manual_tracking簡介
manual_tracking是moviepy的工具模塊moviepy.video.tools.tracking提供的函數,該模塊還提供了autoTrack函數和findAround,其中findAround是供manual_tracking和autoTrack使用,autoTrack是根據匹配模式自動選擇跟蹤對象,後面再介紹。manual_tracking用於手工選擇要跟蹤的位置。
manual_tracking(clip, t1=None, t2=None, fps=None, nobjects = 1, savefile = None)
說明:
t1、t2:用於指定需要進行跟蹤的剪輯位置範圍,如果t2為None,則只跟蹤t1位置的幀,如果t1、t2都為None則跟蹤整個剪輯
fps:該參數不是剪輯本身的fps,而是跟蹤時剪輯的以秒為單位的時間範圍內需要顯示的幀數,即在跟蹤時,每秒的時間長度範圍內需要抽取fps參數設定的幀數圖像來顯示,如t1和t2定義的剪輯跟蹤長度為30秒,fps設置為2,則跟蹤時moviepy會從剪輯中每秒抽取2幀顯示,跟蹤者需要對60幀圖像標記跟蹤位置
nobjects :每幀中需要跟蹤位置的個數,預設為1,當需要跟蹤多個對象時設置為實際跟蹤對象的個數
savefile :跟蹤數據需要寫入文件時由該參數指定保存文件的名稱,可以帶路徑。
manual_tracking會根據參數 t1、t2以及fps和nobjects 設定,將剪輯 t1到t2位置的子剪輯逐幀顯示出來,讓操作者通過滑鼠點擊跟蹤對象如人臉的中心,manual_tracking記下點擊位置的xy坐標和對應剪輯的位置t,直到該時間段內所有幀都會,返回值為一個Trajectory對象組成的列表,列表元素的個數為nobjects參數的值指定。
三
方法一:實現自己的fx、fy方法
從headblur的參數介紹可以獲知,fx、fy是返回二者參數t所在剪輯位置的跟蹤點坐標,因此老猿按照這個目標來實現了這2個函數,由於跟蹤點的手工跟蹤一般不會設置fps很大,如設置為2,因此實際手工跟蹤的幀的數量遠少於實際數量,因此這個函數需要確保沒跟蹤的幀在fx、fy函數中也能返回對應坐標,老猿的處理很簡單粗暴,對這種幀就返回距其最近的跟蹤幀的坐標。
3.1、實現代碼:
if __name__ == '__main__':def main(): movie_in = sys.argv[1] #參數指定的視頻文件名if len(sys.argv) == 3: #是否指定了只加載視頻的前n秒,n為浮點數 subclip_s = float(sys.argv[2])else: subclip_s = None
3.2、執行效果
clip = VideoFileClip(movie_in)if subclip_s is not None: clip = clip.subclip(0, subclip_s)
tracking = moviepy.video.tools.tracking.manual_tracking(clip, fps=2)[0] #取返回的第一個跟蹤對象,實際上nobjects使用的是默認值1,因此也就一個跟蹤對象 trackingInf = list(zip(tracking.tt, tracking.xx, tracking.yy)) #將跟蹤點相關的時間、橫坐標信息、縱坐標保存到列表trackingInf,即列表中每個元素為元組,該元組為(t,x,y)形式
#定義fx和fy函數:def fx(t):nonlocal trackingInf t1 = t2 = Nonefor tc in trackingInf:if t == tc[0]: #t即為跟蹤的幀return tc[1]elif t > tc[0]: #記錄t前面最近的跟蹤幀 t1 = tcelse:#找到t之後最近的跟蹤幀if t1: #前面有跟蹤幀 d1 = t - t1[0] d2 = tc[0] - t d = min(d1, d2)return t1[1] if d == d1 else tc[1]else: #t應該至少前面有個跟蹤幀、後面也有個跟蹤幀,否則為異常數據return 0
return tc[1] #返回x坐標
def fy(t):nonlocal trackingInf t1 = t2 = Nonefor tc in trackingInf:if t == tc[0]:return tc[2]elif t > tc[0]: t1 = tcelse:if t1: d1 = t - t1[0] d2 = tc[0] - t d = min(d1, d2)if d > 0.5: return 0return t1[2] if d == d1 else tc[2]else:return 0
return tc[2]#返回y坐標
clip_blurred = clip.fx(vfx.headblur, fx, fy, 30) #進行模糊化處理,圓半徑設置為30像素 clip_blurred.write_videofile(movie_in + '_blurred_me.mp4', bitrate="3000k") #輸出文件
main()啟動參數設置為F:\video\zbl1.mp4 54,對應視頻為卓別林《淘金記》,加載視頻的前4秒,跟蹤設置為整個剪輯跟蹤,fps為2,因此需要選擇8個幀的跟蹤數據。最終輸出後的視頻動畫:
總體效果看起來還不錯,就是當頭部動作幅度比較大時有少許幀會遮不住。
四方法二:參考老版本實現的to_fxfy
上面的代碼雖然達到了想要的效果,但一點也不Pythonic,老猿也非常不滿意,但實現這個的同時,老猿想到了一個主意,就是找到以前能夠提供to_fxfy的tracking.py文件對應的moviepy版本,參考老版本的to_fxfy來實現自己的to_fxfy,但這個老版本可不好找。
4.1、查找提供了to_fxfy的moviepy版本按照ucsheep《MoviePy - 中文文檔4-MoviePy實戰案例-追蹤人臉,打馬賽克》發布的時間是2018年9月,但網上也沒有找到moviepy的版本發布時間,沒辦法只好根據自己的猜測一個個版本安裝查看。
首先使用命令:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple moviepy==3來安裝一個沒有的版本,因為moviepy最新發布正式版本為1.03,指定3的版本是肯定沒有的,此時安裝程序會報錯並列出所有的moviepy版本。相關輸出信息如下:
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simpleCollecting moviepy==3 ERROR: Could not find a version that satisfies the requirement moviepy==3 (from versions: 0.2.1.6.3.linux-i686, 0.2.1, 0.2.1.1, 0.2.1.2, 0.2.1.3, 0.2.1.4, 0.2.1.5, 0.2.1.6, 0.2.1.6.1, 0.2.1.6.2, 0.2.1.6.3, 0.2.1.6.4, 0.2.1.6.5, 0.2.1.6.7,0.2.1.6.8, 0.2.1.6.9, 0.2.1.6.91, 0.2.1.6.92, 0.2.1.6.93, 0.2.1.7, 0.2.1.7.2, 0.2.1.7.3, 0.2.1.7.8, 0.2.1.7.9, 0.2.1.7.10, 0.2.1.7.11, 0.2.1.7.12, 0.2.1.7.13,0.2.1.7.14, 0.2.1.7.15, 0.2.1.7.16, 0.2.1.7.17, 0.2.1.7.18, 0.2.1.7.19, 0.2.1.7.20, 0.2.1.7.21, 0.2.1.7.22, 0.2.1.8, 0.2.1.8.1, 0.2.1.8.2, 0.2.1.8.3, 0.2.1.8.4,0.2.1.8.5, 0.2.1.8.6, 0.2.1.8.7, 0.2.1.8.8, 0.2.1.8.9, 0.2.1.8.10, 0.2.1.8.11,0.2.1.8.12, 0.2.1.9, 0.2.1.9.1, 0.2.1.9.2, 0.2.1.9.3, 0.2.1.9.4, 0.2.1.9.5, 0.2.1.9.7, 0.2.2, 0.2.2.1, 0.2.2.2, 0.2.2.3, 0.2.2.4, 0.2.2.5, 0.2.2.6, 0.2.2.7, 0.2.2.8, 0.2.2.9, 0.2.2.10, 0.2.2.11, 0.2.2.12, 0.2.2.13, 0.2.3.1, 0.2.3.2, 0.2.3.3, 0.2.3.4, 0.2.3.5, 1.0.0, 1.0.1, 1.0.2, 1.0.3, 2.0.0.dev1)ERROR: No matching distribution found for moviepy==3
可以看到moviepy的版本很多,老猿根據大致的時間前後安裝了10個版本,最後確認了0.2.1.8.5是最後一個提供to_fxfy的版本。
4.2、仿照舊版的to_fxfy函數實現自己的to_fxfy函數老版本中,to_fxfy的函數實現代碼如下:
from scipy.interpolate import interp1d
def to_fxfy(txy_list, **kwargs):""" Transforms a list [ (ti, (xi,yi)) ] into 2 functions (fx,fy) where fx : t -> x(t) and fy : t -> y(t). If the time t is out of the bounds of the tracking time interval fx and fy return the position of the object at the start or at the end of the tracking time interval. Keywords can be passed to decide the kind of interpolation, see the doc of ``scipy.interpolate.interp1d``."""
tt, xx, yy = zip(*txy_list) interp_x = interp1d(tt, xx, **kwargs) interp_y = interp1d(tt, yy, **kwargs) fx = lambda t: xx[0] if (t <= tt[0]) else ( xx[-1] if t >= tt[-1]else ( interp_x(t) ) ) fy = lambda t: yy[0] if (t <= tt[0]) else ( yy[-1] if t >= tt[-1]else ( interp_y(t) ) )return fx,fy可以看到該函數使用了scipy.interpolate的插值函數interp1d(這讓老猿想起很多年前在大學學習的數值分析的插值算法,畢業後一直沒用過都忘光了),於是老猿仿照該函數結合現在的manual_tracking版本實現了自己的to_fxfy函數,代碼如下:
def to_fxfy(trackingInf, **kwargs): tt, xx, yy = trackingInf interp_x = interp1d(tt, xx, **kwargs) interp_y = interp1d(tt, yy, **kwargs) fx = lambda t: xx[0] if (t <= tt[0]) else (xx[-1] if t >= tt[-1]else (interp_x(t))) fy = lambda t: yy[0] if (t <= tt[0]) else (yy[-1] if t >= tt[-1]else (interp_y(t)))return fx, fy
參數trackingInf為一個三元組,分別對應跟蹤點的時間列表、x坐標列表和y坐標列表。
4.3、結合to_fxfy函數改造方法一的主程序代碼結合該函數將主程序改為如下代碼:
if __name__ == '__main__': movie_in = sys.argv[1]if len(sys.argv) == 3: #參數指定的視頻文件名 subclip_s = float(sys.argv[2]) #是否指定了只加載視頻的前n秒,n為浮點數else: subclip_s = None
clip = VideoFileClip(movie_in)if subclip_s is not None: clip = clip.subclip(0, subclip_s)
tracking = moviepy.video.tools.tracking.manual_tracking(clip,0,3, fps=2)[0] #取返回的第一個跟蹤對象,實際上nobjects使用的是默認值1,因此也就一個跟蹤對象 trackingInf = (tracking.tt, tracking.xx, tracking.yy)#將跟蹤點相關的時間、橫坐標信息、縱坐標保存到列表trackingInf,即列表中每個元素為元組,該元組為(t,x,y)形式
fx,fy = to_fxfy(trackingInf)
clip_blurred = clip.fx(vfx.headblur, fx, fy, 30) #進行模糊化處理,圓半徑設置為30像素
clip_blurred.write_videofile(movie_in + '_blurred_tofxfy.mp4')可以看到代碼就很有Python特色、簡潔多了,具體在處理這個視頻的效果上比方法一要好,在頭部動作幅度大點時馬賽克的圓也能跟上。
五方法三:使用新版本實現
在方法二實現to_fxfy函數時,老猿想到了兩點,第一點是老版本使用to_fxfy新版本丟棄了,但老版本能支持的功能新版本肯定是有更好的辦法,第二點是看到to_fxfy調用插值函數,而新版本的Trajectory對象就有2個插值對象xi和yi,那麼是否在新版本中將fx和fy使用Trajectory對象的xi和yi代替呢?經驗證測試,最終確認moviepy新版本就是這樣設計的,不過當使用numpy的版本比較高時會遇到另外的問題,可以通過降低numpy版本到1.14.5等辦法來解決。具體的老猿將在另外的文章中介紹。
使用新版本方法的程序代碼與方法二隻有兩點變化,一是無需單獨定義to_fxfy,二是將headblur調用的參數fx、fy換成跟蹤對象的xi和yi。
使用新方法的程序代碼:if __name__ == '__main__': movie_in = sys.argv[1]if len(sys.argv) == 3:
六關於自動跟蹤
moviepy除了使用manual_tracking手動追蹤,另外還提供了autoTrack自動追蹤的方式,該方式提供需要匹配的跟蹤對象的圖像數組,自動在剪輯對應幀中去查找跟蹤對象,老猿將在後面的博文中單獨介紹結合手動追蹤一個幀獲得目標對象的圖像數組後自動追蹤該目標對象在其餘幀中的位置。對於一個較長的剪輯來說,自動追蹤可以避免選擇多個幀來手動跟蹤目標對象,但自動追蹤無法精確判斷對應幀中是否有跟蹤對象,如果一個幀無對應跟蹤對象它也會找到一個認為比較匹配的區域,另外有跟蹤對象的幀中也不能完全保障精確匹配,因此精確度比手動跟蹤低很多。
七
小結
上面介紹了老猿結合manual_tracking使用headblur實現跟蹤人臉打馬賽克的三個階段過程,可以看到由於缺乏文檔資料,在實現中走了些彎路,但問題的解決過程也是頗有成就感的一個過程。
另外用第三種方法會遇到numpy數組只讀等問題,這些問題也花了老猿一些時間去解決,將在後面付費專欄的博文中介紹,到時將文章連結更新到本文中來。
更多moviepy的介紹請參考《PyQt+moviepy音視頻剪輯實戰文章目錄》或CSDN博客專欄《使用PyQt開發圖形界面Python應用》。
掃碼一鍵解鎖76篇原創博文
Qt是跨平臺的C++圖形界面開發平臺,開發的應用可以跨平臺使用,PyQt是基於Qt基礎之上進行的Python封裝,既能利用Qt圖形界面開發的便捷性和內部實現的高效性,又能利用Python語言的便捷性和優雅特色。本課程介紹基於Qt Designer的圖形設計來實現Python圖形界面開發。