此前,我们在开源视频编辑器 Timeline Studio 中加入了深度视频能力:从原始画面中提取远近层次,将其转化为可复用的结构素材。
现在,我们又增加了一种表达方式——线稿图与线稿视频。
深度图描述物体之间的远近关系,线稿则突出轮廓、姿态、构图与画面中的结构变化。两者都可以作为后续创作的参考:在支持相应条件输入的图像或视频生成工作流中,保留一部分原始结构,再尝试改变外观、材质和风格。
这篇文章先介绍线稿功能的实现:如何在浏览器本地处理图片和视频,如何让预览与生成结果使用相同算法,以及如何保持裁剪、变速后的时间关系。
项目源码:Timeline Studio
在线体验:打开编辑器
一、先明确这版“线稿”的技术路线
当前版本采用传统图像处理,没有调用神经网络模型。
处理流程是:
图片 / 视频帧
↓
亮度转换
↓
平滑降噪
↓
Sobel 梯度计算
↓
阈值与软过渡
↓
白底黑线 / 黑底白线
↓
PNG 图片 / WebM 视频
这条路线的优点是启动轻、参数直接、无需下载模型,适合先建立一个完整的浏览器处理闭环。
它也有明确限制:算法依据局部亮度变化寻找边缘,缺少语义理解。因此,背景纹理、衣服褶皱和高频噪声也可能变成线条。
所以,这版输出更准确地说是经过平滑处理的轮廓线稿。它能够提供结构参考,但并不等于模型生成的语义线稿或专业描线结果。
二、用 WebGL 2 完成两遍处理
核心实现位于:
src/lib/lineArt.js
渲染器接收一个画布、输出尺寸和媒体帧,在 WebGL 2 中完成两遍绘制:
- 将输入帧平滑处理,写入中间纹理。
- 从中间纹理提取边缘,绘制到输出画布。
中间结果保留在 GPU 纹理中,避免每一遍处理都把像素读回 JavaScript。
输入纹理
│
▼
平滑着色器
│
▼
中间纹理 + Framebuffer
│
▼
边缘着色器
│
▼
输出 Canvas
对于视频,每一帧重复使用已经创建的着色器程序、纹理和帧缓冲,而不是逐帧重新初始化渲染环境。
三、第一步:把颜色转换为亮度
轮廓提取首先需要一个单通道的亮度表示。
实现使用下面的加权方式:
float gray(vec2 p) {
return dot(
texture(source, p).rgb,
vec3(0.2126, 0.7152, 0.0722)
);
}
绿色通道权重更高,蓝色通道权重较低。
后续计算关注的是相邻位置的亮度变化,因此即使画面颜色丰富,进入边缘检测阶段后,也会被归结为亮度结构。
这同样解释了一种常见情况:两个颜色不同、亮度接近的区域,边界不一定明显。
四、第二步:先平滑,减少碎线
直接对原图做边缘检测,容易把细碎纹理全部提取出来。
当前实现先使用一个 3×3 加权平滑核:
1 2 1
2 4 2
1 2 1
总权重:16
中心像素权重为 4,上下左右为 2,四个角为 1。
这一步能够压低部分高频变化,让后续计算更关注较明显的边缘。
不过,降噪与保留细节之间存在取舍。平滑过强会损失小结构,平滑不足又容易留下碎线。当前版本采用固定的轻量平滑,再通过细节参数调节边缘保留程度。
五、第三步:用 Sobel 计算边缘强度
Sobel 算子分别计算横向和纵向的亮度梯度。
对应卷积核为:
Gx: Gy:
-1 0 1 -1 -2 -1
-2 0 2 0 0 0
-1 0 1 1 2 1
两个方向的结果合成为梯度强度:
gradient = sqrt(Gx² + Gy²)
梯度越大,说明附近的亮度变化越明显,越有可能形成可见边缘。
当前着色器中的核心计算为:
float gradient = length(vec2(
c + 2.0 * f + i - a - 2.0 * e - g,
g + 2.0 * h + i - a - 2.0 * b - c
));
这里的字母代表采样中心周围的八个位置。
这种计算对每个输出像素独立执行,适合放到片元着色器中并行处理。
六、“细节”控制的是边缘阈值
界面中的“细节”范围为 0~100,默认值是 55。
它对应的核心变化是:
float threshold = mix(
0.42,
0.035,
detail / 100.0
);
细节值越高,阈值越低,较弱的边缘也会保留下来;细节值越低,则优先保留明显轮廓。
为了避免硬阈值产生生硬的黑白跳变,实现使用 smoothstep:
float ink = smoothstep(
threshold * 0.5,
threshold,
gradient
);
这让阈值附近的边缘具有一定软过渡。
需要注意,高细节不一定意味着更好的参考图。对于复杂背景,适当减少细节,反而能突出主体结构。
七、线条粗细如何适配不同分辨率
如果线条参数直接对应固定像素距离,换一个输出分辨率,视觉粗细就可能明显变化。
当前实现以最长边 1080 像素作为参考尺度:
scaledThickness =
thickness * Math.max(width, height) / 1080;
再将这个数值用于边缘采样距离:
vec2 d = pixel * thickness;
界面提供 0.5~3 倍调节,默认值为 1。
这里的“粗细”通过改变采样间距影响边缘外观,并不是严格意义上对二值线条执行形态学膨胀。因此,参数变化也可能影响附近细节的响应。
黑白反转则直接切换输出亮度:
float value = inverted ? ink : 1.0 - ink;
默认输出白底黑线,也可以生成黑底白线。
八、预览与素材生成复用同一个渲染器
图像处理功能很容易出现一种问题:预览使用简化算法,生成时使用另一套实现,最后用户看到的结果与预览不一致。
这次我们让两个路径共同使用:
createLineArtRenderer(canvas, width, height)
它提供两个主要方法:
renderer.draw(media, options);
renderer.dispose();
调节参数时,预览重新绘制当前帧;生成素材时,则使用同一个渲染器处理图片或连续视频帧。
这减少了两套算法之间的偏差,也让后续维护集中在一个位置。
当前处理尺寸默认限制最长边不超过 1080,并保持比例、不放大小素材,同时将尺寸规整为偶数,以适配视频编码。
九、图片与视频如何进入同一条处理路径
图片和视频的输入方式不同,但最终都需要转换成可上传到 WebGL 的图像源。
图片路径为:
Blob
↓
ImageBitmap
↓
2D Canvas
↓
WebGL 纹理
这里额外经过一次 Canvas,是为了统一图像方向。
实际接入时发现,ImageBitmap 对纹理上传翻转设置的行为与其他图像源存在区别。先绘制到 Canvas,可以让图片和视频沿用一致的方向处理路径,避免生成结果上下颠倒。
视频路径则使用 Mediabunny 读取媒体轨道,通过 CanvasSink 获取指定时间对应的画面。
十、视频转换要保留裁剪与变速关系
视频线稿不能只从原始文件的第零秒开始逐帧处理。
用户选中的片段可能已经裁剪,也可能应用了固定倍速或速度曲线。
因此,输出视频的每个采样点,都需要先映射回源素材时间:
getVisualSourceTime(segment, localTime)
对于固定倍速,可以近似理解为:
源时间 = 素材入点 + 片段内时间 × 倍速
速度曲线则需要沿用编辑器既有的映射逻辑。
当前输出采用 24 fps:
const fps = 24;
const count = Math.ceil(duration * fps);
每一帧的片段内时间为:
t(i) = i / 24
处理流程为:
输出帧时间
↓
映射为源素材时间
↓
读取源画面
↓
提取线稿
↓
写入输出时间戳
这样,线稿视频才能跟随当前片段的裁剪范围和播放节奏。
24 fps 是当前生成素材的输出帧率,并不意味着处理过程在所有设备上都能达到实时速度。
十一、生成 WebM,并保留对应源音频
当前视频输出使用:
- WebM 容器;
- VP9 视频编码;
- 约 4 Mbps 视频码率;
- 有源音频时,使用 Opus 编码。
音频复用已有的源音频准备与离线混音流程,按照片段的源区间和变速关系生成对应声音。
这里保留的是该片段对应的源音频,不是把整个工程中的旁白、音乐和其他轨道一起混入线稿素材。
生成结束后,结果保存到“我的素材”,由用户决定何时插入时间线。
这也方便保留原片、线稿版本和后续生成版本,用于并排比较或继续组合。
十二、取消与资源清理也属于功能实现
长视频处理过程中,用户需要能够取消任务。
当前实现使用 AbortController,在读取源画面、处理帧、编码和最终封装等阶段检查取消信号。
结束或取消后,需要释放:
视频输入与解码资源
WebGL 纹理
Framebuffer
着色器与程序
顶点缓冲
界面进度也区分了逐帧处理和最终封装:帧处理阶段最多显示到 99%,完成封装并成功保存素材后,再显示完成。
这样,“处理完最后一帧”和“已经拿到可用文件”就不会被混为一谈。
十三、线稿与深度视频,分别提供什么参考
此前的深度视频与现在的线稿视频,可以看作两类不同的结构表达:
| 素材类型 | 主要表达 | 适合关注的内容 |
|---|---|---|
| 深度视频 | 远近关系、空间层次 | 前后景、空间布局、主体距离变化 |
| 线稿视频 | 边缘、轮廓、构图变化 | 主体形状、姿态轮廓、结构随时间的变化 |
例如,想保留一个镜头的前后景关系,可以考虑深度参考;想保留产品轮廓或人物动作的外形变化,可以考虑线稿参考。
“具有参考能力”指这些输出可以成为结构参考素材。 能否用于生成控制,还取决于下游工作流是否支持对应输入,以及它对预处理方式、分辨率、帧率和时间对齐的要求。
当前功能完成的是浏览器内的线稿提取与素材生成,并不代表已经自动接通所有图像或视频生成模型。
对于要求更干净语义轮廓的场景,传统边缘检测也可能不够,需要另外评估学习式线稿模型。
十四、功能入口与源码
当前入口位于:
效果 → 线稿图
支持:
- 图片生成 PNG;
- 视频生成 24 fps WebM;
- 细节、线条粗细与黑白反转;
- 参数实时预览;
- 当前裁剪与速度曲线时间映射;
- 对应源音频保留;
- 取消任务与素材保存;
- 13 种界面语言及手机布局。
核心源码入口:
src/lib/lineArt.js
渲染器、媒体读取、视频生成与资源释放
src/components/LineArtPanel.jsx
参数面板、预览、任务状态与取消
src/i18nLineArt.js
多语言文案
从深度视频到线稿视频,我们希望逐步让编辑器具备生成“中间结构素材”的能力。原始视频可以继续承担内容表达,深度和轮廓则提供不同维度的参考,供后续剪辑、视觉实验和支持相应条件的生成工作流使用。
欢迎体验,也欢迎提交复杂背景、快速运动和细小轮廓等实际案例,帮助我们继续改进处理质量。
GitHub:MartinDelophy/ai-video-editor
在线体验:Timeline Studio