
簡(jiǎn)介CG提取工具是一套面向計(jì)算機(jī)圖形學(xué)從業(yè)者、游戲逆向愛(ài)好者與美術(shù)資源管理人員的實(shí)用工具集用于從游戲、動(dòng)畫及3D項(xiàng)目中導(dǎo)出圖像序列、紋理貼圖、3D模型與動(dòng)畫幀數(shù)據(jù)解決跨平臺(tái)資產(chǎn)共享、資源重用與逆向分析中的格式解析難題。壓縮包共520個(gè)文件約5.56MB以325個(gè)txt說(shuō)明文檔與170個(gè)cui配置腳本為主體輔以9個(gè)dll動(dòng)態(tài)庫(kù)、7個(gè)exe可執(zhí)行程序及l(fā)oc、db、xml等輔助文件覆蓋kirikiri2、RealLive、Leaf等多家引擎的解析配置便于按引擎類型快速定位所需模塊。目前已有4751人學(xué)習(xí)下載適合具備一定文件格式理解能力的中高級(jí)用戶參考。資源涉及文件結(jié)構(gòu)解析、圖像處理、3D模型格式轉(zhuǎn)換、動(dòng)畫序列提取、紋理貼圖導(dǎo)出、編解碼與資源解密等技術(shù)點(diǎn)可幫助讀者理解CG資源提取的完整流程與工具組織方式提升數(shù)字資源管理與創(chuàng)作效率。1. 從一堆散裝素材里把 CG 提取出來(lái)這套工具到底解決什么問(wèn)題做視覺(jué)類項(xiàng)目的人多半遇到過(guò)這種場(chǎng)景美術(shù)給了一批序列幀、視頻組丟來(lái)幾個(gè) MP4、策劃又塞了幾張帶黑邊的設(shè)定圖要求你統(tǒng)一轉(zhuǎn)成可用的 CG 素材。手動(dòng)一幀幀摳、一張張裁干到凌晨三點(diǎn)還容易漏幀。CG 提取工具就是沖著這個(gè)痛點(diǎn)來(lái)的——它把視頻抽幀、圖像裁剪、透明通道處理、批量重命名這幾件事串成一條流水線輸入一個(gè)目錄輸出規(guī)整的 PNG 序列或單幀圖集。適合做動(dòng)畫預(yù)演、游戲過(guò)場(chǎng)拆解、AI 訓(xùn)練集清洗的從業(yè)者也適合剛接觸素材管線、想先跑通流程的新手。下面按「它怎么工作 → 怎么裝怎么跑 → 參數(shù)怎么調(diào) → 哪里容易翻車」的順序拆一遍。2. CG 提取的底層邏輯抽幀、裁剪與通道處理怎么串起來(lái)2.1 抽幀不是簡(jiǎn)單截圖時(shí)間軸采樣與關(guān)鍵幀判定很多人以為抽幀就是按秒截屏實(shí)際上一段 24fps 的視頻每秒有 24 張畫面全抽出來(lái)既占空間又冗余。CG 提取工具通常提供三種采樣模式固定間隔、關(guān)鍵幀優(yōu)先、場(chǎng)景切換檢測(cè)。固定間隔適合做動(dòng)態(tài)模糊分析關(guān)鍵幀優(yōu)先適合提取動(dòng)作節(jié)點(diǎn)場(chǎng)景切換檢測(cè)則用于把不同鏡頭拆開。底層依賴的是解碼器輸出的時(shí)間戳PTS和幀類型標(biāo)記I/P/B 幀。I 幀是完整畫面P/B 幀依賴前后幀預(yù)測(cè)所以關(guān)鍵幀提取本質(zhì)上是在篩選 I 幀。工具內(nèi)部一般用 FFmpeg 的select濾鏡或 OpenCV 的CAP_PROP_POS_FRAMES做定位前者快但依賴外部二進(jìn)制后者純 Python 但大文件容易內(nèi)存吃緊。常見做法是先用 FFmpeg 做粗篩把 I 幀時(shí)間戳導(dǎo)成列表再用 OpenCV 按列表精確讀取。這樣既避開了解碼器重復(fù)尋址的開銷也方便后續(xù)做裁剪。參數(shù)上-vsync 0能防止幀重復(fù)-frame_pts 1讓輸出文件名帶時(shí)間戳省得自己寫映射表。2.2 裁剪與透明通道把黑邊和綠幕處理干凈抽出來(lái)的幀往往帶黑邊、水印或純色背景。裁剪模塊一般提供兩種方式固定像素裁剪和自動(dòng)邊界檢測(cè)。固定裁剪就是給四個(gè)坐標(biāo)適合批量處理同一分辨率的素材自動(dòng)邊界檢測(cè)則通過(guò)掃描邊緣像素的方差來(lái)判斷內(nèi)容區(qū)域適合尺寸不統(tǒng)一的輸入。透明通道處理分兩類一是把純色背景轉(zhuǎn)成 alpha 通道二是直接讀取帶 alpha 的源文件。前者用色度鍵控chroma key思路設(shè)定一個(gè)目標(biāo)色和容差把相近像素的 alpha 置零后者依賴解碼器支持比如 PNG 序列或 ProRes 4444。這里有個(gè)容易忽略的點(diǎn)色度鍵控在 YUV 空間做比在 RGB 空間做更穩(wěn)因?yàn)榱炼茸兓瘜?duì)色度影響小。工具如果只提供 RGB 閾值遇到光照不均的綠幕就會(huì)邊緣發(fā)灰。我一般會(huì)先轉(zhuǎn) YUV對(duì) U/V 分量設(shè)閾值再回寫 alpha。2.3 批量重命名與元數(shù)據(jù)落盤提取出來(lái)的幀如果叫frame_0001.png這種名字后期合成時(shí)根本對(duì)不上號(hào)。工具通常支持模板化命名比如{項(xiàng)目}_{鏡頭}_{時(shí)間戳}.png時(shí)間戳可以取幀號(hào)、毫秒或 SMPTE 時(shí)間碼。元數(shù)據(jù)落盤指的是把每幀對(duì)應(yīng)的原始時(shí)間、幀類型、裁剪坐標(biāo)寫進(jìn)一個(gè) CSV 或 JSON方便回溯。這一步看著不起眼但當(dāng)你需要把某一幀還原回原視頻位置時(shí)沒(méi)有元數(shù)據(jù)就只能靠肉眼比對(duì)。提示元數(shù)據(jù)里的時(shí)間戳建議統(tǒng)一用毫秒整數(shù)避免浮點(diǎn)誤差導(dǎo)致對(duì)不齊。3. 把工具跑起來(lái)環(huán)境、命令與參數(shù)配置3.1 環(huán)境準(zhǔn)備與依賴安裝這套工具通常是 Python 寫的依賴 FFmpeg、OpenCV、Pillow 和 NumPy。FFmpeg 建議用系統(tǒng)包管理器裝不要用 pip 的ffmpeg-python代替二進(jìn)制因?yàn)槌閹阅懿钜唤亍penCV 用opencv-python-headless就行服務(wù)器上不需要 GUI。下面是一段環(huán)境初始化腳本注釋里寫了每個(gè)包的作用。# 更新包索引并安裝 FFmpegUbuntu/Debian 系 sudo apt update sudo apt install -y ffmpeg # 創(chuàng)建虛擬環(huán)境避免污染系統(tǒng) Python python3 -m venv cg_env source cg_env/bin/activate # 安裝核心依賴 pip install opencv-python-headless4.9.0.80 # 解碼與抽幀 pip install Pillow10.3.0 # 圖像裁剪與通道處理 pip install numpy1.26.4 # 矩陣運(yùn)算 pip install tqdm4.66.4 # 進(jìn)度條批量處理時(shí)看進(jìn)度邏輯說(shuō)明FFmpeg 負(fù)責(zé)視頻解碼和關(guān)鍵幀定位OpenCV 負(fù)責(zé)精確讀幀和圖像變換Pillow 處理 alpha 通道和保存NumPy 做像素級(jí)運(yùn)算。版本號(hào)不是必須一致但 OpenCV 4.x 和 NumPy 1.26 搭配比較穩(wěn)2.x 的 NumPy 在某些舊版 OpenCV 上會(huì)報(bào) ABI 錯(cuò)誤。參數(shù)說(shuō)明opencv-python-headless不帶 GUI 依賴體積小如果你需要cv2.imshow調(diào)試換成opencv-python。虛擬環(huán)境目錄名隨意但建議和項(xiàng)目目錄分開方便清理。3.2 抽幀命令與采樣參數(shù)假設(shè)輸入是input.mp4輸出目錄是frames/下面這條命令按每秒 2 幀采樣輸出 PNG 序列并保留時(shí)間戳文件名。ffmpeg -i input.mp4 \ -vf fps2 \ -vsync 0 \ -frame_pts 1 \ frames/frame_%05d.png邏輯說(shuō)明-vf fps2表示每秒抽 2 幀適合做動(dòng)態(tài)分析如果要關(guān)鍵幀優(yōu)先改成-vf selecteq(pict_type,I) -vsync 0。-vsync 0禁止幀率同步防止重復(fù)幀-frame_pts 1讓文件名里的編號(hào)對(duì)應(yīng)原始 PTS而不是重新編號(hào)。參數(shù)說(shuō)明fps值越大抽得越密但輸出體積線性增長(zhǎng)。做 AI 訓(xùn)練集一般 1~5 fps 夠用做動(dòng)畫拆解可以到 12 fps。frame_%05d里的%05d表示 5 位補(bǔ)零超過(guò) 99999 幀會(huì)溢出長(zhǎng)視頻建議用%06d。3.3 裁剪與透明通道的 Python 實(shí)現(xiàn)抽完幀后用下面這段腳本做批量裁剪和綠幕摳除。輸入目錄frames/輸出目錄cropped/裁剪區(qū)域是左上角 (100, 50) 到右下角 (900, 700)綠幕目標(biāo)色是 (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_okTrue) 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 # 裁剪按坐標(biāo)切片注意 y 在前 x 在后 x1, y1, x2, y2 CROP_BOX cropped img[y1:y2, x1:x2] # 綠幕摳除計(jì)算與目標(biāo)色的歐氏距離小于容差則 alpha 置零 if cropped.shape[2] 3: cropped cv2.cvtColor(cropped, cv2.COLOR_BGR2BGRA) diff np.linalg.norm(cropped[:, :, :3].astype(np.int16) - KEY_COLOR, axis2) mask diff TOLERANCE cropped[mask, 3] 0 # 透明 cv2.imwrite(os.path.join(OUTPUT_DIR, fname), cropped)邏輯說(shuō)明cv2.imread用IMREAD_UNCHANGED保留 alpha 通道裁剪時(shí)注意 OpenCV 的坐標(biāo)是img[y, x]不是img[x, y]寫反了會(huì)裁出奇怪區(qū)域。綠幕摳除用歐氏距離而不是逐通道閾值能更好處理漸變邊緣。astype(np.int16)防止 uint8 相減溢出。參數(shù)說(shuō)明CROP_BOX的四個(gè)值按實(shí)際畫面調(diào)整可以先抽一幀用畫圖工具量。TOLERANCE越大摳得越狠但容易誤傷主體綠幕均勻時(shí) 40~60 夠用光照不均時(shí)降到 30 并配合形態(tài)學(xué)腐蝕。KEY_COLOR是 BGR 順序如果你從設(shè)計(jì)稿拿到的是 RGB 值記得反轉(zhuǎn)。3.4 批量重命名與元數(shù)據(jù)導(dǎo)出裁剪完的幀如果還要按鏡頭分組可以用下面這段腳本重命名并導(dǎo)出 CSV。假設(shè)文件名里已經(jīng)帶了原始幀號(hào)我們按每 100 幀一個(gè)鏡頭分組。import os import csv import re SRC_DIR cropped DST_DIR final META_PATH metadata.csv SHOT_SIZE 100 os.makedirs(DST_DIR, exist_okTrue) 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 fshot{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)邏輯說(shuō)明sorted保證文件按編號(hào)順序處理避免操作系統(tǒng)返回亂序。re.search從文件名里提數(shù)字如果文件名格式變了要同步改正則。CSV 用DictWriter寫字段順序可控。參數(shù)說(shuō)明SHOT_SIZE按實(shí)際鏡頭長(zhǎng)度調(diào)整動(dòng)畫一般 50~200 幀一個(gè)鏡頭。new_name模板可以改成帶項(xiàng)目前綴方便多批次合并。4. 避坑與排查抽幀、裁剪、通道處理里的五個(gè)常見翻車點(diǎn)4.1 抽出來(lái)的幀全是重復(fù)的現(xiàn)象輸出目錄里相鄰幀看起來(lái)一模一樣文件大小也接近。原因-vsync默認(rèn)是auto在可變幀率視頻上會(huì)補(bǔ)幀。解決顯式加-vsync 0或者用-fps_mode passthrough新版 FFmpeg。如果還重復(fù)檢查源視頻是不是 VFR用ffprobe看r_frame_rate和avg_frame_rate是否一致。4.2 裁剪坐標(biāo)對(duì)不上裁出黑邊或切掉主體現(xiàn)象按設(shè)計(jì)稿坐標(biāo)裁剪結(jié)果偏了幾十像素。原因視頻有旋轉(zhuǎn)元數(shù)據(jù)或像素寬高比不是 1:1。解決先用ffprobe看rotate和sample_aspect_ratio有旋轉(zhuǎn)就加-vf transpose1先轉(zhuǎn)正寬高比非 1 時(shí)用-vf scaleiw*sar:ih校正。另外注意 OpenCV 讀出來(lái)的尺寸可能和 FFmpeg 報(bào)告的不一致以實(shí)際img.shape為準(zhǔn)。4.3 綠幕摳完邊緣發(fā)灰或有殘留現(xiàn)象主體邊緣一圈半透明灰邊或者背景還有零星綠點(diǎn)。原因RGB 空間做閾值光照漸變導(dǎo)致邊緣像素偏離目標(biāo)色。解決轉(zhuǎn) YUV 空間對(duì) U/V 分量設(shè)閾值或者用cv2.inRange加形態(tài)學(xué)開運(yùn)算去噪。殘留綠點(diǎn)可以用cv2.morphologyEx做閉運(yùn)算再膨脹 alpha 通道。4.4 大批量處理時(shí)內(nèi)存爆掉現(xiàn)象跑幾千幀后進(jìn)程被 kill或者越來(lái)越慢。原因OpenCV 的imread每次分配新數(shù)組Python 垃圾回收不及時(shí)。解決用with上下文或手動(dòng)del img每處理 500 幀調(diào)用一次gc.collect()。更穩(wěn)的做法是用生成器逐幀讀不要一次性list(os.listdir)加載所有路徑。4.5 輸出文件名沖突導(dǎo)致覆蓋現(xiàn)象不同鏡頭的幀重名后寫的覆蓋先寫的。原因重命名模板里沒(méi)有唯一標(biāo)識(shí)。解決模板里加入鏡頭號(hào)或源文件哈希比如{shot}_{frame}_{md5[:6]}.png。如果已經(jīng)覆蓋了只能從原始視頻重新抽所以第一次跑建議先輸出到臨時(shí)目錄確認(rèn)命名唯一后再移入正式目錄。注意任何批量重命名操作前先在小樣本目錄上跑一遍確認(rèn)文件名不沖突再全量執(zhí)行。5. 進(jìn)階技巧用幀差法自動(dòng)切分鏡頭并驗(yàn)證提取完整性前面講的都是按固定規(guī)則抽幀但實(shí)際項(xiàng)目里鏡頭長(zhǎng)度不一固定間隔會(huì)浪費(fèi)大量存儲(chǔ)。更聰明的做法是用幀差法自動(dòng)檢測(cè)鏡頭切換只在切換點(diǎn)附近抽幀。原理很簡(jiǎn)單計(jì)算相鄰幀的直方圖差異或像素絕對(duì)差超過(guò)閾值就判定為新鏡頭。下面這段腳本在抽幀階段就做鏡頭切分輸出每個(gè)鏡頭的起始幀和結(jié)束幀。import cv2 import numpy as np def detect_shots(video_path, threshold30.0, min_shot_len10): cap cv2.VideoCapture(video_path) prev_hist None shots [] start 0 idx 0 while True: ret, frame cap.read() if not ret: break # 轉(zhuǎn)灰度并計(jì)算直方圖降低計(jì)算量 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: # 用相關(guān)性比較直方圖值越小差異越大 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 # 調(diào)用示例 shots detect_shots(input.mp4, threshold35.0, min_shot_len15) for i, (s, e) in enumerate(shots): print(f鏡頭 {i1}: 幀 {s} 到 {e}共 {e-s1} 幀)邏輯說(shuō)明cv2.calcHist把每幀壓成 64 維直方圖比逐像素比較快一個(gè)數(shù)量級(jí)。compareHist用相關(guān)性返回值 1 表示完全相同0 表示無(wú)關(guān)。threshold是差異百分比35 表示直方圖相關(guān)性低于 0.65 就切鏡頭。min_shot_len防止把閃光或快速運(yùn)動(dòng)誤判為切鏡頭。參數(shù)說(shuō)明threshold越大越不敏感動(dòng)畫一般 30~40實(shí)拍可以到 50。min_shot_len按幀率算24fps 下 15 幀約 0.6 秒低于這個(gè)時(shí)長(zhǎng)的鏡頭多半是誤檢。直方圖維度 64 是速度和精度的折中調(diào)到 256 更準(zhǔn)但慢一倍。拿到鏡頭列表后可以只對(duì)每個(gè)鏡頭的首幀、中間幀和尾幀做提取存儲(chǔ)量能降一個(gè)數(shù)量級(jí)。驗(yàn)證提取完整性也有個(gè)笨辦法但很管用把提取出的幀按順序拼回視頻用ffmpeg -framerate 24 -i final/shot%03d_frame%05d.png -c:v libx264 out.mp4然后和原視頻并排播放肉眼掃一遍有沒(méi)有跳幀或錯(cuò)位。我一般會(huì)在拼接時(shí)加-vf drawtexttext%{frame_num}把幀號(hào)燒進(jìn)去方便定位問(wèn)題幀。從那以后我每次跑完提取都強(qiáng)制走一遍「拼回視頻 抽檢 10 幀元數(shù)據(jù)」的流程寧可多花十分鐘也不想到合成階段才發(fā)現(xiàn)少了一幀。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取