Untitled
unknown
plain_text
a year ago
19 kB
12
Indexable
import numpy as np
import cv2 as cv
import glob
import os
import random
from skimage.feature import hog
from Parameters import Parameters
from sklearn.svm import LinearSVC
import pickle
# Inițializare parametri
params = Parameters()
# Harta fișier-adnotări -> folder
ANNOTATIONS_TO_FOLDER = {
'./antrenare/dad_annotations.txt': 'dad',
'./antrenare/deedee_annotations.txt': 'deedee',
'./antrenare/dexter_annotations.txt': 'dexter',
'./antrenare/mom_annotations.txt': 'mom'
}
class FacialDetector:
def __init__(self, params):
self.params = params
self.best_model = None
def load_annotations(self, file_path):
"""
Încarcă adnotările dintr-un fișier: image_name x_min y_min x_max y_max
Returnează un dict: {image_name: [(x_min,y_min,x_max,y_max), ...]}
"""
annotations = {}
with open(file_path, 'r') as f:
for line in f:
parts = line.strip().split()
image_name = parts[0]
x_min, y_min, x_max, y_max = map(int, parts[1:5])
if image_name not in annotations:
annotations[image_name] = []
annotations[image_name].append((x_min, y_min, x_max, y_max))
return annotations
def analyze_svm_training(self, positive_features, negative_features):
"""
Afișează scoruri, histograme, coeficienți SVM.
"""
if self.best_model is None:
print("SVM-ul nu a fost antrenat încă!")
return
positive_scores = self.best_model.decision_function(positive_features)
print(f"Scoruri pozitive (primele 10): {positive_scores[:10]}")
negative_scores = self.best_model.decision_function(negative_features)
print(f"Scoruri negative (primele 10): {negative_scores[:10]}")
print("Coeficienți SVM (primele 10):")
print(self.best_model.coef_[0][:10])
print(f"Bias-ul SVM: {self.best_model.intercept_[0]}")
import matplotlib.pyplot as plt
plt.hist(positive_scores, bins=30, alpha=0.5, label="Positive Scores")
plt.hist(negative_scores, bins=30, alpha=0.5, label="Negative Scores")
plt.axvline(x=0, color="red", linestyle="--", label="Decision Boundary")
plt.legend()
plt.title("Distribuția scorurilor SVM")
plt.xlabel("Score")
plt.ylabel("Count")
plt.show()
def train_classifier(self, positive_features, negative_features):
"""
Căutăm cel mai bun C pentru SVM.
"""
Cs = [10 ** -5, 10 ** -4, 10 ** -3, 10 ** -2, 10 ** -1, 10 ** 0, 10 ** 1]
best_c = 0
best_accuracy = 0
best_model = None
training_examples = np.vstack((positive_features, negative_features))
train_labels = np.hstack((np.ones(positive_features.shape[0]), np.zeros(negative_features.shape[0])))
print("Începem căutarea celui mai bun C...")
for c in Cs:
print(f"Antrenăm un clasificator pentru C = {c}")
model = LinearSVC(C=c, max_iter=10000)
model.fit(training_examples, train_labels)
acc = model.score(training_examples, train_labels)
print(f"Precizia pentru C = {c}: {acc}")
if acc > best_accuracy:
best_accuracy = acc
best_c = c
best_model = model
self.best_model = best_model
print(f"Cel mai bun C: {best_c} cu precizia: {best_accuracy}")
model_path = os.path.join(self.params.dir_save_files, 'svm_model.pkl')
with open(model_path, 'wb') as f:
pickle.dump(best_model, f)
print(f"Modelul optim SVM a fost salvat în {model_path}.")
def generate_positive_patches(self, image_path, bounding_boxes):
img = cv.imread(image_path, cv.IMREAD_GRAYSCALE)
positive_patches = []
for (x_min, y_min, x_max, y_max) in bounding_boxes:
patch = img[y_min:y_max, x_min:x_max]
if patch is not None and patch.shape[0] > 0 and patch.shape[1] > 0:
patch = cv.resize(patch, (self.params.dim_window, self.params.dim_window))
positive_patches.append(patch)
return positive_patches
def generate_negative_patches(self, image_path, bounding_boxes, num_negatives=10):
"""
Generează patch-uri negative. Asigurăm că nu se suprapune cu box-uri pozitive.
"""
img = cv.imread(image_path, cv.IMREAD_GRAYSCALE)
if img is None:
print(f"Eroare la încărcarea imaginii {image_path}.")
return []
img_h, img_w = img.shape
negative_patches = []
max_attempts = 50
for _ in range(num_negatives):
attempts = 0
while attempts < max_attempts:
x_min = random.randint(0, img_w - self.params.dim_window)
y_min = random.randint(0, img_h - self.params.dim_window)
x_max = x_min + self.params.dim_window
y_max = y_min + self.params.dim_window
intersects = False
for (bx_min, by_min, bx_max, by_max) in bounding_boxes:
if (x_min < bx_max and x_max > bx_min and
y_min < by_max and y_max > by_min):
intersects = True
break
if not intersects:
patch = img[y_min:y_max, x_min:x_max]
if patch is not None and patch.shape[0] > 0 and patch.shape[1] > 0:
patch = cv.resize(patch, (self.params.dim_window, self.params.dim_window))
negative_patches.append(patch)
break
attempts += 1
return negative_patches
def get_positive_descriptors(self):
"""
Citește imagini + adnotări => extrage patch-uri pozitive => HOG.
"""
annotations_files = [
'./antrenare/dad_annotations.txt',
'./antrenare/deedee_annotations.txt',
'./antrenare/dexter_annotations.txt',
'./antrenare/mom_annotations.txt'
]
positive_descriptors = []
for annotation_file in annotations_files:
if not os.path.exists(annotation_file):
print(f"Fișierul {annotation_file} nu există!")
continue
folder = ANNOTATIONS_TO_FOLDER[annotation_file]
print(f"[INFO] Trecem la fișierul de adnotări: {annotation_file}, pentru folderul {folder}")
annotations = self.load_annotations(annotation_file)
processed_images = set()
for image_name, bounding_boxes in annotations.items():
if image_name in processed_images:
continue
path = os.path.join('./antrenare', folder, image_name)
if not os.path.exists(path):
print(f"[WARN] Imaginea {image_name} nu există în folderul {folder}!")
continue
positive_patches = self.generate_positive_patches(path, bounding_boxes)
from skimage.feature import hog
for patch in positive_patches:
hog_descriptor = hog(
patch,
pixels_per_cell=(self.params.dim_hog_cell, self.params.dim_hog_cell),
cells_per_block=(2, 2),
feature_vector=True
)
positive_descriptors.append(hog_descriptor)
processed_images.add(image_name)
print(f"[INFO] Număr total de descriptori pozitivi generați: {len(positive_descriptors)}")
return np.array(positive_descriptors)
def get_negative_descriptors(self):
"""
Citește imagini + adnotări => extrage patch-uri negative => HOG.
"""
annotations_files = [
'./antrenare/dad_annotations.txt',
'./antrenare/deedee_annotations.txt',
'./antrenare/dexter_annotations.txt',
'./antrenare/mom_annotations.txt'
]
negative_descriptors = []
for annotation_file in annotations_files:
if not os.path.exists(annotation_file):
continue
folder = ANNOTATIONS_TO_FOLDER[annotation_file]
annotations = self.load_annotations(annotation_file)
processed_images = set()
for image_name, bounding_boxes in annotations.items():
if image_name in processed_images:
continue
path = os.path.join('./antrenare', folder, image_name)
if not os.path.exists(path):
continue
negative_patches = self.generate_negative_patches(path, bounding_boxes, num_negatives=10)
from skimage.feature import hog
for patch in negative_patches:
hog_descriptor = hog(
patch,
pixels_per_cell=(self.params.dim_hog_cell, self.params.dim_hog_cell),
cells_per_block=(2, 2),
feature_vector=True
)
negative_descriptors.append(hog_descriptor)
processed_images.add(image_name)
print(f"[INFO] Număr total de descriptori negativi generați: {len(negative_descriptors)}")
return np.array(negative_descriptors)
def run(self):
"""
Detectarea fețelor folosind sliding window la MULTIPLE scale.
"""
test_images_path = os.path.join(self.params.dir_test_examples, '*.jpg')
test_files = glob.glob(test_images_path)
# liste pentru colectare
detections_list = []
scores_list = []
file_names_list = []
# definim scări (exemplu)
scales = [0.4, 0.6, 0.8, 1.0, 1.3, 1.6, 2.0]
for test_file in test_files:
img = cv.imread(test_file, cv.IMREAD_GRAYSCALE)
if img is None:
print(f"Imaginea {test_file} nu a putut fi încărcată.")
continue
original_h, original_w = img.shape
for scale in scales:
new_w = int(original_w * scale)
new_h = int(original_h * scale)
if new_w < self.params.dim_window or new_h < self.params.dim_window:
continue
resized_img = cv.resize(img, (new_w, new_h))
step = self.params.dim_window // 4
from skimage.feature import hog
for y in range(0, new_h - self.params.dim_window, step):
for x in range(0, new_w - self.params.dim_window, step):
patch = resized_img[y:y + self.params.dim_window, x:x + self.params.dim_window]
hog_descriptor = hog(
patch,
pixels_per_cell=(self.params.dim_hog_cell, self.params.dim_hog_cell),
cells_per_block=(2, 2),
feature_vector=True
)
score = self.best_model.decision_function([hog_descriptor])[0]
if score > self.params.threshold:
# transformăm coordonatele
x_min = int(x / scale)
y_min = int(y / scale)
x_max = int((x + self.params.dim_window) / scale)
y_max = int((y + self.params.dim_window) / scale)
detections_list.append([x_min, y_min, x_max, y_max])
scores_list.append(score)
file_names_list.append(os.path.basename(test_file))
# convertim la np.array
detections = np.array(detections_list)
scores = np.array(scores_list)
file_names = np.array(file_names_list)
print("Aplicăm Non-Maximum Suppression...")
final_boxes, final_scores, keep_idx = self.non_max_suppression(detections, scores, iou_threshold=0.5)
final_file_names = file_names[keep_idx]
print(f"Număr total de detecții după NMS: {len(final_boxes)}")
return final_boxes, final_scores, final_file_names
def eval_detections(self, detections, scores, file_names):
"""
Evaluează detectările și calculează AP.
"""
if not os.path.exists(self.params.path_annotations):
print(f"Debug: Fișierul de adnotări nu există la calea: {self.params.path_annotations}")
return
else:
print(f"Debug: Fișierul de adnotări a fost găsit la calea: {self.params.path_annotations}")
# 1) Citim fișierul de adnotări
ground_truth = {}
total_gt = 0
with open(self.params.path_annotations, "r") as f:
for line in f:
parts = line.strip().split()
file_name = parts[0]
bbox = list(map(int, parts[1:5]))
if file_name not in ground_truth:
ground_truth[file_name] = []
ground_truth[file_name].append(bbox)
total_gt += 1
# 2) Parcurgem toate detecțiile sub formă (file_name, score, x1,y1,x2,y2)
all_dets = []
for i, fn in enumerate(file_names):
box = detections[i]
sc = scores[i]
all_dets.append((fn, sc, box))
# Sortăm global după scor, descrescător
all_dets = sorted(all_dets, key=lambda x: x[1], reverse=True)
iou_threshold = 0.3
tp_array = []
fp_array = []
# Ținem evidența box-urilor GT deja asociate
matched_gt = {} # {file_name: [False, False, ...] pt fiecare box}
for fn in ground_truth:
matched_gt[fn] = [False] * len(ground_truth[fn])
# 3) Marcare TP / FP
for (fname, score, box_det) in all_dets:
if fname not in ground_truth:
# nu avem adnotări pt imaginea asta => FP
tp_array.append(0)
fp_array.append(1)
continue
gtlist = ground_truth[fname]
best_iou = 0
best_idx = -1
for idx, gt_box in enumerate(gtlist):
iou_val = self.compute_iou(box_det, gt_box)
if iou_val > best_iou:
best_iou = iou_val
best_idx = idx
if best_iou >= iou_threshold and best_idx != -1 and not matched_gt[fname][best_idx]:
tp_array.append(1)
fp_array.append(0)
matched_gt[fname][best_idx] = True
else:
tp_array.append(0)
fp_array.append(1)
# 4) Calculăm precision-recall + AP
tp_cum = np.cumsum(tp_array)
fp_cum = np.cumsum(fp_array)
recalls = tp_cum / float(total_gt)
precisions = tp_cum / (tp_cum + fp_cum + 1e-10)
ap = self.voc_ap(recalls, precisions) # exemplu funcție voc_ap
# 5) Afișăm metrici la ultimul punct (adică la threshold fix)
# (dar e un alt threshold, practic pe scor...)
# Totuși, True Pos/False Pos/False Neg pot fi interpretate la final
final_tp = tp_cum[-1]
final_fp = fp_cum[-1]
final_fn = total_gt - final_tp
precision = final_tp / float(final_tp + final_fp + 1e-10)
recall = final_tp / float(total_gt + 1e-10)
print(f"Evaluare finală:")
print(f"True Positives: {int(final_tp)}")
print(f"False Positives: {int(final_fp)}")
print(f"False Negatives: {int(final_fn)}")
print(f"Precision: {precision:.4f}")
print(f"Recall: {recall:.4f}")
print(f"Average Precision: {ap:.4f}")
# Salvăm
results_path = os.path.join(self.params.dir_save_files, "evaluation_results.txt")
with open(results_path, "w") as f:
f.write(f"True Positives: {int(final_tp)}\n")
f.write(f"False Positives: {int(final_fp)}\n")
f.write(f"False Negatives: {int(final_fn)}\n")
f.write(f"Precision: {precision:.4f}\n")
f.write(f"Recall: {recall:.4f}\n")
f.write(f"Average Precision: {ap:.4f}\n")
print(f"Rezultatele evaluării au fost salvate în {results_path}.")
def voc_ap(self, rec, prec):
"""
Computăm Average Precision folosind metoda "VOC2007 11-point interpolation"
sau direct area sub PR. Aici facem "area sub curba" simplu:
Metodă simplă: AP = sum((r[i] - r[i-1]) * p[i])
"""
# Adăugăm puncte inițiale (0,1)
mrec = np.concatenate(([0.0], rec, [1.0]))
mpre = np.concatenate(([1.0], prec, [0.0]))
# Pentru a fi robust, transformăm mpre în envelope
for i in range(mpre.size - 1, 0, -1):
mpre[i - 1] = max(mpre[i - 1], mpre[i])
# Apoi sumăm pe segmente unde recall crește
i_idx = np.where(mrec[1:] != mrec[:-1])[0]
ap = np.sum((mrec[i_idx + 1] - mrec[i_idx]) * mpre[i_idx + 1])
return ap
def non_max_suppression(self, boxes, scores, iou_threshold=0.3):
if len(boxes) == 0:
return boxes, scores, []
order = np.argsort(scores)[::-1]
keep = []
while len(order) > 0:
i = order[0]
keep.append(i)
ious = []
for j in order[1:]:
iou_ij = self.compute_iou(boxes[i], boxes[j])
ious.append(iou_ij)
ious = np.array(ious)
inds = np.where(ious <= iou_threshold)[0]
order = order[inds + 1]
keep = np.array(keep, dtype=int)
final_boxes = boxes[keep]
final_scores = scores[keep]
return final_boxes, final_scores, keep
def compute_iou(self, boxA, boxB):
xA = max(boxA[0], boxB[0])
yA = max(boxA[1], boxB[1])
xB = min(boxA[2], boxB[2])
yB = min(boxA[3], boxB[3])
interW = max(0, xB - xA)
interH = max(0, yB - yA)
interArea = interW * interH
boxAArea = (boxA[2] - boxA[0]) * (boxA[3] - boxA[1])
boxBArea = (boxB[2] - boxB[0]) * (boxB[3] - boxB[1])
iou = interArea / float(boxAArea + boxBArea - interArea + 1e-10)
return iou
# Inițializăm detectorul facial
facial_detector = FacialDetector(params)
Editor is loading...
Leave a Comment