如何使用?Python為你的在線會(huì)議創(chuàng)建一個(gè)假的攝像頭
想象一下。你正在參加在線會(huì)議,出于某種原因,你并不想打開(kāi)攝像頭。但是如果你看到其他人都打開(kāi)了,你覺(jué)得你也得打開(kāi),所以迅速整理自己的頭發(fā),確保衣著整潔,然后不情愿地打開(kāi)相機(jī)。我們都經(jīng)歷過(guò)這種情況。
有一個(gè)好消息。在 Python 的幫助下,不再?gòu)?qiáng)制開(kāi)啟攝像頭。將向你展示如何為你的在線會(huì)議創(chuàng)建一個(gè)假的攝像頭,如下所示:
當(dāng)然,這張臉不一定是比爾蓋茨的,它也可以是你自己。
現(xiàn)在將向你展示如何在 Python 中編寫(xiě)代碼。在文章的最后,將解釋如何為自己使用這個(gè)假的攝像頭。
創(chuàng)建一個(gè)簡(jiǎn)單的假網(wǎng)絡(luò)攝像頭
首先,我們將導(dǎo)入一些模塊,尤其是 openCV。
import cv2 import numpy as np import pickle import pyaudio import struct import math import argparse import os
接下來(lái)我們將創(chuàng)建一個(gè)函數(shù)來(lái)從視頻中提取所有幀:
def read_frames(file, video_folder): frames = [] cap = cv2.VideoCapture(os.path.join('videos', video_folder, file)) frame_rate = cap.get(cv2.CAP_PROP_FPS) if not cap.isOpened(): print("Error opening video file") while cap.isOpened(): ret, frame = cap.read() if ret: frames.append(frame) else: break cap.release() return frames, frame_rate
現(xiàn)在我們有了框架,我們可以創(chuàng)建一個(gè)循環(huán),一個(gè)接一個(gè)地顯示它們。當(dāng)?shù)竭_(dá)最后一幀時(shí),我們向后播放視頻,然后當(dāng)我們到達(dá)第一幀時(shí),我們將向前播放,我們將永遠(yuǎn)重復(fù)這個(gè)過(guò)程。這樣就不會(huì)出現(xiàn)從最后一幀到第一幀的突然過(guò)渡。我們也會(huì)這樣做,以便我們可以按“q”停止網(wǎng)絡(luò)攝像頭。
frames, frame_rate = read_frames('normal.mov', 'bill_gates') def next_frame_index(i, reverse): if i == len(frames) - 1: reverse = True if i == 0: reverse = False if not reverse: i += 1 else: i -= 1 return i, reverse rev = False i = 0 while True: frame = frames[i] cv2.imshow('Webcam', frame) pressed_key = cv2.waitKey(int(1000/frame_rate)) & 0xFF if pressed_key == ord("q"): break i, rev = next_frame_index(i, mode, rev)
有了這個(gè),我們就有了一個(gè)可以無(wú)縫播放的簡(jiǎn)單網(wǎng)絡(luò)攝像頭。
但我們并不止步于此。
添加不同的模式
如果我們的假網(wǎng)絡(luò)攝像頭頭像可以做的不僅僅是被動(dòng)地凝視,那將更有說(shuō)服力。例如,有時(shí)在開(kāi)會(huì)時(shí),你需要點(diǎn)頭表示同意、微笑、交談或做其他事情。
所以我們希望我們的網(wǎng)絡(luò)攝像頭有多種“模式”,我們可以隨時(shí)通過(guò)按下鍵盤(pán)上的一個(gè)鍵來(lái)切換。
為此,你需要為每種模式錄制一個(gè)簡(jiǎn)短的錄音,例如你只是微笑的錄音。然后我們可以從每個(gè)視頻中讀取幀,并將它們存儲(chǔ)在字典中。當(dāng)我們檢測(cè)到按鍵(例如,“s”切換到“微笑模式”)時(shí),我們將活動(dòng)模式更改為新模式并開(kāi)始播放相應(yīng)視頻中的幀。
video_files = [file for file in os.listdir(os.path.join('videos', folder)) if file not in ['transitions_dict.p', '.DS_Store']] frames, frame_rates = {}, {} for file in video_files: mode_name = file.split('.')[0] frames[mode_name], frame_rates[mode_name] = read_frames(file, folder) modes = list(frames.keys()) commands = {mode[0]: mode for mode in modes if mode != 'normal'} mode = "normal" frame_rate = frame_rates[mode] rev = False i = 0 while True: frame = frames[mode][i] cv2.imshow('Webcam', frame) pressed_key = cv2.waitKey(int(1000/frame_rate)) & 0xFF if pressed_key == ord("q"): break for command, new_mode in commands.items(): if pressed_key == ord(command): i, mode, frame_rate = change_mode(mode, new_mode, i) i, rev = next_frame_index(i, mode, rev)
默認(rèn)情況下,這樣做是為了切換到指定模式,鍵盤(pán)命令是模式名稱(chēng)的第一個(gè)字母。現(xiàn)在我把這個(gè)'change_mode'函數(shù)作為一個(gè)黑盒子,稍后會(huì)解釋它。
優(yōu)化過(guò)渡
所以我們想從一個(gè)視頻切換到另一個(gè),比如說(shuō)從正常模式到點(diǎn)頭模式。如何以最佳方式從一個(gè)模式過(guò)渡到另一個(gè)模式(即過(guò)渡盡可能平滑)?
當(dāng)我們進(jìn)行過(guò)渡時(shí),我們希望轉(zhuǎn)到與我們當(dāng)前所處的最相似的新模式的框架。
為此,我們可以首先定義圖像之間的距離度量。這里使用一個(gè)簡(jiǎn)單的歐幾里得距離,它查看兩個(gè)圖像的每個(gè)像素之間的差異。
有了這個(gè)距離,我們現(xiàn)在可以找到最接近我們當(dāng)前的圖像,并切換到這個(gè)。例如,如果我們想從普通模式過(guò)渡到點(diǎn)頭模式,并且我們?cè)谄胀ㄒ曨l的第 132 幀,我們將知道我們必須轉(zhuǎn)到點(diǎn)頭視頻的第 86 幀才能獲得最平滑的過(guò)渡。
我們可以為每一幀以及從每種模式到所有其他模式預(yù)先計(jì)算所有這些最佳轉(zhuǎn)換。這樣我們就不必在每次想要切換模式時(shí)都重新計(jì)算。還壓縮了圖像,以便計(jì)算執(zhí)行時(shí)間更短。我們還將存儲(chǔ)圖像之間的最佳距離。
video_files = [file for file in os.listdir(os.path.join('videos', video_folder)) if file not in ['transitions_dict.p', '.DS_Store']] frames = {} for file in video_files: mode_name = file.split('.')[0] frames[mode_name] = read_frames(file, video_folder) modes = list(frames.keys()) compression_ratio = 10 height, width = frames["normal"][0].shape[:2] new_height, new_width = height // compression_ratio, width // compression_ratio, def compress_img(img): return cv2.resize(img.mean(axis=2), (new_width, new_height)) frames_compressed = {mode: np.array([compress_img(img) for img in frames[mode]]) for mode in modes} transitions_dict = {mode:{} for mode in modes} for i in range(len(modes)): for j in tqdm(range(i+1, len(modes))): mode_1, mode_2 = modes[i], modes[j] diff = np.expand_dims(frames_compressed[mode_1], axis=0) - np.expand_dims(frames_compressed[mode_2], axis=1) dists = np.linalg.norm(diff, axis=(2, 3)) transitions_dict[mode_1][mode_2] = (dists.argmin(axis=0), dists.min(axis=0)) transitions_dict[mode_2][mode_1] = (dists.argmin(axis=1), dists.min(axis=1)) pickle.dump(transitions_dict, open(os.path.join('videos', video_folder, 'transitions_dict.p'), 'wb'))
現(xiàn)在可以展示“change_mode”函數(shù),該函數(shù)從預(yù)先計(jì)算的字典中檢索要轉(zhuǎn)換到的最佳幀。這樣做是為了如果你按下例如“s”切換到微笑模式,再次按下它將切換回正常模式。
def change_mode(current_mode, toggled_mode, i): if current_mode == toggled_mode: toggled_mode = 'normal' new_i = transitions_dict[current_mode][toggled_mode][0][i] dist = transitions_dict[current_mode][toggled_mode][1][i] return new_i, toggled_mode, frame_rates[toggled_mode]
我們還可以添加另一項(xiàng)改進(jìn)使我們的過(guò)渡更加無(wú)縫,不是總是立即切換模式,而是等待一段時(shí)間以獲得更好的過(guò)渡。例如,如果我們的頭像在點(diǎn)頭,我們可以等到頭部通過(guò)中間位置才轉(zhuǎn)換到正常模式。為此,我們將引入一個(gè)時(shí)間窗口(這里我將其設(shè)置為 0.5 秒),這樣我們將在切換模式之前等待在此窗口內(nèi)轉(zhuǎn)換的最佳時(shí)間。
switch_mode_max_delay_in_s = 0.5 def change_mode(current_mode, toggled_mode, i): if current_mode == toggled_mode: toggled_mode = 'normal' # Wait for the optimal frame to transition within acceptable window max_frames_delay = int(frame_rate * switch_mode_max_delay_in_s) global rev if rev: frames_to_wait = max_frames_delay-1 - transitions_dict[current_mode][toggled_mode][1][max(0, i+1 - max_frames_delay):i+1].argmin() else: frames_to_wait = transitions_dict[current_mode][toggled_mode][1][i:i + max_frames_delay].argmin() print(f'Wait {frames_to_wait} frames before transitioning') for _ in range(frames_to_wait): i, rev = next_frame_index(i, current_mode, rev) frame = frames[mode][i] cv2.imshow('Frame', frame) cv2.waitKey(int(1000 / frame_rate)) new_i = transitions_dict[current_mode][toggled_mode][0][i] dist = transitions_dict[current_mode][toggled_mode][1][i] return new_i, toggled_mode, frame_rates[toggled_mode]
現(xiàn)在我們的過(guò)渡更加順暢。但是,它們有時(shí)可能很明顯。所以另一個(gè)想法是有目的地為視頻添加凍結(jié),就像那些在不穩(wěn)定連接時(shí)可能發(fā)生的凍結(jié)一樣(就是如果網(wǎng)絡(luò)不穩(wěn)定視頻就卡住了),并使用它們來(lái)掩蓋過(guò)渡(我們將使凍結(jié)持續(xù)時(shí)間與兩個(gè)圖像之間的距離成比例)。我們還將添加隨機(jī)凍結(jié),這樣模式就不會(huì)變得明顯。所以我們添加了這些新的代碼:
# In the change_mode function: dist = transitions_dict[current_mode][toggled_mode][1][i] if freezes: freeze_duration = int(transition_freeze_duration_constant * dist) cv2.waitKey(freeze_duration) # In the main loop: # Random freezes if freezes: if np.random.randint(frame_rate * 10) == 1: nb_frames_freeze = int(np.random.uniform(0.2, 1.5) * frame_rate) for _ in range(nb_frames_freeze): cv2.waitKey(int(1000 / frame_rate)) i, rev = next_frame_index(i, mode, rev)
使用或不使用這些凍結(jié)保留為選項(xiàng)。
好的,現(xiàn)在我們已經(jīng)真正涵蓋了這些過(guò)渡的基礎(chǔ)。我們還能為網(wǎng)絡(luò)攝像頭添加什么?
語(yǔ)音檢測(cè)
另一件有趣的事情是添加語(yǔ)音檢測(cè),這樣當(dāng)我們說(shuō)話時(shí),視頻里的“我”就會(huì)說(shuō)話。
這是使用 pyaudio 完成的。感謝這個(gè) stackoverflow 線程(https://stackoverflow.com/questions/4160175/detect-tap-with-pyaudio-from-live-mic)。
基本上,這個(gè)想法是查看一段時(shí)間內(nèi)來(lái)自麥克風(fēng)的聲音的平均幅度,如果它足夠高,可以認(rèn)為我們一直在說(shuō)話。最初這段代碼是為了檢測(cè)敲擊噪音,但它也可以很好地檢測(cè)語(yǔ)音。
AMPLITUDE_THRESHOLD = 0.010 FORMAT = pyaudio.paInt16 SHORT_NORMALIZE = (1.0/32768.0) CHANNELS = 1 RATE = 44100 INPUT_BLOCK_TIME = 0.025 INPUT_FRAMES_PER_BLOCK = int(RATE*INPUT_BLOCK_TIME) def get_rms(block): count = len(block)/2 format = "%dh" % count shorts = struct.unpack(format, block) sum_squares = 0.0 for sample in shorts: n = sample * SHORT_NORMALIZE sum_squares += n*n return math.sqrt( sum_squares / count ) pa = pyaudio.PyAudio() stream = pa.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=INPUT_FRAMES_PER_BLOCK) def detect_voice(): error_count = 0 voice_detected = False try: block = stream.read(INPUT_FRAMES_PER_BLOCK, exception_on_overflow=False) except (IOError, e): error_count += 1 print("(%d) Error recording: %s" % (error_count, e)) amplitude = get_rms(block) if amplitude > AMPLITUDE_THRESHOLD: voice_detected = True return voice_detected
現(xiàn)在我們可以將它添加到主循環(huán)中。這樣做是為了在切換回正常模式之前,我們需要在一定數(shù)量的連續(xù)幀內(nèi)檢測(cè)到?jīng)]有聲音,這樣我們就不會(huì)太頻繁地切換。
# In the main loop: if voice_detection: if detect_voice(): quiet_count = 0 if mode != "talking": i, mode, frame_rate = change_mode(mode, "talking", i) else: if mode == "talking": quiet_count += 1 if quiet_count > stop_talking_threshold: quiet_count = 0 i, mode, frame_rate = change_mode(mode, "normal", i)
現(xiàn)在,當(dāng)我們通過(guò)麥克風(fēng)說(shuō)話時(shí),我們可以讓我們的頭像開(kāi)始和停止說(shuō)話。我這樣做是為了通過(guò)按“v”來(lái)激活或停用語(yǔ)音檢測(cè)。
這些都是迄今為止實(shí)現(xiàn)的所有功能。歡迎提出進(jìn)一步改進(jìn)的建議。
如何使用假網(wǎng)絡(luò)攝像頭
首先,從這里下載所有代碼:https://github.com/FrancoisLeRoux1/Fake-webcam
你要做的是錄制一些你自己的視頻(在我的 Mac 上,為此使用了 Photo Booth 應(yīng)用程序),并將它們放在“視頻”文件夾內(nèi)的一個(gè)新文件夾中。你將能夠?yàn)椴煌脑O(shè)置創(chuàng)建不同的文件夾,例如,你可以在其中穿不同的襯衫,或者讓你的頭發(fā)看起來(lái)不同。
這些視頻可以而且應(yīng)該很短(大約 10 秒的視頻),否則如果你拍攝較長(zhǎng)的視頻,計(jì)算最佳過(guò)渡可能需要很長(zhǎng)時(shí)間。你需要一個(gè)名為“normal”的視頻,這將是你的默認(rèn)模式。
然后,如果你想讓你的化身說(shuō)話,你必須錄制一個(gè)名為“talking”的視頻,你說(shuō)的是隨機(jī)的胡言亂語(yǔ)。
在此之后,你可以錄制你想要的任何其他模式(例如,“微笑”、“點(diǎn)頭”、“再見(jiàn)”……)。默認(rèn)情況下,激活/停用這些模式的命令將是其名稱(chēng)的第一個(gè)字母(例如,對(duì)于“微笑”,請(qǐng)按“s”)。
然后你必須計(jì)算最佳轉(zhuǎn)換。為此,只需運(yùn)行腳本 compute-transitions.py
這應(yīng)該需要幾分鐘。
然后當(dāng)你完成后,你就可以啟動(dòng)你的假網(wǎng)絡(luò)攝像頭了。為此,請(qǐng)運(yùn)行 fake-webcam.py 腳本。你需要指定視頻所在的“視頻”內(nèi)的文件夾。你還可以指定是否要使用凍結(jié)。
所以現(xiàn)在你應(yīng)該讓你的假相機(jī)運(yùn)行起來(lái)。接下來(lái),你可以將其設(shè)置為在線會(huì)議的網(wǎng)絡(luò)攝像頭。為此,我使用了 OBS:https://obsproject.com/
選擇正確的 Python 窗口作為源,然后單擊 Start Virtual Camera。
你現(xiàn)在應(yīng)該可以在你最喜歡的在線會(huì)議應(yīng)用程序中選擇此虛擬攝像頭作為你的網(wǎng)絡(luò)攝像頭了!
到此這篇關(guān)于使用Python為你的在線會(huì)議創(chuàng)建一個(gè)假的攝像頭的文章就介紹到這了,更多相關(guān)Python攝像頭內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python3 xpath和requests應(yīng)用詳解
這篇文章主要介紹了python3 xpath和requests應(yīng)用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03Python學(xué)習(xí)筆記之列表和成員運(yùn)算符及列表相關(guān)方法詳解
這篇文章主要介紹了Python學(xué)習(xí)筆記之列表和成員運(yùn)算符及列表相關(guān)方法,結(jié)合實(shí)例形式詳細(xì)分析了Python列表相關(guān)的概念、原理、成員函數(shù)與相關(guān)使用技巧,需要的朋友可以參考下2019-08-08Pyhton爬蟲(chóng)知識(shí)之正則表達(dá)式詳解
正則表達(dá)式又稱(chēng)規(guī)則表達(dá)式,計(jì)算機(jī)科學(xué)的一個(gè)概念,正則表達(dá)式通常被用來(lái)檢索、替換那些符合某個(gè)模式(規(guī)則)的文本,這篇文章主要給大家介紹了關(guān)于Pyhton爬蟲(chóng)知識(shí)之正則表達(dá)式的相關(guān)資料,需要的朋友可以參考下2022-04-04利用Python寫(xiě)個(gè)摸魚(yú)監(jiān)控進(jìn)程
繼打游戲、看視頻等摸魚(yú)行為被監(jiān)控后,現(xiàn)在打工人離職的傾向也會(huì)被監(jiān)控。今天就帶大家領(lǐng)略一下怎么寫(xiě)幾行Python代碼,就能監(jiān)控電腦,感興趣的可以學(xué)習(xí)一下2022-02-02