File size: 7,332 Bytes
e23cf4c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | #!/usr/bin/env python
"""Build test_data: 12 method (4 nhóm x 3) cân bằng real=fake theo protocol DF40.
Nhóm / method:
FS (face-swap): simswap, faceswap, blendface
FR (face-reenact): wav2lip, sadtalker, fomm
EFS (entire synth): StyleGAN2, VQGAN, ddim
FE (face-edit): stargan, starganv2, styleclip [unknown — real/fake đi kèm]
Nguồn:
real known (cdf): data/real-root/Celeb-DF-v2/... (đã trích theo JSON)
fake known (cdf): /Volumes/quangmanh/Downloads/DF40/<method>/cdf/frames/
unknown (FE) : /Volumes/quangmanh/Downloads/DF40/<method>/{real,fake}/*.jpg
Cân bằng: mỗi method lấy N real + N fake (mặc định 1000), seed cố định theo method.
Manifest test_data/manifest.csv: method,video,path — real rows method="real"
(khớp eval_df40_all_methods.py: method=="real" -> label 0, còn lại -> label 1).
Chạy:
.venv/bin/python scripts/build_test_data.py --dry-run
.venv/bin/python scripts/build_test_data.py --n 1000
"""
import argparse
import csv
import json
import os
import random
import shutil
import sys
from concurrent.futures import ThreadPoolExecutor
JSON_PREFIX = "deepfakes_detection_datasets/"
JSON_DIR = "data/dataset_json"
AIR = "/Volumes/quangmanh/Downloads/DF40"
CELEB_REAL = "data/real-root"
DST = "test_data"
WORKERS = 8
GROUPS = {
"FS": ["simswap", "faceswap", "blendface"],
"FR": ["wav2lip", "sadtalker", "fomm"],
"EFS": ["StyleGAN2", "VQGAN", "ddim"],
"FE": ["stargan", "starganv2", "styleclip"],
}
KNOWN = {m: g for g, ms in GROUPS.items() for m in ms if g != "FE"}
UNKNOWN = {m: g for g, ms in GROUPS.items() for m in ms if g == "FE"}
def air_path(json_path, method):
"""JSON path -> đường dẫn vật lý trên Air (cấu trúc lệch nhau theo method).
- cdf thường: .../<method>/cdf/frames/<vid>/<f>.png (simswap, faceswap...)
- EFS: JSON .../<method>/cdf/{Celeb-real,YouTube-real}/ -> Air .../cdf/Fake_from_{...}/
- stargan nested: .../<method>/{real,fake}/<id>.jpg -> Air .../{real,fake}/{real,fake}/<id>.jpg
"""
rel = json_path[len(JSON_PREFIX):]
assert rel.startswith("DF40/"), json_path
rel = rel[len("DF40/"):]
rel = rel.replace("cdf/Celeb-real/", "cdf/Fake_from_Celeb-real/")
rel = rel.replace("cdf/YouTube-real/", "cdf/Fake_from_Youtube-real/")
if method == "stargan":
rel = rel.replace("/real/", "/real/real/")
rel = rel.replace("/fake/", "/fake/fake/")
return os.path.join(AIR, rel)
def celeb_real_path(json_path):
"""deepfakes_detection_datasets/Celeb-DF-v2/... -> data/real-root/..."""
return os.path.join(CELEB_REAL, json_path[len(JSON_PREFIX):])
def collect_side(method, json_key, kind):
"""(video, json_path) list cho 1 bên Real/Fake từ split 'test'."""
d = json.load(open(os.path.join(JSON_DIR, json_key + ".json")))[json_key]
sd = d[f"{method}_{kind}"]
out = []
for vid, info in sd["test"].items():
for p in info["frames"]:
out.append((vid, p))
return out
def plan_known(method, n, rng):
"""Plan cho method known (cdf): (src, rel_dst, video) per class."""
real = collect_side(method, f"{method}_cdf", "Real")
fake = collect_side(method, f"{method}_cdf", "Fake")
picks = {}
for label, pool in (("real", real), ("fake", fake)):
keep = rng.sample(pool, min(n, len(pool)))
rows = []
for vid, p in keep:
if label == "real":
src = celeb_real_path(p) # data/real-root/Celeb-DF-v2/...
else:
src = air_path(p, method) # Air DF40/<method>/cdf/frames/...
basename = os.path.basename(p)
rel = f"{method}/{label}/{vid}/{basename}"
rows.append((src, os.path.join(DST, rel), vid))
picks[label] = rows
return picks
def plan_unknown(method, n, rng):
"""Plan cho method unknown (FE): real/fake jpg phẳng trên Air."""
real = collect_side(method, method, "Real")
fake = collect_side(method, method, "Fake")
picks = {}
for label, pool in (("real", real), ("fake", fake)):
keep = rng.sample(pool, min(n, len(pool)))
rows = []
for vid, p in keep: # p = .../DF40/<method>/real|<label>/<id>.jpg
src = air_path(p, method)
basename = os.path.basename(p)
rel = f"{method}/{label}/{basename}" # flat: mỗi ảnh 1 "video"
rows.append((src, os.path.join(DST, rel), vid))
picks[label] = rows
return picks
def copy_all(todos):
"""Copy (src, dst) song song, trả (ok, missing)."""
ok = missing = 0
lock = threading_lock()
def one(item):
nonlocal ok, missing
src, dst = item
if os.path.exists(dst): # đã có (re-run) -> bỏ qua
with lock:
ok += 1
return
if os.path.isfile(src):
os.makedirs(os.path.dirname(dst), exist_ok=True)
shutil.copy2(src, dst)
with lock:
ok += 1
else:
with lock:
missing += 1
with ThreadPoolExecutor(max_workers=WORKERS) as ex:
ex.map(one, todos)
return ok, missing
def threading_lock():
import threading
return threading.Lock()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--n", type=int, default=1000, help="số ảnh mỗi class mỗi method")
ap.add_argument("--dry-run", action="store_true")
args = ap.parse_args()
methods = {**KNOWN, **UNKNOWN}
all_rows = [] # manifest rows
summary = []
todos = [] # (src, dst) to copy
for method, group in sorted(methods.items()):
rng = random.Random(method) # seed cố định theo tên method
plan = plan_known(method, args.n, rng) if method in KNOWN \
else plan_unknown(method, args.n, rng)
n_real = len(plan["real"]); n_fake = len(plan["fake"])
summary.append((method, group, n_real, n_fake))
if args.dry_run:
print(f" [{group}] {method:10s} real={n_real:5d} fake={n_fake:5d}")
continue
for label, rows in plan.items():
for src, dst, vid in rows:
mlabel = "real" if label == "real" else method
all_rows.append([mlabel, vid, os.path.relpath(dst, DST)])
todos.append((src, dst))
if args.dry_run:
print(f"\nDry-run xong. {len(methods)} method, tổng "
f"{sum(s[2] + s[3] for s in summary):,} ảnh. Bỏ --dry-run để copy.")
return
print(f"Copy {len(todos):,} ảnh vào {DST}/ ...")
ok, missing = copy_all(todos)
with open(os.path.join(DST, "manifest.csv"), "w", newline="") as f:
w = csv.writer(f)
w.writerow(["method", "video", "path"])
w.writerows(all_rows)
print(f"\nCopy xong: ok={ok:,} missing={missing:,}")
print(f"{'method':12s}{'nhóm':4s}{'real':>7s}{'fake':>7s}")
for method, group, nr, nf in summary:
print(f"{method:12s}{group:4s}{nr:7d}{nf:7d}")
print(f"\nManifest: {DST}/manifest.csv ({len(all_rows):,} dòng)")
print(f"Tổng: real={sum(s[2] for s in summary):,} fake={sum(s[3] for s in summary):,}")
if __name__ == "__main__":
main()
|