Travaux pratiques de traitement d'images numériques - L2TI
Dans tous les cas la première étape consiste à découper l'image par bloc puis
sur chacun de ces blocs à faire un traitement particulier sur le bloc pour coder ...
part of the document
Complément sur le fonctionnement du codage de Huffman
G. Dauphin
1. Codage sans perte : lalgorithme de Huffman
Le codage de Huffman est un HYPERLINK "http://fr.wikipedia.org/wiki/Algorithmique" \o "Algorithmique" algorithme de HYPERLINK "http://fr.wikipedia.org/wiki/Compression_de_donn%C3%A9es_sans_perte" \o "Compression de données sans perte" compression de données sans perte élaboré par HYPERLINK "http://fr.wikipedia.org/wiki/David_Albert_Huffman" \o "David Albert Huffman" David Albert Huffman et publié en 1952. Le code est déterminé à partir d'une estimation des probabilités d'apparition des symboles de source, un code court étant associé aux symboles de source les plus fréquents. Le principe du codage de Huffman repose sur la création d'un arbre binaire, cest-à-dire un arbre où chaque nud a deux fils, le fils de gauche est étiqueté par un zéro et le fils de droite est étiqueté par un 1. Chaque terminaison représente un des symboles à coder. Le code associé au symbole est une succession de 0 et de 1, la succession qui correspond au parcours depuis la racine jusquà la terminaison correspondant au symbole.
On cherche ici à simuler sous Matlab le fonctionnement du codage de Huffman pour une séquence de symboles dans un alphabet à 256 symboles. Les étapes 1 à 4 correspondent au codage dune séquence (constitution dun arbre et transformation de cet arbre en une table de conversion des symboles en code). Les étapes 5 et 6 correspondent au décodage (fabrication dun arbre à partir dune table de conversion et transformation dune succession de codes en une succession de symboles).
Etape 1 : calcul de la fréquence des symboles
A partir de la séquence de symboles, on cherche à déterminer le nombre doccurrences de chaque symbole. En effet plus un symbole est fréquent, plus il est préférable dutiliser pour ce symbole un code court.
Lalgorithme proposé consiste dabord à parcourir lensemble des symboles. Pour chacun de ces symboles on forme un vecteur dont la taille est égale à celle de la séquence et dont les composantes sont égales à 1 lorsquà cet indice la composante de la séquence est égale au symbole et égales à 0 sinon. La fréquence du symbole est égale à la somme de ce vecteur, on stocke cette fréquence dans un autre vecteur dont la taille est égale à la liste des symboles. Cet algorithme est implémenté dans symbole2frequence.m
Etape 2 : création dun arbre binaire correspondant au code de Huffman
Affecter à chaque symbole un code composé de 0 et de 1, cest en fait dessiner un arbre binaire dont les terminaisons sont les symboles et dont les liens sont de deux types, gauche pour 0 et droite pour 1. Lalgorithme de Huffman donne une méthode pour construire cet arbre en assurant que les terminaisons sont dautant plus éloignées de la racine que la fréquence du symbole associé à cette terminaison est dautant plus faible.
Limplémentation proposée ici requière dune part une liste des symboles, ici les chiffres de 0 à 255 et une liste des fréquences de chacun de ces symboles. Limplémentation fournit à la fin un arbre binaire. Cet algorithme est implémenté dans frequence2arbre.m
On ne considère dabord que les symboles ayant au moins une occurrence dans la séquence. Puis on ordonne ces symboles et leurs fréquences du moins fréquent dans la séquence au plus fréquent. On met cette liste ordonnée des symboles sous la forme dune liste darbres, ces arbres étant réduits à un seul des symboles.
A chaque itération, on choisit les deux arbres dans la liste des arbres ayant la fréquence la plus faible (donc a priori les deux premiers arbres de la liste des arbres). On réunit ces deux arbres en un seul. Pour affecter à ce nouvel arbre une fréquence égale à la fréquence des deux arbres constitutifs, on remplace aussi les deux premiers éléments du vecteur des fréquences par un élément égal à la somme des fréquences. Ainsi à chaque itération, il y a un arbre en moins dans la liste des arbres et de même une fréquence en moins dans la liste des fréquences. On réordonne la liste des arbres et la liste des fréquences de la plus petite fréquence à la plus grande fréquence.
On répète cette itération jusquà ce que la liste des arbres soit en fait composée dun seul arbre.
Instructions Matlab à tester pour réaliser le programme :
Simulation dune liste de fréquences pour un alphabet composé de 6 symboles.
>>frequence=floor(20*rand(1,6))
Une liste des symboles
>>liste=1 :6
Suppression des symboles ayant une fréquence nulle
>>indice=find(frequence~=0)
Modification de la liste
>>listeModifiee=liste(indice)
Ordonnancement de la liste des fréquences
>>sort(frequence)
Ordonnancement commun de la liste des symboles et de la liste des fréquences en fonction de la liste des fréquences
>>[frequenceTrie,nouvelOrdre]=sort(frequence)
>>listeTrie=listeModifiee(nouvelOrdre)
Transformation dune liste en ensemble darbres composés des éléments de la liste
>>listeArbres=num2cell(liste)
Récupération dun arbre dans la liste
>>unArbre=listeArbres{2}
Fusion de deux arbres en un arbre
>>unAutreArbre=3, nouvelArbre=[{unArbre}, {unAutreArbre}],
Affichage dun arbre
>>celldisp(nouvelArbre)
Nombre de fils de la racine dun arbre
>>length(listeArbres)
Etape 3 : Constitution dune table de conversion à partir dun arbre
Lalgorithme consiste à parcourir lensemble de larbre, de concaténer au fur et à mesure du parcours un vecteur code du symbole 0 si le lien de gauche est utilisé et du symbole 1 si le lien de droite est utilisé.
Lalgorithme proposé utilisée requière un arbre binaire et produit une table dont chaque case est une structure contenant dune part la valeur et dautre part le code binaire associé. Il est implémenté dans arbre2table.m Limplémentation classique dun parcours darbre consiste à utiliser une implémentation récursive, parce qualors on bénéficie de lutilisation de piles qui sont programmées par le langage de programmation. Limplémentation proposée ici consiste à refabriquer ces piles sous la forme dune liste de tâches à effectuer, liste qui sincrémente à chaque fois quon rencontre un nouveau nud et qui est décrémentée à la fin de la réalisation de la tâche. Pour chaque tâche à effectuer, il est nécessaire aussi de sauvegarder les codes déjà rencontrés, on utilise donc aussi une liste des codes qui est incrémenté et décrémenté de la même façon que la liste des tâches.
Une tâche à effectuer consiste à vérifier larbre est en fait une terminaison, si cest le cas on place le symbole lu dans la terminaison dans une nouvelle case de la table et le code associé à la tâche dans le champ code de cette nouvelle case de la table. Si larbre nest pas une terminaison, on crée deux nouvelles tâches avec deux nouveaux codes. Les nouvelles tâches sont définies par les deux arbres fils et les codes sont la concaténation du code précédent et de 0 pour le premier fils et 1 pour le deuxième fils.
Ces tâches sont à répéter jusquà ce que la liste des tâches soit vide.
Instructions Matlab à tester pour réaliser le programme :
Exemple darbre
>>arbre={{1,2},{3,4}}
Exemple darbre vide
>>arbreVide={[]}
Déclaration dune table de structures
>>table=[] ;
Incrémentation dune table de structures à deux champs
>>table(end+1).champ1=1 ;
>>table(end).champ2=[2 3] ;
Etape 4 : Conversion dune séquence de symboles en une succession de code
La table de conversion permet de trouver le code associé à chaque symbole, il suffit donc de concaténer tous ces codes pour avoir le code associé à la séquence. Limplémentation proposée consiste dabord à créer une table dindexation qui à chaque symbole associe la case correspondante dans la table de conversion. Ensuite il suffit de parcourir la séquence et de concaténer les codes trouvés. Cet algorithme est implémenté dans sequence2code.m
Etape 5 : Fabrication dun arbre binaire à partir de la table de conversion
Dans la séquence codée il est nécessaire aussi de rajouter linformation soit sur larbre binaire soit sur la table de conversion, mais de fait cest plus simple de mettre la table de conversion que larbre binaire. Lors de létape 6, le décodage de la séquence codée se fait avec larbre binaire. Aussi il est nécessaire davoir un algorithme capable de transformer une table de conversion en arbre binaire.
Limplémentation classique consiste à parcourir toutes les cases de la table de conversion et pour chaque case à parcourir le code en créant si besoin larbre au fur et à mesure et en y affectant la valeur associée à la case. MatLab nest pas un langage adapté à cela dans la mesure où il ny a pas de passage par référence (ou de pointeurs). En revanche cest un langage interprété et non un langage compilé, ce qui permet de créer larbre en fabriquant pour chaque code, la chaîne de caractère qui lorsquelle est évaluée, crée si nécessaire la partie de larbre manquante et affecte la valeur à la case souhaitée. Limplémentation proposée consiste à réaliser cette tâche pour chaque case du tableau. Cet algorithme est implémenté dans table2arbre.m
Instructions MatLab à tester pour réaliser le programme :
Création dun arbre vide
>>arbre={}
Transformation de larbre pour contenir le symbole 3 pour le code 001
>>arbre{1}{1}{2}=3
Transformation du code en instruction à interpréter
>>code=[0,0,1],
>>prefixe=arbre
>>suffixe=repmat({x},1,length(code))
>>suffixe(2)=num2str(code(1)+1)
>>suffixe(5)=num2str(code(2)+1)
>>suffixe(8)=num2str(code(3)+1)
>>instruction=[prefixe, suffixe, =3;]
Evaluation de linstruction
>>eval(instruction)
Etape 6 : décodage dune séquence codée par lintermédiaire dun arbre
Lalgorithme requière la séquence à décoder et un arbre binaire représentant la signification du code. Lalgorithme produit une séquence de symbole. Lalgorithme consiste à parcourir en parallèle la séquence à décoder et larbre binaire en procédant en plusieurs itérations. Au début de litération on se place à la racine de larbre. On se déplace dans larbre en prenant le lien de gauche ou de droite suivant que le code lu est 0 ou 1. Litération prend fin lorsquon arrive à une terminaison de larbre. Le symbole sur cette terminaison est placé dans le vecteur de la séquence de symboles. Litération est répétée jusquà atteindre la fin de la séquence à décoder. Cet algorithme est implémenté dans
sequenceCodee2sequenceDecodee.m
Lien avec lentropie
Question : Choisissez une image en niveau de gris synthétique et une image synthétique (avec peu de niveaux de gris). Transformez chacune de ces images en un vecteur contenant lensemble des niveaux de gris à valeurs dans lensemble 0 à 255. Réalisez la compression sans perte, puis la décompression sans perte de ces deux images. Evaluez la place mémoire nécessaire pour stocker chacune de ces images et commentez en calculant avec lentropie.
2. Codage à laide dune transformation bloc par bloc
On ne considère ici que des images en niveaux de gris. Il sagit de quatre méthodes de codage par bloc. Dans tous les cas la première étape consiste à découper limage par bloc puis sur chacun de ces blocs à faire un traitement particulier sur le bloc pour coder limage. Cest ce traitement particulier qui simplifie limage et éventuellement diminue la taille de limage ou les valeurs que peut prendre les coefficients. Ce traitement introduit aussi une dégradation de limage. Il faut ensuite appliquer un codage sans perte sur limage transformée en une séquence de nombre à valeurs dans un ensemble de taille finie. Le décodage sobtient en appliquant le codage sans perte inverse, en reformant limage globale éventuellement après avoir appliqué une transformation affine et en appliquant le traitement inverse sur chaque bloc.
Instructions MatLab à tester pour réaliser le programme :
>>im2col
>>col2im
La fonction MatLab blkproc permet de travailler bloc par bloc, elle ne requière pas que le résultat du traitement forme un bloc de même taille que le bloc de départ. Par contre blkproc ne fonctionne pas sur des images couleurs.
>>moyenneParBloc=blkproc(im,[8 8],@mean2);
2.1 Codage par troncature de blocs (BTC)
Chaque bloc de limage est représenté par une avec deux niveaux de quantifications et deux paramètres. Le seuillage des pixels se fait en fonction de la moyenne des niveaux de gris des pixels du bloc
EMBED Equation.3
où m est la moyenne du bloc, EMBED Equation.3 sont les pixels du bloc et EMBED Equation.3 sont les pixels du bloc après quantification. Les deux paramètres peuvent être EMBED Equation.3 la valeur moyenne des pixels tels que EMBED Equation.3 et EMBED Equation.3 la valeurs moyenne des pixels tels que EMBED Equation.3 , ou alors m et EMBED Equation.3 où EMBED Equation.3 est lécart-type du bloc. La reconstruction du bloc se fait dans le premier cas en remplaçant les pixels tels que EMBED Equation.3 par A pour la première méthode ou par EMBED Equation.3 pour la deuxième méthode, et en remplaçant les autres pixels par B pour la première méthode ou par EMBED Equation.3 . EMBED Equation.3 désigne la fraction de pixels tels que EMBED Equation.3 au sein du bloc. Le codage avec la première méthode est implémenté dans BTC1.m et le décodage avec la première méthode est implémenté dans iBTC1.m
Instructions MatLab à tester pour réaliser le programme :
>>seuillageBloc=inline(x>mean2(x),x) ;
>> imSeuil=blkproc(im,[8 8],seuillageBloc);
Question : Choisissez une image en niveaux de gris. Réduisez cette image de façon à ce que la hauteur et la largeur de cette image soient des multiples de 8. Appliquez le codage et le décodage à cette image avec les deux méthodes. Décrivez les dégradations observées.
2.2 Codage par troncature des coefficients de la DCT
Cest lalgorithme implémenté dans la compression JPEG. Lalgorithme consiste à appliquer la DCT sur chaque bloc de 8x8 puis à quantifier les coefficients obtenu suivant la table dallocation qui détermine pour chaque bloc le nombre de niveaux de quantifications (voir le polycopié de cours pour un exemple de table dallocation). Dans limplémentation proposée on conserve aussi les valeurs maximales et minimales des coefficients de la dct de lensemble des blocs de limage.
m=[ 16 11 10 16 24 40 51 61
12 12 14 19 26 58 60 55
14 13 16 24 40 57 69 56
14 17 22 29 51 87 80 62
18 22 37 56 68 109 103 77
24 35 55 64 81 104 113 92
49 64 78 87 103 121 120 101
72 92 95 98 112 100 103 99]*qualite
ordre=[1 9 2 3 10 17 25 18 11 4 5 12 19 26 33
41 34 27 20 13 6 7 14 21 28 35 42 49 57 50
43 36 29 22 15 8 16 23 30 37 44 51 58 59 52
45 38 31 24 32 39 46 53 60 61 54 47 40 48 55
62 63 56 64] ;
Annexe
Forum MatLab :
function frequence=symbole2frequence(sequence)
% sequence est supposée être un vecteur ligne composé de chiffres entre 0 et 255, chiffres
%appelés symboles. Ce programme calcule le nombre doccurrence dun symbole dans
%sequence. frequence est un vecteur de taille 1 256 et dont les composantes dindice k
%indiquent le nombre doccurrence du symbole k-1 dans sequence.
frequence=zeros(1,256) ;
for k=1 :255
frequence(k)=sum(sequence(:)==k-1) ;
end;
function arbre=frequence2arbre(frequence)
%frequence est un vecteur de taille 1 256 contenant le nombre doccurences dun des symbols
%0 à 255 dans sequence. arbre est un arbre binaire contenant à ses terminaisons les symboles,
%il est construit en fonction de lalgorithme de Huffman.
symboles=find(frequence~=0);
frequence=frequence(symboles);
[frequence,indexTrie]=sort(frequence);
symbolesTries=symboles(indexTrie);
arbre=num2cell(symbolesTries);
while (length(arbre)>2)
arbre=[{[arbre(1),arbre(2)]}, arbre(3:end)];
frequence=[frequence(1)+frequence(2) frequence(3:end)];
[frequence,index]=sort(frequence);
arbre=arbre(index);
end;
function table=arbre2table(arbre)
%arbre est un arbre binaire contenant à ses terminaisons les symboles et pour chaque symbole
%la liste des liens utilisés pour aller de la racine à la terminaison est le code associé au %symbole. La table contient autant de cases quil ny a de symboles dans larbre et pour
%chaque case il y a un champ indiquant la valeur et un autre champ indiquant le code associé.
listeTaches.arbre={arbre};
listeTaches.code={[]};
table=[];
while (length(listeTaches.arbre)>0)
arbre=listeTaches.arbre{1}; listeTaches.arbre=listeTaches.arbre(2:end);
code=listeTaches.code{1}; listeTaches.code=listeTaches.code(2:end);
if (iscell(arbre{1}))
listeTaches.arbre=[listeTaches.arbre, {arbre{1}}];
listeTaches.code=[listeTaches.code, {[code,0]}];
else
table(end+1).valeur=arbre{1};
table(end).code=[code,0];
end;
if (iscell(arbre{2}))
listeTaches.arbre=[listeTaches.arbre, {arbre{2}}];
listeTaches.code=[listeTaches.code, {[code,1]}];
else
table(end+1).valeur=arbre{2};
table(end).code=[code,1];
end;
end;
function sequenceCode=sequence2code(sequence,table)
%sequence est une succession de symboles à valeurs dans 0..255 table contient autant de
%cases que de symboles et pour chaque case, il y a un champ indiquant le numéro du
%symbole et un autre champ indiquant le code.
valeur2index=[] ;
for k=1:length(table)
valeur2index(table(k).valeur)=k;
end;
sequenceCode=[];
for l=1:length(sequence)
sequenceCode=[sequenceCode table(valeur2index(sequence(l)+1)).code];
end ;
function arbre=table2arbre(table)
%table contient autant de cases que de symboles et pour chaque case, il y a un champ
%indiquant le numéro du symbole et un autre champ indiquant le code. arbre est larbre
%binaire dont les terminaisons sont les symboles indiqués dans la table et le chemin pour aller
%de la racine aux terminaisons est le code indiqué dans table.
arbre={};
for k=1:length(table)
code=table(k).code;
suffixe=repmat('{x}',1,length(code));
for l=1:length(code)
suffixe(2+3*(l-1))=num2str(1+code(l));
end;
eval(['arbre',suffixe,'=',num2str(table(k).valeur),';']);
end;
function sequenceDecodee=sequenceCodee2sequenceDecodee(sequenceCodee,arbre)
%sequenceCodee est un vecteur ligne composé de 0 et de 1, avec une particularité qui est
%quil est la concaténation de codes. arbre est un arbre binaire dont lensemble des codes
%dont sequenceCodee est la concaténation, ces codes permettant daller de la racine à une
%terminaison. sequenceDecodee est une succession de symbole à valeurs dans lensemble 0
%à 255.
sequenceDecodee=[] ;
while(~isempty(sequenceCodee))
parcoursArbre=arbre;
while (iscell(parcoursArbre))
parcoursArbre=parcoursArbre{1+sequenceCodee (1)};
sequenceCodee=sequenceCodee(2:end);
end;
sequenceDecodee=[sequenceDecodee parcoursArbre-1];
end;
function [imSeuil,moyenneA,moyenneB]=BTC1(im)
%im doit être une image en niveau de gris dont la hauteur et la largeur sont des multiples de 8
%imSeuil est une matrice de même taille que im qui vaut 1 pour les pixels supérieurs à la %moyenne des pixels de leur blocs et 0 sinon. moyenneA est la moyenne dans chaque bloc %des pixels pour lesquels imSeuil vaut 0. moyenneB est la moyenne dans chaque bloc des %pixels pour lesquels imSeuil vaut 1.
seuillageBloc=inline('x>mean2(x)','x');
imSeuil=blkproc(im,[8 8],seuillageBloc);
sommeMatrice=inline('sum(sum(x))','x');
nombrePixelA=blkproc(imSeuil,[8 8],sommeMatrice);
moyenneA=blkproc(im.*imSeuil,[8 8],sommeMatrice)./nombrePixelA;
moyenneB=blkproc(im.*(1-imSeuil),[8 8],sommeMatrice)./(64-nombrePixelA);
function imDecodee=iBTC1(imSeuil,moyenneA,moyenneB)
%imSeuil est une matrice avec des 1 et des 0, moyenneA et moyenneB sont des matrices 8 %fois plus petites à la fois en hauteur et en largeur. imDecodee est une matrice de même taille
%que imSeuil.
agrandissement=inline('x*ones(8)','x');
imDecodee=imSeuil.*blkproc(moyenneA,[1 1],agrandissement);
imDecodee=imDecodee+(1-imSeuil).*blkproc(moyenneB,[1 1],agrandissement);