Spaces:
Sleeping
Sleeping
| # KeyFrameSelection/Similarties.py | |
| import cv2 | |
| from PIL import Image | |
| import imagehash | |
| from skimage.metrics import structural_similarity as ssim | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| import torch | |
| from transformers import CLIPProcessor, CLIPModel | |
| import numpy as np | |
| from concurrent.futures import ThreadPoolExecutor | |
| clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32") | |
| clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32") | |
| clip_model.eval() | |
| def _resize_gray(frame): | |
| return cv2.resize(cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY), (128, 128)) | |
| def hash_filter(records, hash_threshold=5, ssim_threshold=0.90, ssim_compare_window=3): | |
| """ | |
| Filters out visually similar frames using perceptual hashing and SSIM. | |
| Args: | |
| records (list): List of tuples (frame, frame_idx) representing sampled video frames. | |
| hash_threshold (int, optional): Maximum Hamming distance between perceptual hashes to consider frames as duplicates. Defaults to 5. | |
| ssim_threshold (float, optional): Maximum SSIM score to consider frames as distinct. Defaults to 0.90. | |
| ssim_compare_window (int, optional): Number of most recent accepted frames to compare against using SSIM. Defaults to 3. | |
| Returns: | |
| list: List of tuples (frame, frame_idx) representing filtered, distinct keyframes. | |
| """ | |
| resized_cache = {idx: _resize_gray(frame) for frame, idx in records} | |
| def compute_hash(frame): | |
| return imagehash.phash(Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))) | |
| with ThreadPoolExecutor() as executor: | |
| hashes = list(executor.map(lambda x: compute_hash(x[0]), records)) | |
| seen_hashes = [] | |
| distinct = [] | |
| for i, (frame, frame_idx) in enumerate(records): | |
| img_hash = hashes[i] | |
| if any(abs(img_hash - h) <= hash_threshold for h in seen_hashes): | |
| continue | |
| seen_hashes.append(img_hash) | |
| is_distinct = True | |
| resized_gray = resized_cache[frame_idx] | |
| for _, prev_idx in distinct[-ssim_compare_window:]: | |
| prev_gray = resized_cache[prev_idx] | |
| if ssim(resized_gray, prev_gray) > ssim_threshold: | |
| is_distinct = False | |
| break | |
| if is_distinct: | |
| distinct.append((frame, frame_idx)) | |
| return distinct | |
| def _get_clip_embeddings(frames): | |
| """ | |
| Computes the CLIP image embedding for a given video frame. | |
| Args: | |
| frame (np.ndarray): A single video frame in BGR format (as returned by OpenCV). | |
| Returns: | |
| np.ndarray: A normalized 1D NumPy array representing the CLIP image embedding. | |
| """ | |
| images = [Image.fromarray(cv2.cvtColor(f, cv2.COLOR_BGR2RGB)) for f in frames] | |
| inputs = clip_processor(images=images, return_tensors="pt", padding=True) | |
| with torch.no_grad(): | |
| features = clip_model.get_image_features(**inputs) | |
| normed = torch.nn.functional.normalize(features, p=2, dim=1) | |
| return normed.cpu().numpy() | |
| def clip_filter(records, similarity_threshold=0.85, compare_window=5, batch_size=8): | |
| """ | |
| Filters frames using CLIP embeddings and cosine similarity in batch mode (CPU-optimized). | |
| Args: | |
| records (list): List of (frame, frame_idx) tuples. | |
| similarity_threshold (float): Max cosine similarity to keep frame distinct. | |
| compare_window (int): How many past frames to compare against. | |
| batch_size (int): Number of frames to embed per batch (for speed). | |
| Returns: | |
| list: Filtered list of (frame, frame_idx) tuples with distinct content. | |
| """ | |
| frames, frame_idxs = zip(*records) | |
| embeddings = [] | |
| for i in range(0, len(frames), batch_size): | |
| batch_frames = frames[i:i+batch_size] | |
| batch_embs = _get_clip_embeddings(batch_frames) | |
| embeddings.extend(batch_embs) | |
| distinct = [] | |
| past_embeddings = [] | |
| for i, emb in enumerate(embeddings): | |
| is_distinct = True | |
| for prev_emb in past_embeddings[-compare_window:]: | |
| sim = cosine_similarity([emb], [prev_emb])[0][0] | |
| if sim > similarity_threshold: | |
| is_distinct = False | |
| break | |
| if is_distinct: | |
| distinct.append((frames[i], frame_idxs[i])) | |
| past_embeddings.append(emb) | |
| return distinct | |