CVAT FFmpeg 升级兼容性方案:用静态缓存脚本复现视频分帧(utils/ffmpeg_compatibility 详解)
发布时间:2026/9/14 20:51:46 作者:尧图编辑部 阅读量:1,286
)
CVAT FFmpeg 升级兼容性方案用静态缓存脚本复现视频分帧utils/ffmpeg_compatibility 详解【免费下载链接】cvatComputer Vision Annotation Tool (CVAT) is a leading platform for building high-quality visual datasets for vision AI. It offers open-source, cloud, and enterprise products, as well as labeling services, for image, video, and 3D annotation with AI-assisted labeling, quality assurance, team collaboration, analytics, and developer APIs.项目地址: https://gitcode.com/GitHub_Trending/cvat/cvat本文基于 CVAT 仓库中的 utils/ffmpeg_compatibility/README.md 及其配套脚本讲解 CVAT 2.47.0 将 FFmpeg 从 4.3.1 升级到 8.0 后可能出现的“视频分帧不一致”问题以及如何通过官方提供的generate_chunks_for_task工具将指定任务切换为静态帧缓存static chunks用旧版 FFmpeg 重新生成帧数据。读完本文你将理解静态缓存与按需解码两种存储方式的差异、该脚本的完整执行链路以及它的使用前提与限制。背景CVAT 2.47.0 升级了视频解码依赖 FFmpegCVAT 处理视频任务时依赖 FFmpeg 将视频文件逐帧切分为图片帧。按照 utils/ffmpeg_compatibility/README.md 的说明In version 2.47.0, CVAT upgraded the FFMPEG library it uses to split videos into frames from 4.3.1 to 8.0.这一升级在仓库的 Dockerfile 中得到印证基础镜像通过构建阶段源码编译 FFmpeg并将版本固定为环境变量FFMPEG_VERSION8.0输出目录为/opt/ffmpegARG PREFIX/opt/ffmpeg ENV FFMPEG_VERSION8.0 \ ... WORKDIR /tmp/ffmpeg RUN curl -sL https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz --output - | ... COPY --frombuild-image-av /opt/ffmpeg/lib /usr/lib为什么不同 FFmpeg 版本会分帧出不同结果README 中明确指出There is a small chance that some video files may be split into frames differently by different FFmpeg versions.即不同 FFmpeg 版本对部分视频文件的解码帧数、帧切分边界可能存在细微差异。对于依赖帧序号稳定性的标注与质检场景升级后重新解码同一视频可能出现帧数或与升级前不一致的情况。针对这种情况README 给出的对策是this script may be used to switch a task to static chunks and generate frames with the old FFMPEG version.也就是借助旧版 CVAT 镜像内置的旧版 FFmpeg把目标任务的视频重新切帧并固化为静态帧缓存文件之后服务端读取帧时直接读文件不再实时调用 FFmpeg 解码从而消除版本差异带来的影响。方案原理把任务从“缓存解码”切换为“静态帧文件”理解这个脚本需要先了解 CVAT 引擎层对媒体数据的两种存储方式。在 cvat/apps/engine/models.py 中定义了Data模型的存储方式枚举class StorageMethodChoice(str, Enum): CACHE cache FILE_SYSTEM file_systemCACHE按需解码。前端请求某个区间的帧时服务端实时调用媒体提取器对视频即 FFmpeg 解码产生帧数据FILE_SYSTEM静态缓存。视频在任务创建时就被完整切帧并写入静态 chunk 文件原始质量与压缩质量各一份后续所有帧读取都来自文件。静态 chunk 的生成入口是 cvat/apps/engine/task.py 中的_create_static_chunks函数。在任务导入的主流程里task.py只有当配置settings.MEDIA_CACHE_ALLOW_STATIC_CACHE开启且storage_method为FILE_SYSTEM时才会调用if ( settings.MEDIA_CACHE_ALLOW_STATIC_CACHE and db_data.storage_method models.StorageMethodChoice.FILE_SYSTEM ): _create_static_chunks(db_task, media_extractorextractor, upload_dirupload_dir)_create_static_chunks内部按 segment 切分 chunk多线程地调用save_as_chunk分别为FrameQuality.ORIGINAL原始质量与FrameQuality.COMPRESSED压缩质量写入静态 chunk 文件。ffmpeg_compatibility脚本正是复用了这条既有链路只不过把“媒体提取器”替换成了旧镜像里的旧版 FFmpeg 解码器。使用方式一条 docker compose 命令前置条件README 中的注意事项原文保留NOTE: This option requires administrator access to the server instance. If you do not have such access, please try to contact the server administration.即该操作需要对 CVAT 服务器实例有管理员权限需要访问 docker compose 环境、挂载数据卷并直连数据库。操作步骤如果你的 CVAT 是通过 docker 部署的在仓库根目录下执行docker compose \ -f docker-compose.yml \ \ # optionally -f docker-compose.dev.yml \ -f ./utils/ffmpeg_compatibility/docker-compose.yml \ run --rm generate_chunks_for_task task_id其中task_id是要处理的任务 ID整数。如果是开发环境部署可加上-f docker-compose.dev.yml对应 README 中注释掉的那一行。配套 compose 文件做了什么utils/ffmpeg_compatibility/docker-compose.yml 定义了一个一次性服务generate_chunks_for_taskservices: generate_chunks_for_task: image: cvat/server:v2.46.0 environment: CVAT_POSTGRES_HOST: cvat_db depends_on: - cvat_db volumes: - cvat_data:/home/django/data - ./utils/ffmpeg_compatibility/switch_task_to_static_cache.py:/home/django/switch_task_to_static_cache.py:ro networks: - cvat entrypoint: [python, switch_task_to_static_cache.py]关键设计点镜像固定为cvat/server:v2.46.0——即 FFmpeg 升级之前的旧版发行版。脚本必须跑在旧镜像里才能用旧版 FFmpeg 重新切帧CVAT_POSTGRES_HOST: cvat_db与depends_on: cvat_db让一次性容器复用主实例的cvat_db数据库服务cvat网络内解析操作真实任务数据cvat_data:/home/django/data挂载与主实例相同的数据卷保证能访问到原始上传的视频文件与静态缓存目录脚本以只读方式挂载进容器并作为entrypoint直接以python执行容器随run --rm用完即删。脚本实现详解switch_task_to_static_cache.pyutils/ffmpeg_compatibility/switch_task_to_static_cache.py 是整个工具的主体约 107 行。文件开头的注释解释了为何它“看起来不走常规范式”# This script is intended to be used with an old release of CVAT, so it uses the internal # API as it existed in that release. Because of this, we cannot use Pylint, since it will attempt # to match the API calls against the current API and report spurious problems. # pylint: disableall即该脚本面向的是旧版 CVATv2.46.0的内部 API若用当前代码库去静态检查会误报因此整体禁用 pylint。下面按执行顺序拆解。1. 迁移版本守门确保运行在预期的旧版数据库结构上EXPECTED_LAST_ENGINE_MIGRATION 0095_fix_related_names def _ensure_last_engine_applied_migration_name(): recorder MigrationRecorder(connection) app_name engine applied_migrations_names list( recorder.Migration.objects.filter(appapp_name).values_list(name, flatTrue) ) ... assert highest_by_number EXPECTED_LAST_ENGINE_MIGRATION, (...)脚本启动后第一件事是用 Django 的MigrationRecorder查询engine应用已应用的迁移并按编号取最大值断言其等于0095_fix_related_names。若数据库结构与此不符例如连接到了更新版本的实例脚本会中止并提示人工核对后更新脚本中的期望值。这是防止“旧脚本 新库结构”错配的第一道保险。2. 构造与旧版一致的媒体提取器def _build_extractor(data: models.Data): details { source_path: [os.path.join(data.get_upload_dirname(), data.video.path)], step: data.get_frame_step(), start: data.start_frame, stop: data.stop_frame, } return MEDIA_TYPES[video]extractor从Data模型取出视频在上传目录中的路径、帧步长get_frame_step()与起止帧实例化MEDIA_TYPES[video][extractor]——即旧版代码中基于旧 FFmpeg 的视频提取器。后续所有帧都由它解码这是“用旧 FFMPEG 版本生成帧”的落点。3. 校验任务类型清理旧的静态缓存assert ( task.mode interpolation ), fTask #{task_id} is not a video task (mode{task.mode}).只有mode interpolation的任务才是视频任务否则直接报错退出。def _cleanup_static_cache(data: models.Data): for folder in (data.get_compressed_cache_dirname(), data.get_original_cache_dirname()): if os.path.exists(folder): shutil.rmtree(folder) os.makedirs(folder)在重新切帧前先删除该任务的压缩/原始静态缓存目录并重建避免新旧帧文件混杂。4. 在主事务中切换存储方式并生成静态 chunkwith transaction.atomic(): task: models.Task models.Task.objects.select_for_update().get(pktask_id) ... data: models.Data task.data extractor _build_extractor(data) data.storage_method models.StorageMethodChoice.FILE_SYSTEM _cleanup_static_cache(data) with patch(cvat.apps.engine.task.ImportRQMeta, return_valueMock()) as mock: type(mock.for_job.return_value).task_progress _PropertyMock() _create_static_chunks( task, media_extractorextractor, upload_dirdata.get_upload_dirname() ) data.save(update_fields[storage_method])要点有三select_for_update()对任务行加锁防止脚本运行期间任务被并发修改storage_method切换为FILE_SYSTEM这是核心变更——切换后服务端读取帧不再走 FFmpeg 实时解码而是读静态 chunk 文件从根本上锁定本次由旧版 FFmpeg 产出的帧数据Mock 掉ImportRQMeta_create_static_chunks内部通过 RQ 任务元数据ImportRQMeta更新“准备数据 chunk”的进度。本脚本不是跑在 RQ worker 里没有真实任务上下文因此用unittest.mock.patch替换它并把task_progress属性挂到一个自定义_PropertyMock上——进度值一被写入就驱动tqdm进度条刷新于是命令行里能看到分帧进度with tqdm(total1.0, bar_format{l_bar}{bar}| {n:.2f}/{total:.2f}) as pbar: class _PropertyMock(PropertyMock): def __set__(self, instance, value): pbar.n value pbar.refresh()全部完成后输出Task #{task_id}: switched to static cache.适用前提与限制结合 README 与源码使用该工具前需要确认管理员权限必须能操作部署 CVAT 的 docker compose 环境并访问cvat_db、cvat_data卷仅支持视频任务脚本断言task.mode interpolation图片/点云/音频类任务会直接报错数据库迁移版本约束当前脚本期望engine应用最后应用的迁移为0095_fix_related_names数据库结构不匹配时会中止并要求人工核对见 switch_task_to_static_cache.py镜像版本耦合compose 文件固定使用cvat/server:v2.46.0旧镜像docker-compose.yml。脚本依赖的是该旧发行版的内部 APIcvat.apps.engine.task._create_static_chunks等若更换镜像或升级目标实例需相应调整脚本与镜像版本执行后任务行为变化任务切换为FILE_SYSTEM存储方式后帧读取走静态缓存文件而非实时解码磁盘占用会相应增加相关源码索引文件说明utils/ffmpeg_compatibility/README.md本工具的使用说明原文档utils/ffmpeg_compatibility/docker-compose.yml一次性容器定义固定旧版镜像 v2.46.0utils/ffmpeg_compatibility/switch_task_to_static_cache.py切换静态缓存并重新切帧的主脚本cvat/apps/engine/models.pyStorageMethodChoiceCACHE / FILE_SYSTEM枚举定义cvat/apps/engine/task.py_create_static_chunks静态 chunk 生成实现Dockerfile当前主镜像中 FFmpeg 8.0 的构建配置该工具体现了 CVAT 对解码器大版本升级的工程化兜底思路不在业务层做版本兼容而是提供“回到旧解码器重新固化数据”的旁路脚本用一次性的存储方式切换cache→file_system把历史帧数据与新版 FFmpeg 解耦。【免费下载链接】cvatComputer Vision Annotation Tool (CVAT) is a leading platform for building high-quality visual datasets for vision AI. It offers open-source, cloud, and enterprise products, as well as labeling services, for image, video, and 3D annotation with AI-assisted labeling, quality assurance, team collaboration, analytics, and developer APIs.项目地址: https://gitcode.com/GitHub_Trending/cvat/cvat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考