Tips
背景
想用班上一体机的摄像头来拍班级晚修的延时摄影,因此写了这个简单的 Python 程序。非常好用哈,在晚修前运行这个程序,没有痕迹,下晚修后就能看到成片了 ^_^
全程只需要 opencv-python 和 requests 两个依赖,核心就是一个 main.py,简单粗暴。
Note
附带效果:程序运行期间摄像头会被一直占用,所以老师也没法通过希沃云班远程看我们晚修了(嘘,这算是写这个程序的一大动力)🤫
功能特性
- 定时自动拍摄:设置开始 / 结束时间(默认 18:55–20:35,正好覆盖一节晚修),到点自动开拍,结束自动保存
- 拍摄间隔可调:默认 1 秒拍一帧,一节晚修 100 分钟 ≈ 6000 帧,最终以 30fps 合成,成片约 3 分多钟
- 自动时间戳:每帧右上角叠加当前时间,背景色根据画面亮度自适应(亮背景黑底白字,暗背景白底黑字)
- 双输出日志:控制台和日志文件同步记录,方便排查"为什么没拍上"
- 可选 WebDAV 上传:拍完自动把成片传到网盘(默认关闭,需要自己改配置)
项目结构
classNightLapse/
├── main.py # 全部代码,单文件
├── requirements.txt # opencv-python、requests
├── LICENSE # MIT
└── .gitignore # 忽略 outputs/ logs/ 等
核心实现解析
配置区
所有参数都集中在文件头部,改起来非常直观:
WEBDAV = False # 是否启用 WebDAV 上传
START_HOUR, START_MINUTE = 18, 55
END_HOUR, END_MINUTE = 20, 35
CAMERA_INDEX = 0 # 摄像头编号
OUTPUT_FILE = f"outputs/{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.avi"
FPS = 30 # 最终视频帧率
FRAME_WIDTH = 1920 # 试了几个,只有这个能拍全班级
FRAME_HEIGHT = 1080 # 3224 那个不够宽,拍不全
INTERVAL_SECONDS = 1 # 拍摄间隔,单位秒
SHOW_TIMESTAMP = True # 是否显示时间
LOG_FILE = f"logs/{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log"
这里有个小细节:分辨率是试出来的,只有 1920x1080 能把整个班级拍全。输出文件和日志文件名都带时间戳,每次运行不会互相覆盖。
主流程 main()
整体思路很清晰:
# 1. 如果已经过了结束时间,直接退出
# 2. 如果还没到开始时间,睡到开始前 2 秒
# 3. 打开摄像头、设分辨率、读一帧确定尺寸
# 4. 创建 VideoWriter,精确对齐开始时间拍第一帧
# 5. while 循环按绝对时刻拍摄,直到结束时间
# 6. finally 里释放资源,统计帧数,可选上传 WebDAV
值得一说的有两个点:
提前 2 秒打开摄像头。摄像头初始化需要时间,如果到点才开始初始化,第一帧会晚于预定时刻。所以程序在开始时间前 2 秒就打开摄像头,等真正到点了直接抓帧,保证第一帧精确落在 18:55:00。
绝对时刻调度防漂移。拍摄循环里没有用"睡 1 秒"这种相对间隔,而是维护一个 next_capture 绝对时刻,每拍完一帧就 next_capture += timedelta(seconds=INTERVAL_SECONDS),再睡到那个时刻:
# 下一个预定拍摄时刻(绝对时间,避免累积偏差)
next_capture += timedelta(seconds=INTERVAL_SECONDS)
这样即使某次 sleep 或抓帧慢了,也不会把误差累积到后面,长跑一节课也不会越拍越飘。这个细节很关键,延时摄影最怕的就是节奏漂移。
时间戳 draw_timestamp()
每帧右上角叠加时间,背景色是根据画面亮度自适应的——先取背景框区域的平均亮度,亮背景用黑底白字,暗背景用白底黑字:
mean_brightness = cv2.mean(gray)[0]
if mean_brightness > 128:
bg_color = (0, 0, 0) # 黑色背景
text_color = (255, 255, 255) # 白色文字
else:
bg_color = (255, 255, 255) # 白色背景
text_color = (0, 0, 0) # 黑色文字
再用 cv2.addWeighted 做 0.6 透明度的半透明底,效果比直接白字好看不少,晚修教室灯光忽明忽暗也不会看不清。
日志 logprint()
同时写控制台和日志文件,每行都带时间戳。晚上在教室一体机上跑的时候,你不可能一直盯着屏幕看,出问题了翻日志就行:
def logprint(message, end='\n'):
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {message}", end=end)
with open(LOG_FILE, 'a', encoding='utf-8') as f:
f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {message} {end}")
WebDAV 上传 upload_webdav()
默认关闭。开启后拍完会把成片用标准 PUT 请求传到 WebDAV 网盘(用的是 HTTPBasicAuth 认证),这样不用回教室就能在手机上看到成片:
resp = requests.put(
url,
data=data,
auth=HTTPBasicAuth(USERNAME, PASSWORD),
timeout=120,
verify=False, # 忽略 SSL 证书问题
)
如果要用 WebDAV,记得自己改配置并重新编译。
异常兜底
while 循环外面包了 try / except KeyboardInterrupt,即使中途被人 Ctrl+C 打断,也会在 finally 里正常释放摄像头、保存已经拍到的部分,不会丢帧丢文件。
使用方法
pip install -r requirements.txt
python main.py
到点自动开拍,结束自动保存成片到 outputs/ 下。日志在 logs/ 下。
想部署到班上一体机的话,可以根据 .gitignore 里预留的 build/、dist/、*.spec 用 PyInstaller 打成 exe,直接双击运行,非常省事。
总结
一个单文件就能搞定的延时摄影小工具:定时开拍、自动时间戳、防漂移调度、日志兜底,功能不多但都很实用。代码量不大,感兴趣的可以直接去看源码:classNightLapse。
下次晚修,记得提前 2 分钟运行它,下课后就能收获一份属于全班的三分钟延时大片了 🎬
说些什么吧!