Download preprocessing.py from iron04/deepfake_efficientnet: direct link, hf CLI and curl.
- Browser
- Download file 3.38 kB
-
https://huggingface.co/iron04/deepfake_efficientnet/resolve/main/preprocessing.py
- Command line
-
hf download hf://iron04/deepfake_efficientnet/preprocessing.py
-
curl -L -o preprocessing.py https://huggingface.co/iron04/deepfake_efficientnet/resolve/main/preprocessing.py
3.38 kB
| """ | |
| preprocessing.py | |
| Lightweight video preprocessing for deepfake detection. | |
| Input: | |
| MP4/AVI/MOV/etc. | |
| Output: | |
| Tensor with shape [T, C, H, W], where T is the sampled frame count. | |
| The default pipeline samples 16 frames uniformly from the video, resizes | |
| them to 224x224, and applies ImageNet normalization. | |
| """ | |
| from pathlib import Path | |
| from typing import List | |
| import cv2 | |
| import numpy as np | |
| import torch | |
| from PIL import Image | |
| from torchvision import transforms | |
| DEFAULT_NUM_FRAMES = 16 | |
| DEFAULT_IMAGE_SIZE = 224 | |
| def build_transform(image_size: int = DEFAULT_IMAGE_SIZE): | |
| """Transform compatible with ImageNet-pretrained CNN backbones.""" | |
| return transforms.Compose([ | |
| transforms.Resize((image_size, image_size)), | |
| transforms.ToTensor(), | |
| transforms.Normalize( | |
| mean=[0.485, 0.456, 0.406], | |
| std=[0.229, 0.224, 0.225], | |
| ), | |
| ]) | |
| def sample_frame_indices(total_frames: int, num_frames: int = DEFAULT_NUM_FRAMES) -> np.ndarray: | |
| """Return uniformly distributed frame indices.""" | |
| if total_frames <= 0: | |
| raise ValueError("Video contains no readable frames.") | |
| if total_frames <= num_frames: | |
| return np.linspace(0, total_frames - 1, num_frames).astype(int) | |
| return np.linspace(0, total_frames - 1, num_frames).astype(int) | |
| def read_video_frames( | |
| video_path: str, | |
| num_frames: int = DEFAULT_NUM_FRAMES, | |
| ) -> List[Image.Image]: | |
| """Read uniformly sampled RGB frames from a video.""" | |
| cap = cv2.VideoCapture(str(video_path)) | |
| if not cap.isOpened(): | |
| raise ValueError(f"Could not open video: {video_path}") | |
| total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
| indices = sample_frame_indices(total_frames, num_frames) | |
| wanted = set(indices.tolist()) | |
| frames = {} | |
| frame_idx = 0 | |
| while True: | |
| ok, frame = cap.read() | |
| if not ok: | |
| break | |
| if frame_idx in wanted: | |
| frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
| frames[frame_idx] = Image.fromarray(frame) | |
| frame_idx += 1 | |
| if frame_idx > indices[-1]: | |
| break | |
| cap.release() | |
| # Keep the requested order and duplicate the last available frame if | |
| # a video has fewer readable frames than expected. | |
| output = [frames[i] for i in indices if i in frames] | |
| if not output: | |
| raise ValueError(f"No readable frames found in: {video_path}") | |
| while len(output) < num_frames: | |
| output.append(output[-1].copy()) | |
| return output[:num_frames] | |
| def preprocess_video( | |
| video_path: str, | |
| num_frames: int = DEFAULT_NUM_FRAMES, | |
| image_size: int = DEFAULT_IMAGE_SIZE, | |
| ) -> torch.Tensor: | |
| """ | |
| Convert one video into a normalized tensor. | |
| Returns: | |
| Tensor of shape [T, C, H, W]. | |
| """ | |
| transform = build_transform(image_size) | |
| frames = read_video_frames(video_path, num_frames) | |
| tensor = torch.stack([transform(frame) for frame in frames]) | |
| return tensor | |
| def save_preprocessed_video( | |
| video_path: str, | |
| output_path: str, | |
| num_frames: int = DEFAULT_NUM_FRAMES, | |
| image_size: int = DEFAULT_IMAGE_SIZE, | |
| ) -> None: | |
| """Optional utility to cache a processed video as a .pt file.""" | |
| tensor = preprocess_video(video_path, num_frames, image_size) | |
| Path(output_path).parent.mkdir(parents=True, exist_ok=True) | |
| torch.save(tensor, output_path) | |