使用霍夫曼编码进行图像压缩原理和实现细节

霍夫曼编码是一种基本的压缩方法, 已被证明在图像和视频压缩标准中有用。在图像上应用霍夫曼编码技术时, 源符号可以是图像的像素强度, 也可以是强度映射函数的输出。
先决条件:霍夫曼编码|文件处理
霍夫曼编码技术的第一步是将输入图像缩小为有序直方图, 其中某个像素强度值的出现概率为

prob_pixel = numpix/totalnum

其中numpix是具有特定强度值的像素的出现次数, 并且总数是输入图像中的像素总数。
让我们拍摄一张8 X 8的图片
使用霍夫曼编码进行图像压缩原理和实现细节

文章图片
像素强度值为:
使用霍夫曼编码进行图像压缩原理和实现细节

文章图片
该图像包含46个不同的像素强度值, 因此, 我们将有46个唯一的霍夫曼代码字。
显然, 并非所有像素强度值都可能出现在图像中, 因此不会有非零的出现概率。
从这里开始, 输入图像中的像素强度值将被视为叶节点。
现在, 有两个基本步骤可以构建霍夫曼树:
建立霍夫曼树:
  1. 将两个概率最低的叶子节点合并为一个新节点。
  2. 用新节点替换两个叶节点, 并根据新的概率值对节点进行排序。
  3. 继续步骤(a)和(b), 直到获得概率值为1.0的单个节点。我们称这个节点为根
从根开始回溯, 为每个中间节点分配” 0″ 或” 1″ , 直到到达叶节点
在此示例中, 我们将为左侧的子节点分配” 0″ , 为右侧的子节点分配” 1″ 。
现在, 让我们研究一下实现:
第1步 :
将图像读入2D数组(Image)
如果图像是.bmp格式,那么图像可以通过使用此链接中给出的代码读入2D数组(https://github.com/sadimanna/compression/blob/master/readbmp.c)。
int i, j; char filename[] = "Input_Image.bmp" ; int data = http://www.srcmini.com/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; }

计算霍夫曼码字的最大长度
如y.s.b u- mostafa和R.J.McEliece在其论文《Huffman codes中的最大码字长度》中所示,如果
使用霍夫曼编码进行图像压缩原理和实现细节

文章图片
, 存在一个源, 其最小概率为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),指向左(*左)和右(*右)子节点的指针,以及Huffman码字(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; };

步骤4
声明结构数组。数组的每个元素对应于霍夫曼树中的一个节点。
//Declaring structs struct pixfreq* pix_freq; struct huffcode* huffcodes;

为什么要使用两个结构数组?
最初, struct数组pix_freq, 以及struct数组哈夫码将仅包含霍夫曼树中所有叶节点的信息。
结构数组pix_freq将用于存储霍夫曼树和数组的所有节点哈夫码将用作更新(和排序)的树。
记住, 只有哈夫码将在每次迭代中排序, 而不是pix_freq
在每次迭代中, 通过组合频率最低的两个节点而创建的新节点将被附加到pix_freq数组, 也哈夫码数组。
但是数组哈夫码在将新节点添加到节点后, 将根据出现的可能性再次对节点进行排序。
新节点在数组pix_freq中的位置将存储在struct huffcode的arrloc字段中。
当将指针分配给新节点的左子节点和右子节点时,将使用arrloc字段。
步骤4继续…
现在, 如果有?叶节点的数量, 整个霍夫曼树中的节点总数将等于2N-1
然后将两个节点合并并替换为新节点父节点, 节点数减少了1在每次迭代中。因此, 长度为节点用于数组哈夫码, 它将用作更新和排序的霍夫曼节点。
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和huffcodes。
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数组进行排序
请注意, 有必要对哈夫码数组, 但不是pix_freq数组, 因为我们已经将像素值的位置存储在阿罗洛克的领域哈夫码数组。
//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 Treefloat 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。哈夫码排序后, 新节点已添加到其新位置, 并且阿罗洛克指向中新节点的索引pix_freq数组。此外, 请注意, 在新节点(索引11)之后的所有数组元素哈夫码数组已移位1, 像素值188的数组元素将排除在更新的数组中。
现在,在下一个(第2)迭代中,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仍然是更新数组的最后一个元素, 也会将其排除在外。
此代码中要注意的另一件事是, 如果在任何后续迭代中, 在第一次迭代中形成的新节点是另一个新节点的子节点, 则可以使用以下命令访问在第一次迭代中获得的指向新节点的指针的阿罗洛克存储在哈夫码数组, 如本行代码所示
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);

功能可伦计算代码字的长度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个像素)。
图像压缩码
//C Code for //Image Compression #include < stdio.h> #include < stdlib.h> //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 = http://www.srcmini.com/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); }

代码编译和执行:
首先,将文件保存为“huffman.c”。
要编译C文件, 请打开终端(Ctrl + Alt + T)并输入以下代码行:
gcc -o huffman huffman.c
要执行代码, 请输入
./huffman
图像压缩码输出:
使用霍夫曼编码进行图像压缩原理和实现细节

文章图片
【使用霍夫曼编码进行图像压缩原理和实现细节】霍夫曼树:
使用霍夫曼编码进行图像压缩原理和实现细节

文章图片

    推荐阅读