VirtualDub 的动态裁剪






4.73/5 (9投票s)
本文介绍了一个为 VirtualDub 软件添加新的动态裁剪功能的项目。
引言
有一天,我偶然看到了一段视频,那是一段我们小时候制作的老式 8mm 胶片转录的视频。这次转录制作得并不专业,没有使用任何特殊设备,甚至连三脚架都没有,因此存在许多问题。其中最糟糕的是转录的屏幕区域出现零星的、未补偿的运动。我试图寻找一个能够动态裁剪运动区域的软件,但没有成功。没有免费软件具备这样的功能,而所有商业软件都过于昂贵。于是我产生了一个自己编写这样的软件的想法。我选择VirtualDub作为基础,因为它已经提供了一些静态裁剪功能。我需要做的就是让它动态化,即裁剪区域的位置可以作为时间的函数来定义。我决定让它尽可能灵活,以便用户可以编辑、保存和恢复动态裁剪设置,并将其设计为一个可扩展的功能。
设计
该解决方案的“主力”是在 ClippingControl.h 中定义的新的 IVDDynClippingStorage
接口。
class VDINTERFACE IVDDynClippingStorage : public vdrefcounted<IVDRefCount> {
public:
// Initialize clipping map
virtual void Init(int sourceW, int sourceH) = 0;
// Get source width
virtual int GetWidth() = 0;
// Get source height
virtual int GetHeight() = 0;
// Load clipping map from a file
virtual VDStringW Load(wchar_t *filename, HWND parent) = 0;
// Save clipping map to a file
virtual VDStringW Dump(wchar_t *filename, HWND parent) = 0;
// Display configuration interface
virtual void Configure(HWND parent) = 0;
// Set clipping bounds for position
virtual int SetClipBounds(sint64 pos, const vdrect32& r) = 0;
// Get clipping bounds for position
virtual int GetClipBounds(sint64 pos, vdrect32& r) = 0;
// Get position for clipping map index
virtual sint64 GetClipPos(int idx) = 0;
// Get clipping map size
virtual int GetTotalClipPos() = 0;
// Get the resulted crop size
virtual void GetCrop(vdrect32& r) = 0;
// Delete clipping map entry by index
virtual void DelClipBounds(int idx) = 0;
// Crop pixmap for given position
virtual bool DoCrop(VDPixmap& dst, sint64 pos, vdrect32& crop_area) = 0;
// Add/drop range to/from clipping map
virtual void ChangeTimeline(sint64 pos, sint64 nframes, bool add) = 0;
};
它定义了一组用于管理裁剪图和执行实际动态裁剪的方法。ClippingControl.cpp 中定义的 VDDynClippingStorage
类实现了该接口。可以为每个 FilterInstance
创建一个该类的实例(然而,在一个过滤器链中需要多个具有动态裁剪功能的过滤器是非常可疑的)。
下图显示了该解决方案组件之间的关系
+--------------+ +--------------+
| | VDGetDynClippingStorage() | |
| Script.cpp +-----------+ | Job.cpp |
| | | | |
| | +--|---------------------------+ | |
| | | | | | |
| | | | ClippingControl.cpp | | |
| | | V | | |
| | Load() | +--------------------------+ | Dump() | |
| +--------->| IVDDynClippingStorage |<---------| |
| | | | +----------------------+ | | | |
+------------+-+ | | | VDDynClippingStorage | | | +--+-----------+
| | | | | | | |
SetClippingStorage() | | | | | | GetClippingStorage()
| | | | | | | |
| +------>| +----------------------+ | | |
| | | +---^-------^----------^---+ | |
| | | | | | | |
| DoCrop() | Configure() | | | |
| | | | GetClipBounds() | | |
| | | | | SetClipBounds()| |
| | | | | | | |
| | | +---|-------|----------|---+ | |
| | | | |IVDClippingControl| | | |
| | | | +-+-------+----------+-+ | | |
| | | | | VDClippingControl | | | |
| | | | | | | | |
| | | | +----------------------+ | | |
| | | +----^----------------^----+ | |
| | +------|----------------|------+ |
| | | | |
| | GetClippingStorage() SetClippingStorage() |
| | | | |
| | +------+----------------+------+ |
| | | | |
| | | Filtdlg.cpp | |
| | | | |
| | +------+----------------+------+ |
| | | | |
| | SetClippingStorage() GetClippingStorage() |
| | | | |
+--V----+------------V----------------V------------------V--+
| |
| FilterInstance.cpp |
| |
+-----------------------------------------------------------+
裁剪控件的基本功能也进行了一些更改。这些更改与动态裁剪没有严格关系,但非常方便。
- 从现在开始,您不仅可以通过左键拖动裁剪区域的侧边和角来拉伸它,还可以通过右键单击来移动整个裁剪区域!尝试一下,您会发现当您逐帧调整裁剪区域的位置时,它多么有用。
- 一个新的“锁定纵横比”复选框允许裁剪区域保持原始视频的纵横比。建议在启用动态裁剪时始终勾选它。
补丁会影响以下 VirtualDub 组件:
- 裁剪控件(ClippingControl.h/.cpp, FiltDlg.cpp, 资源)
- 过滤器实例类(FilterInstance.h/.cpp)
- 处理设置的保存(Job.cpp)和加载(Script.cpp)
以下项目文件已修改:
- src\virtualdub\h\clippingcontrol.h
- src\virtualdub\h\filterinstance.h
- src\virtualdub\res\resource.h
- src\virtualdub\res\virtualdub.rc
- src\virtualdub\source\clippingcontrol.cpp
- src\virtualdub\source\filtdlg.cpp
- src\virtualdub\source\filterinstance.cpp
- src\virtualdub\source\job.cpp
- src\virtualdub\source\script.cpp
构建
您应该修补 VirtualDub
的源树并重新构建它。
有 D ncropping 两种方法可以修补源树。
- 您可以将存档解压到
VirtualDub
1.9.9 源树的根目录,覆盖其文件,或者 - 使用提供的 dyncropping.patch 文件,其中包含由 GNU diff 生成的 diff 信息。在
VirtualDub
源树的根目录下,使用 Win32 版本的 patch 运行它,如下所示:C:\VirtualDub-1.9.9-src> patch -p1 -i dyncropping.patch
这对于非 1.9.9 版本的 VirtualDub
也可能效果很好。
之后,像往常一样运行构建过程。
运行
加载视频,添加任何视频过滤器,选择“裁剪...”,然后在“过滤器输入裁剪”控件中勾选“动态裁剪”复选框。为了获得更好的结果,请也勾选“锁定纵横比”。

