简介:CG提取工具是一套面向计算机图形学从业者、游戏逆向爱好者与美术资源管理人员的实用工具集,用于从游戏、动画及3D项目中导出图像序列、纹理贴图、3D模型与动画帧数据,解决跨平台资产共享、资源重用与逆向分析中的格式解析难题。压缩包共520个文件,约5.56MB,以325个txt说明文档与170个cui配置脚本为主体,辅以9个dll动态库、7个exe可执行程序及loc、db、xml等辅助文件,覆盖kirikiri2、RealLive、Leaf等多家引擎的解析配置,便于按引擎类型快速定位所需模块。目前已有4751人学习下载,适合具备一定文件格式理解能力的中高级用户参考。资源涉及文件结构解析、图像处理、3D模型格式转换、动画序列提取、纹理贴图导出、编解码与资源解密等技术点,可帮助读者理解CG资源提取的完整流程与工具组织方式,提升数字资源管理与创作效率。
1. 从一堆散装素材里把 CG 提取出来:这套工具到底解决什么问题
做视觉类项目的人多半遇到过这种场景:美术给了一批序列帧、视频组丢来几个 MP4、策划又塞了几张带黑边的设定图,要求你统一转成可用的 CG 素材。手动一帧帧抠、一张张裁,干到凌晨三点还容易漏帧。CG 提取工具就是冲着这个痛点来的——它把视频抽帧、图像裁剪、透明通道处理、批量重命名这几件事串成一条流水线,输入一个目录,输出规整的 PNG 序列或单帧图集。适合做动画预演、游戏过场拆解、AI 训练集清洗的从业者,也适合刚接触素材管线、想先跑通流程的新手。下面按「它怎么工作 → 怎么装怎么跑 → 参数怎么调 → 哪里容易翻车」的顺序拆一遍。
2. CG 提取的底层逻辑:抽帧、裁剪与通道处理怎么串起来
2.1 抽帧不是简单截图:时间轴采样与关键帧判定
很多人以为抽帧就是按秒截屏,实际上一段 24fps 的视频,每秒有 24 张画面,全抽出来既占空间又冗余。CG 提取工具通常提供三种采样模式:固定间隔、关键帧优先、场景切换检测。固定间隔适合做动态模糊分析,关键帧优先适合提取动作节点,场景切换检测则用于把不同镜头拆开。底层依赖的是解码器输出的时间戳(PTS)和帧类型标记(I/P/B 帧)。I 帧是完整画面,P/B 帧依赖前后帧预测,所以关键帧提取本质上是在筛选 I 帧。工具内部一般用 FFmpeg 的select滤镜或 OpenCV 的CAP_PROP_POS_FRAMES做定位,前者快但依赖外部二进制,后者纯 Python 但大文件容易内存吃紧。
常见做法是先用 FFmpeg 做粗筛,把 I 帧时间戳导成列表,再用 OpenCV 按列表精确读取。这样既避开了解码器重复寻址的开销,也方便后续做裁剪。参数上,-vsync 0能防止帧重复,-frame_pts 1让输出文件名带时间戳,省得自己写映射表。
2.2 裁剪与透明通道:把黑边和绿幕处理干净
抽出来的帧往往带黑边、水印或纯色背景。裁剪模块一般提供两种方式:固定像素裁剪和自动边界检测。固定裁剪就是给四个坐标,适合批量处理同一分辨率的素材;自动边界检测则通过扫描边缘像素的方差来判断内容区域,适合尺寸不统一的输入。透明通道处理分两类:一是把纯色背景转成 alpha 通道,二是直接读取带 alpha 的源文件。前者用色度键控(chroma key)思路,设定一个目标色和容差,把相近像素的 alpha 置零;后者依赖解码器支持,比如 PNG 序列或 ProRes 4444。
这里有个容易忽略的点:色度键控在 YUV 空间做比在 RGB 空间做更稳,因为亮度变化对色度影响小。工具如果只提供 RGB 阈值,遇到光照不均的绿幕就会边缘发灰。我一般会先转 YUV,对 U/V 分量设阈值,再回写 alpha。
2.3 批量重命名与元数据落盘
提取出来的帧如果叫frame_0001.png这种名字,后期合成时根本对不上号。工具通常支持模板化命名,比如{项目}_{镜头}_{时间戳}.png,时间戳可以取帧号、毫秒或 SMPTE 时间码。元数据落盘指的是把每帧对应的原始时间、帧类型、裁剪坐标写进一个 CSV 或 JSON,方便回溯。这一步看着不起眼,但当你需要把某一帧还原回原视频位置时,没有元数据就只能靠肉眼比对。
提示:元数据里的时间戳建议统一用毫秒整数,避免浮点误差导致对不齐。
3. 把工具跑起来:环境、命令与参数配置
3.1 环境准备与依赖安装
这套工具通常是 Python 写的,依赖 FFmpeg、OpenCV、Pillow 和 NumPy。FFmpeg 建议用系统包管理器装,不要用 pip 的ffmpeg-python代替二进制,因为抽帧性能差一截。OpenCV 用opencv-python-headless就行,服务器上不需要 GUI。下面是一段环境初始化脚本,注释里写了每个包的作用。
# 更新包索引并安装 FFmpeg(Ubuntu/Debian 系) sudo apt update sudo apt install -y ffmpeg # 创建虚拟环境,避免污染系统 Python python3 -m venv cg_env source cg_env/bin/activate # 安装核心依赖 pip install opencv-python-headless==4.9.0.80 # 解码与抽帧 pip install Pillow==10.3.0 # 图像裁剪与通道处理 pip install numpy==1.26.4 # 矩阵运算 pip install tqdm==4.66.4 # 进度条,批量处理时看进度逻辑说明:FFmpeg 负责视频解码和关键帧定位,OpenCV 负责精确读帧和图像变换,Pillow 处理 alpha 通道和保存,NumPy 做像素级运算。版本号不是必须一致,但 OpenCV 4.x 和 NumPy 1.26 搭配比较稳,2.x 的 NumPy 在某些旧版 OpenCV 上会报 ABI 错误。
参数说明:opencv-python-headless不带 GUI 依赖,体积小;如果你需要cv2.imshow调试,换成opencv-python。虚拟环境目录名随意,但建议和项目目录分开,方便清理。
3.2 抽帧命令与采样参数
假设输入是input.mp4,输出目录是frames/,下面这条命令按每秒 2 帧采样,输出 PNG 序列,并保留时间戳文件名。
ffmpeg -i input.mp4 \ -vf "fps=2" \ -vsync 0 \ -frame_pts 1 \ frames/frame_%05d.png逻辑说明:-vf "fps=2"表示每秒抽 2 帧,适合做动态分析;如果要关键帧优先,改成-vf "select='eq(pict_type,I)'" -vsync 0。-vsync 0禁止帧率同步,防止重复帧;-frame_pts 1让文件名里的编号对应原始 PTS,而不是重新编号。
参数说明:fps值越大抽得越密,但输出体积线性增长。做 AI 训练集一般 1~5 fps 够用;做动画拆解可以到 12 fps。frame_%05d里的%05d表示 5 位补零,超过 99999 帧会溢出,长视频建议用%06d。
3.3 裁剪与透明通道的 Python 实现
抽完帧后,用下面这段脚本做批量裁剪和绿幕抠除。输入目录frames/,输出目录cropped/,裁剪区域是左上角 (100, 50) 到右下角 (900, 700),绿幕目标色是 (0, 255, 0),容差 60。
import cv2 import numpy as np import os from tqdm import tqdm INPUT_DIR = "frames" OUTPUT_DIR = "cropped" CROP_BOX = (100, 50, 900, 700) # x1, y1, x2, y2 KEY_COLOR = np.array([0, 255, 0]) # BGR 顺序,注意不是 RGB TOLERANCE = 60 os.makedirs(OUTPUT_DIR, exist_ok=True) for fname in tqdm(os.listdir(INPUT_DIR)): if not fname.lower().endswith(".png"): continue img = cv2.imread(os.path.join(INPUT_DIR, fname), cv2.IMREAD_UNCHANGED) if img is None: continue # 裁剪:按坐标切片,注意 y 在前 x 在后 x1, y1, x2, y2 = CROP_BOX cropped = img[y1:y2, x1:x2] # 绿幕抠除:计算与目标色的欧氏距离,小于容差则 alpha 置零 if cropped.shape[2] == 3: cropped = cv2.cvtColor(cropped, cv2.COLOR_BGR2BGRA) diff = np.linalg.norm(cropped[:, :, :3].astype(np.int16) - KEY_COLOR, axis=2) mask = diff < TOLERANCE cropped[mask, 3] = 0 # 透明 cv2.imwrite(os.path.join(OUTPUT_DIR, fname), cropped)逻辑说明:cv2.imread用IMREAD_UNCHANGED保留 alpha 通道;裁剪时注意 OpenCV 的坐标是img[y, x],不是img[x, y],写反了会裁出奇怪区域。绿幕抠除用欧氏距离而不是逐通道阈值,能更好处理渐变边缘。astype(np.int16)防止 uint8 相减溢出。
参数说明:CROP_BOX的四个值按实际画面调整,可以先抽一帧用画图工具量。TOLERANCE越大抠得越狠,但容易误伤主体;绿幕均匀时 40~60 够用,光照不均时降到 30 并配合形态学腐蚀。KEY_COLOR是 BGR 顺序,如果你从设计稿拿到的是 RGB 值,记得反转。
3.4 批量重命名与元数据导出
裁剪完的帧如果还要按镜头分组,可以用下面这段脚本重命名并导出 CSV。假设文件名里已经带了原始帧号,我们按每 100 帧一个镜头分组。
import os import csv import re SRC_DIR = "cropped" DST_DIR = "final" META_PATH = "metadata.csv" SHOT_SIZE = 100 os.makedirs(DST_DIR, exist_ok=True) rows = [] files = sorted([f for f in os.listdir(SRC_DIR) if f.endswith(".png")]) for idx, fname in enumerate(files): shot_id = idx // SHOT_SIZE + 1 frame_num = int(re.search(r"(\d+)", fname).group(1)) new_name = f"shot{shot_id:03d}_frame{frame_num:05d}.png" os.rename(os.path.join(SRC_DIR, fname), os.path.join(DST_DIR, new_name)) rows.append({"shot": shot_id, "frame": frame_num, "file": new_name}) with open(META_PATH, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=["shot", "frame", "file"]) writer.writeheader() writer.writerows(rows)逻辑说明:sorted保证文件按编号顺序处理,避免操作系统返回乱序。re.search从文件名里提数字,如果文件名格式变了要同步改正则。CSV 用DictWriter写,字段顺序可控。
参数说明:SHOT_SIZE按实际镜头长度调整,动画一般 50~200 帧一个镜头。new_name模板可以改成带项目前缀,方便多批次合并。
4. 避坑与排查:抽帧、裁剪、通道处理里的五个常见翻车点
4.1 抽出来的帧全是重复的
现象:输出目录里相邻帧看起来一模一样,文件大小也接近。原因:-vsync默认是auto,在可变帧率视频上会补帧。解决:显式加-vsync 0,或者用-fps_mode passthrough(新版 FFmpeg)。如果还重复,检查源视频是不是 VFR,用ffprobe看r_frame_rate和avg_frame_rate是否一致。
4.2 裁剪坐标对不上,裁出黑边或切掉主体
现象:按设计稿坐标裁剪,结果偏了几十像素。原因:视频有旋转元数据或像素宽高比不是 1:1。解决:先用ffprobe看rotate和sample_aspect_ratio,有旋转就加-vf "transpose=1"先转正;宽高比非 1 时用-vf "scale=iw*sar:ih"校正。另外注意 OpenCV 读出来的尺寸可能和 FFmpeg 报告的不一致,以实际img.shape为准。
4.3 绿幕抠完边缘发灰或有残留
现象:主体边缘一圈半透明灰边,或者背景还有零星绿点。原因:RGB 空间做阈值,光照渐变导致边缘像素偏离目标色。解决:转 YUV 空间,对 U/V 分量设阈值,或者用cv2.inRange加形态学开运算去噪。残留绿点可以用cv2.morphologyEx做闭运算再膨胀 alpha 通道。
4.4 大批量处理时内存爆掉
现象:跑几千帧后进程被 kill,或者越来越慢。原因:OpenCV 的imread每次分配新数组,Python 垃圾回收不及时。解决:用with上下文或手动del img,每处理 500 帧调用一次gc.collect()。更稳的做法是用生成器逐帧读,不要一次性list(os.listdir)加载所有路径。
4.5 输出文件名冲突导致覆盖
现象:不同镜头的帧重名,后写的覆盖先写的。原因:重命名模板里没有唯一标识。解决:模板里加入镜头号或源文件哈希,比如{shot}_{frame}_{md5[:6]}.png。如果已经覆盖了,只能从原始视频重新抽,所以第一次跑建议先输出到临时目录,确认命名唯一后再移入正式目录。
注意:任何批量重命名操作前,先在小样本目录上跑一遍,确认文件名不冲突再全量执行。
5. 进阶技巧:用帧差法自动切分镜头并验证提取完整性
前面讲的都是按固定规则抽帧,但实际项目里镜头长度不一,固定间隔会浪费大量存储。更聪明的做法是用帧差法自动检测镜头切换,只在切换点附近抽帧。原理很简单:计算相邻帧的直方图差异或像素绝对差,超过阈值就判定为新镜头。下面这段脚本在抽帧阶段就做镜头切分,输出每个镜头的起始帧和结束帧。
import cv2 import numpy as np def detect_shots(video_path, threshold=30.0, min_shot_len=10): cap = cv2.VideoCapture(video_path) prev_hist = None shots = [] start = 0 idx = 0 while True: ret, frame = cap.read() if not ret: break # 转灰度并计算直方图,降低计算量 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) hist = cv2.calcHist([gray], [0], None, [64], [0, 256]) hist = cv2.normalize(hist, hist).flatten() if prev_hist is not None: # 用相关性比较直方图,值越小差异越大 diff = 1.0 - cv2.compareHist(prev_hist, hist, cv2.HISTCMP_CORREL) if diff > threshold / 100.0 and (idx - start) >= min_shot_len: shots.append((start, idx - 1)) start = idx prev_hist = hist idx += 1 shots.append((start, idx - 1)) cap.release() return shots # 调用示例 shots = detect_shots("input.mp4", threshold=35.0, min_shot_len=15) for i, (s, e) in enumerate(shots): print(f"镜头 {i+1}: 帧 {s} 到 {e},共 {e-s+1} 帧")逻辑说明:cv2.calcHist把每帧压成 64 维直方图,比逐像素比较快一个数量级。compareHist用相关性,返回值 1 表示完全相同,0 表示无关。threshold是差异百分比,35 表示直方图相关性低于 0.65 就切镜头。min_shot_len防止把闪光或快速运动误判为切镜头。
参数说明:threshold越大越不敏感,动画一般 30~40,实拍可以到 50。min_shot_len按帧率算,24fps 下 15 帧约 0.6 秒,低于这个时长的镜头多半是误检。直方图维度 64 是速度和精度的折中,调到 256 更准但慢一倍。
拿到镜头列表后,可以只对每个镜头的首帧、中间帧和尾帧做提取,存储量能降一个数量级。验证提取完整性也有个笨办法但很管用:把提取出的帧按顺序拼回视频,用ffmpeg -framerate 24 -i final/shot%03d_frame%05d.png -c:v libx264 out.mp4,然后和原视频并排播放,肉眼扫一遍有没有跳帧或错位。我一般会在拼接时加-vf "drawtext=text='%{frame_num}'"把帧号烧进去,方便定位问题帧。
从那以后我每次跑完提取,都强制走一遍「拼回视频 + 抽检 10 帧元数据」的流程,宁可多花十分钟,也不想到合成阶段才发现少了一帧。希望帮到你。
本文还有配套的精品资源,点击获取