Spaces:
Sleeping
Sleeping
Download functions.py from RaxoM21/MIR_Group07: direct link, hf CLI and curl.
- Browser
- Download file 18.5 kB
-
https://huggingface.co/spaces/RaxoM21/MIR_Group07/resolve/main/functions.py
- Command line
-
hf download hf://spaces/RaxoM21/MIR_Group07/functions.py
-
curl -L -o functions.py https://huggingface.co/spaces/RaxoM21/MIR_Group07/resolve/main/functions.py
18.5 kB
| import numpy as np | |
| import cv2 | |
| cv = cv2 | |
| import os | |
| import time | |
| import operator | |
| from skimage import img_as_ubyte | |
| from skimage.feature import graycomatrix, graycoprops, local_binary_pattern | |
| from skimage.transform import resize | |
| from scipy.spatial.distance import euclidean | |
| # --- Fonctions de distance / similarité --- | |
| def euclidian_distance(x,y): | |
| return np.sqrt(np.sum((x-y)**2)) | |
| def manhattan_distance(x,y): | |
| return np.sum(np.abs(x-y)) | |
| def chebyshev_distance(x,y): | |
| return np.max(np.abs(x-y)) | |
| def minkowski_distance(x,y,p): | |
| return np.sum(np.abs(x-y)**p)**(1/p) | |
| def cosine_similarity(x,y): | |
| return np.dot(x,y)/(np.linalg.norm(x)*np.linalg.norm(y)) | |
| def chi_squared_distance(x,y): | |
| return np.sum((x-y)**2/(x+y+1e-10)) | |
| def intersection_distance(x,y): | |
| return 1 - np.sum(np.minimum(x,y)) / (np.sum(x) + 1e-10) | |
| def bhattacharyya_distance(x,y): | |
| x_norm = np.abs(x) / (np.sum(np.abs(x)) + 1e-10) | |
| y_norm = np.abs(y) / (np.sum(np.abs(y)) + 1e-10) | |
| coefficient = np.sum(np.sqrt(x_norm * y_norm)) | |
| return -np.log(coefficient + 1e-10) | |
| def bhatta(x,y): | |
| return bhattacharyya_distance(x,y) | |
| def chiSquareDistance(x, y): | |
| return np.sum((x - y)**2 / (x + y + 1e-10)) | |
| def bruteForceMatching(des1, des2, is_binary=False): | |
| if is_binary: | |
| bf = cv.BFMatcher(cv.NORM_HAMMING) | |
| des1 = np.uint8(np.round(des1)) | |
| des2 = np.uint8(np.round(des2)) | |
| else: | |
| bf = cv.BFMatcher(cv.NORM_L2) | |
| matches = bf.knnMatch(des1, des2, k=2) | |
| good = [] | |
| for m, n in matches: | |
| if m.distance < 0.75 * n.distance: | |
| good.append([m]) | |
| return len(good) | |
| def flann(des1, des2, is_binary=False): | |
| if is_binary: | |
| index_params = dict(algorithm=6, | |
| table_number=6, | |
| key_size=12, | |
| multi_probe_level=1) | |
| des1 = np.uint8(np.round(des1)) | |
| des2 = np.uint8(np.round(des2)) | |
| else: | |
| index_params = dict(algorithm=1, trees=5) | |
| search_params = dict(checks=50) | |
| matcher = cv.FlannBasedMatcher(index_params, search_params) | |
| matches = matcher.knnMatch(des1, des2, k=2) | |
| good = [] | |
| for m, n in matches: | |
| if m.distance < 0.7 * n.distance: | |
| good.append([m]) | |
| return len(good) | |
| # --- Descripteurs --- | |
| def generateHistogramme_HSV(image): | |
| # Pixel counts are integers — return int32 so np.savetxt uses '%d' (writes "256" not "2.56e+02") | |
| start_time = time.time() | |
| img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) | |
| histH = cv2.calcHist([img],[0],None,[180],[0,180]) | |
| histS = cv2.calcHist([img],[1],None,[256],[0,256]) | |
| histV = cv2.calcHist([img],[2],None,[256],[0,256]) | |
| feature = np.concatenate((histH, np.concatenate((histS,histV),axis=None)),axis=None).astype(np.int32) | |
| print(f"Hist HSV terminé en {time.time()-start_time}s") | |
| return feature | |
| def generateHistogramme_Color(image): | |
| # Pixel counts are integers — return int32 so np.savetxt uses '%d' | |
| start_time = time.time() | |
| histB = cv2.calcHist([image],[0],None,[256],[0,256]) | |
| histG = cv2.calcHist([image],[1],None,[256],[0,256]) | |
| histR = cv2.calcHist([image],[2],None,[256],[0,256]) | |
| feature = np.concatenate((histB, np.concatenate((histG,histR),axis=None)),axis=None).astype(np.int32) | |
| print(f"Hist Couleur terminé en {time.time()-start_time}s") | |
| return feature | |
| def generateSIFT(image): | |
| # nfeatures=200 caps keypoints (200×128 max vs ~500×128 default) | |
| # Returns uint8 array — SIFT bins are integers 0-255, so no precision is lost | |
| # and np.savetxt writes "128" instead of "1.280000000000000000e+02", ~6x smaller files | |
| start_time = time.time() | |
| sift = cv2.SIFT_create(nfeatures=200) | |
| key_point1, descrip1 = sift.detectAndCompute(image, None) | |
| if descrip1 is not None: | |
| descrip1 = descrip1.astype(np.uint8) | |
| print(f"SIFT terminé en {time.time()-start_time}s") | |
| return descrip1 | |
| def generateORB(image): | |
| # nfeatures=200 matches SIFT cap; uint8 cast ensures '%d' format in make_descriptor_as_files | |
| start_time = time.time() | |
| orb = cv2.ORB_create(nfeatures=200) | |
| key_point1, descrip1 = orb.detectAndCompute(image, None) | |
| if descrip1 is not None: | |
| descrip1 = descrip1.astype(np.uint8) | |
| print(f"ORB terminé en {time.time()-start_time}s") | |
| return descrip1 | |
| def generateGLCM(image): | |
| start_time = time.time() | |
| gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
| gray = img_as_ubyte(gray) | |
| distances = [1, 2] | |
| angles = [0, np.pi/4, np.pi/2, 3*np.pi/4] | |
| glcmMatrix = graycomatrix(gray, distances=distances, angles=angles, normed=True) | |
| glcmProperties1 = graycoprops(glcmMatrix, "contrast").ravel() | |
| glcmProperties2 = graycoprops(glcmMatrix, "dissimilarity").ravel() | |
| glcmProperties3 = graycoprops(glcmMatrix, "homogeneity").ravel() | |
| glcmProperties4 = graycoprops(glcmMatrix, "energy").ravel() | |
| glcmProperties5 = graycoprops(glcmMatrix, "correlation").ravel() | |
| glcmProperties6 = graycoprops(glcmMatrix, "ASM").ravel() | |
| feature = np.array([ | |
| glcmProperties1, glcmProperties2, glcmProperties3, | |
| glcmProperties4, glcmProperties5, glcmProperties6 | |
| ]).ravel() | |
| print(f"GLCM terminé en {time.time()-start_time}s") | |
| return feature | |
| def generateLBP(image): | |
| start_time = time.time() | |
| points = 8 | |
| radius = 1 | |
| method = 'default' | |
| img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
| img = cv2.resize(img, (350, 350)) | |
| fullLBPmatrix = local_binary_pattern(img, points, radius, method) | |
| histograms, edges = np.histogram(fullLBPmatrix.ravel(), bins=10, range=(0, 2**points)) | |
| histograms = histograms.astype("float") | |
| histograms /= histograms.sum() + 1e-6 | |
| print(f"LBP terminé en {time.time()-start_time}s") | |
| return histograms | |
| def generateHOG(image): | |
| start_time = time.time() | |
| cellSize = (25, 25) | |
| blockSize = (50, 50) | |
| blockStride = (25, 25) | |
| nBins = 9 | |
| winSize = (350, 350) | |
| img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) | |
| img = cv2.resize(img, winSize) | |
| hog = cv2.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nBins) | |
| vect_features = hog.compute(img) | |
| print(f"HOG terminé en {time.time()-start_time}s") | |
| return vect_features | |
| def generateCLIP(image): | |
| """Extraction CLIP avec conversion BGR -> RGB obligatoire.""" | |
| start_time = time.time() | |
| # Remplace l'ancienne ligne par celle-ci : | |
| from embedders.clip_hf import CLIPEmbedder | |
| embedder = CLIPEmbedder() | |
| # Conversion OpenCV BGR -> RGB pour CLIP (très important) | |
| rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) | |
| from PIL import Image as PILImage | |
| pil_img = PILImage.fromarray(rgb) | |
| feature = embedder.encode_image(pil_img) | |
| print(f"CLIP terminé en {time.time()-start_time:.3f}s") | |
| return feature | |
| # singleton ViT | |
| _vit_model = None | |
| _vit_processor = None | |
| def _load_vit(): | |
| global _vit_model, _vit_processor | |
| if _vit_model is None: | |
| import torch | |
| from transformers import ViTModel, ViTImageProcessor | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| _vit_processor = ViTImageProcessor.from_pretrained("google/vit-base-patch16-224") | |
| _vit_model = ViTModel.from_pretrained("google/vit-base-patch16-224").to(device) | |
| weights_dir = os.environ.get( | |
| "MIR_WEIGHTS_PATH", | |
| os.path.join(os.path.dirname(os.path.abspath(__file__)), "weights") | |
| ) | |
| finetuned_path = os.path.join(weights_dir, "vit_finetuned.pth") | |
| if os.path.exists(finetuned_path): | |
| print(f"[ViT] Chargement poids fine-tunés: {finetuned_path}") | |
| state_dict = torch.load(finetuned_path, map_location=device, weights_only=True) | |
| result = _vit_model.load_state_dict(state_dict, strict=False) | |
| if result.missing_keys: | |
| print(f"[ViT] {len(result.missing_keys)} clé(s) manquante(s)") | |
| print(f"[ViT] Modèle chargé sur {device}.") | |
| else: | |
| print(f"[ViT] Pas de poids fine-tunés, utilisation du pretrained sur {device}.") | |
| _vit_model.eval() | |
| return _vit_model, _vit_processor | |
| def generateViT(image): | |
| """Extraction vecteur ViT 768-dim normalisé L2. Entrée: image BGR (numpy).""" | |
| start_time = time.time() | |
| import torch | |
| from PIL import Image as PILImage | |
| model, processor = _load_vit() | |
| device = next(model.parameters()).device | |
| rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) | |
| pil_img = PILImage.fromarray(rgb) | |
| inputs = processor(images=pil_img, return_tensors="pt") | |
| pixel_values = inputs["pixel_values"].to(device) | |
| with torch.no_grad(): | |
| outputs = model(pixel_values=pixel_values) | |
| patch_embeddings = outputs.last_hidden_state[:, 1:, :] | |
| mean_embedding = patch_embeddings.mean(dim=1) | |
| mean_embedding = mean_embedding / mean_embedding.norm(dim=-1, keepdim=True) | |
| feature = mean_embedding.cpu().numpy().flatten().astype(np.float32) | |
| print(f"ViT terminé en {time.time()-start_time:.3f}s") | |
| return feature | |
| # singleton ViT | |
| _vit_model = None | |
| _vit_processor = None | |
| def _load_vit(): | |
| global _vit_model, _vit_processor | |
| if _vit_model is None: | |
| import torch | |
| from transformers import ViTForImageClassification, ViTImageProcessor, ViTModel | |
| import json | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| _vit_processor = ViTImageProcessor.from_pretrained("google/vit-base-patch16-224") | |
| weights_dir = os.environ.get( | |
| "MIR_WEIGHTS_PATH", | |
| os.path.join(os.path.dirname(os.path.abspath(__file__)), "weights") | |
| ) | |
| finetuned_path = os.path.join(weights_dir, "vit_finetuned.pth") | |
| classes_path = os.path.join(weights_dir, "vit_classes.json") | |
| if os.path.exists(finetuned_path) and os.path.exists(classes_path): | |
| print(f"[ViT] Loading fine-tuned weights: {finetuned_path}") | |
| with open(classes_path, 'r') as f: | |
| class_map = json.load(f) | |
| num_classes = len(class_map) | |
| model_full = ViTForImageClassification.from_pretrained( | |
| "google/vit-base-patch16-224", | |
| num_labels=num_classes, | |
| ignore_mismatched_sizes=True | |
| ) | |
| state_dict = torch.load(finetuned_path, map_location=device, weights_only=True) | |
| model_full.load_state_dict(state_dict, strict=False) | |
| _vit_model = model_full.vit.to(device) | |
| print(f"[ViT] Fine-tuned model loaded on {device}.") | |
| else: | |
| print(f"[ViT] No fine-tuned weights found, using pretrained on {device}.") | |
| _vit_model = ViTModel.from_pretrained("google/vit-base-patch16-224").to(device) | |
| _vit_model.eval() | |
| return _vit_model, _vit_processor | |
| # singleton ResNet50 | |
| _resnet_model = None | |
| def _load_resnet(): | |
| global _resnet_model | |
| if _resnet_model is None: | |
| import torch | |
| import torch.nn as nn | |
| from torchvision import models | |
| import json | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| resnet = models.resnet50(weights=models.ResNet50_Weights.DEFAULT) | |
| weights_dir = os.environ.get( | |
| "MIR_WEIGHTS_PATH", | |
| os.path.join(os.path.dirname(os.path.abspath(__file__)), "weights") | |
| ) | |
| finetuned_path = os.path.join(weights_dir, "resnet_finetuned.pth") | |
| classes_path = os.path.join(weights_dir, "resnet_classes.json") | |
| if os.path.exists(finetuned_path) and os.path.exists(classes_path): | |
| print(f"[ResNet50] Loading fine-tuned weights: {finetuned_path}") | |
| with open(classes_path, 'r') as f: | |
| class_map = json.load(f) | |
| num_classes = len(class_map) | |
| resnet.fc = nn.Linear(resnet.fc.in_features, num_classes) | |
| state_dict = torch.load(finetuned_path, map_location=device, weights_only=True) | |
| resnet.load_state_dict(state_dict, strict=False) | |
| print(f"[ResNet50] Fine-tuned model loaded on {device}.") | |
| else: | |
| print(f"[ResNet50] No fine-tuned weights found, using pretrained on {device}.") | |
| _resnet_model = nn.Sequential(*list(resnet.children())[:-1]).to(device) | |
| _resnet_model.eval() | |
| return _resnet_model | |
| def generateResNet(image): | |
| """Extraction vecteur ResNet50 2048-dim normalisé L2. Entrée: image BGR (numpy).""" | |
| start_time = time.time() | |
| import torch | |
| from torchvision import transforms | |
| from PIL import Image as PILImage | |
| model = _load_resnet() | |
| device = next(model.parameters()).device | |
| rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) | |
| pil_img = PILImage.fromarray(rgb) | |
| preprocess = transforms.Compose([ | |
| transforms.Resize(256), | |
| transforms.CenterCrop(224), | |
| transforms.ToTensor(), | |
| transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), | |
| ]) | |
| input_tensor = preprocess(pil_img).unsqueeze(0).to(device) | |
| with torch.no_grad(): | |
| features = model(input_tensor) | |
| features = features.squeeze() | |
| features = features / features.norm() | |
| feature = features.cpu().numpy().astype(np.float32) | |
| print(f"ResNet50 terminé en {time.time()-start_time:.3f}s") | |
| return feature | |
| def extractReqFeatures(fileName, algo_choice): | |
| print(algo_choice) | |
| if fileName: | |
| img = cv2.imread(fileName) | |
| resized_img = resize(img, (128*4, 64*4)) | |
| if algo_choice == 1: | |
| start_time = time.time() | |
| histB = cv2.calcHist([img],[0],None,[256],[0,256]) | |
| histG = cv2.calcHist([img],[1],None,[256],[0,256]) | |
| histR = cv2.calcHist([img],[2],None,[256],[0,256]) | |
| vect_features = np.concatenate((histB, np.concatenate((histG,histR),axis=None)),axis=None) | |
| print(f"Hist couleur requête terminé en {time.time()-start_time}s") | |
| elif algo_choice == 2: | |
| start_time = time.time() | |
| hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) | |
| histH = cv2.calcHist([hsv],[0],None,[180],[0,180]) | |
| histS = cv2.calcHist([hsv],[1],None,[256],[0,256]) | |
| histV = cv2.calcHist([hsv],[2],None,[256],[0,256]) | |
| vect_features = np.concatenate((histH, np.concatenate((histS,histV),axis=None)),axis=None) | |
| print(f"HSV requête terminé en {time.time()-start_time}s") | |
| elif algo_choice == 3: | |
| start_time = time.time() | |
| sift = cv2.SIFT_create() | |
| kps, vect_features = sift.detectAndCompute(img, None) | |
| print(f"SIFT requête terminé en {time.time()-start_time}s") | |
| elif algo_choice == 4: | |
| start_time = time.time() | |
| orb = cv2.ORB_create() | |
| key_point1, vect_features = orb.detectAndCompute(img, None) | |
| print(f"ORB requête terminé en {time.time()-start_time}s") | |
| elif algo_choice == 5: | |
| vect_features = generateGLCM(img) | |
| elif algo_choice == 6: | |
| vect_features = generateLBP(img) | |
| elif algo_choice == 7: | |
| vect_features = generateHOG(img) | |
| np.savetxt("Methode_"+str(algo_choice)+"_query.txt", vect_features) | |
| print("sauvegardé") | |
| return vect_features | |
| def distance_f(l1, l2, distanceName, is_binary=False): | |
| if distanceName == "Euclidienne": | |
| distance = euclidean(l1, l2) | |
| elif distanceName in ["Correlation", "Chi carre", "Intersection", "Bhattacharyya"]: | |
| if distanceName == "Correlation": | |
| distance = cv2.compareHist(np.float32(l1), np.float32(l2), cv2.HISTCMP_CORREL) | |
| elif distanceName == "Chi carre": | |
| distance = chiSquareDistance(l1, l2) | |
| elif distanceName == "Intersection": | |
| distance = cv2.compareHist(np.float32(l1), np.float32(l2), cv2.HISTCMP_INTERSECT) | |
| elif distanceName == "Bhattacharyya": | |
| distance = bhatta(l1, l2) | |
| elif distanceName == "Brute force": | |
| distance = bruteForceMatching(l1, l2, is_binary=is_binary) | |
| elif distanceName == "Flann": | |
| distance = flann(l1, l2, is_binary=is_binary) | |
| elif distanceName == "Cosine Similarity": | |
| distance = cosine_similarity(l1, l2) | |
| return distance | |
| def getkNeighbors(lfeatures, req, k, distanceName): | |
| start_time = time.time() | |
| ldistances = [] | |
| for i in range(len(lfeatures)): | |
| dist = distance_f(req, lfeatures[i][1], distanceName) | |
| ldistances.append((lfeatures[i][0], lfeatures[i][1], dist)) | |
| if distanceName in ["Correlation", "Brute force", "Flann", "Cosine Similarity"]: | |
| order = True | |
| else: | |
| order = False | |
| ldistances.sort(key=operator.itemgetter(2), reverse=order) | |
| lneighbors = [] | |
| for i in range(k): | |
| lneighbors.append(ldistances[i]) | |
| print(f"kNN terminé en {time.time()-start_time}s") | |
| return lneighbors | |
| def concatenate_multiple_descriptors(list_of_features): | |
| flattened_features = [np.array(f).ravel() for f in list_of_features] | |
| combined_feature = np.concatenate(flattened_features, axis=None) | |
| return combined_feature | |
| def make_descriptor_as_files(dataset_path, descriptor_func, descriptor_name): | |
| """Applique un descripteur aux images dont le 1er chiffre est pair et sauvegarde en .txt.""" | |
| if not os.path.exists("descriptors"): | |
| os.makedirs("descriptors") | |
| target_dir = os.path.join("descriptors", descriptor_name) | |
| if not os.path.exists(target_dir): | |
| os.makedirs(target_dir) | |
| for filename in os.listdir(dataset_path): | |
| if filename[0].isdigit() and int(filename[0]) % 2 == 0: | |
| img_path = os.path.join(dataset_path, filename) | |
| img = cv.imread(img_path) | |
| if img is not None: | |
| try: | |
| feature = descriptor_func(img) | |
| num_image = os.path.splitext(filename)[0] | |
| save_path = os.path.join(target_dir, f"{num_image}_{descriptor_name}.txt") | |
| # integers (histograms, SIFT, ORB) → '%d'; floats → '%.8f' (vs default %.18e) | |
| if np.issubdtype(np.asarray(feature).dtype, np.integer): | |
| np.savetxt(save_path, feature, fmt='%d') | |
| else: | |
| np.savetxt(save_path, feature, fmt='%.8f') | |
| except Exception as e: | |
| print(f"Erreur {filename}: {e}") | |
| print(f"Indexation {descriptor_name} pour images paires terminée!") | |