MIR_Group07 / functions.py
RaxoM21's picture
Fix: serve Flickr8k images from correct directory
3cb3118
Raw History Blame Contribute Delete
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!")