Principes de l'algorithme ViBe
L'algorithme ViBe, pour Visual Background Extractor, est une technique performante pour la modélisation de fond et la détection d'objets en mouvement dans les flux vidéo. Introduit par Barnich et Van Droogenbroeck en 2009, il se distingue par son efficacité et sa simplicité.
Avantages clés
- Initialisation en une seule image : Le modèle de fond peut être initialisé à partir de la première trame.
- Mise à jour stochastique : Adaptation continue aux changements du fond.
- Propagation spatiale : Réduction des artefacts de type "fantôme".
- Efficacité computationnelle : Compatible avec le traitement en temps réel.
- Faible empreinte mémoire : Chaque pixel nécessite le stockage d'un nombre limité d'échantillons.
Mécanisme de fonctionnement
- Modèle de fond : Chaque pixel est associé à un ensemble de N échantillons de valeurs passées.
- Détection du premier plan : Un pixel est classé comme premier plan si le nombre d'échantillons de fond proches de sa valeur courante est inférieur à un seuil minimum.
- Stratégie de mise à jour : Les échantillons de fond sont mis à jour aléatoirement, incluant une propagation vers les pixels voisins.
Implémentation sous MATLAB
Programme principal : extraction_premier_plan.m
%% Script principal pour l'extraction de premier plan avec ViBe
clear; clc; close all;
%% Configuration des paramètres
parametres = struct();
parametres.nbEchantillons = 20; % Nombre d'échantillons par pixel
parametres.seuilCorrespondance = 20; % Seuil de différence en niveaux de gris
parametres.correspondancesMin = 2; % Nombre minimum de correspondances pour le fond
parametres.facteurMiseAJour = 16; % Facteur φ, probabilité de mise à jour = 1/φ
parametres.rayonVoisinage = 20; % Rayon pour l'initialisation
parametres.operationsMorpho = true; % Appliquer des opérations morphologiques
%% Chemins des fichiers vidéo
cheminEntree = 'video_source.avi';
cheminSortie = 'video_resultat.avi';
% Générer une vidéo exemple si nécessaire
if ~exist(cheminEntree, 'file')
fprintf('Fichier vidéo introuvable, création d\'un exemple...\n');
creerVideoExemple();
end
%% Initialisation des lecteurs et écrivains vidéo
lecteurVideo = VideoReader(cheminEntree);
nombreTotalImages = lecteurVideo.NumFrames;
hauteur = lecteurVideo.Height;
largeur = lecteurVideo.Width;
ecrivainVideo = VideoWriter(cheminSortie, 'Motion JPEG AVI');
ecrivainVideo.FrameRate = lecteurVideo.FrameRate;
open(ecrivainVideo);
%% Initialisation du modèle de fond ViBe
fprintf('Initialisation du modèle de fond ViBe...\n');
premiereImage = readFrame(lecteurVideo);
if size(premiereImage, 3) == 3
premiereImageNiveauxGris = rgb2gray(premiereImage);
else
premiereImageNiveauxGris = premiereImage;
end
modeleFond = initialiserModeleViBe(premiereImageNiveauxGris, parametres);
%% Boucle de traitement principale
fprintf('Début du traitement vidéo, total d\'images : %d\n', nombreTotalImages);
compteurImages = 0;
tempsTraitement = zeros(nombreTotalImages-1, 1);
figure('Position', [100, 100, 1200, 400]);
subplot(1,3,1); handle1 = imshow(premiereImage); title('Image originale');
subplot(1,3,2); handle2 = imshow(zeros(hauteur, largeur)); title('Masque de premier plan');
subplot(1,3,3); handle3 = imshow(premiereImage); title('Résultat de détection');
while hasFrame(lecteurVideo)
compteurImages = compteurImages + 1;
imageCourante = readFrame(lecteurVideo);
if size(imageCourante, 3) == 3
imageCouranteNG = rgb2gray(imageCourante);
else
imageCouranteNG = imageCourante;
end
tic;
[masquePremierPlan, modeleFond] = detecterPremierPlanViBe(...
double(imageCouranteNG), modeleFond, parametres);
if parametres.operationsMorpho
masquePremierPlan = traitementMorphologique(masquePremierPlan);
end
tempsTraitement(compteurImages) = toc;
imageResultat = afficherResultats(imageCourante, masquePremierPlan);
set(handle1, 'CData', imageCourante);
set(handle2, 'CData', masquePremierPlan);
set(handle3, 'CData', imageResultat);
drawnow;
writeVideo(ecrivainVideo, imageResultat);
if mod(compteurImages, 50) == 0
fprintf('Images traitées %d/%d, temps moyen : %.3f s/image\n', ...
compteurImages, nombreTotalImages-1, mean(tempsTraitement(1:compteurImages)));
end
end
close(ecrivainVideo);
fprintf('Traitement terminé !\n');
fprintf('Total d\'images traitées : %d\n', compteurImages);
fprintf('Temps de traitement moyen : %.4f s/image\n', mean(tempsTraitement));
fprintf('Vidéo résultat enregistrée dans : %s\n', cheminSortie);
figure;
plot(tempsTraitement, 'b-', 'LineWidth', 1.5);
xlabel('Numéro d\'image');
ylabel('Temps de traitement (s)');
title('Statistiques de temps de traitement ViBe');
grid on;
Fonction d'initialisatoin : initialiserModeleViBe.m
function modeleFond = initialiserModeleViBe(premiereImage, parametres)
% Initialisation du modèle de fond ViBe
% Entrées :
% premiereImage - Image initiale (niveaux de gris)
% parametres - Structure des paramètres
% Sorties :
% modeleFond - Structure du modèle de fond
[hauteurImg, largeurImg] = size(premiereImage);
premiereImage = double(premiereImage);
modeleFond = struct();
modeleFond.echantillons = zeros(hauteurImg, largeurImg, parametres.nbEchantillons, 'uint8');
modeleFond.hauteur = hauteurImg;
modeleFond.largeur = largeurImg;
modeleFond.nbEchantillons = parametres.nbEchantillons;
fprintf('Remplissage de l\'échantillon de fond...\n');
for ligne = 1:hauteurImg
for colonne = 1:largeurImg
debutLigne = max(1, ligne - 1);
finLigne = min(hauteurImg, ligne + 1);
debutColonne = max(1, colonne - 1);
finColonne = min(largeurImg, colonne + 1);
voisinage = premiereImage(debutLigne:finLigne, debutColonne:finColonne);
voisinage = voisinage(:);
for echantillon = 1:parametres.nbEchantillons
if ~isempty(voisinage)
indexAleatoire = randi(length(voisinage));
modeleFond.echantillons(ligne, colonne, echantillon) = voisinage(indexAleatoire);
else
modeleFond.echantillons(ligne, colonne, echantillon) = premiereImage(ligne, colonne);
end
end
end
end
fprintf('Modèle initialisé, dimensions de l\'échantillon : %d×%d×%d\n', ...
hauteurImg, largeurImg, parametres.nbEchantillons);
end
Fonction de détection : detecterPremierPlanViBe.m
function [masque, modeleMisAJour] = detecterPremierPlanViBe(...
imageActuelle, modeleFond, parametres)
% Détection du premier plan et mise à jour du modèle
% Entrées :
% imageActuelle - Image courante (double, niveaux de gris)
% modeleFond - Modèle de fond actuel
% parametres - Structure des paramètres
% Sorties :
% masque - Masque binaire du premier plan
% modeleMisAJour - Modèle de fond mis à jour
[hauteurImg, largeurImg] = size(imageActuelle);
masque = false(hauteurImg, largeurImg);
echantillons = modeleFond.echantillons;
nbEch = modeleFond.nbEchantillons;
for ligne = 1:hauteurImg
for colonne = 1:largeurImg
pixelCourant = imageActuelle(ligne, colonne);
nbCorrespondances = 0;
for k = 1:nbEch
if abs(double(echantillons(ligne, colonne, k)) - pixelCourant) < parametres.seuilCorrespondance
nbCorrespondances = nbCorrespondances + 1;
if nbCorrespondances >= parametres.correspondancesMin
break;
end
end
end
if nbCorrespondances < parametres.correspondancesMin
masque(ligne, colonne) = true;
else
masque(ligne, colonne) = false;
if rand() < 1/parametres.facteurMiseAJour
echantillonAlea = randi(nbEch);
echantillons(ligne, colonne, echantillonAlea) = uint8(pixelCourant);
if rand() < 1/parametres.facteurMiseAJour
deltaLigne = randi(3) - 2;
deltaCol = randi(3) - 2;
ligneVoisine = ligne + deltaLigne;
colonneVoisine = colonne + deltaCol;
if ligneVoisine >= 1 && ligneVoisine <= hauteurImg && colonneVoisine >= 1 && colonneVoisine <= largeurImg
echantillonAlea2 = randi(nbEch);
echantillons(ligneVoisine, colonneVoisine, echantillonAlea2) = uint8(pixelCourant);
end
end
end
end
end
end
modeleMisAJour = modeleFond;
modeleMisAJour.echantillons = echantillons;
end
Traitement morphologique : traitementMorphologique.m
function masqueTraite = traitementMorphologique(masqueBinaire)
% Post-traitement morphologique pour le nettoyage du masque
% Entrée :
% masqueBinaire - Masque binaire brut
% Sortie :
% masqueTraite - Masque nettoyé
masqueTraite = bwareaopen(masqueBinaire, 50);
elementStructurant1 = strel('disk', 3);
masqueTraite = imclose(masqueTraite, elementStructurant1);
elementStructurant2 = strel('disk', 2);
masqueTraite = imopen(masqueTraite, elementStructurant2);
masqueTraite = imfill(masqueTraite, 'holes');
masqueTraite = medfilt2(masqueTraite, [3, 3]);
end
Visualisation des résultats : afficherResultats.m
function imageFinale = afficherResultats(imageOriginale, masquePremierPlan)
% Création d'une image de visualisation
% Entrées :
% imageOriginale - Image source (RGB ou niveaux de gris)
% masquePremierPlan - Masque binaire
% Sortie :
% imageFinale - Image annotée
if size(imageOriginale, 3) == 1
imageRGB = cat(3, imageOriginale, imageOriginale, imageOriginale);
else
imageRGB = imageOriginale;
end
canalRouge = imageRGB(:,:,1);
canalVert = imageRGB(:,:,2);
canalBleu = imageRGB(:,:,3);
canalRouge(masquePremierPlan) = 255;
canalVert(masquePremierPlan) = 0;
canalBleu(masquePremierPlan) = 0;
imageFinale = cat(3, canalRouge, canalVert, canalBleu);
statistiques = regionprops(masquePremierPlan, 'BoundingBox', 'Area');
regionValide = statistiques([statistiques.Area] > 100);
for k = 1:length(regionValide)
boite = regionValide(k).BoundingBox;
imageFinale = insertShape(imageFinale, 'Rectangle', boite, ...
'Color', 'green', 'LineWidth', 2);
end
end
Création d'une vidéo exemple : creerVideoExemple.m
function creerVideoExemple()
% Génération d'une vidéo synthétique pour les tests
cheminVideo = 'video_exemple.avi';
ecrivain = VideoWriter(cheminVideo, 'Motion JPEG AVI');
ecrivain.FrameRate = 10;
open(ecrivain);
for idx = 1:100
image = uint8(128 * ones(240, 320, 3));
positionRectangle = [50 + idx, 100, 40, 40];
image = insertShape(image, 'FilledRectangle', positionRectangle, ...
'Color', 'white', 'Opacity', 1);
rectangleStatique = [150, 150, 30, 30];
image = insertShape(image, 'FilledRectangle', rectangleStatique, ...
'Color', 'blue', 'Opacity', 1);
bruit = uint8(randn(240, 320, 3) * 10);
image = min(255, max(0, double(image) + double(bruit)));
writeVideo(ecrivain, image);
end
close(ecrivain);
fprintf('Vidéo exemple créée : %s\n', cheminVideo);
end
Optimisation des paramètres
Description des paramètres clés
% Plages de valeurs recommandées
parametres.nbEchantillons = 20; % [10-30]
parametres.seuilCorrespondance = 20; % [10-40], plus sensible si bas
parametres.correspondancesMin = 2; % [1-5], plus sensible si bas
parametres.facteurMiseAJour = 16; % [8-32], mise à jour plus lente si haut
Suggestions pour différents scénarios
% Scénario fond statique avec objets en mouvement marqués
param_statique = struct('nbEchantillons', 15, 'seuilCorrespondance', 15, ...
'correspondancesMin', 2, 'facteurMiseAJour', 20);
% Scénario fond dynamique (feuilles, eau)
param_dynamique = struct('nbEchantillons', 25, 'seuilCorrespondance', 25, ...
'correspondancesMin', 3, 'facteurMiseAJour', 12);
% Scénario faible luminosité ou fort bruit
param_faibleLumiere = struct('nbEchantillons', 20, 'seuilCorrespondance', 30, ...
'correspondancesMin', 2, 'facteurMiseAJour', 16);
Fonctionnalités avancées
Support des images couleur
function modeleFondCouleur = initialiserViBeCouleur(imageCouleur, parametres)
% Initialisation ViBe pour les images couleur
[hauteurImg, largeurImg, ~] = size(imageCouleur);
modeleFondCouleur = struct();
modeleFondCouleur.echantillons = zeros(hauteurImg, largeurImg, 3, parametres.nbEchantillons, 'uint8');
for canal = 1:3
donneesCanal = imageCouleur(:,:,canal);
for ligne = 1:hauteurImg
for colonne = 1:largeurImg
debutLigne = max(1, ligne-1); finLigne = min(hauteurImg, ligne+1);
debutColonne = max(1, colonne-1); finColonne = min(largeurImg, colonne+1);
voisinage = donneesCanal(debutLigne:finLigne, debutColonne:finColonne);
voisinage = voisinage(:);
for k = 1:parametres.nbEchantillons
if ~isempty(voisinage)
indexAlea = randi(length(voisinage));
modeleFondCouleur.echantillons(ligne, colonne, canal, k) = voisinage(indexAlea);
else
modeleFondCouleur.echantillons(ligne, colonne, canal, k) = donneesCanal(ligne, colonne);
end
end
end
end
end
end
Traitement en temps réel avec webcam
function traitementTempsReelViBe()
% Détection de premier plan en flux continu
camera = webcam();
parametres.nbEchantillons = 20;
parametres.seuilCorrespondance = 25;
parametres.correspondancesMin = 2;
parametres.facteurMiseAJour = 16;
premiereCapture = snapshot(camera);
if size(premiereCapture, 3) == 3
premiereCaptureNG = rgb2gray(premiereCapture);
else
premiereCaptureNG = premiereCapture;
end
modeleFond = initialiserModeleViBe(premiereCaptureNG, parametres);
figure('Name', 'ViBe temps réel');
subplot(1,2,1); h1 = imshow(premiereCapture); title('Flux entrant');
subplot(1,2,2); h2 = imshow(zeros(size(premiereCaptureNG))); title('Détection');
compteur = 0;
while true
compteur = compteur + 1;
imageBrute = snapshot(camera);
imageNG = rgb2gray(imageBrute);
[masque, modeleFond] = detecterPremierPlanViBe(double(imageNG), modeleFond, parametres);
masque = traitementMorphologique(masque);
set(h1, 'CData', imageBrute);
set(h2, 'CData', masque);
drawnow;
if strcmp(get(gcf, 'CurrentKey'), 'escape')
break;
end
end
clear camera;
end
ViBe multi-échelle pour une meilleure précision
function [masqueFinal, modeleMisAJour] = viBeMultiEchelle(...
imageCourante, modeleFond, parametres)
% Détection à plusieurs échelles
niveauxPyramide = 2;
imagesRedimensionnees = cell(niveauxPyramide, 1);
masquesNiveaux = cell(niveauxPyramide, 1);
imagesRedimensionnees{1} = imageCourante;
for niveau = 2:niveauxPyramide
imagesRedimensionnees{niveau} = imresize(imageCourante, 1/(2^(niveau-1)));
end
for niveau = 1:niveauxPyramide
[masquesNiveaux{niveau}, modeleFond] = detecterPremierPlanViBe(...
imagesRedimensionnees{niveau}, modeleFond, parametres);
end
masqueFinal = masquesNiveaux{1};
for niveau = 2:niveauxPyramide
masqueAgrandi = imresize(masquesNiveaux{niveau}, size(masqueFinal));
masqueFinal = masqueFinal | masqueAgrandi;
end
modeleMisAJour = modeleFond;
end
Résolution des problèmes courants
Phénomène de "fantôme"
Problème : Un objet en mouvement quitte la scène, mais son ancienne position est encore détectée comme premier plan.
Solutions :
- Augmenter la valeur de
facteurMiseAJourpour ralentir la mise à jour du fond. - Implémenter une mise à jour conservatrice : ne mettre à jour que si le pixel est classé fond sur plusieurs trames consécutives.
- Ajouter une vérification de cohérence du mouvement.
Sensibilité au bruit
Problème : Le bruit de l'image cause des fausses détections.
Solutions :
- Augmenter
seuilCorrespondance. - Augmenter
correspondancesMin. - Ajouter un pré-filtrage (filtre gaussien, filtre médian).
- Renforcer les opérations morphologiques post-traitement.
Lenteur de calcul
Problème : Traitement lent pour les vidéos haute résolution.
Solutions :
- Utiliser des opérations vectorielles à la place des boucles.
- Implémenter une version accélérée par GPU.
- Réduire la résolution de l'image.
- Accélérer les parties critiques avec des fonctions C/MEX.
Exemples d'application
Détection de véhicules
param_vehicules = struct('nbEchantillons', 25, 'seuilCorrespondance', 30, ...
'correspondancesMin', 3, 'facteurMiseAJour', 20, 'operationsMorpho', true);
function masqueVehicule = postTraitementVehicules(masqueBrut)
% Filtrage spécifique pour la détection de véhicules
statistiques = regionprops(masqueBrut, 'Area', 'BoundingBox');
aireMinVehicule = 500;
aireMaxVehicule = 5000;
masqueVehicule = false(size(masqueBrut));
for i = 1:length(statistiques)
if statistiques(i).Area >= aireMinVehicule && statistiques(i).Area <= aireMaxVehicule
boite = round(statistiques(i).BoundingBox);
masqueVehicule(boite(2):boite(2)+boite(4)-1, ...
boite(1):boite(1)+boite(3)-1) = true;
end
end
end
Détection de piétons
param_pietons = struct('nbEchantillons', 20, 'seuilCorrespondance', 20, ...
'correspondancesMin', 2, 'facteurMiseAJour', 16, 'operationsMorpho', true);
function masquePieton = postTraitementPietons(masqueBrut)
% Traitement adapté aux piétons
elementStructurant = strel('disk', 2);
masquePieton = imopen(masqueBrut, elementStructurant);
masquePieton = imclose(masquePieton, elementStructurant);
statistiques = regionprops(masquePieton, 'Area', 'Eccentricity');
aireMinPieton = 100;
aireMaxPieton = 1000;
excentriciteMax = 0.9;
regionsValides = false(length(statistiques), 1);
for i = 1:length(statistiques)
if statistiques(i).Area >= aireMinPieton && ...
statistiques(i).Area <= aireMaxPieton && ...
statistiques(i).Eccentricity <= excentriciteMax
regionsValides(i) = true;
end
end
masquePieton = ismember(bwlabel(masquePieton), find(regionsValides));
end
Utilisation et remarques
Pour utiliser cette implémentation :
- Sauvegardez chaque fonction dans un fichier .m distinct.
- Exécutez
extraction\_premier\_plan.mpour lancer le traitement vidéo. - Ajustez les paramètres selon votre scénario spécifique.
- Utilisez
traitementTempsReelViBe()pour tester avec une webcam en direct.
Considérations importantes :
- Lors de la première exécution, une vidéo exemple sera générée si aucun fichier source n'est fourni.
- Le traitement de longues vidéos peut nécessiter du temps ; testez d'abord sur un court extrait.
- L'ajustement des paramètres est crucial pour optimiser les performances selon l'environnement.
- La consommation mémoire dépend de la taille de l'image et du nombre d'échantillons.