<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
想象一下。你正在參加線上會議,出於某種原因,你並不想開啟攝像頭。但是如果你看到其他人都開啟了,你覺得你也得開啟,所以迅速整理自己的頭髮,確保衣著整潔,然後不情願地開啟相機。我們都經歷過這種情況。
有一個好訊息。在 Python 的幫助下,不再強制開啟攝像頭。將向你展示如何為你的線上會議建立一個假的攝像頭,如下所示:
當然,這張臉不一定是比爾蓋茲的,它也可以是你自己。
現在將向你展示如何在 Python 中編寫程式碼。在文章的最後,將解釋如何為自己使用這個假的攝像頭。
首先,我們將匯入一些模組,尤其是 openCV。
import cv2 import numpy as np import pickle import pyaudio import struct import math import argparse import os
接下來我們將建立一個函數來從視訊中提取所有幀:
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
現在我們有了框架,我們可以建立一個迴圈,一個接一個地顯示它們。當到達最後一幀時,我們向後播放視訊,然後當我們到達第一幀時,我們將向前播放,我們將永遠重複這個過程。這樣就不會出現從最後一幀到第一幀的突然過渡。我們也會這樣做,以便我們可以按“q”停止網路攝像頭。
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)
有了這個,我們就有了一個可以無縫播放的簡單網路攝像頭。
但我們並不止步於此。
如果我們的假網路攝像頭頭像可以做的不僅僅是被動地凝視,那將更有說服力。例如,有時在開會時,你需要點頭表示同意、微笑、交談或做其他事情。
所以我們希望我們的網路攝像頭有多種“模式”,我們可以隨時通過按下鍵盤上的一個鍵來切換。
為此,你需要為每種模式錄製一個簡短的錄音,例如你只是微笑的錄音。然後我們可以從每個視訊中讀取幀,並將它們儲存在字典中。當我們檢測到按鍵(例如,“s”切換到“微笑模式”)時,我們將活動模式更改為新模式並開始播放相應視訊中的幀。
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)
預設情況下,這樣做是為了切換到指定模式,鍵盤命令是模式名稱的第一個字母。現在我把這個'change_mode'函數作為一個黑盒子,稍後會解釋它。
所以我們想從一個視訊切換到另一個,比如說從正常模式到點頭模式。如何以最佳方式從一個模式過渡到另一個模式(即過渡儘可能平滑)?
當我們進行過渡時,我們希望轉到與我們當前所處的最相似的新模式的框架。
為此,我們可以首先定義影象之間的距離度量。這裡使用一個簡單的歐幾里得距離,它檢視兩個影象的每個畫素之間的差異。
有了這個距離,我們現在可以找到最接近我們當前的影象,並切換到這個。例如,如果我們想從普通模式過渡到點頭模式,並且我們在普通視訊的第 132 幀,我們將知道我們必須轉到點頭視訊的第 86 幀才能獲得最平滑的過渡。
我們可以為每一幀以及從每種模式到所有其他模式預先計算所有這些最佳轉換。這樣我們就不必在每次想要切換模式時都重新計算。還壓縮了影象,以便計算執行時間更短。我們還將儲存影象之間的最佳距離。
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'))
現在可以展示“change_mode”函數,該函數從預先計算的字典中檢索要轉換到的最佳幀。這樣做是為了如果你按下例如“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]
我們還可以新增另一項改進使我們的過渡更加無縫,不是總是立即切換模式,而是等待一段時間以獲得更好的過渡。例如,如果我們的頭像在點頭,我們可以等到頭部通過中間位置才轉換到正常模式。為此,我們將引入一個時間視窗(這裡我將其設定為 0.5 秒),這樣我們將在切換模式之前等待在此視窗內轉換的最佳時間。
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]
現在我們的過渡更加順暢。但是,它們有時可能很明顯。所以另一個想法是有目的地為視訊新增凍結,就像那些在不穩定連線時可能發生的凍結一樣(就是如果網路不穩定視訊就卡住了),並使用它們來掩蓋過渡(我們將使凍結持續時間與兩個影象之間的距離成比例)。我們還將新增隨機凍結,這樣模式就不會變得明顯。所以我們新增了這些新的程式碼:
# 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)
使用或不使用這些凍結保留為選項。
好的,現在我們已經真正涵蓋了這些過渡的基礎。我們還能為網路攝像頭新增什麼?
另一件有趣的事情是新增語音檢測,這樣當我們說話時,視訊裡的“我”就會說話。
這是使用 pyaudio 完成的。感謝這個 stackoverflow 執行緒(https://stackoverflow.com/questions/4160175/detect-tap-with-pyaudio-from-live-mic)。
基本上,這個想法是檢視一段時間內來自麥克風的聲音的平均幅度,如果它足夠高,可以認為我們一直在說話。最初這段程式碼是為了檢測敲擊噪音,但它也可以很好地檢測語音。
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
現在我們可以將它新增到主迴圈中。這樣做是為了在切換回正常模式之前,我們需要在一定數量的連續幀內檢測到沒有聲音,這樣我們就不會太頻繁地切換。
# 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)
現在,當我們通過麥克風說話時,我們可以讓我們的頭像開始和停止說話。我這樣做是為了通過按“v”來啟用或停用語音檢測。
這些都是迄今為止實現的所有功能。歡迎提出進一步改進的建議。
首先,從這裡下載所有程式碼:https://github.com/FrancoisLeRoux1/Fake-webcam
你要做的是錄製一些你自己的視訊(在我的 Mac 上,為此使用了 Photo Booth 應用程式),並將它們放在“視訊”資料夾內的一個新資料夾中。你將能夠為不同的設定建立不同的資料夾,例如,你可以在其中穿不同的襯衫,或者讓你的頭髮看起來不同。
這些視訊可以而且應該很短(大約 10 秒的視訊),否則如果你拍攝較長的視訊,計算最佳過渡可能需要很長時間。你需要一個名為“normal”的視訊,這將是你的預設模式。
然後,如果你想讓你的化身說話,你必須錄製一個名為“talking”的視訊,你說的是隨機的胡言亂語。
在此之後,你可以錄製你想要的任何其他模式(例如,“微笑”、“點頭”、“再見”……)。預設情況下,啟用/停用這些模式的命令將是其名稱的第一個字母(例如,對於“微笑”,請按“s”)。
然後你必須計算最佳轉換。為此,只需執行指令碼 compute-transitions.py
這應該需要幾分鐘。
然後當你完成後,你就可以啟動你的假網路攝像頭了。為此,請執行 fake-webcam.py 指令碼。你需要指定視訊所在的“視訊”內的資料夾。你還可以指定是否要使用凍結。
所以現在你應該讓你的假相機執行起來。接下來,你可以將其設定為線上會議的網路攝像頭。為此,我使用了 OBS:https://obsproject.com/
選擇正確的 Python 視窗作為源,然後單擊 Start Virtual Camera。
你現在應該可以在你最喜歡的線上會議應用程式中選擇此虛擬攝像頭作為你的網路攝像頭了!
到此這篇關於使用Python為你的線上會議建立一個假的攝像頭的文章就介紹到這了,更多相關Python攝像頭內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45