Spaces:
Sleeping
Sleeping
Download detectors/ForensicConcept/upstream/data/datasets.py from Truebees/DeepShield-Detectors: direct link, hf CLI and curl.
- Browser
- Download file 35.4 kB
-
https://huggingface.co/spaces/Truebees/DeepShield-Detectors/resolve/main/detectors/ForensicConcept/upstream/data/datasets.py
- Command line
-
hf download hf://spaces/Truebees/DeepShield-Detectors/detectors/ForensicConcept/upstream/data/datasets.py
-
curl -L -o datasets.py https://huggingface.co/spaces/Truebees/DeepShield-Detectors/resolve/main/detectors/ForensicConcept/upstream/data/datasets.py
35.4 kB
| import cv2 | |
| import numpy as np | |
| import inspect | |
| import re | |
| import os | |
| os.environ.setdefault("NO_ALBUMENTATIONS_UPDATE", "1") | |
| import albumentations as A | |
| from albumentations import Downscale, Compose, ImageCompression, GaussNoise, MotionBlur, GaussianBlur | |
| from torchvision import transforms, datasets | |
| import torchvision.transforms.functional as TF | |
| from random import random, choice | |
| from io import BytesIO | |
| from PIL import Image | |
| from PIL import ImageFile | |
| from PIL import ImageFilter | |
| from PIL import ImageOps | |
| from PIL import UnidentifiedImageError | |
| from scipy.ndimage import gaussian_filter | |
| from torchvision.transforms import InterpolationMode | |
| import torch | |
| ImageFile.LOAD_TRUNCATED_IMAGES = True | |
| CLIP_MEAN = [0.48145466, 0.4578275, 0.40821073] | |
| CLIP_STD = [0.26862954, 0.26130258, 0.27577711] | |
| IMAGENET_MEAN = [0.485, 0.456, 0.406] | |
| IMAGENET_STD = [0.229, 0.224, 0.225] | |
| def _supports_albu_arg(cls, arg_name): | |
| try: | |
| return arg_name in inspect.signature(cls.__init__).parameters | |
| except Exception: | |
| return False | |
| def _build_downscale_aug(): | |
| """Build Downscale across the Albumentations 1.x and 2.x APIs.""" | |
| if _supports_albu_arg(Downscale, "scale_range"): | |
| return Downscale( | |
| scale_range=(0.25, 0.75), | |
| interpolation_pair={ | |
| "downscale": cv2.INTER_LINEAR, | |
| "upscale": cv2.INTER_LINEAR, | |
| }, | |
| p=0.5, | |
| ) | |
| return Downscale( | |
| scale_min=0.25, | |
| scale_max=0.75, | |
| interpolation=cv2.INTER_LINEAR, | |
| p=0.5, | |
| ) | |
| def _build_testshift_like_aug(p_apply=1.0, max_groups=3): | |
| # group 1) blur | |
| blur_group = A.OneOf([ | |
| A.GaussianBlur(sigma_limit=(0.1, 5.0), p=1.0), | |
| A.Defocus(radius=(1, 8), alias_blur=(0.1, 0.5), p=1.0), | |
| ], p=1.0) | |
| # group 2) color distortion | |
| color_group = A.OneOf([ | |
| A.ChromaticAberration( | |
| primary_distortion_limit=(0.01, 0.08), | |
| secondary_distortion_limit=(0.01, 0.12), | |
| p=1.0, | |
| ), | |
| A.ColorJitter(brightness=0.0, contrast=0.0, saturation=1.0, hue=0.0, p=1.0), | |
| ], p=1.0) | |
| # group 3) jpeg | |
| if _supports_albu_arg(A.ImageCompression, "quality_range"): | |
| jpeg_group = A.ImageCompression(compression_type="jpeg", quality_range=(4, 43), p=1.0) | |
| else: | |
| jpeg_group = A.ImageCompression(quality_lower=4, quality_upper=43, p=1.0) | |
| # group 4) noise | |
| if _supports_albu_arg(A.GaussNoise, "std_range"): | |
| gauss_noise = A.GaussNoise(std_range=(0.032, 0.100), mean_range=(0.0, 0.0), per_channel=True, p=1.0) | |
| else: | |
| gauss_noise = A.GaussNoise(var_limit=(0.032 ** 2, 0.100 ** 2), mean=0.0, per_channel=True, p=1.0) | |
| if hasattr(A, "SaltAndPepper"): | |
| impulse_noise = A.SaltAndPepper(amount=(0.001, 0.03), salt_vs_pepper=(0.45, 0.55), p=1.0) | |
| else: | |
| impulse_noise = A.ISONoise(color_shift=(0.01, 0.05), intensity=(0.1, 0.5), p=1.0) | |
| noise_group = A.OneOf([ | |
| gauss_noise, | |
| impulse_noise, | |
| ], p=1.0) | |
| # group 5) brightness | |
| brightness_group = A.RandomBrightnessContrast( | |
| brightness_limit=(-0.8, 0.8), | |
| contrast_limit=(0.0, 0.0), | |
| brightness_by_max=True, | |
| p=1.0, | |
| ) | |
| # group 6) spatial distortion | |
| spatial_group = A.OneOf([ | |
| A.GridDistortion( | |
| num_steps=5, | |
| distort_limit=(0.05, 0.5), | |
| interpolation=cv2.INTER_LINEAR, | |
| border_mode=cv2.BORDER_REFLECT_101, | |
| p=1.0, | |
| ), | |
| A.Sequential([ | |
| A.ToGray(num_output_channels=3, p=1.0), | |
| A.Posterize(num_bits=(3, 5), p=1.0), | |
| ], p=1.0), | |
| ], p=1.0) | |
| # group 7) sharpness / contrast | |
| sharp_contrast_group = A.RandomToneCurve(scale=0.2, p=1.0) | |
| groups = [ | |
| blur_group, | |
| color_group, | |
| jpeg_group, | |
| noise_group, | |
| brightness_group, | |
| spatial_group, | |
| sharp_contrast_group, | |
| ] | |
| max_groups = max(1, min(int(max_groups), len(groups))) | |
| choices = [A.SomeOf(groups, n=n, replace=False, p=1.0) for n in range(1, max_groups + 1)] | |
| return A.Compose([ | |
| A.OneOf(choices, p=1.0), | |
| ], p=float(p_apply)) | |
| def dataset_folder(opt, root): | |
| if opt.mode == 'binary': | |
| return binary_dataset(opt, root) | |
| if opt.mode == 'filename': | |
| return FileNameDataset(opt, root) | |
| raise ValueError('opt.mode needs to be binary or filename.') | |
| def _apply_single_robustness(img, robustness_type, params=None): | |
| params = dict(params or {}) | |
| robustness_type = str(robustness_type or "").strip().lower() | |
| if not robustness_type: | |
| return img | |
| if robustness_type == "jpeg": | |
| quality = int(params.get("jpeg_quality", 75) or 75) | |
| out = BytesIO() | |
| img.save(out, format="JPEG", quality=quality) | |
| out.seek(0) | |
| with Image.open(out) as aug_img: | |
| return aug_img.convert("RGB") | |
| if robustness_type == "gaussian_blur": | |
| sigma = float(params.get("gaussian_blur_sigma", 1.0) or 1.0) | |
| return img.filter(ImageFilter.GaussianBlur(radius=sigma)) | |
| if robustness_type == "gaussian_noise": | |
| std = float(params.get("gaussian_noise_std", 0.1) or 0.1) | |
| img_np = np.asarray(img).astype(np.float32) / 255.0 | |
| noise = np.random.normal(loc=0.0, scale=std, size=img_np.shape).astype(np.float32) | |
| img_np = np.clip(img_np + noise, 0.0, 1.0) | |
| img_np = (img_np * 255.0).round().astype(np.uint8) | |
| return Image.fromarray(img_np) | |
| if robustness_type == "downsample": | |
| scale = float(params.get("downsample_scale", 0.5) or 0.5) | |
| width, height = img.size | |
| down_w = max(1, int(round(width * scale))) | |
| down_h = max(1, int(round(height * scale))) | |
| down = img.resize((down_w, down_h), resample=Image.BILINEAR) | |
| return down.resize((width, height), resample=Image.BILINEAR) | |
| if robustness_type == "color_jitter": | |
| brightness = float(params.get("cj_brightness", 0.0) or 0.0) | |
| contrast = float(params.get("cj_contrast", 0.0) or 0.0) | |
| saturation = float(params.get("cj_saturation", 0.0) or 0.0) | |
| hue = float(params.get("cj_hue", 0.0) or 0.0) | |
| out = img | |
| if brightness != 0.0: | |
| out = TF.adjust_brightness(out, 1.0 + brightness) | |
| if contrast != 0.0: | |
| out = TF.adjust_contrast(out, 1.0 + contrast) | |
| if saturation != 0.0: | |
| out = TF.adjust_saturation(out, 1.0 + saturation) | |
| if hue != 0.0: | |
| hue = max(-0.5, min(0.5, hue)) | |
| out = TF.adjust_hue(out, hue) | |
| return out | |
| raise ValueError(f"Unsupported robustness_type: {robustness_type}") | |
| def _apply_explicit_robustness(img, opt): | |
| robustness_chain = getattr(opt, "robustness_chain", None) or [] | |
| if robustness_chain: | |
| out = img | |
| for idx, entry in enumerate(robustness_chain): | |
| if not isinstance(entry, dict): | |
| raise ValueError(f"robustness_chain[{idx}] must be a dict, got {type(entry).__name__}") | |
| out = _apply_single_robustness( | |
| out, | |
| entry.get("type", ""), | |
| entry.get("params", {}), | |
| ) | |
| return out | |
| return _apply_single_robustness( | |
| img, | |
| getattr(opt, "robustness_type", ""), | |
| { | |
| "jpeg_quality": getattr(opt, "robustness_jpeg_quality", 75), | |
| "gaussian_blur_sigma": getattr(opt, "robustness_gaussian_blur_sigma", 1.0), | |
| "gaussian_noise_std": getattr(opt, "robustness_gaussian_noise_std", 0.1), | |
| "downsample_scale": getattr(opt, "robustness_downsample_scale", 0.5), | |
| "cj_brightness": getattr(opt, "robustness_cj_brightness", 0.0), | |
| "cj_contrast": getattr(opt, "robustness_cj_contrast", 0.0), | |
| "cj_saturation": getattr(opt, "robustness_cj_saturation", 0.0), | |
| "cj_hue": getattr(opt, "robustness_cj_hue", 0.0), | |
| }, | |
| ) | |
| import base64 | |
| from io import BytesIO | |
| from PIL import Image | |
| import numpy as np | |
| import torchvision.transforms | |
| from scipy.ndimage import gaussian_filter | |
| class ImageProcessor: | |
| def __init__(self, opt): | |
| self.opt = opt | |
| def data_augment(self, img): | |
| noise_type = getattr(self.opt, "noise_type", None) | |
| noise_ratio = getattr(self.opt, "noise_ratio", None) | |
| if noise_type == 'jpg': | |
| img_processed = self.pil_jpg_new(img, noise_ratio) | |
| elif noise_type == 'resize': | |
| width, height = img.size | |
| img_processed = torchvision.transforms.Resize((int(height / noise_ratio), int(width / noise_ratio)))(img) | |
| elif noise_type == 'blur': | |
| img = np.array(img) | |
| self.gaussian_blur(img, noise_ratio) | |
| img_processed = Image.fromarray(img) | |
| elif noise_type is None: | |
| img_processed = img | |
| return img_processed | |
| def pil_jpg_new(self, img, compress_val): | |
| out = BytesIO() | |
| img.save(out, format='jpeg', quality=compress_val) | |
| img = Image.open(out) | |
| # load from memory before ByteIO closes | |
| img = np.array(img) | |
| img = Image.fromarray(img) | |
| out.close() | |
| return img | |
| def gaussian_blur(self, img, sigma): | |
| # Check if the image has a third dimension | |
| if img.ndim == 3: | |
| # Check the number of channels in the image | |
| channels = img.shape[2] | |
| # if channels == 0: | |
| # # If there are no channels, return the image as is | |
| # return img | |
| if channels == 1: | |
| # If there is one channel, apply the filter to the single channel | |
| gaussian_filter(img[:, :, 0], output=img[:, :, 0], sigma=sigma) | |
| elif channels == 2: | |
| # If there are two channels, apply the filter to both channels | |
| gaussian_filter(img[:, :, 0], output=img[:, :, 0], sigma=sigma) | |
| gaussian_filter(img[:, :, 1], output=img[:, :, 1], sigma=sigma) | |
| elif channels == 3: | |
| # If there are three channels, apply the filter to all three channels | |
| gaussian_filter(img[:, :, 0], output=img[:, :, 0], sigma=sigma) | |
| gaussian_filter(img[:, :, 1], output=img[:, :, 1], sigma=sigma) | |
| gaussian_filter(img[:, :, 2], output=img[:, :, 2], sigma=sigma) | |
| else: | |
| # If the image does not have a third dimension, apply the filter to the 2D image | |
| gaussian_filter(img, output=img, sigma=sigma) | |
| # return img | |
| def encode_image(image_path, processor=None): | |
| with open(image_path, "rb") as image_file: | |
| img = Image.open(image_file) | |
| # Apply data augmentation if processor is provided | |
| if processor is not None: | |
| # print(processor.opt) | |
| img = processor.data_augment(img) | |
| # Convert image to Base64 | |
| buffered = BytesIO() | |
| img.save(buffered, format="PNG") | |
| img_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8') | |
| return img, img_base64 | |
| class Options: | |
| def __init__(self, noise_type=None, noise_ratio=None): | |
| """ | |
| 初始化数据增强的配置参数。 | |
| :param noise_type: 噪声类型,可选值为 'jpg', 'resize', 'blur' 或 None | |
| :param noise_ratio: 噪声强度或比例,具体含义取决于 noise_type | |
| """ | |
| self.noise_type = noise_type | |
| self.noise_ratio = noise_ratio | |
| def __str__(self): | |
| """返回对象的字符串表示,便于调试""" | |
| return (f"Options(noise_type={self.noise_type}, noise_ratio={self.noise_ratio}") | |
| # Robust ImageFolder: skip unreadable/corrupted files instead of crashing workers. | |
| class SafeImageFolder(datasets.ImageFolder): | |
| def __init__(self, *args, skip_broken=True, skip_log_limit=0, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| self.skip_broken = bool(skip_broken) | |
| self.skip_log_limit = max(0, int(skip_log_limit)) | |
| self._skip_log_count = 0 | |
| def _is_image_decode_error(exc): | |
| return isinstance(exc, (UnidentifiedImageError, OSError, ValueError)) | |
| def __getitem__(self, index): | |
| if not self.skip_broken: | |
| return super().__getitem__(index) | |
| total = len(self.samples) | |
| if total == 0: | |
| raise IndexError("SafeImageFolder is empty") | |
| idx = int(index) % total | |
| for _ in range(total): | |
| try: | |
| return super().__getitem__(idx) | |
| except Exception as exc: | |
| if not self._is_image_decode_error(exc): | |
| raise | |
| bad_path = self.samples[idx][0] | |
| if self._skip_log_count < self.skip_log_limit: | |
| print( | |
| f"[Data] Skipping unreadable image: {bad_path} " | |
| f"({type(exc).__name__}: {exc})" | |
| ) | |
| self._skip_log_count += 1 | |
| idx = (idx + 1) % total | |
| raise RuntimeError( | |
| "Unable to fetch a readable image from dataset. " | |
| "All candidates appear unreadable." | |
| ) | |
| class FlatLabeledImageDataset(torch.utils.data.Dataset): | |
| """Fallback dataset for flat folders: labels parsed from filename suffix (_real/_fake).""" | |
| def __init__(self, samples, transform=None, skip_broken=True, skip_log_limit=0): | |
| self.samples = list(samples) | |
| self.transform = transform | |
| self.skip_broken = bool(skip_broken) | |
| self.skip_log_limit = max(0, int(skip_log_limit)) | |
| self._skip_log_count = 0 | |
| self.targets = [int(t) for _, t in self.samples] | |
| self.class_to_idx = {'0_real': 0, '1_fake': 1} | |
| self.classes = ['0_real', '1_fake'] | |
| self.imgs = self.samples | |
| def _is_image_decode_error(exc): | |
| return isinstance(exc, (UnidentifiedImageError, OSError, ValueError)) | |
| def __len__(self): | |
| return len(self.samples) | |
| def __getitem__(self, index): | |
| total = len(self.samples) | |
| if total == 0: | |
| raise IndexError("FlatLabeledImageDataset is empty") | |
| idx = int(index) % total | |
| for _ in range(total): | |
| path, target = self.samples[idx] | |
| try: | |
| with Image.open(path) as img: | |
| img = img.convert("RGB") | |
| if self.transform is not None: | |
| img = self.transform(img) | |
| return img, int(target) | |
| except Exception as exc: | |
| if (not self.skip_broken) or (not self._is_image_decode_error(exc)): | |
| raise | |
| if self._skip_log_count < self.skip_log_limit: | |
| print( | |
| f"[Data] Skipping unreadable image: {path} " | |
| f"({type(exc).__name__}: {exc})" | |
| ) | |
| self._skip_log_count += 1 | |
| idx = (idx + 1) % total | |
| raise RuntimeError("Unable to fetch a readable image from flat dataset.") | |
| # Example usage: | |
| # opt = SomeOptionsClass() # Replace with your actual options class | |
| # print(opt_class) | |
| def _find_binary_roots(root): | |
| """Yield directories whose immediate children contain 0_real and 1_fake.""" | |
| binary_roots = [] | |
| valid_pairs = ( | |
| {'0_real', '1_fake'}, | |
| {'0_fake', '1_real'}, | |
| {'raw', 'synthesis'}, # deepfake datasets | |
| {'real', 'fake'}, | |
| ) | |
| for current_root, dirs, _ in os.walk(root): | |
| present = set(dirs) | |
| if any(pair.issubset(present) for pair in valid_pairs): | |
| binary_roots.append(current_root) | |
| # ensure deterministic ordering so label mapping is stable | |
| binary_roots.sort() | |
| return binary_roots | |
| def infer_single_label_from_root(root): | |
| """Infer fixed binary label from directory name for one-class dataset roots.""" | |
| # Canonical deepfake aliases used by Celeb-DF/FF++ style trees. | |
| alias_to_label = { | |
| # real | |
| "0_real": 0, | |
| "1_real": 0, | |
| "real": 0, | |
| "raw": 0, | |
| "original_sequences": 0, | |
| "celeb-real-mtcnn": 0, | |
| "youtube-real-mtcnn": 0, | |
| # fake | |
| "1_fake": 1, | |
| "0_fake": 1, | |
| "fake": 1, | |
| "synthesis": 1, | |
| "manipulated_sequences": 1, | |
| "celeb-synthesis-mtcnn": 1, | |
| } | |
| base = os.path.basename(os.path.abspath(root)).strip().lower() | |
| return alias_to_label.get(base) | |
| def _collect_recursive_image_samples(root, label): | |
| exts = {".jpg", ".jpeg", ".png", ".bmp", ".webp", ".tif", ".tiff"} | |
| samples = [] | |
| for current_root, _, files in os.walk(root): | |
| for fname in files: | |
| if os.path.splitext(fname)[1].lower() in exts: | |
| samples.append((os.path.join(current_root, fname), int(label))) | |
| samples.sort(key=lambda x: x[0]) | |
| return samples | |
| def _resolve_dfdc_labels_path(root, opt): | |
| root_abs = os.path.abspath(root) | |
| explicit_candidates = [ | |
| getattr(opt, "dfdc_labels_npy", None), | |
| getattr(opt, "data_dfdc_labels_npy", None), | |
| ] | |
| candidates = [c for c in explicit_candidates if c] | |
| candidates.extend( | |
| [ | |
| os.path.join(os.path.dirname(root_abs), "labels.npy"), | |
| os.path.join(os.path.dirname(os.path.dirname(root_abs)), "labels.npy"), | |
| os.path.join(root_abs, "labels.npy"), | |
| ] | |
| ) | |
| seen = set() | |
| for candidate in candidates: | |
| p = os.path.abspath(os.path.expanduser(str(candidate))) | |
| if p in seen: | |
| continue | |
| seen.add(p) | |
| if os.path.isfile(p): | |
| return p | |
| return None | |
| def is_dfdc_train_faces_root(root, opt=None): | |
| root_abs = os.path.abspath(root) | |
| if os.path.basename(root_abs).strip().lower() != "train_faces": | |
| return False | |
| opt_obj = opt if opt is not None else object() | |
| return _resolve_dfdc_labels_path(root_abs, opt_obj) is not None | |
| def _build_dfdc_train_faces_dataset(opt, root, transform): | |
| root_abs = os.path.abspath(root) | |
| if not is_dfdc_train_faces_root(root_abs, opt): | |
| return None | |
| labels_path = _resolve_dfdc_labels_path(root_abs, opt) | |
| if labels_path is None: | |
| return None | |
| try: | |
| labels_raw = np.load(labels_path, allow_pickle=True).item() | |
| except Exception as exc: | |
| raise RuntimeError(f"Failed to load DFDC labels file: {labels_path} ({exc})") from exc | |
| if not isinstance(labels_raw, dict): | |
| raise RuntimeError(f"DFDC labels file is not a dict: {labels_path}") | |
| video_to_label = {} | |
| for k, v in labels_raw.items(): | |
| key = str(k) | |
| if key.endswith(".mp4"): | |
| key = key[:-4] | |
| try: | |
| iv = int(v) | |
| except (TypeError, ValueError): | |
| continue | |
| if iv in (0, 1): | |
| video_to_label[key] = iv | |
| if not video_to_label: | |
| raise RuntimeError(f"No valid binary labels parsed from: {labels_path}") | |
| exts = {".jpg", ".jpeg", ".png", ".bmp", ".webp", ".tif", ".tiff"} | |
| samples = [] | |
| total_video_dirs = 0 | |
| missing_label_video_dirs = 0 | |
| missing_label_examples = [] | |
| for part_name in sorted(os.listdir(root_abs)): | |
| part_dir = os.path.join(root_abs, part_name) | |
| if not os.path.isdir(part_dir): | |
| continue | |
| for video_name in sorted(os.listdir(part_dir)): | |
| video_dir = os.path.join(part_dir, video_name) | |
| if not os.path.isdir(video_dir): | |
| continue | |
| total_video_dirs += 1 | |
| label = video_to_label.get(video_name) | |
| if label is None: | |
| missing_label_video_dirs += 1 | |
| if len(missing_label_examples) < 5: | |
| missing_label_examples.append(video_dir) | |
| continue | |
| for fname in sorted(os.listdir(video_dir)): | |
| if os.path.splitext(fname)[1].lower() in exts: | |
| samples.append((os.path.join(video_dir, fname), int(label))) | |
| if not samples: | |
| raise RuntimeError( | |
| f"Detected DFDC train_faces root but no labeled image files were collected under {root_abs}" | |
| ) | |
| if missing_label_video_dirs > 0: | |
| print( | |
| f"[DFDC] Warning: {missing_label_video_dirs}/{total_video_dirs} video folders in {root_abs} " | |
| f"missing labels from {labels_path}. Examples: {missing_label_examples}" | |
| ) | |
| real_count = sum(1 for _, t in samples if int(t) == 0) | |
| fake_count = len(samples) - real_count | |
| print( | |
| f"[DFDC] Loaded train_faces with labels from {labels_path}: " | |
| f"samples={len(samples)} real={real_count} fake={fake_count}" | |
| ) | |
| skip_broken_images = bool(getattr(opt, "skip_broken_images", True)) | |
| skip_log_limit = int(getattr(opt, "skip_broken_images_log_limit", 0)) | |
| return FlatLabeledImageDataset( | |
| samples=samples, | |
| transform=transform, | |
| skip_broken=skip_broken_images, | |
| skip_log_limit=skip_log_limit, | |
| ) | |
| def _build_binary_datasets(opt, root, transform): | |
| """Create ImageFolder datasets for every binary directory discovered under root.""" | |
| binary_roots = _find_binary_roots(root) | |
| if not binary_roots: | |
| dfdc_dataset = _build_dfdc_train_faces_dataset(opt, root, transform) | |
| if dfdc_dataset is not None: | |
| return dfdc_dataset | |
| # Fallback: flat folder with filename labels like xxx_real.png / xxx_fake.png | |
| exts = {'.jpg', '.jpeg', '.png', '.bmp', '.webp', '.tif', '.tiff'} | |
| suffix_re = re.compile(r'(?:^|[_-])(real|fake)$', flags=re.IGNORECASE) | |
| flat_samples = [] | |
| root_path = os.path.abspath(root) | |
| for p in sorted(os.listdir(root_path)): | |
| abs_p = os.path.join(root_path, p) | |
| if not os.path.isfile(abs_p): | |
| continue | |
| ext = os.path.splitext(p)[1].lower() | |
| if ext not in exts: | |
| continue | |
| stem = os.path.splitext(p)[0] | |
| m = suffix_re.search(stem) | |
| if not m: | |
| continue | |
| label = 0 if m.group(1).lower() == 'real' else 1 | |
| flat_samples.append((abs_p, label)) | |
| skip_broken_images = bool(getattr(opt, "skip_broken_images", True)) | |
| skip_log_limit = int(getattr(opt, "skip_broken_images_log_limit", 0)) | |
| if flat_samples: | |
| return FlatLabeledImageDataset( | |
| samples=flat_samples, | |
| transform=transform, | |
| skip_broken=skip_broken_images, | |
| skip_log_limit=skip_log_limit, | |
| ) | |
| forced_label = infer_single_label_from_root(root) | |
| if forced_label is not None: | |
| recursive_samples = _collect_recursive_image_samples(root, forced_label) | |
| if not recursive_samples: | |
| raise RuntimeError( | |
| f"Single-label root inferred for {root}, but no image files were found recursively." | |
| ) | |
| return FlatLabeledImageDataset( | |
| samples=recursive_samples, | |
| transform=transform, | |
| skip_broken=skip_broken_images, | |
| skip_log_limit=skip_log_limit, | |
| ) | |
| raise RuntimeError( | |
| f"Could not locate binary folders (0_real/1_fake, raw/synthesis, real/fake), " | |
| f"flat *_real/*_fake files, or a known single-label root alias under {root}" | |
| ) | |
| datasets_list = [] | |
| skip_broken_images = bool(getattr(opt, "skip_broken_images", True)) | |
| skip_log_limit = int(getattr(opt, "skip_broken_images_log_limit", 0)) | |
| for binary_root in binary_roots: | |
| dset = SafeImageFolder( | |
| binary_root, | |
| transform=transform, | |
| skip_broken=skip_broken_images, | |
| skip_log_limit=skip_log_limit, | |
| ) | |
| idx_to_class = {idx: cls_name for cls_name, idx in dset.class_to_idx.items()} | |
| class_names = set(idx_to_class.values()) | |
| if class_names == {'0_real', '1_fake'}: | |
| desired_order = {'0_real': 0, '1_fake': 1} | |
| elif class_names == {'0_fake', '1_real'}: | |
| # reverse-labelled datasets: map back to canonical ordering | |
| desired_order = {'1_real': 0, '0_fake': 1} | |
| elif class_names == {'raw', 'synthesis'}: | |
| desired_order = {'raw': 0, 'synthesis': 1} | |
| elif class_names == {'real', 'fake'}: | |
| desired_order = {'real': 0, 'fake': 1} | |
| else: | |
| raise RuntimeError( | |
| f"Unsupported class names {class_names} under {binary_root}; " | |
| "expected 0_real/1_fake, 0_fake/1_real, raw/synthesis, or real/fake" | |
| ) | |
| remapped = [] | |
| for path, target in dset.samples: | |
| cls_name = idx_to_class[target] | |
| if cls_name not in desired_order: | |
| raise RuntimeError( | |
| f"Encountered unexpected class '{cls_name}' in {binary_root}; expected one of {list(desired_order.keys())}" | |
| ) | |
| remapped.append((path, desired_order[cls_name])) | |
| if remapped != dset.samples: | |
| dset.samples = remapped | |
| dset.imgs = remapped | |
| dset.targets = [t for _, t in remapped] | |
| # Normalise metadata to canonical labels for downstream consumers | |
| dset.class_to_idx = {'0_real': 0, '1_fake': 1} | |
| dset.classes = ['0_real', '1_fake'] | |
| datasets_list.append(dset) | |
| if len(datasets_list) == 1: | |
| return datasets_list[0] | |
| return torch.utils.data.ConcatDataset(datasets_list) | |
| def _pad_to_min_size(img, target_h, target_w): | |
| """Pad image with zeros so that height/width are at least target sizes.""" | |
| width, height = img.size | |
| pad_w = max(target_w - width, 0) | |
| pad_h = max(target_h - height, 0) | |
| if pad_w == 0 and pad_h == 0: | |
| return img | |
| left = pad_w // 2 | |
| right = pad_w - left | |
| top = pad_h // 2 | |
| bottom = pad_h - top | |
| return ImageOps.expand(img, border=(left, top, right, bottom), fill=0) | |
| def binary_dataset(opt, root): | |
| # Allow switching to a simplified (legacy) preprocessing pipeline to match the NPR script. | |
| legacy_preprocess = getattr(opt, "legacy_preprocess", False) | |
| if opt.isTrain: | |
| crop_func = transforms.RandomCrop(opt.cropSize) | |
| elif opt.no_crop: | |
| crop_func = transforms.Lambda(lambda img: img) | |
| else: | |
| crop_func = transforms.CenterCrop(opt.cropSize) | |
| if opt.isTrain and not opt.no_flip: | |
| flip_func = transforms.RandomHorizontalFlip() | |
| else: | |
| flip_func = transforms.Lambda(lambda img: img) | |
| if opt.no_resize: | |
| rz_func = transforms.Lambda(lambda img: img) | |
| else: | |
| # rz_func = transforms.Lambda(lambda img: custom_resize(img, opt)) | |
| rz_func = transforms.Resize((opt.loadSize, opt.loadSize)) | |
| # rz_func = transforms.Resize(opt.loadSize) | |
| if legacy_preprocess: | |
| # Match the older flow: resize -> basic augment -> crop -> flip -> IMAGENET normalize. | |
| normalize_transform = transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD) | |
| # normalize_transform = transforms.Normalize(mean=CLIP_MEAN, std=CLIP_STD) | |
| transform = transforms.Compose([ | |
| transforms.Lambda(lambda img: _apply_explicit_robustness(img, opt)), | |
| rz_func, | |
| transforms.Lambda(lambda img: data_augment(img, opt)), | |
| crop_func, | |
| flip_func, | |
| transforms.ToTensor(), | |
| normalize_transform, | |
| ]) | |
| return _build_binary_datasets(opt, root, transform) | |
| NOISE_TYPE = getattr(opt, "noise_type", None) | |
| NOISE_RATIO = getattr(opt, "noise_ratio", None) | |
| opt_class = Options(noise_type=NOISE_TYPE, noise_ratio=NOISE_RATIO) | |
| processor = ImageProcessor(opt_class) | |
| # 定义Albumentations增强(兼容不同版本的 ImageCompression 参数) | |
| def _build_image_compression(): | |
| try: | |
| return ImageCompression(quality_range=(60, 100), p=0.5) | |
| except TypeError: | |
| return ImageCompression(quality_lower=60, quality_upper=100, p=0.5) | |
| aug = Compose([ | |
| _build_image_compression(), | |
| GaussNoise(p=0.2), | |
| MotionBlur(p=0.2), | |
| GaussianBlur(blur_limit=3, p=0.5), | |
| _build_downscale_aug(), | |
| ]) | |
| # 自定义转换函数:将PIL图像转换为NumPy数组,应用Albumentations增强,再转回PIL | |
| def albumentations_aug(img): | |
| # 将PIL图像转换为NumPy数组 (H, W, C) RGB格式 | |
| img_np = np.array(img) | |
| # 应用Albumentations增强 | |
| augmented = aug(image=img_np) | |
| # 从字典中提取增强后的图像 | |
| img_aug = augmented['image'] | |
| # 将NumPy数组转回PIL图像 | |
| return Image.fromarray(img_aug) | |
| DATA_AUG = getattr(opt, "data_aug", False) | |
| USE_TESTSHIFT_LIKE_AUG_TRAIN = bool(getattr(opt, "use_testshift_like_aug", False)) and bool(opt.isTrain) | |
| USE_TESTSHIFT_LIKE_AUG_EVAL = bool(getattr(opt, "use_testshift_like_aug_eval", False)) and (not bool(opt.isTrain)) | |
| USE_TESTSHIFT_LIKE_AUG = USE_TESTSHIFT_LIKE_AUG_TRAIN or USE_TESTSHIFT_LIKE_AUG_EVAL | |
| TESTSHIFT_LIKE_APPLY_PROB = float(getattr(opt, "testshift_like_apply_prob", 1.0)) | |
| TESTSHIFT_LIKE_MAX_GROUPS = int(getattr(opt, "testshift_like_max_groups", 3)) | |
| USE_AUG_UTILS_EVAL = bool(getattr(opt, "use_aug_utils_eval", False)) and (not bool(opt.isTrain)) | |
| USE_AUG_UTILS_TRAIN = bool(getattr(opt, "use_aug_utils_train", False)) and ( | |
| bool(opt.isTrain) or USE_AUG_UTILS_EVAL | |
| ) | |
| AUG_UTILS_PROB = float(getattr(opt, "aug_utils_prob", 1.0)) | |
| AUG_UTILS_MAX_DISTORTIONS = int(getattr(opt, "aug_utils_max_distortions", 3)) | |
| AUG_UTILS_NUM_LEVELS = int(getattr(opt, "aug_utils_num_levels", 5)) | |
| # 创建数据增强变换(deepfake专用 TestShift-like 与原有增强互斥,避免叠加偏移) | |
| if USE_TESTSHIFT_LIKE_AUG: | |
| testshift_aug = _build_testshift_like_aug( | |
| p_apply=TESTSHIFT_LIKE_APPLY_PROB, | |
| max_groups=TESTSHIFT_LIKE_MAX_GROUPS, | |
| ) | |
| def _testshift_like_aug(img): | |
| img_np = np.array(img) | |
| out = testshift_aug(image=img_np)["image"] | |
| return Image.fromarray(out) | |
| data_aug = transforms.Lambda(_testshift_like_aug) | |
| print( | |
| f"[Aug] Using testshift-like aug (prob={TESTSHIFT_LIKE_APPLY_PROB}, " | |
| f"max_groups={TESTSHIFT_LIKE_MAX_GROUPS})" | |
| ) | |
| elif DATA_AUG: | |
| data_aug = transforms.Lambda(albumentations_aug) | |
| else: | |
| data_aug = transforms.Lambda(lambda img: img) | |
| if isinstance(opt.cropSize, (tuple, list)): | |
| target_h = opt.cropSize[0] | |
| target_w = opt.cropSize[1] if len(opt.cropSize) > 1 else opt.cropSize[0] | |
| else: | |
| target_h = target_w = opt.cropSize | |
| apply_padding = opt.no_resize and (opt.isTrain or not opt.no_crop) | |
| pad_func = transforms.Lambda(lambda img: _pad_to_min_size(img, target_h, target_w)) if apply_padding else transforms.Lambda(lambda img: img) | |
| trainmode = str(getattr(opt, 'trainmode', '')).lower() | |
| modelname = str(getattr(opt, 'modelname', '')) | |
| if trainmode == 'lora' and modelname.startswith('CLIP:'): | |
| normalize_transform = transforms.Normalize(mean=CLIP_MEAN, std=CLIP_STD) | |
| else: | |
| normalize_transform = transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD) | |
| # Optional: apply augmentations from aug_utils_train on tensor in [0, 1]. | |
| aug_utils_transform = transforms.Lambda(lambda tens: tens) | |
| if USE_AUG_UTILS_TRAIN: | |
| try: | |
| from aug_utils_train.utils_data import distort_images as aug_utils_distort_images | |
| def _apply_aug_utils_train(tens): | |
| if random() > AUG_UTILS_PROB: | |
| return tens | |
| distorted, _, _ = aug_utils_distort_images( | |
| tens, | |
| max_distortions=AUG_UTILS_MAX_DISTORTIONS, | |
| num_levels=AUG_UTILS_NUM_LEVELS, | |
| ) | |
| return distorted | |
| aug_utils_transform = transforms.Lambda(_apply_aug_utils_train) | |
| stage = "train" if bool(opt.isTrain) else "eval" | |
| print( | |
| f"[Aug] Using aug_utils_train@{stage} (prob={AUG_UTILS_PROB}, " | |
| f"max_distortions={AUG_UTILS_MAX_DISTORTIONS}, num_levels={AUG_UTILS_NUM_LEVELS})" | |
| ) | |
| except Exception as exc: | |
| print(f"[Aug] Failed to enable aug_utils_train, fallback to default aug only: {exc}") | |
| transform = transforms.Compose([ | |
| transforms.Lambda(lambda img: processor.data_augment(img)), | |
| transforms.Lambda(lambda img: _apply_explicit_robustness(img, opt)), | |
| # 使用自定义函数替换原processor.data_augment | |
| data_aug, | |
| rz_func, # 后续PyTorch变换 (如调整大小) | |
| pad_func, # 当禁用resize且图像不足裁剪尺寸时补零 | |
| crop_func, # 裁剪 | |
| flip_func, # 翻转 | |
| transforms.ToTensor(), | |
| aug_utils_transform, | |
| normalize_transform, | |
| ]) | |
| return _build_binary_datasets(opt, root, transform) | |
| class FileNameDataset(datasets.ImageFolder): | |
| def name(self): | |
| return 'FileNameDataset' | |
| def __init__(self, opt, root): | |
| self.opt = opt | |
| super().__init__(root) | |
| def __getitem__(self, index): | |
| # Loading sample | |
| path, target = self.samples[index] | |
| return path | |
| def data_augment(img, opt): | |
| img = np.array(img) | |
| if random() < opt.blur_prob: | |
| sig = sample_continuous(opt.blur_sig) | |
| gaussian_blur(img, sig) | |
| if random() < opt.jpg_prob: | |
| method = sample_discrete(opt.jpg_method) | |
| qual = sample_discrete(opt.jpg_qual) | |
| img = jpeg_from_key(img, qual, method) | |
| return Image.fromarray(img) | |
| def sample_continuous(s): | |
| if len(s) == 1: | |
| return s[0] | |
| if len(s) == 2: | |
| rg = s[1] - s[0] | |
| return random() * rg + s[0] | |
| raise ValueError("Length of iterable s should be 1 or 2.") | |
| def sample_discrete(s): | |
| if len(s) == 1: | |
| return s[0] | |
| return choice(s) | |
| def gaussian_blur(img, sigma): | |
| gaussian_filter(img[:,:,0], output=img[:,:,0], sigma=sigma) | |
| gaussian_filter(img[:,:,1], output=img[:,:,1], sigma=sigma) | |
| gaussian_filter(img[:,:,2], output=img[:,:,2], sigma=sigma) | |
| def cv2_jpg(img, compress_val): | |
| img_cv2 = img[:,:,::-1] | |
| encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), compress_val] | |
| result, encimg = cv2.imencode('.jpg', img_cv2, encode_param) | |
| decimg = cv2.imdecode(encimg, 1) | |
| return decimg[:,:,::-1] | |
| def pil_jpg(img, compress_val): | |
| out = BytesIO() | |
| img = Image.fromarray(img) | |
| img.save(out, format='jpeg', quality=compress_val) | |
| img = Image.open(out) | |
| # load from memory before ByteIO closes | |
| img = np.array(img) | |
| out.close() | |
| return img | |
| jpeg_dict = {'cv2': cv2_jpg, 'pil': pil_jpg} | |
| def jpeg_from_key(img, compress_val, key): | |
| method = jpeg_dict[key] | |
| return method(img, compress_val) | |
| # rz_dict = {'bilinear': Image.BILINEAR, | |
| # 'bicubic': Image.BICUBIC, | |
| # 'lanczos': Image.LANCZOS, | |
| # 'nearest': Image.NEAREST} | |
| rz_dict = {'bilinear': InterpolationMode.BILINEAR, | |
| 'bicubic': InterpolationMode.BICUBIC, | |
| 'lanczos': InterpolationMode.LANCZOS, | |
| 'nearest': InterpolationMode.NEAREST} | |
| def custom_resize(img, opt): | |
| interp = sample_discrete(opt.rz_interp) | |
| return TF.resize(img, (opt.loadSize,opt.loadSize), interpolation=rz_dict[interp]) | |