广告位联系
返回顶部
分享到

使用Python为你的在线会议创建一个假的摄像头

python 来源:互联网 作者:佚名 发布时间:2022-08-29 21:15:46 人浏览
摘要

想象一下。你正在参加在线会议,出于某种原因,你并不想打开摄像头。但是如果你看到其他人都打开了,你觉得你也得打开,所以迅速整理自己的头发,确保衣着整洁,然后不情愿地

想象一下。你正在参加在线会议,出于某种原因,你并不想打开摄像头。但是如果你看到其他人都打开了,你觉得你也得打开,所以迅速整理自己的头发,确保衣着整洁,然后不情愿地打开相机。我们都经历过这种情况。

有一个好消息。在 Python 的帮助下,不再强制开启摄像头。将向你展示如何为你的在线会议创建一个假的摄像头,如下所示:

d82f44ce06650e6dbc7ec36e1d324c2e.gif

aadc4345edf8cb12b0c7c494aa571a2a.gif

当然,这张脸不一定是比尔盖茨的,它也可以是你自己。

现在将向你展示如何在 Python 中编写代码。在文章的最后,将解释如何为自己使用这个假的摄像头。

创建一个简单的假网络摄像头

首先,我们将导入一些模块,尤其是 openCV。

1

2

3

4

5

6

7

8

import cv2

import numpy as np

import pickle

import pyaudio

import struct

import math

import argparse

import os

接下来我们将创建一个函数来从视频中提取所有帧:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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”停止网络摄像头。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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”切换到“微笑模式”)时,我们将活动模式更改为新模式并开始播放相应视频中的帧。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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 帧才能获得最平滑的过渡。

我们可以为每一帧以及从每种模式到所有其他模式预先计算所有这些最佳转换。这样我们就不必在每次想要切换模式时都重新计算。还压缩了图像,以便计算执行时间更短。我们还将存储图像之间的最佳距离。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

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”切换到微笑模式,再次按下它将切换回正常模式。

1

2

3

4

5

6

7

8

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 秒),这样我们将在切换模式之前等待在此窗口内转换的最佳时间。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

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]

现在我们的过渡更加顺畅。但是,它们有时可能很明显。所以另一个想法是有目的地为视频添加冻结,就像那些在不稳定连接时可能发生的冻结一样(就是如果网络不稳定视频就卡住了),并使用它们来掩盖过渡(我们将使冻结持续时间与两个图像之间的距离成比例)。我们还将添加随机冻结,这样模式就不会变得明显。所以我们添加了这些新的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

# 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)。

基本上,这个想法是查看一段时间内来自麦克风的声音的平均幅度,如果它足够高,可以认为我们一直在说话。最初这段代码是为了检测敲击噪音,但它也可以很好地检测语音。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

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

现在我们可以将它添加到主循环中。这样做是为了在切换回正常模式之前,我们需要在一定数量的连续帧内检测到没有声音,这样我们就不会太频繁地切换。

1

2

3

4

5

6

7

8

9

10

11

12

13

# 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。

你现在应该可以在你最喜欢的在线会议应用程序中选择此虚拟摄像头作为你的网络摄像头了!


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://blog.csdn.net/woshicver/article/details/126515965
相关文章
  • Python Django教程之实现新闻应用程序

    Python Django教程之实现新闻应用程序
    Django是一个用Python编写的高级框架,它允许我们创建服务器端Web应用程序。在本文中,我们将了解如何使用Django创建新闻应用程序。 我们将
  • 书写Python代码的一种更优雅方式(推荐!)

    书写Python代码的一种更优雅方式(推荐!)
    一些比较熟悉pandas的读者朋友应该经常会使用query()、eval()、pipe()、assign()等pandas的常用方法,书写可读性很高的「链式」数据分析处理代码
  • Python灰度变换中伽马变换分析实现

    Python灰度变换中伽马变换分析实现
    1. 介绍 伽马变换主要目的是对比度拉伸,将图像灰度较低的部分进行修正 伽马变换针对的是对单个像素点的变换,也就是点对点的映射 形
  • 使用OpenCV实现迷宫解密的全过程

    使用OpenCV实现迷宫解密的全过程
    一、你能自己走出迷宫吗? 如下图所示,可以看到是一张较为复杂的迷宫图,相信也有人尝试过自己一点一点的找出口,但我们肉眼来解谜
  • Python中的数据精度问题的介绍

    Python中的数据精度问题的介绍
    一、python运算时精度问题 1.运行时精度问题 在Python中(其他语言中也存在这个问题,这是计算机采用二进制导致的),有时候由于二进制和
  • Python随机值生成的常用方法

    Python随机值生成的常用方法
    一、随机整数 1.包含上下限:[a, b] 1 2 3 4 import random #1、随机整数:包含上下限:[a, b] for i in range(10): print(random.randint(0,5),end= | ) 查看运行结
  • Python字典高级用法深入分析讲解
    一、 collections 中 defaultdict 的使用 1.字典的键映射多个值 将下面的列表转成字典 l = [(a,2),(b,3),(a,1),(b,4),(a,3),(a,1),(b,3)] 一个字典就是一个键对
  • Python浅析多态与鸭子类型使用实例
    什么多态:同一事物有多种形态 为何要有多态=》多态会带来什么样的特性,多态性 多态性指的是可以在不考虑对象具体类型的情况下而直
  • Python字典高级用法深入分析介绍
    一、 collections 中 defaultdict 的使用 1.字典的键映射多个值 将下面的列表转成字典 l = [(a,2),(b,3),(a,1),(b,4),(a,3),(a,1),(b,3)] 一个字典就是一个键对
  • Python淘宝或京东等秒杀抢购脚本实现(秒杀脚本

    Python淘宝或京东等秒杀抢购脚本实现(秒杀脚本
    我们的目标是秒杀淘宝或京东等的订单,这里面有几个关键点,首先需要登录淘宝或京东,其次你需要准备好订单,最后要在指定时间快速
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计