霍夫曼编码是一种基本的压缩方法,已被证明在图像和视频压缩标准中很有用。在图像上应用霍夫曼编码技术时,源符号可以是图像的像素强度,也可以是强度映射函数的输出。
先决条件:霍夫曼编码|文件处理
霍夫曼编码技术的第一步是将输入图像缩小为有序直方图,其中某个像素强度值的出现概率为
prob_pixel = numpix/totalnum
其中numpix是具有特定强度值的像素的出现次数,而totalnum是输入图像中像素的总数。
让我们拍摄一张8 X 8的图片
像素强度值为:
该图像包含46个不同的像素强度值,因此,我们将有46个唯一的霍夫曼代码字。
显然,并非所有像素强度值都可能出现在图像中,因此不会有非零的出现概率。
从这里开始,输入图像中的像素强度值将被视为叶节点。
现在,有两个基本步骤可以构建霍夫曼树:
- 建立霍夫曼树:
- 将两个概率最低的叶节点合并为一个新节点。
- 用新节点替换两个叶节点,并根据新的概率值对节点进行排序。
- 继续执行步骤(a)和(b),直到获得概率值为1.0的单个节点。我们称这个节点为根
- 从根开始回溯,将“ 0”或“ 1”分配给每个中间节点,直到到达叶节点
在此示例中,我们将为左侧的子节点分配“ 0”,为右侧的子节点分配“ 1”。
现在,让我们研究一下实现:
步骤1 :
将图像读入2D数组( image )
如果Image为.bmp格式,则可以使用此处此链接中给出的代码将Image读取到2D数组中。
int i, j;
char filename[] = "Input_Image.bmp";
int data = 0, offset, bpp = 0, width, height;
long bmpsize = 0, bmpdataoff = 0;
int** image;
int temp = 0;
// Reading the BMP File
FILE* image_file;
image_file = fopen(filename, "rb");
if (image_file == NULL)
{
printf("Error Opening File!!");
exit(1);
}
else
{
// Set file position of the
// stream to the beginning
// Contains file signature
// or ID "BM"
offset = 0;
// Set offset to 2, which
// contains size of BMP File
offset = 2;
fseek(image_file, offset, SEEK_SET);
// Getting size of BMP File
fread(&bmpsize, 4, 1, image_file);
// Getting offset where the
// pixel array starts
// Since the information
// is at offset 10 from
// the start, as given
// in BMP Header
offset = 10;
fseek(image_file, offset, SEEK_SET);
// Bitmap data offset
fread(&bmpdataoff, 4, 1, image_file);
// Getting height and width of the image
// Width is stored at offset 18 and height
// at offset 22, each of 4 bytes
fseek(image_file, 18, SEEK_SET);
fread(&width, 4, 1, image_file);
fread(&height, 4, 1, image_file);
// Number of bits per pixel
fseek(image_file, 2, SEEK_CUR);
fread(&bpp, 2, 1, image_file);
// Setting offset to start of pixel data
fseek(image_file, bmpdataoff, SEEK_SET);
// Creating Image array
image = (int**)malloc(height * sizeof(int*));
for (i = 0; i < height; i++)
{
image[i] = (int*)malloc(width * sizeof(int));
}
// int image[height][width]
// can also be done
// Number of bytes in the
// Image pixel array
int numbytes = (bmpsize - bmpdataoff) / 3;
// Reading the BMP File
// into Image Array
for (i = 0; i < height; i++)
{
for (j = 0; j < width; j++)
{
fread(&temp, 3, 1, image_file);
// the Image is a
// 24-bit BMP Image
temp = temp & 0x0000FF;
image[i][j] = temp;
}
}
}
创建图像中存在的像素强度值的直方图
// Creating the Histogram
int hist[256];
for (i = 0; i < 256; i++)
hist[i] = 0;
for (i = 0; i < height; i++)
for (j = 0; j < width; j++)
hist[image[i][j]] += 1;
查找具有非零出现概率的像素强度值的数量
由于像素强度的值在0到255的范围内,因此并不是所有像素强度值都可以出现在图像中(从直方图以及图像矩阵中可以看出),因此不会出现非零的出现概率。此步骤的另一个目的是,具有非零概率值的像素强度值的数量将为我们提供图像中叶节点的数量。
// Finding number of
// non-zero occurrences
int nodes = 0;
for (i = 0; i < 256; i++)
{
if (hist[i] != 0)
nodes += 1;
}
计算霍夫曼码字的最大长度
如YSAbu-Mostafa和RJMcEliece在他们的论文“霍夫曼码中的最大码字长度”中所示, ,则在概率最小为p的源的任何有效前缀码中,最长的码字长度最多为K&If ,存在一个源,其最小概率为p,并且具有霍夫曼码,其最长单词的长度为K。 ,存在这样一种来源,对于该来源,每个最佳代码都具有长度为K的最长字。
这里, 是个斐波那契数。
Gallager [1] noted that every Huffman tree is efficient, but in fact it is easy to see more
generally that every optimal tree is efficient
斐波那契数列是:0、1、1、2、3、5、8、13、21、34、55、89、144,…
在我们的示例中,最低概率(p)为0.015625
因此,
1/p = 64
For K = 9,
F(K+2) = F(11) = 55
F(K+3) = F(12) = 89
所以,
1/F(K+3) < p < 1/F(K+2)
Hence optimal length of code is K=9
// Calculating max length
// of Huffman code word
i = 0;
while ((1 / p) < fib(i))
i++;
int maxcodelen = i - 3;
// Function for getting Fibonacci
// numbers defined outside main
int fib(int n)
{
if (n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
第2步
定义一个结构,该结构将包含像素强度值( pix ),其对应的概率( freq ),指向left( * left )和right( * right )子节点的指针,以及霍夫曼代码word( code )的字符串数组)。
这些结构是在main()内部定义的,以便使用最大代码长度( maxcodelen )来声明结构pixfreq的代码数组字段
// Defining Structures pixfreq
struct pixfreq
{
int pix;
float freq;
struct pixfreq *left, *right;
char code[maxcodelen];
};
第三步
定义另一个Struct,它将包含像素强度值( pix ),其对应的概率( freq )和一个附加字段,这些字段将用于存储新生成的节点的位置( arrloc )。
// Defining Structures huffcode
struct huffcode
{
int pix, arrloc;
float freq;
};
第四步
声明一个结构数组。数组的每个元素对应于霍夫曼树中的一个节点。
// Declaring structs
struct pixfreq* pix_freq;
struct huffcode* huffcodes;
为什么要使用两个结构数组?
最初,结构数组pix_freq以及结构数组huffcode将仅包含霍夫曼树中所有叶节点的信息。
结构数组pix_freq将用于存储霍夫曼树的所有节点,数组霍夫代码将用作更新的(和排序的)树。
请记住,在每次迭代中只会对huffcode进行排序,而不会对pix_freq进行排序
在每次迭代中,通过组合频率最低的两个节点而创建的新节点将被附加到pix_freq数组的末尾,以及huffcodes数组的末尾。
但是在将新的节点添加到数组后,数组huffcode将根据出现的可能性再次排序。
新节点在pix_freq数组中的位置将存储在struct huffcode的arrloc字段中。
将指针分配给新节点的左子节点和右子节点时,将使用arrloc字段。
步骤4继续…
现在,如果有N个叶节点,则整个霍夫曼树中的节点总数将等于2N-1
在将两个节点合并并替换为新的父节点之后,每次迭代时,节点数将减少1 。因此,对于数组huffcode而言,具有一定长度的节点就足够了,该长度将用作更新和排序的Huffman节点。
int totalnodes = 2 * nodes - 1;
pix_freq = (struct pixfreq*)malloc(sizeof(struct pixfreq) * totalnodes);
huffcodes = (struct huffcode*)malloc(sizeof(struct huffcode) * nodes);
第5步
使用叶节点的信息初始化两个数组pix_freq和huffcode 。
j = 0;
int totpix = height * width;
float tempprob;
for (i = 0; i < 256; i++)
{
if (hist[i] != 0)
{
// pixel intensity value
huffcodes[j].pix = i;
pix_freq[j].pix = i;
// location of the node
// in the pix_freq array
huffcodes[j].arrloc = j;
// probability of occurrence
tempprob = (float)hist[i] / (float)totpix;
pix_freq[j].freq = tempprob;
huffcodes[j].freq = tempprob;
// Declaring the child of
// leaf node as NULL pointer
pix_freq[j].left = NULL;
pix_freq[j].right = NULL;
// initializing the code
// word as end of line
pix_freq[j].code[0] = '\0';
j++;
}
}
第6步
根据像素强度值出现的可能性对huffcodes数组进行排序
请注意,有必要对huffcodes数组进行排序,而不是pix_freq数组,因为我们已经将像素值的位置存储在huffcodes数组的arrloc字段中。
// Sorting the histogram
struct huffcode temphuff;
// Sorting w.r.t probability
// of occurrence
for (i = 0; i < nodes; i++)
{
for (j = i + 1; j < nodes; j++)
{
if (huffcodes[i].freq < huffcodes[j].freq)
{
temphuff = huffcodes[i];
huffcodes[i] = huffcodes[j];
huffcodes[j] = temphuff;
}
}
}
步骤7
建立霍夫曼树
我们首先将出现概率最低的两个节点组合在一起,然后用新节点替换两个节点。这个过程一直持续到我们拥有根节点为止。形成的第一个父节点将存储在pix_freq数组中的索引节点处,而获得的后续父节点将以较高的index值存储。
// Building Huffman Tree
float sumprob;
int sumpix;
int n = 0, k = 0;
int nextnode = nodes;
// Since total number of
// nodes in Huffman Tree
// is 2*nodes-1
while (n < nodes - 1)
{
// Adding the lowest
// two probabilities
sumprob = huffcodes[nodes - n - 1].freq + huffcodes[nodes - n - 2].freq;
sumpix = huffcodes[nodes - n - 1].pix + huffcodes[nodes - n - 2].pix;
// Appending to the pix_freq Array
pix_freq[nextnode].pix = sumpix;
pix_freq[nextnode].freq = sumprob;
pix_freq[nextnode].left = &pix_freq[huffcodes[nodes - n - 2].arrloc];
// arrloc points to the location
// of the child node in the
// pix_freq array
pix_freq[nextnode].right = &pix_freq[huffcodes[nodes - n - 1].arrloc];
pix_freq[nextnode].code[0] = '\0';
// Using sum of the pixel values as
// new representation for the new node
// since unlike strings, we cannot
// concatenate because the pixel values
// are stored as integers. However, if we
// store the pixel values as strings
// we can use the concatenated string as
// a representation of the new node.
i = 0;
// Sorting and Updating the huffcodes
// array simultaneously New position
// of the combined node
while (sumprob <= huffcodes[i].freq)
i++;
// Inserting the new node in
// the huffcodes array
for (k = nnz; k >= 0; k--)
{
if (k == i)
{
huffcodes[k].pix = sumpix;
huffcodes[k].freq = sumprob;
huffcodes[k].arrloc = nextnode;
}
else if (k > i)
// Shifting the nodes below
// the new node by 1
// For inserting the new node
// at the updated position k
huffcodes[k] = huffcodes[k - 1];
}
n += 1;
nextnode += 1;
}
该代码如何工作?
让我们来看一个例子:
原来
第一次迭代后
如您所见,在第一次迭代之后,新节点已添加到pix_freq数组中,其索引为46。并且在huffcode中,新节点在排序后已添加到其新位置,并且arrloc指向索引pix_freq数组中新节点的位置。另外,请注意, huffcodes数组中新节点之后(索引11处)的所有数组元素均已移位1,并且像素值188的数组元素被排除在更新后的数组中。
现在,在下一个(第二次)迭代中,将合并迭代170和174,因为已经合并了175和188。
就变量节点而言,最低的两个节点的索引,而n为
left_child_index=(nodes-n-2)
和
right_child_index=(nodes-n-1)
在第二次迭代中,n的值为1(因为n从0开始)。
对于值为170的节点
left_child_index=46-1-2=43
对于值为174的节点
right_child_index=46-1-1=44
因此,即使175仍然是更新数组的最后一个元素,也会将其排除在外。
此代码中要注意的另一件事是,如果在任何后续迭代中,第一次迭代中形成的新节点是另一个新节点的子节点,则可以使用以下命令访问在第一次迭代中获得的指向新节点的指针存储在huffcodes阵列arrloc,如在这行代码进行
pix_freq[nextnode].right = &pix_freq[huffcodes[nodes - n - 1].arrloc];
步骤8
从根回溯到叶节点以分配代码字
从根开始,我们将“ 0”分配给左子节点,将“ 1”分配给右子节点。
现在,由于我们将新形成的节点附加到数组pix_freq ,因此可以预期,根将是索引totalnodes-1处数组的最后一个元素。
因此,我们从最后一个索引开始,遍历数组,将代码字分配给左右子节点,直到到达在索引节点处形成的第一个父节点。我们不对叶节点进行迭代,因为这些节点的左,右子节点均具有NULL指针。
// Assigning Code through backtracking
char left = '0';
char right = '1';
int index;
for (i = totalnodes - 1; i >= nodes; i--) {
if (pix_freq[i].left != NULL) {
strconcat(pix_freq[i].left->code, pix_freq[i].code, left);
}
if (pix_freq[i].right != NULL) {
strconcat(pix_freq[i].right->code, pix_freq[i].code, right);
}
}
void strconcat(char* str, char* parentcode, char add)
{
int i = 0;
while (*(parentcode + i) != '\0') {
*(str + i) = *(parentcode + i);
i++;
}
str[i] = add;
str[i + 1] = '\0';
}
最后一步
编码图像
// Encode the Image
int pix_val;
// Writing the Huffman encoded
// Image into a text file
FILE* imagehuff = fopen("encoded_image.txt", "wb");
for (r = 0; r < height; r++)
for (c = 0; c < width; c++) {
pix_val = image[r];
for (i = 0; i < nodes; i++)
if (pix_val == pix_freq[i].pix)
fprintf(imagehuff, "%s", pix_freq[i].code);
}
fclose(imagehuff);
// Printing Huffman Codes
printf("Huffmann Codes::\n\n");
printf("pixel values -> Code\n\n");
for (i = 0; i < nodes; i++) {
if (snprintf(NULL, 0, "%d", pix_freq[i].pix) == 2)
printf(" %d -> %s\n", pix_freq[i].pix, pix_freq[i].code);
else
printf(" %d -> %s\n", pix_freq[i].pix, pix_freq[i].code);
}
另一个需要注意的重要点
表示每个像素所需的平均位数。
// Calculating Average number of bits
float avgbitnum = 0;
for (i = 0; i < nodes; i++)
avgbitnum += pix_freq[i].freq * codelen(pix_freq[i].code);
函数codelen计算代码字的长度OR,即表示像素所需的位数。
int codelen(char* code)
{
int l = 0;
while (*(code + l) != '\0')
l++;
return l;
}
对于此特定示例图像
Average number of bits = 5.343750
示例图像的打印结果
pixel values -> Code
72 -> 011001
75 -> 010100
79 -> 110111
83 -> 011010
84 -> 00100
87 -> 011100
89 -> 010000
93 -> 010111
94 -> 00011
96 -> 101010
98 -> 101110
100 -> 000101
102 -> 0001000
103 -> 0001001
105 -> 110110
106 -> 00110
110 -> 110100
114 -> 110101
115 -> 1100
118 -> 011011
119 -> 011000
122 -> 1110
124 -> 011110
125 -> 011111
127 -> 0000
128 -> 011101
130 -> 010010
131 -> 010011
136 -> 00111
138 -> 010001
139 -> 010110
140 -> 1111
142 -> 00101
143 -> 010101
146 -> 10010
148 -> 101011
149 -> 101000
153 -> 101001
155 -> 10011
163 -> 101111
167 -> 101100
169 -> 101101
170 -> 100010
174 -> 100011
175 -> 100000
188 -> 100001
编码图像:
0111010101000110011101101010001011010000000101111
00010001101000100100100100010010101011001101110111001
00000001100111101010010101100001111000110110111110010
10110001000000010110000001100001100001110011011110000
10011001101111111000100101111100010100011110000111000
01101001110101111100000111101100001110010010110101000
0111101001100101101001010111
该编码图像的长度为342位,其中原始图像的总位数为512位。 (8位各64个像素)。
Image Compression Code
// C Code for
// Image Compression
#include
#include
// function to calculate word length
int codelen(char* code)
{
int l = 0;
while (*(code + l) != '\0')
l++;
return l;
}
// function to concatenate the words
void strconcat(char* str, char* parentcode, char add)
{
int i = 0;
while (*(parentcode + i) != '\0')
{
*(str + i) = *(parentcode + i);
i++;
}
if (add != '2')
{
str[i] = add;
str[i + 1] = '\0';
}
else
str[i] = '\0';
}
// function to find fibonacci number
int fib(int n)
{
if (n <= 1)
return n;
return fib(n - 1) + fib(n - 2);
}
// Driver code
int main()
{
int i, j;
char filename[] = "Input_Image.bmp";
int data = 0, offset, bpp = 0, width, height;
long bmpsize = 0, bmpdataoff = 0;
int** image;
int temp = 0;
// Reading the BMP File
FILE* image_file;
image_file = fopen(filename, "rb");
if (image_file == NULL)
{
printf("Error Opening File!!");
exit(1);
}
else
{
// Set file position of the
// stream to the beginning
// Contains file signature
// or ID "BM"
offset = 0;
// Set offset to 2, which
// contains size of BMP File
offset = 2;
fseek(image_file, offset, SEEK_SET);
// Getting size of BMP File
fread(&bmpsize, 4, 1, image_file);
// Getting offset where the
// pixel array starts
// Since the information is
// at offset 10 from the start,
// as given in BMP Header
offset = 10;
fseek(image_file, offset, SEEK_SET);
// Bitmap data offset
fread(&bmpdataoff, 4, 1, image_file);
// Getting height and width of the image
// Width is stored at offset 18 and
// height at offset 22, each of 4 bytes
fseek(image_file, 18, SEEK_SET);
fread(&width, 4, 1, image_file);
fread(&height, 4, 1, image_file);
// Number of bits per pixel
fseek(image_file, 2, SEEK_CUR);
fread(&bpp, 2, 1, image_file);
// Setting offset to start of pixel data
fseek(image_file, bmpdataoff, SEEK_SET);
// Creating Image array
image = (int**)malloc(height * sizeof(int*));
for (i = 0; i < height; i++)
{
image[i] = (int*)malloc(width * sizeof(int));
}
// int image[height][width]
// can also be done
// Number of bytes in
// the Image pixel array
int numbytes = (bmpsize - bmpdataoff) / 3;
// Reading the BMP File
// into Image Array
for (i = 0; i < height; i++)
{
for (j = 0; j < width; j++)
{
fread(&temp, 3, 1, image_file);
// the Image is a
// 24-bit BMP Image
temp = temp & 0x0000FF;
image[i][j] = temp;
}
}
}
// Finding the probability
// of occurrence
int hist[256];
for (i = 0; i < 256; i++)
hist[i] = 0;
for (i = 0; i < height; i++)
for (j = 0; j < width; j++)
hist[image[i][j]] += 1;
// Finding number of
// non-zero occurrences
int nodes = 0;
for (i = 0; i < 256; i++)
if (hist[i] != 0)
nodes += 1;
// Calculating minimum probability
float p = 1.0, ptemp;
for (i = 0; i < 256; i++)
{
ptemp = (hist[i] / (float)(height * width));
if (ptemp > 0 && ptemp <= p)
p = ptemp;
}
// Calculating max length
// of code word
i = 0;
while ((1 / p) > fib(i))
i++;
int maxcodelen = i - 3;
// Defining Structures pixfreq
struct pixfreq
{
int pix, larrloc, rarrloc;
float freq;
struct pixfreq *left, *right;
char code[maxcodelen];
};
// Defining Structures
// huffcode
struct huffcode
{
int pix, arrloc;
float freq;
};
// Declaring structs
struct pixfreq* pix_freq;
struct huffcode* huffcodes;
int totalnodes = 2 * nodes - 1;
pix_freq = (struct pixfreq*)malloc(sizeof(struct pixfreq) * totalnodes);
huffcodes = (struct huffcode*)malloc(sizeof(struct huffcode) * nodes);
// Initializing
j = 0;
int totpix = height * width;
float tempprob;
for (i = 0; i < 256; i++)
{
if (hist[i] != 0)
{
// pixel intensity value
huffcodes[j].pix = i;
pix_freq[j].pix = i;
// location of the node
// in the pix_freq array
huffcodes[j].arrloc = j;
// probability of occurrence
tempprob = (float)hist[i] / (float)totpix;
pix_freq[j].freq = tempprob;
huffcodes[j].freq = tempprob;
// Declaring the child of leaf
// node as NULL pointer
pix_freq[j].left = NULL;
pix_freq[j].right = NULL;
// initializing the code
// word as end of line
pix_freq[j].code[0] = '\0';
j++;
}
}
// Sorting the histogram
struct huffcode temphuff;
// Sorting w.r.t probability
// of occurrence
for (i = 0; i < nodes; i++)
{
for (j = i + 1; j < nodes; j++)
{
if (huffcodes[i].freq < huffcodes[j].freq)
{
temphuff = huffcodes[i];
huffcodes[i] = huffcodes[j];
huffcodes[j] = temphuff;
}
}
}
// Building Huffman Tree
float sumprob;
int sumpix;
int n = 0, k = 0;
int nextnode = nodes;
// Since total number of
// nodes in Huffman Tree
// is 2*nodes-1
while (n < nodes - 1)
{
// Adding the lowest two probabilities
sumprob = huffcodes[nodes - n - 1].freq + huffcodes[nodes - n - 2].freq;
sumpix = huffcodes[nodes - n - 1].pix + huffcodes[nodes - n - 2].pix;
// Appending to the pix_freq Array
pix_freq[nextnode].pix = sumpix;
pix_freq[nextnode].freq = sumprob;
pix_freq[nextnode].left = &pix_freq[huffcodes[nodes - n - 2].arrloc];
pix_freq[nextnode].right = &pix_freq[huffcodes[nodes - n - 1].arrloc];
pix_freq[nextnode].code[0] = '\0';
i = 0;
// Sorting and Updating the
// huffcodes array simultaneously
// New position of the combined node
while (sumprob <= huffcodes[i].freq)
i++;
// Inserting the new node
// in the huffcodes array
for (k = nodes; k >= 0; k--)
{
if (k == i)
{
huffcodes[k].pix = sumpix;
huffcodes[k].freq = sumprob;
huffcodes[k].arrloc = nextnode;
}
else if (k > i)
// Shifting the nodes below
// the new node by 1
// For inserting the new node
// at the updated position k
huffcodes[k] = huffcodes[k - 1];
}
n += 1;
nextnode += 1;
}
// Assigning Code through
// backtracking
char left = '0';
char right = '1';
int index;
for (i = totalnodes - 1; i >= nodes; i--)
{
if (pix_freq[i].left != NULL)
strconcat(pix_freq[i].left->code, pix_freq[i].code, left);
if (pix_freq[i].right != NULL)
strconcat(pix_freq[i].right->code, pix_freq[i].code, right);
}
// Encode the Image
int pix_val;
int l;
// Writing the Huffman encoded
// Image into a text file
FILE* imagehuff = fopen("encoded_image.txt", "wb");
for (i = 0; i < height; i++)
for (j = 0; j < width; j++)
{
pix_val = image[i][j];
for (l = 0; l < nodes; l++)
if (pix_val == pix_freq[l].pix)
fprintf(imagehuff, "%s", pix_freq[l].code);
}
// Printing Huffman Codes
printf("Huffmann Codes::\n\n");
printf("pixel values -> Code\n\n");
for (i = 0; i < nodes; i++) {
if (snprintf(NULL, 0, "%d", pix_freq[i].pix) == 2)
printf(" %d -> %s\n", pix_freq[i].pix, pix_freq[i].code);
else
printf(" %d -> %s\n", pix_freq[i].pix, pix_freq[i].code);
}
// Calculating Average Bit Length
float avgbitnum = 0;
for (i = 0; i < nodes; i++)
avgbitnum += pix_freq[i].freq * codelen(pix_freq[i].code);
printf("Average number of bits:: %f", avgbitnum);
}