在裁剪控件中逐帧浏览视频,并根据您的需要调整裁剪区域的大小和位置。所有位置和大小的更改都将保存在渲染图谱中。如果您将视频倒回到开始并再次逐帧浏览,您会注意到裁剪区域如何移动以跟随您的调整!您可以点击“选项...”并选择所需的设置。

完成后,点击“确定”关闭“过滤器输入裁剪”,再点击“确定”关闭“过滤器”。运行“预览过滤后的...”并在左侧和右侧的 UI 面板上观察正在发生的事情。使用“另存为 AVI...”保存最终渲染。
结论
总的来说,此功能可用于手动“运动后补偿”和稳定,适用于以下情况:
- 使用没有硬件运动补偿功能的摄像机拍摄的老视频
- 在复杂运动情况下拍摄的视频(在汽车、船上、奔跑中)
- 儿童或有残疾的人拍摄的视频
- “嘈杂”、黑暗、低对比度的视频,在这种情况下自动算法通常会失败或产生不良结果
此外,它还可以用于一些特殊的视频效果(数字变焦、拉伸变形等)。
它非常基础,但它能完成它应该做的事情。还有几点我想分享一下。
目前只实现了裁剪图顶点之间的线性插值。可以尝试实现基于三次样条的插值,看看它是否能改善渲染结果并减少抖动。此外,还可以尝试在IVDDynClippingStorage::DoCrop
中实现一个实际的运动补偿算法,该算法将缓存和分析帧。
还有一个“无插值”模式,可用于包含需要静态裁剪的场景集的视频,这避免了逐个剪切/裁剪/粘贴它们。
使用线性插值,通常需要两次裁剪调整才能获得最佳结果。
重采样选择有三种设置:原始帧大小、最大裁剪大小和自定义。在任何情况下,结果都将被重采样到可被 16 整除的圆整大小,以实现与压缩算法的通用兼容性。尝试将自定义大小强制设置为非 16 整除或大于源大小的值将导致错误。
VirtualDub
内置了两种重采样算法:“双线性”和“最近邻”。“三次样条”不是在 VirtualDub
的核心中实现的,而是作为插件实现的,因此不受支持。
当您使用右键拖动裁剪区域时,可选地在其上显示一个网格线似乎很有用(也许当您按下并按住右键,然后再按左键时它会显示出来)。这将允许更精确地定位裁剪区域。我没有实现它,而是将其作为对其他人的想法留下了。
IVDDynClippingStorage::ChangeTimeline
方法在 VDDynClippingStorage
中定义并实现,但它目前没有被使用——以防万一,这可能是一个未来的扩展。
所有裁剪图顶点索引都是从 1 开始的。也就是说,您在界面上看到的索引就是您在图谱中的索引。