基于Python+OpenCV制作屏幕錄制工具
最近有在使用屏幕錄制軟件錄制桌面,在用的過程中突發(fā)奇想,使用python能不能做屏幕錄制工具,也鍛煉下自己的動手能力。接下準(zhǔn)備寫使用python如何做屏幕錄制工具的系列文章:
大概上述四個部分,希望自己能夠盡快完善,接下來開始使用python制作屏幕錄制部分。
應(yīng)用平臺
- windows 10
- python 3.7
屏幕錄制部分
屏幕錄制可以簡單地理解為將屏幕快照以動圖的形式播放,這里我選用PIL下的ImageGrab來截取屏幕畫面,首先
pip install Pillow
之后需要將截取到的快照數(shù)組合成為視頻,使用cv2模塊
pip install opencv-python
ImageGrab類不能直接存儲為視頻,使用numpy模塊進(jìn)行數(shù)組化,再通過cv2.COLOR_BGR2RGB轉(zhuǎn)換為cv2色彩通道。
pip install numpy
屏幕錄制主要代碼:
import?numpy?as?np from?PIL?import?ImageGrab import?cv2 im?=?ImageGrab.grab() width,?high?=?im.size??#?獲取屏幕的寬和高 fourcc?=?cv2.VideoWriter_fourcc(*'I420')??#?設(shè)置視頻編碼格式 fps?=?15??#?設(shè)置幀率 video?=?cv2.VideoWriter('test.avi',?fourcc,?fps,?(width,?high)) while?True:??#?開始錄制 ????im?=?ImageGrab.grab() ????im_cv?=?cv2.cvtColor(np.array(im),?cv2.COLOR_BGR2RGB) ????#?圖像寫入 ????video.write(im_cv) ????if?xx:??#?當(dāng)某某條件滿足中斷循環(huán) ????????break video.release()??#?釋放緩存,持久化視頻
測試運(yùn)行可以保存屏幕快照為視頻,但操作起來不優(yōu)雅,也不利于后續(xù)的操作。
封裝成類,繼承線程父類,方便使用鍵盤來控制視頻錄制的結(jié)束。
from?threading?import?Thread class?ScreenshotVideo(Thread): ????def?__init__(self): ?????"""初始化參數(shù)""" ????????super().__init__()
詳細(xì)代碼將在文末給出。
計算視頻最優(yōu)fps及使用numpy計算中間幀數(shù)組
實際操作中視頻錄制在不同電腦中會出現(xiàn)不一樣的幀率,導(dǎo)致視頻播放或快或慢,需要根據(jù)不同的電腦計算出相應(yīng)的最優(yōu)fps值。
def?video_best_fps(self,?path): ????"""獲取電腦錄制視頻的最優(yōu)幀率""" ????video?=?cv2.VideoCapture(path)??#?讀取視頻 ????fps?=?video.get(cv2.CAP_PROP_FPS)??#?獲取當(dāng)前視頻的幀率 ????count?=?video.get(cv2.CAP_PROP_FRAME_COUNT)??#?獲取視頻幀數(shù),即該視頻有多少幅畫面 ????self.best_fps?=?int(fps?*?((int(count)?/?fps)?/?self.spend_time))???#?計算播放時間與錄制時間對比得到最優(yōu)幀率 ????video.release()
再調(diào)整幀率參數(shù)進(jìn)行錄制視頻就減弱了視頻播放太快或者太慢。也可以給視頻增加幀數(shù)從而延長播放時間,這里我采用一種很簡單的方法增加視頻幀,僅供參考。
from?numba?import?jit #?使用numpy計算相鄰兩幀圖像且更接近于后一幀的圖像 #?調(diào)用jit方法加速數(shù)組計算 @jit(nopython=True) def?average_n(x,?y): ????"""Numpy計算趨近值""" ????return?((x?+?y?+?y)?//?3).astype(x.dtype)
該方法僅針對于設(shè)置的fps比最優(yōu)fps要高時,處理后的視頻觀感,視頻還是較為急促,但是細(xì)節(jié)幀增多,所以播放時長會比未處理前的要長,略有殘影。
使用pynput監(jiān)聽鍵盤按鍵
在視頻錄制中,并不知道視頻何時結(jié)束,所以用while循環(huán)包裹錄制代碼,但也不可能讓代碼無休止的運(yùn)行下去,在此使用監(jiān)聽鍵盤模塊來中斷錄制代碼的運(yùn)行。
from?pynput?import?keyboard??#?pip?install?pynput def?hotkey(self): ????"""熱鍵監(jiān)聽""" ????with?keyboard.Listener(on_press=self.on_press)?as?listener: ????????listener.join() def?on_press(self,?key): ????try: ????????if?key.char?==?'t':??#?錄屏結(jié)束,保存視頻 ????????????self.flag?=?True ????????elif?key.char?==?'k':??#?錄屏中止,刪除文件 ????????????self.flag?=?True ????????????self.kill?=?True ????except?Exception?as?e: ????????print(e)
按下鍵盤“T”鍵時,結(jié)束錄制,保存視頻。“K”鍵則是停止錄制,刪除緩存文件。
如何保存MP4格式視頻
視頻編碼格式應(yīng)該為('a', 'v', 'c', '1'),文件后綴為'.mp4',在錄制前先去https://github.com/cisco/openh264/releases下下載對應(yīng)平臺的dll.bz2文件,將壓縮包解壓放在項目文件夾下。再運(yùn)行代碼,成功會出現(xiàn)一行編碼說明:
OpenH264 Video Codec provided by Cisco Systems, Inc.
源碼
本文實現(xiàn)的源碼如下:
import?time from?PIL?import?ImageGrab import?cv2 from?pathlib?import?Path import?numpy?as?np from?numba?import?jit from?pynput?import?keyboard from?threading?import?Thread @jit(nopython=True) def?average_n(x,?y): ????"""Numpy計算趨近值""" ????return?((x?+?y?+?y)?//?3).astype(x.dtype) class?ScreenshotVideo(Thread): ????def?__init__(self,?width,?high,?path='',?fps=15): ????????"""初始化參數(shù)""" ????????super().__init__() ????????self.save_file?=?path ????????self.best_fps?=?fps ????????self.fps?=?fps ????????self.width?=?width ????????self.high?=?high ????????self.spend_time?=?1 ????????self.flag?=?False ????????self.kill?=?False ????????self.video?=?None ????def?__call__(self,?path): ????????"""重載視頻路徑,便于類的二次調(diào)用""" ????????self.save_file?=?Path(path) ????????self.video?=?self.init_videowriter(self.save_file) ????@staticmethod ????def?screenshot(): ????????"""靜態(tài)方法,屏幕截圖,并轉(zhuǎn)換為np.array數(shù)組""" ????????return?np.array(ImageGrab.grab()) ????@staticmethod ????def?get_fourcc(name): ????????"""視頻編碼字典""" ????????fourcc_maps?=?{'.avi':?'I420', ???????????????????????'.m4v':?'mp4v', ???????????????????????'.mp4':?'avc1', ???????????????????????'.ogv':?'THEO', ???????????????????????'.flv':?'FLV1', ???????????????????????} ????????return?fourcc_maps.get(name) ????def?init_videowriter(self,?path): ????????"""獲取視頻編碼并新建視頻文件""" ????????if?not?path: ????????????raise?Exception('視頻路徑未設(shè)置,請設(shè)置\nvideo?=?ScreenshotVideo(fps,width,high)\nvideo?=?video(video_path)') ????????path?=?Path(path)?if?isinstance(path,?str)?else?path ????????fourcc?=?cv2.VideoWriter_fourcc(*self.get_fourcc(path.suffix)) ????????return?cv2.VideoWriter(path.as_posix(),?fourcc,?self.fps,?(self.width,?self.high)) ????def?video_record_doing(self,?img): ????????"""將BGR數(shù)組轉(zhuǎn)換為RGB數(shù)組""" ????????im_cv?=?cv2.cvtColor(img,?cv2.COLOR_BGR2RGB) ????????self.video.write(im_cv) ????def?video_record_end(self): ????????"""錄制結(jié)束,根據(jù)條件判斷文件是否保存""" ????????self.video.release() ????????cv2.destroyAllWindows() ????????if?self.save_file?and?self.kill: ????????????Path(self.save_file).unlink() ????def?video_best_fps(self,?path): ????????"""獲取電腦錄制視頻的最優(yōu)幀率""" ????????video?=?cv2.VideoCapture(path) ????????fps?=?video.get(cv2.CAP_PROP_FPS) ????????count?=?video.get(cv2.CAP_PROP_FRAME_COUNT) ????????self.best_fps?=?int(fps?*?((int(count)?/?fps)?/?self.spend_time)) ????????video.release() ????def?pre_video_record(self): ????????"""預(yù)錄制,以獲取最佳fps值""" ????????self.video?=?self.init_videowriter('test.mp4') ????????start_time?=?time.time() ????????for?_?in?range(10): ????????????im?=?self.screenshot() ????????????self.video_record_doing(im) ????????self.spend_time?=?round(time.time()?-?start_time,?4) ????????self.video_record_end() ????????time.sleep(2) ????????self.video_best_fps('test.mp4') ????????Path('test.mp4').unlink() ????def?insert_frame_array(self,?frame_list): ????????"""Numpy增強(qiáng)截圖信息""" ????????fps_n?=?round(self.fps?/?self.best_fps) ????????if?fps_n?<=?0: ????????????return?frame_list ????????times?=?int(np.log2(fps_n))??#?倍率 ????????for?_?in?range(times): ????????????frame_list2?=?map(average_n,?[frame_list[0]]?+?frame_list[:-1],?frame_list) ????????????frame_list?=?[[x,?y]?for?x,?y?in?zip(frame_list2,?frame_list)] ????????????frame_list?=?[j?for?i?in?frame_list?for?j?in?i] ????????return?frame_list ????def?frame2video_run(self): ????????"""使用opencv將連續(xù)型截圖轉(zhuǎn)換為視頻""" ????????self.video?=?self.init_videowriter(self.save_file) ????????start_time?=?time.time() ????????frame_list?=?[] ????????while?True: ????????????frame_list.append(self.screenshot()) ????????????if?self.flag: ????????????????break ????????self.spend_time?=?round(time.time()?-?start_time,?4) ????????if?not?self.kill:??#?視頻錄制不被終止將逐幀處理圖像 ????????????frame_list?=?self.insert_frame_array(frame_list) ????????????for?im?in?frame_list: ????????????????self.video_record_doing(im) ????????self.video_record_end() ????def?hotkey(self): ????????"""熱鍵監(jiān)聽""" ????????with?keyboard.Listener(on_press=self.on_press)?as?listener: ????????????listener.join() ????def?on_press(self,?key): ????????try: ????????????if?key.char?==?'t':??#?錄屏結(jié)束,保存視頻 ????????????????self.flag?=?True ????????????elif?key.char?==?'k':??#?錄屏中止,刪除文件 ????????????????self.flag?=?True ????????????????self.kill?=?True ????????except?Exception?as?e: ????????????print(e) ????def?run(self): ????????#?運(yùn)行函數(shù) ????????#?設(shè)置守護(hù)線程 ????????Thread(target=self.hotkey,?daemon=True).start() ????????#?運(yùn)行截圖函數(shù) ????????self.frame2video_run() screen?=?ImageGrab.grab() width,?high?=?screen.size video?=?ScreenshotVideo(width,?high,?fps=60) video.pre_video_record()??#?預(yù)錄制獲取最優(yōu)fps video('test1.mp4') video.run()
總結(jié)
本文目前使用了opencv和相關(guān)模塊對屏幕進(jìn)行錄制并轉(zhuǎn)換為視頻保存,學(xué)習(xí)將多個函數(shù)封裝為類,方便后續(xù)功能開發(fā)。學(xué)習(xí)的道路是無止境的,大膽的邁步走吧!
以上就是基于Python+OpenCV制作屏幕錄制工具的詳細(xì)內(nèi)容,更多關(guān)于Python OpenCV屏幕錄制的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解決Pycharm 包已經(jīng)下載,但是運(yùn)行代碼提示找不到模塊的問題
今天小編就為大家分享一篇解決Pycharm 包已經(jīng)下載,但是運(yùn)行代碼提示找不到模塊的問題。具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-085分鐘教會你用Docker部署一個Python應(yīng)用
Docker是一個開源項目,為開發(fā)人員和系統(tǒng)管理員提供了一個開放平臺,可以將應(yīng)用程序構(gòu)建、打包為一個輕量級容器,并在任何地方運(yùn)行,下面這篇文章主要給大家介紹了關(guān)于如何通過5分鐘教會你用Docker部署一個Python應(yīng)用,需要的朋友可以參考下2022-06-06python協(xié)程之動態(tài)添加任務(wù)的方法
今天小編就為大家分享一篇python協(xié)程之動態(tài)添加任務(wù)的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-02-02Python中使用logging和traceback模塊記錄日志和跟蹤異常
今天小編就為大家分享一篇關(guān)于Python中使用logging和traceback模塊記錄日志和跟蹤異常,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-04-04python使用原始套接字發(fā)送二層包(鏈路層幀)的方法
今天小編就為大家分享一篇python使用原始套接字發(fā)送二層包(鏈路層幀)的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-07-07