Documente Academic
Documente Profesional
Documente Cultură
TXN. DATOS
MULTIMEDIA
INTRODUCCIN A LA COMPRESIN:
HUFFMAN ENTROPA
1. Introduccin
El objetivo de esta prctica es doble, por un lado ser capaz de entender el concepto de
entropa, y por otro conocer los fundamentos del algoritmo de codificacin de datos
Huffman. Para lograr estos objetivos, en primer lugar realizaremos una implementacin
del algoritmo de Huffman y del clculo de la entropa, y en segundo lugar
codificaremos diversos conjuntos de datos, observando los resultados para ver la
relacin existente entre el ndice de compresin obtenido por Huffman y la estimacin
ptima ofrecida por la entropa.
Para el desarrollo de la prctica, se proporciona un cdigo fuente incompleto, que
contiene una implementacin de Huffman que se deber analizar y completar. Esta
implementacin se ha escrito en lenguaje ANSI C++, y la interaccin con el usuario se
realiza por medio de paso de comandos en una ventana de sistema, por lo que su
compilacin se puede realizar en cualquier entorno de desarrollo C++, tanto Windows
como Linux.
2. Codificacin Huffman
En compresin, buscamos ser capaces de representar un conjunto de smbolos (o cadena
de smbolos) obtenidos a partir de un cierto alfabeto, usando el menor nmero de bits
posible, pero preservando en todo momento la capacidad de descomprimir o decodificar
la informacin. En general, el sistema que realiza el proceso directo lo llamamos
compresor o codificador, mientras que el que reconstruye los datos originales (o una
aproximacin a ellos si realizamos compresin con prdidas) lo llamamos descompresor
o decodificador.
El algoritmo de codificacin/compresin Huffman se propuso en 1952 como una forma
sencilla y ptima de mapear cada smbolo de un alfabeto con un cdigo (codeword) de
longitud ptima. De esta forma, para comprimir cada smbolo de la cadena,
simplemente debemos usar el cdigo que se ha calculado mediante Huffman. Para
conseguir esta asignacin ptima, los smbolos se representan con cdigos cuya
longitud es inversamente proporcional a la probabilidad del smbolo. De esta forma, los
smbolos menos probables se representan con cdigos ms largos, y los ms probables
con cdigos ms cortos.
El proceso de asignacin de cdigos se lleva a cabo mediante la construccin de un
rbol binario, desde las hojas hacia la raz, de manera que los nodos hoja son los
smbolos del alfabeto. En la construccin del rbol, los nodos menos probables se unen
sucesivamente para formar otro nodo de mayor probabilidad, de forma que cada uno de
los enlaces aade un bit al cdigo de los smbolos que estamos juntando. Este proceso
termina cuando slo se dispone de un nodo, de forma que ste representa la raz del
rbol.
Para mejorar su comprensin, veamos un ejemplo sobre este proceso. Supongamos que
queremos codificar la siguiente cadena de smbolos:
S={aabaacc}
que usa el alfabeto
A={a, b, c}
La probabilidad de cada uno de los smbolos vendr dada por las siguientes expresiones:
P(a)=4/7
P(b)=1/7
P(c)=2/7
Como es lgico, el smbolo a, que se repite mucho, nos interesa representarlo con el
menor nmero de bits posibles.
El primer paso del algoritmo ser plantear un grafo no conexo de nodos que representan
cada uno de los smbolos del alfabeto, junto con su probabilidad asociada. Para mejorar
su comprensin y facilitar el clculo, en lugar de usar directamente las probabilidades
P(a), P(b), P(c), usaremos una cuenta de repeticiones, a modo de histograma, de forma
que
H(a)=4
H(b)=1
H(c)=2
El grafo inicial ser el siguiente:
H(a)=4
H(b)=1
H(c)=2
El primer paso ser juntar los nodos menos probables en un nuevo nodo, asignando un
bit distinto a cada uno de los enlaces. El grafo resultante es el siguiente:
bc
0
a
H(bc)=3
H(a)=4
A continuacin nos quedan dos nodos por unir, repetimos la misma operacin y
obtenemos ya el rbol final.
abc H(abc)=7
1
0
bc
0
1
c
unir las etiquetas asociadas a cada uno de los enlaces que se han recorrido. De esta
forma, los cdigos finales en el ejemplo son los siguientes:
C(a)=0
C(b)=10
C(c)=11
3. Entropa
La entropa es un concepto que representa los lmites de la codificacin basada en la
entropa, en la que se codifican los datos sin necesidad de conocer la naturaleza de los
mismos. Huffman es un ejemplo de codificacin basada en la entropa. La entropa
denota el mnimo nmero de bits por smbolo necesarios para representar una cadena.
Es un ndice que denota la cantidad de informacin que existe en una fuente de datos (la
cadena a codificar).
Aunque el clculo de la entropa general no se puede calcular, de forma prctica se suele
emplear la entropa de primer orden como una aproximacin. Esta entropa viene
definida como sigue:
1
H = P (a i ) log 2
P(ai )
ai A
siendo ai cada uno de los smbolos del alfabeto A.
En el ejemplo del punto anterior, la entropa se puede calcular como
1
1
1
+ P(b) log 2
+ P(c) log 2
P(a)
P(b)
P (c )
4
7 1
2
7
H = log 2 + log 2 7 + log 2 = 1,38
2
7
4 7
7
H = P(a ) log 2
Fjate que aunque la entropa nos dice que los datos se pueden codificar usando 1,38
bits por smbolo, el resultado final usando Huffman es que han sido necesarios 1,43 bits
por smbolo, ya que la entropa indica una cota inferior de los bits por smbolo
necesarios.
4. Cdigo fuente
El programa que implementa Huffman est formado por cuatro mdulos/ficheros
principales:
- codificador.cpp: obtiene los datos del usuario por parmetro (o si no se indica
ningn dato, se asignan valores por defecto) y llama a las rutinas necesarias para
realizar la codificacin Huffman.
- Huffman.cpp: este mdulo es el encargado de implementar Huffman.
- Histograma.cpp: sirve para realizar una cuenta de smbolos en la cadena
original, y calcular as las probabilidades necesarias.
- FichBits.cpp: es un sencillo mdulo para leer/escribir en disco bits sueltos, o
palabras de cualquier longitud.
Veamos cada uno de estos mdulos con algo ms de detalle.
4.1 FichBits.cpp
Posteriormente, cada una de las palabras generadas se pueden guardar en disco por
medio de la funcin
void EscribePalabra(int nbits, int palabra);
a la que se le pasa como primer parmetro la longitud del cdigo, y como segundo
parmetro el cdigo a almacenar. Como alternativa, si slo deseamos escribir un bit se
puede usar la funcin void EscribeBit(int bit);
Finalmente, se debe finalizar el proceso, cerrando as el fichero creado y liberando los
recursos ocupados, por medio de una llamada a la funcin
void FinalizaEscritura();
Para realizar el proceso de lectura del fichero, que ser necesario en la decodificacin,
usaremos las funciones int InicializaLectura(char *nombre) y void
4.2 Histograma.cpp
Este mdulo est formado por tres variables globales, y siete rutinas que operan sobre
ellas. Las variables son:
-
El resto de rutinas de este mdulo pueden ser consultadas en el fichero fuente, y se usan
para finalizar el histograma (liberar recursos), para guardar el histograma en disco, para
leerlo a partir del disco, y para imprimir el histograma por pantalla.
4.3 Huffman.cpp
} TNodo;
La informacin sobre el propio nodo viene dada por los atributos cuenta y simbolo,
que representan el nmero de veces que aparecen los smbolos representados por ese
nodo en la cadena a codificar (es decir, la probabilidad de ese nodo), y el propio
smbolo que se representa. Fjate que el campo smbolo slo es realmente necesario
cuando el nodo representa a una hoja, sin embargo la cuenta debe actualizarse
continuamente.
Los atributos infDer e infIzq se usan para indicar cules son los nodos hijo derecho e
hijo izquierdo del nodo actual, formando de esta forma el rbol. Observa que estos
atributos para los nodos hoja apuntarn siempre a NULL.
El ltimo de los atributos, der, se emplea durante la construccin del rbol,
manteniendo una lista enlazada horizontal que indica cules son los nodos que se
pueden juntar para seguir construyendo el rbol. Para entender el objetivo de esta lista
ser necesario recordar en algo ms de detalle la construccin de los rboles. Recuerda
que inicialmente el grafo est formado por nodos independientes. A continuacin,
juntamos los dos nodos menos probables del grafo y formamos as un nuevo rbol. A
partir de este momento, los nodos que se encuentran en las hojas de este rbol ya no son
susceptibles de ser juntados, sino que nicamente se considera el nodo raz de este
nuevo rbol. De esta forma, fjate que estamos juntando los nodos raz de los rboles
para hacer rboles mayores. Por tanto, es necesario mantener una lista de cules son los
nodos raz de los rboles, y que son susceptibles de ser unidos para crear rboles
mayores.
A continuacin veremos un ejemplo grfico para facilitar la comprensin de estas
estructuras. Un nodo aislado lo vamos a representar grficamente como sigue:
der
simbolo
(cuenta)
infIzq
infDer
a
NULL
b
NULL
NULL
c
NULL
NULL
d
NULL
NULL
NULL
NULL
Observa como todos los nodos inicialmente forman rboles aislados, y sus punteros a
descendientes apuntan a NULL. Adems se ha formado la lista horizontal (con el
puntero der) para identificar la raz de cada rbol. En esta lista, el ltimo de los nodos
apuntara a NULL. Para su localizacin y manipulacin, es necesario indicar cul es el
primer elemento de esta lista. Este primer elemento vendr identificado por la variable
global de Huffman.cpp, inicioHoriz.
inicioHoriz
cd
a
NULL
b
NULL
NULL
NULL
NULL
NULL
d
NULL
NULL
NULL
La lista horizontal sigue apuntando a la raz de los subrboles que se han construido (en
este caso dos). Finalmente, el rbol quedara como sigue
nodoRaiz
abcd
NULL
ab
inicioHoriz
cd
a
NULL
b
NULL
NULL
NULL
NULL
d
NULL
NULL
NULL
Este ltimo ejemplo nos sirve para introducir otra variable global del mdulo
Huffman.cpp, la variable nodoRaiz, que debe apuntar a la raz del rbol de Huffman
una vez construido. Este puntero ser la forma que tenemos para referenciar el rbol
para su posterior recorrido.
La funcin que se encarga de construir el rbol de Huffman es
TNodo *ConstruyeArbol()
Esta funcin tiene dos partes. En primer lugar se construye el grafo inicial, formado por
un nodo por cada smbolo del alfabeto. En segundo lugar, se realiza el proceso de
construccin del rbol. Para ello, se utiliza las funciones
TNodo *SacaMenor()
void JuntaNodo(TNodo *Nodo1, TNodo *Nodo2)
La primera de ellas saca el nodo con menor ndice de aparicin (por tanto, con menor
probabilidad) de la lista horizontal, y devuelve un puntero a ese nodo. La segunda
funcin aade un nuevo rbol a la lista horizontal, formado por la unin de los dos
nodos pasados por parmetro (y de sus descendientes).
La construccin del rbol consistir en sacar sucesivamente los dos nodos menores e ir
juntndolos, hasta que no quede ningn nodo en la lista horizontal. Finalmente, la
funcin debe devolver el nodo raz del rbol construido.
indicando por parmetro (1) la longitud del alfabeto a usar, (2) la longitud de la cadena,
(3) la propia cadena a codificar, y (4) el nombre con el fichero que va a contener la
cadena codificada. Es importante destacar que la cadena a codificar debe contener un
vector de bytes que representan con un 0 al primer smbolo (a), con un 1 al segundo
smbolo (b), etc., as que previamente se debe haber normalizado la cadena usando la
funcin ConvierteCadena()(ver mdulo codificador.cpp).
Bsicamente en la inicializacin de Huffman: (a) se inicializa el fichero de escritura, (b)
se calcula el histograma a partir de los datos de la cadena, (c) se construye el rbol de
Huffman, (d) se guarda el histograma en disco para que el decodificador lo tenga
disponible, (e) se construye la tabla de cdigos (codewords) a usar en la codificacin a
partir del rbol, y (f) se libera la memoria usada por el rbol.
Una vez creada la tabla de cdigos, para codificar un smbolo de la cadena es suficiente
con llamar a la funcin
void CodificaSimbolo(int simbolo);
que debe recorrer el rbol desde la raz a las hojas, usando los bits que se leen del
fichero. Para realizar este proceso se puede usar la funcin auxiliar
TNodo *Descendiente(TNodo *nodo, unsigned char bit)
que devuelve el hijo izquierdo o derecho del nodo que se pasa por parmetro. Con el
parmetro bit se especifica si se desea obtener el hijo izquierdo o el derecho.
4.3.5 Otras funciones
Finalmente,
en
Este es el mdulo principal (main) del programa. En primer lugar se realiza la lectura de
parmetros por consola, asignando valores por defecto si no se especifican (variables
globales cadenaPorDefecto, ficheroPorDefecto y opcionPorDefecto).
Posteriormente, mediante la funcin int ConvierteCadena(), comprueba que todos
los valores de la cadena a codificar estn dentro del alfabeto que se est usando, que
viene definido entre a y e (este ltimo valor se puede cambiar modificando
MAX_SIMBOLO), y convierte la cadena a codificar, de una representacin en cdigo
ASCII a una representacin ms adecuada para el mdulo Huffman, en la que la a se
representa con un 0, la b con un 1, etc.
Despus, si la opcin seleccionada es codificar, se inicializa la codificacin Huffman,
codificando posteriormente uno a uno los smbolos de la cadena de entrada, de la
siguiente forma
for (f=0;f<nsimbolos;f++)
CodificaSimbolo(cadena[f]);
6. Trabajo a realizar
El trabajo a realizar en esta prctica es el siguiente:
1) Leer la memoria y entender cmo funciona el programa para la codificacin y
decodificacin de datos usando Huffman.
2) Implementar las funciones que se han dejado en blanco. En concreto, hay que
completar las siguientes funciones:
-
De codificador.cpp:
10
o int
ConvierteCadena
(unsigned
int
longcadena,
char
*cadenaOriginal, char *cadena): Esta es una funcin muy sencilla,
De Histograma.cpp:
o void InicializaHistograma(): En esta funcin, ya se encuentra
implementada la parte para reservar memoria para el histograma. Lo
siguiente que debes implementar es inicializar a cero todas las posiciones
del histograma, para a continuacin completarlo a partir de la
informacin que viene dada en la cadena que se lee por parmetro, y
que tiene de longitud longitudCadena.
Para poder probar sin problemas que el histograma se ha implementado
correctamente, puedes aadir temporalmente al final de esta funcin las
instrucciones ImprimeHistograma();exit(0);
Es importante que recuerdes que, en este punto del programa, los
smbolos del vector cadena ya estn normalizados por la funcin
ConvierteCadena().
o
printf("\nEntropia: %f\n",CalculaEntropia());exit(0);
De Huffman.cpp:
o TNodo *ConstruyeArbol(): En esta funcin se proporciona ya
implementada la construccin del grafo inicial a partir de la informacin
del histograma. La segunda parte de la funcin es la que hay que
completar, y en ella se realiza la construccin del rbol. Para esto, hay
11
int DecodificaSimbolo().
nuevamente este proceso con 100 y con ms as. Hacia que valor parece
converger la entropa? Y el resultado de Huffman? Por qu crees que se da
este resultado?
13
smbolo probable (a) con tan slo medio bit. De hecho, esta es la idea que
subyace en la codificacin aritmtica.
En esta extensin de la prctica puedes probar a agrupar distinta cantidad de
smbolos, y comparar el resultado con la versin original, viendo qu sucede al
variar las caractersticas estadsticas de los smbolos del alfabeto (por ejemplo,
concentrando las probabilidades en uno o dos smbolos, distribuyndolas entre
todos los smbolos del alfabeto, etc).
3) Implementa una versin completa: adaptativa y con smbolos agrupados.
Uno de los principales inconvenientes de la codificacin agrupando smbolos es
que aumenta el tamao del alfabeto, y por tanto la cantidad de informacin
estadstica que el codificador debe comunicar al decodificador para que ste sea
capaz de construir el rbol de Huffman. Para evitar la necesidad de transmitir
estas estadsticas puedes realizar una versin adaptativa del codificador de
smbolos agrupados, tal y como se ha explicado en la primera ampliacin de
prcticas.
Una vez implementadas las cuatro versiones (la original y las tres extensiones
propuestas), sera interesante que las compararas entre ellas en trminos de
eficiencia en compresin y costes computacionales. Para ello puedes usar
distintos tipos de datos de entrada, como un fichero de texto (en cdigo ASCII) o
informacin multimedia (p.ej., una imagen sin comprimir, en formato crudo).
Ten en cuenta que el compresor que has implementado es sin prdidas (lossless)
y de carcter general, con lo que la tasa de compresin que obtendrs con
informacin multimedia ser mucho ms baja que en compresores de imgenes
especficos con prdidas (lossy).
4) Estudio y uso de una implementacin eficiente de Huffman adaptativo.
En
http://www.cipr.rpi.edu/~said/FastAC.html
puedes
encontrar
una
implementacin eficiente de Huffman adaptativo realizada por Amir Said (un
experto de reconocido prestigio en el campo de la compresin de datos). En esta
ampliacin os proponemos estudiar esta implementacin, y evaluarla,
comparndola con la implementacin de Huffman bsica que habis completado
en prcticas. Tambin sera interesante realizar una comparacin de esta versin
de Huffman con alguna implementacin de un codificador aritmtico adaptativo.
14