昨日收到通知,以后上网课可能要开摄像头了...

老师:@所有人 同学们晚上好,今天学校公文通发布了通知要求老师加强课堂互动以及考勤,提高学生的参与率和积极性。经过深思熟虑,【下节课我们试行一个方案,上课刚开始限时签到考勤】,最后三名签到的倒霉蛋子陪我一起整堂开摄像头开麦互动,并且课堂随机选人回答问题(其实还是为了互动)
签到需要用到云班课,请大家一定提前下载好云班课app(网页版无法签到)!!!班级号xxxxxx
没有签到的同学按缺勤处理
(如果有自愿开摄像头的同学也可以联系我)

为了应付一下,打算搞个虚拟摄像头。最开始试了试e2eSoft VCam,有水印,破解不了。然后试了OBS Studio,感觉还行,可以通过滤镜把摄像头弄得很模糊,其他功能也很强大,不过我还想模拟卡顿或者其他效果。最后是用UnityCapture+pyvirtualcam+CV2实现了。

流程

首先是下载UnityCapture,解压并运行Install/Install.bat
然后通过pip安装一些库

pip install opencv-python
pip install pyvirtualcam
pip install numpy

然后新建一个py文件,写入如下代码。

import pyvirtualcam
import numpy as np
import cv2

usevidpath = r"C:\Users\Administrator\Desktop\forcam.mp4"
usecam = 1

class Blocker:
    def __init__(self, maxloop, proba_range) -> None:
        self.cnt = 1
        self.maxindex = 0
        self.maxloop = maxloop
        self.inloop = False
        self.proba_range = proba_range

    def execute(self):
        if not self.inloop:
            proba = np.random.uniform(self.proba_range[0], self.proba_range[1])
            if np.random.random() < proba:
                self.inloop = True
                return 1
            return 0
        self.cnt += 1
        if self.cnt > self.maxloop[self.maxindex]:
            self.cnt = 1
            self.inloop = False
            self.maxindex += 1
            if self.maxindex >= len(self.maxloop):
                self.maxindex = 0
            return 0
        return 1


campBlocker = Blocker([8, 0, 4, 9, 0, 4, 4], [0.02, 0.09])

def camprocess():
    global cam, frame, lastframe, campBlocker
    if frame is None:
        cam.send(lastframe)
        return
    if campBlocker.execute():
        frame = lastframe
        frame = cv2.resize(frame, (35, 35))
    else:
        lastframe = frame
        frame = cv2.resize(frame, (50, 50))
    frame = cv2.resize(frame, (cam.width, cam.height))
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    cam.send(frame)


def vidprocess():
    global cam, frame
    if frame is None:
        return
    frame = cv2.resize(frame, (cam.width, cam.height))
    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    # frame = cv2.flip(frame, 1)
    cam.send(frame)


def switchcap():
    global usecam, cap
    usecam *= -1
    print("usecam:", usecam)
    if usecam == 1:
        cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
    else:
        cap.release()
        cap = cv2.VideoCapture(usevidpath)


with pyvirtualcam.Camera(width=320, height=240, fps=5) as cam:
    print(f'Using virtual camera: {cam.device}')
    if usecam == 1:
        cap = cv2.VideoCapture(0)
    else:
        cap = cv2.VideoCapture(usevidpath)
    emptycap = np.zeros((cam.height, cam.width, 3), dtype=np.uint8)
    lastframe = emptycap
    while True:
        sucess, frame = cap.read()
        if usecam == 1:
            camprocess()
        else:
            if sucess:
                vidprocess()
            else:
                cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
                sucess, frame = cap.read()

        # 一键切换录像/视频
        if not sucess:
            cam.send(emptycap)
            frame = emptycap
        cv2.imshow('vcam', frame[:,:, [2, 1, 0]])
        key = cv2.waitKey(50) & 0xFF
        if key == ord('#'):
            switchcap()

        cam.sleep_until_next_frame()

说明:

  1. 运行该py文件,默认是会打开摄像头的,并会弹出一个名称为vcam的窗口。在该窗口内部按下'#' (shift+3)即可切换成播放提前录制的视频,再按下又会切换回摄像头。
  2. 如果视频读取不到,默认会显示黑屏。
  3. 可在第73行找到width=320, height=240, fps=5,修改虚拟摄像头的图像宽高和fps。
  4. 第5行的usevidpath为需要循环播放的视频的路径,该视频的fps最好要与虚拟摄像头的fps一致。
  5. 在函数camprocess内部可对摄像头抓取的图像进行操作。默认只有模糊图像+模拟卡顿

最后使用时,运行py文件,打开会议/上课软件,在设置处选用Unity video capture作为摄像头即可。