挂机即可使用的延时摄影All-In-One工作流
我把延时摄影中反复手动完成的操作,做成了一套以 TimelapseManager 为入口的自动化工具流:配置好相机、依赖和拍摄时段后,创建任务就能自动拍摄包围曝光序列、下载照片、合成 HDR、去闪并导出视频。开启晚霞评分后,它还会找出霞光片段、给相册评分,并通过企业微信推送结果。
这套工具已经能够运行单次、每日循环和持续分批任务。拍摄、HDR 合成与评分可以并行推进,关闭管理窗口后,后台任务仍然继续。本文重点介绍最终能得到什么,以及支撑这套自动化的任务模型、图像处理和异常恢复设计。
运行总览将任务、状态、当前阶段和工作进程放在同一个界面中;图片来自仓库随附的界面演示,任务数据用于展示。
1. 一次任务,得到成片和可筛选的素材
以固定机位拍摄黄昏为例,我希望在设置好时间后,程序完成以下工作:
| 能力 | 最终得到的结果 |
|---|---|
| 定时拍摄与下载 | 按曝光组连续编号的本地照片序列 |
| 曝光融合与去闪 | 融合后的 HDR 画面,以及经过亮度平滑的成片输入 |
| 视频编码 | H.265 编码的 MP4 延时视频 |
| 可选的晚霞分析 | 每个采样点的分数与理由、最高分照片、晚霞区间 |
| 归档与通知 | 按日期和时段组织的相册、最高分目录标记、文本与图片通知 |
假设一次任务从 16:00 拍到 20:00,每六秒拍摄一组三张照片,理想情况下约产生 2400 组、7200 张 JPG。每组融合为一帧,按 30 fps 编码后,四小时的变化就压缩为约 80 秒。这是素材量与成片时长的估算,实际帧数会受曝光、下载耗时和中断影响。
成片与相册输出
一次最高晚霞评分为 4、并完成清理归档的任务,保留结果可以呈现为:
1 | AutoTimelapse/ |
这是输出结构示意。hdr_enfuse 保存曝光融合帧,hdr_video 保存成片,评分文件保留分析结果;日期与时段目录中的 S4 让有价值的拍摄时段在文件管理器里就能被找到。同一天存在多个已标记相册时,外层日期目录使用它们的最高分。
处理过程中还会生成原始曝光组和 hdr_deflick 去闪中间帧,是否继续保留由任务清理配置决定。这样既能直接查看视频,也能保留值得进一步后期的 HDR 序列。
晚霞筛选效果
下面是 SunsetScore 仓库中来自同一延时序列的实际评分样例。普通白灰云得到 1 分,着色较柔和的云层得到 3 分,大范围鲜艳着色的云层得到 4 分。
保留了原始样例中的评分标签,按低、中、高分并排展示;这组样例没有 5 分照片。
对于每天重复拍摄的固定机位,筛选结果减少了逐个打开文件夹、逐帧寻找霞光的工作。成片用于快速回看,评分和晚霞区间用于定位值得细看的素材。
2. 系统架构:一个入口,分工明确的工具
所有项目都放在 GitHub 的 Hanwen Photograph 组织中。TimelapseManager 同时提供 GUI 与 CLI,仓库名仍为 Timelapse-Auto-Script。
Manager 承担任务调度、进程管理、进度汇总和结果处置,图像处理交给专门的工具。各层通过命令行、文件和公开事件协议连接:
flowchart TB
UI["TimelapseManager GUI / CLI"] --> M["任务服务与后台 worker<br/>配置、调度、进程、日志"]
M -->|启动与监控| C["Camera Timelapse Controller<br/>gphoto2 控制相机"]
M -->|启动与监控| B["Bracketlapse<br/>可选对齐与 enfuse 曝光融合"]
C -->|完整、稳定的曝光组| B
B -->|HDR 序列| D["Simple Deflicker<br/>亮度去闪"]
D --> V["FFmpeg<br/>H.265 视频"]
B -->|hdr_ready 经 Manager 转发| S["SunsetScore<br/>本地常驻评分服务"]
S --> A["Manager 结果处置<br/>相册标记、HDR 保留与通知"]
Bracketlapse 负责组织曝光融合、去闪和编码;SunsetScore 负责目录采样、模型推理和评分汇总。Manager 通过它们的公开接口安排工作,各工具可以独立运行,也可以继续替换或改进内部实现。
组织里的 Lumalapse 和 NEF Filter 提供 RAW 精细后期、筛片后整理两条配套路径,目前独立使用。自动拍摄主线直接连接相机控制、Bracketlapse 和 SunsetScore,Simple Deflicker 作为 Bracketlapse 的后期依赖参与处理。
3. 任务管理:让“一键启动”能够持续运行
用预设生成明确的任务
Manager 提供四类创建预设,覆盖临时拍摄、每日定时和长期记录:
| 预设 | 使用场景 | 执行方式 |
|---|---|---|
manual |
明确指定某一天、某个时段 | 填写目录、起止日期和时间、拍摄间隔 |
scheduled_once |
拍摄下一个清晨或黄昏 | 按项目配置生成一次任务 |
scheduled_loop |
每天重复拍摄 | 每轮结束后自动生成并启动后继任务 |
eternal |
持续记录 | 按完整曝光组分批归档,后台依次处理批次 |
定时预设在创建时会展开为参数明确的 manual 任务,循环预设额外携带任务链信息。每一轮都拥有自己的时间范围、输出目录、状态与日志,发生问题时可以定位到具体一次拍摄。
永续模式则把素材按完整曝光组切成批次,让拍摄继续生产新照片,后台依次处理已经归档的批次。批次边界使用曝光组,避免把同一组中的三张照片拆到不同目录。
界面与执行进程分离
GUI 和 CLI 共用任务服务,实际拍摄由独立的后台 worker 执行。任务定义与运行状态分别持久化:
1 | config/tasks/<task-id>.yaml 任务定义 |
因此关闭 GUI 不会结束正在运行的拍摄,重新打开后可以继续查看和控制任务。启动前会校验配置和依赖,并通过启动锁与任务状态检查阻止同一任务重复启动。
停止也有不同语义:提前结束拍摄并完成已有素材的处理、当前任务或批次结束后停止,以及立即停止。对于一段正在记录的黄昏,这些选择分别对应不同的素材和后期边界,不能都等同于结束进程。
4. 图像处理:从包围曝光到稳定的延时画面
曝光组是拍摄与处理的交接单位
Camera Timelapse Controller 通过 gphoto2 控制相机,默认使用相机已设置的 AEB 状态完成三张包围曝光,也支持逐张改变曝光补偿的手动模式。
Bracketlapse 仓库中的 Nikon Z30 设置照片,展示三张包围曝光的配置入口;参数为示例。
下载文件采用“组号 + 组内编号”命名。例如一组照片为 0001_01.jpg、0001_02.jpg 和 0001_03.jpg,已有目录可以从现有最大组号继续编号。这个命名同时为后期提供了明确的分组协议。
Bracketlapse 接受一组输入需要满足两个条件:成员从 01 到 N 齐全,且文件在连续扫描中保持稳定。captXXXX.jpg 一类临时下载文件、不完整组都会被忽略。即使某组缺少一张照片,后面的组也能按组号继续处理。
任务的 processing.group_size 定义 N,未单独配置时使用 eternal.images_per_group。这里必须与实际拍摄张数一致。
把拍摄与下载耗时计入间隔
拍摄间隔指相邻两组开始时间的目标间隔。以六秒为例,如果拍摄与下载用了三秒,就再等待约三秒;若已经超过六秒,则记录警告并立即进入下一组。
可以把单轮等待写成:
1 | 等待时间 = max(0, 目标间隔 - 本轮拍摄与下载耗时) |
这样可以避免每轮在拍摄耗时之外再固定等待六秒,使实际节奏偏离配置。吞吐仍受相机和传输速度限制,设置更小的间隔并不能保证得到相同间隔的照片。
融合、去闪与编码分别处理什么
Bracketlapse 使用 Hugin 的 enfuse 将不同曝光的照片融合成一帧,让同一画面里的亮部和暗部获得更合适的表现。相机发生轻微位移时,可以额外启用 align_image_stack 对齐。
曝光融合解决单帧的明暗取舍,延时播放还需要处理相邻帧之间的亮度跳变。Simple Deflicker 对序列建立亮度直方图,用滑动平均得到目标分布,再通过直方图匹配平滑全局闪烁,同时容纳日落过程中的渐变。
这部分基于 struffel/simple-deflicker 上游维护,我的分支补充了直方图平滑调整、CLI 构建与 macOS 支持。它处理 JPG/PNG 的全局亮度闪烁,室内灯光产生的滚动条纹不属于该算法的处理范围。
最后,FFmpeg 从去闪后的帧生成 H.265 MP4。本文所说的 HDR 指包围曝光融合的画面效果,是否输出 HDR10、HLG 等视频还取决于色彩与编码链路,不能仅由 enfuse 和 H.265 判断。
5. 并行流水线:边拍摄,边合成,边评分
长时间拍摄中,已经完成的曝光组可以立即进入后期。Bracketlapse 在原地 standby 模式下维持一个监听进程,每完成一组融合,就通过标准输出发送事件:
1 | BRACKETLAPSE_EVENT {"event":"hdr_ready","frame_number":1,"path":"/photos/hdr_enfuse/hdr_00001.jpg"} |
这个事件表示一张 HDR 已经可用。Manager 收到后向 SunsetScore 的 JSONL 常驻服务提交扫描请求,评分服务自行扫描目录,只处理新进入采样位置的照片。请求示意如下:
1 | {"id":1,"command":"scan","session":"dusk-20260906","directory":"/photos/hdr_enfuse"} |
scan 提交后不等待本次评分全部结束,拍摄与融合可以继续。任务收尾时,Manager 再发送 finalize,等待补扫、聚合和评分文件写入完成。会话区分不同目录,同一任务及其永续批次复用一个常驻模型,减少重复启动进程和加载模型的开销。
这种协作包含两种粒度:曝光组完成后可以立即融合、增量评分;去闪和最终视频编码则在 HDR 生产结束后收尾。评分可以与后期收尾并行推进,最终素材处置再使用完整结果。
进度来自各阶段的实际结果
拍摄、HDR、评分和视频分别记录进度。视频阶段使用 FFmpeg 的 video_started、video_progress、video_completed 事件,包含已完成帧数与总帧数;成功退出后才发出完成事件。
因此界面能反映“拍摄仍在继续,HDR 已处理多少组,视频正在编码多少帧”。不同设备上的吞吐仍取决于曝光时间、下载速度、CPU/GPU 与磁盘,这种并行设计减少等待,但不承诺固定的实时处理速度。
6. 本地视觉评分与素材保留
从单张分数得到晚霞区间
SunsetScore 使用本地 Qwen3-VL 模型和 llama.cpp 运行时。模型选择视觉类别,程序再将类别映射为 0—5 分,尽量保持分数与理由一致。首次准备好资源后可以离线评分。
默认间隔为十张,对应第 1、11、21、31……张照片。分数主要描述可见云层的霞光着色:普通白云、纯日落或暖色地平线不足以达到晚霞阈值;着色明确的云层可达到 3 分,颜色强烈或覆盖广的云层获得更高分。
只要任意一个采样点达到 3 分,就返回 has_sunset = true。相邻高分采样点合并为晚霞区间,低分或失败位置拆开区间。平均分与最高分用于描述结果,其中最高分也用于相册命名。
选择“任意一个明确样本”作为判定依据,是为了保留短暂出现的霞光。即使四小时中的大部分时间都是普通天空,也不应让长时间的低分稀释那几分钟的画面。抽样仍可能漏过更短的变化,需要根据拍摄节奏调整间隔。
评分描述视觉特征,不是概率,也不等于照片的整体摄影价值。模型不读取地点或 EXIF 时间,视觉上相似的朝霞也可能获得高分。
把评分变成可解释的归档动作
Manager 使用分析结论决定 HDR 的保留方式:
| 结果 | 处理方式 |
|---|---|
| 检测到晚霞 | 保留 hdr_enfuse,受管日期和时段相册添加最高分 -Sx 标记 |
| 成功评分且未检测到晚霞 | 标记 hdr_enfuse,保留三天后再执行清理 |
| 分析失败,或存在失败样本且未检测到晚霞 | 保留 HDR,报告评分失败 |
无晚霞目录里会写入 .timelapse-hdr-enfuse-delete 标记,记录带时区的截止时间。Manager 在服务初始化和后续任务列表检查时扫描 auto_root,检查频率最多每分钟一次,只处理有效标记且已经到期的 hdr_enfuse。如果期间没有运行检查,实际删除会发生在后续检查时。
三天的窗口为复核和另存素材留下了时间。这个策略只覆盖被标记的 HDR 目录,原始照片、去闪中间帧和视频仍按各自的任务清理配置处理。
7. 长期运行需要的恢复、依赖与通知
后期失败时保住已经拍到的内容
拍摄、处理和任务完成是不同的状态。对于有限任务,Bracketlapse 在拍摄期间意外退出,Manager 会记录需要恢复,让相机继续完成拍摄,再从最终照片执行覆盖恢复处理。
如果有限任务最终仍然失败,则跳过清理、保留原始照片。评分失败同样不能被当作“没有晚霞”,否则推理故障会错误地变成删图依据。这两条规则把素材保护放在任务成功判定之前。
相机控制工具在下载成功后会删除相机中的对应副本,因此本地下载目录承担了原片保存职责,任务的失败与清理边界尤其重要。
让任务使用的依赖可以确定
Manager 将工作流工具、Python 环境和评分资源放在应用的 .timelapse/dependencies/ 下。任务从这套私有目录解析命令,减少全局 PATH 或不同 Python 环境带来的差异。
“包管理”页可以检查和安装组件,并对提供构建信息接口的自有工具显示版本、源码分支和构建时间。Camera Timelapse Controller、Bracketlapse、SunsetScore 更新时可以选择分支,便于确认一次任务实际使用的工具版本。
GUI 支持 Windows、macOS 和 Linux,实际相机控制还取决于 gphoto2 在对应平台对相机的支持。当前主流程要求 Bracketlapse 至少为 0.4.1,可选 SunsetScore 至少为 0.12.0。
在电脑之外查看拍摄结果
企业微信 Webhook 可以发送任务状态、评分摘要和最高分照片。运行日志用于定位过程中的问题,通知则让完成结果直接到达日常查看消息的地方。
配置页支持文本与图片测试,截图中的机器人地址使用 YOUR_KEY 占位。
8. 配置一次,重复使用
第一次使用时,先在“包管理”中准备相机控制和后期组件;需要评分时,再准备 SunsetScore 与模型资源。随后通过“配置中心”确认输出目录和时段,“任务管理”负责创建和启动任务。
项目配置的关键片段如下,其余字段沿用程序生成的完整配置:
1 | auto_root: ~/Pictures/AutoTimelapse |
清晨、黄昏是固定配置时段,需要按季节和拍摄地点调整。eternal.batch_groups 控制永续任务的分批规模;对于单次定时任务,拍摄范围由起止时间决定。
日常使用时,选择 scheduled_loop 就能让清晨和黄昏任务接力;只想拍下一轮则选择 scheduled_once;固定某天某个时段使用 manual。CLI 可以查看环境与后台运行状态:
1 | python timelapse.py self-test |
9. 两个配套工具:精细后期与 RAW 整理
对于需要逐段调整画面的素材,Lumalapse 提供 RAW 序列导入、曝光曲线、关键帧调色和视频导出。它通过 LibRaw/rawpy 在线性空间解码,在 log2 亮度域计算曲线,将平滑目标与实测曲线之差作为逐帧 EV 补偿,再叠加人工关键帧曝光调整。
这种方式将去闪与人为设计的明暗渐变结合起来,同时保留高光、阴影、色温等调整能力。分析结果和关键帧缓存到素材目录中的 .lumalapse/,便于重复打开继续编辑。
NEF Filter 则把 JPG 筛片结果应用到 Nikon RAW:查找没有对应 JPG 的 NEF,默认展示删除列表并等待确认。它支持分别指定 JPG 与 NEF 目录,默认非递归扫描,适合筛片后的整理工作。
两者是独立使用的配套工具。自动主线负责从拍摄到初步筛选,值得继续处理的素材可以进入更精细的后期与整理过程。
10. 演进与版本依据
这套实现主要经历了三个阶段:6 月先补齐相机控制、包围曝光合成和去闪等单项能力;7 月用任务管理器、图形界面和晚霞评分建立统一入口;8—9 月完善增量处理、常驻模型、故障恢复与延迟清理。演进最终落在三个设计选择上:以完整曝光组交接数据,以独立任务记录运行,以明确的处理结果决定归档。
本文实现对应的仓库版本如下,可用于核对接口和复现配置。
界面截图来自 TimelapseManager 文档图片,相机照片来自 Bracketlapse 资源目录,评分样例来自 SunsetScore 示例图片;本地配图仅作格式转换、缩放和并排排版。