实验任务:Huffman编解码
已知:
- 原灰度图像Is:b8gray.bmp
图片在实验一中已经给出。
求: - 代码
- 编码后的bpp值
- 原灰度图像的熵
验证: - 编码程序,输入为Is,输出为压缩文件C.dat
- 解码程序,输入为C.dat,输出为解码图像Id
- Is与Id是否相同
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define MAX_TREE_NODES 256
// Huffman树节点
typedef struct Node
{
unsigned char symbol;
int frequency;
struct Node *left;
struct Node *right;
} Node;
// 优先队列
typedef struct PriorityQueue
{
int size;
int capacity;
Node **nodes;
} PriorityQueue;
// 创建Huffman树节点
Node *createNode(unsigned char symbol, int frequency)
{
Node *node = (Node *)malloc(sizeof(Node));
node->symbol = symbol;
node->frequency = frequency;
node->left = NULL;
node->right = NULL;
return node;
}
// 创建优先队列
PriorityQueue *createPriorityQueue(int capacity)
{
PriorityQueue *queue = (PriorityQueue *)malloc(sizeof(PriorityQueue));
queue->size = 0;
queue->capacity = capacity;
queue->nodes = (Node **)malloc(capacity * sizeof(Node *));
return queue;
}
// 交换两个节点
void swapNodes(Node **a, Node **b)
{
Node *temp = *a;
*a = *b;
*b = temp;
}
// 从优先队列中获取最小频率的节点
Node *extractMin(PriorityQueue *queue)
{
int minIndex = 0;
for (int i = 1; i < queue->size; i++)
{
if (queue->nodes[i]->frequency < queue->nodes[minIndex]->frequency)
{
minIndex = i;
}
}
Node *minNode = queue->nodes[minIndex];
queue->size--;
swapNodes(&queue->nodes[minIndex], &queue->nodes[queue->size]);
return minNode;
}
// 插入节点到优先队列
void insertNode(PriorityQueue *queue, Node *node)
{
int i = queue->size;
while (i > 0 && node->frequency < queue->nodes[i - 1]->frequency)
{
queue->nodes[i] = queue->nodes[i - 1];
i--;
}
queue->nodes[i] = node;
queue->size++;
}
// 构建Huffman树
Node *buildHuffmanTree(int *frequencies)
{
PriorityQueue *queue = createPriorityQueue(MAX_TREE_NODES);
for (int i = 0; i < MAX_TREE_NODES; i++)
{
if (frequencies[i] > 0)
{
Node *node = createNode(i, frequencies[i]);
insertNode(queue, node);
}
}
while (queue->size > 1)
{
Node *left = extractMin(queue);
Node *right = extractMin(queue);
Node *parent = createNode('$', left->frequency + right->frequency);
parent->left = left;
parent->right = right;
insertNode(queue, parent);
}
Node *root = extractMin(queue);
free(queue->nodes);
free(queue);
return root;
}
// 递归地构建Huffman编码
void buildHuffmanCodes(Node *node, unsigned long code, int length, unsigned long *codes, int *lengths)
{
if (node->left == NULL && node->right == NULL)
{
codes[node->symbol] = code;
lengths[node->symbol] = length;
}
else
{
buildHuffmanCodes(node->left, (code << 1) | 0, length + 1, codes, lengths);
buildHuffmanCodes(node->right, (code << 1) | 1, length + 1, codes, lengths);
}
}
// 将Huffman编码保存到文件
void saveHuffmanCodes(FILE *file, unsigned long *codes, int *lengths)
{
for (int i = 0; i < MAX_TREE_NODES; i++)
{
if (lengths[i] > 0)
{
unsigned char symbol = (unsigned char)i;
fwrite(&symbol, sizeof(unsigned char), 1, file);
fwrite(&lengths[i], sizeof(int), 1, file);
fwrite(&codes[i], sizeof(unsigned long), 1, file);
}
}
}
// 从文件中加载Huffman编码
void loadHuffmanCodes(FILE *file, unsigned long *codes, int *lengths)
{
unsigned char symbol;
int length;
unsigned long code;
while (fread(&symbol, sizeof(unsigned char), 1, file) == 1)
{
fread(&length, sizeof(int), 1, file);
fread(&code, sizeof(unsigned long), 1, file);
codes[symbol] = code;
lengths[symbol] = length;
}
}
// 获取文件大小
long getFileSize(FILE *file)
{
fseek(file, 0, SEEK_END);
long size = ftell(file);
rewind(file);
return size;
}
// 对图像进行Huffman编码
void encodeImage(FILE *inputFile, FILE *outputFile, unsigned long *codes, int *lengths, int *bpp)
{
unsigned char buffer = 0;
int bufferLength = 0;
unsigned char pixel;
long inputSize = getFileSize(inputFile);
while (fread(&pixel, sizeof(unsigned char), 1, inputFile) == 1)
{
unsigned long code = codes[pixel];
int length = lengths[pixel];
for (int i = 0; i < length; i++)
{
buffer = (buffer << 1) | ((code >> (length - 1 - i)) & 1);
bufferLength++;
if (bufferLength == 8)
{
fwrite(&buffer, sizeof(unsigned char), 1, outputFile);
buffer = 0;
bufferLength = 0;
}
}
}
if (bufferLength > 0)
{
buffer = buffer << (8 - bufferLength);
fwrite(&buffer, sizeof(unsigned char), 1, outputFile);
}
// 计算压缩后的bpp值
long outputSize = getFileSize(outputFile);
*bpp = (int)(outputSize * 8 / (float)inputSize);
}
// 对Huffman编码进行解码
void decodeImage(FILE *inputFile, FILE *outputFile, Node *root, long inputSize)
{
Node *currentNode = root;
unsigned char byte;
unsigned char bit;
long bytesRead = 0;
while (fread(&byte, sizeof(unsigned char), 1, inputFile) == 1 && bytesRead < inputSize)
{
for (int i = 7; i >= 0; i--)
{
bit = (byte >> i) & 1;
if (bit == 0)
{
currentNode = currentNode->left;
}
else
{
currentNode = currentNode->right;
}
if (currentNode->left == NULL && currentNode->right == NULL)
{
fwrite(&(currentNode->symbol), sizeof(unsigned char), 1, outputFile);
currentNode = root;
}
bytesRead++;
if (bytesRead >= inputSize)
{
break;
}
}
}
}
double computeEntropy(int *frequencies, int totalPixels)
{
double entropy = 0.0;
for (int i = 0; i < MAX_TREE_NODES; i++)
{
if (frequencies[i] > 0)
{
double probability = (double)frequencies[i] / totalPixels;
entropy -= probability * log2(probability);
}
}
return entropy;
}
int main()
{
// 读取图像文件和创建输出文件
FILE *input = fopen("b8gray.bmp", "rb");
FILE *output = fopen("C.dat", "wb");
// 计算图像中每个像素的频率
int frequencies[MAX_TREE_NODES] = {0};
unsigned char pixel;
int totalPixels = 0;
while (fread(&pixel, sizeof(unsigned char), 1, input) == 1)
{
frequencies[pixel]++;
totalPixels++;
}
rewind(input);
// 计算灰度图像的熵
double entropy = computeEntropy(frequencies, totalPixels);
printf("原灰度图像的熵:%f\n", entropy);
// 构建Huffman树
Node *root = buildHuffmanTree(frequencies);
// 构建Huffman编码
unsigned long codes[MAX_TREE_NODES] = {0};
int lengths[MAX_TREE_NODES] = {0};
buildHuffmanCodes(root, 0, 0, codes, lengths);
// 将Huffman编码保存到文件
saveHuffmanCodes(output, codes, lengths);
// 对图像进行编码
int bpp;
encodeImage(input, output, codes, lengths, &bpp);
printf("压缩后的bpp值:%d\n", bpp);
fclose(input);
fclose(output);
// 解码
FILE *compressed = fopen("C.dat", "rb");
FILE *decoded = fopen("Id.bmp", "wb");
// 加载Huffman编码
unsigned long loadedCodes[MAX_TREE_NODES] = {0};
int loadedLengths[MAX_TREE_NODES] = {0};
loadHuffmanCodes(compressed, loadedCodes, loadedLengths);
// 重新构建Huffman树
Node *decodedRoot = buildHuffmanTree(loadedLengths);
// 解码图像
long inputSize = getFileSize(compressed);
decodeImage(compressed, decoded, decodedRoot, inputSize);
fclose(compressed);
fclose(decoded);
return 0;
}
上述代码运行后,对b8gray.bmp文件进行编码再解码后,生成的Id.bmp大小与源文件不同,(可能代码有误,之后解决问题后再更改文章)
主要步骤:
-
定义了Huffman树节点结构体(Node)和优先队列结构体(PriorityQueue),用于构建Huffman树和管理节点。
-
创建Huffman树节点的函数(createNode)用于创建一个新的节点。
-
创建优先队列的函数(createPriorityQueue)用于创建一个空的优先队列。
-
交换节点的函数(swapNodes)用于交换两个节点的位置。
-
从优先队列中获取最小频率的节点的函数(extractMin)用于从优先队列中取出频率最小的节点。
-
插入节点到优先队列的函数(insertNode)用于将一个节点插入到优先队列中的适当位置。
-
构建Huffman树的函数(buildHuffmanTree)根据给定的字符频率数组构建Huffman树。
-
递归地构建Huffman编码的函数(buildHuffmanCodes)根据Huffman树节点,计算每个字符的Huffman编码和编码长度。
-
将Huffman编码保存到文件的函数(saveHuffmanCodes)将每个字符的编码和长度保存到输出文件中。
-
从文件中加载Huffman编码的函数(loadHuffmanCodes)从输入文件中加载每个字符的编码和长度。
-
获取文件大小的函数(getFileSize)用于计算给定文件的大小。
-
对图像进行Huffman编码的函数(encodeImage)将输入图像文件中的像素值根据Huffman编码转换为压缩后的二进制数据,并保存到输出文件中。同时计算压缩后的bpp值。
-
对Huffman编码进行解码的函数(decodeImage)将压缩后的二进制数据根据Huffman编码进行解码,并将解码后的像素值写入到输出文件中。
-
主函数中执行以下操作:
- 打开输入图像文件和输出文件。
- 计算图像中每个像素的频率。
- 构建Huffman树。
- 构建Huffman编码。
- 将Huffman编码保存到文件。
- 对图像进行编码并计算压缩后的bpp值。
- 关闭输入和输出文件。
- 打开压缩后的文件和解码后的输出文件。
- 加载Huffman编码。
- 重新构建Huffman树。
- 解码图像。
- 关闭压缩文件和解码文件。
解决遗留问题:上述代码的压缩和解压缩过程是基于像素值进行的,并使用Huffman编码来表示每个像素值。因此,如果输入图像(C.dat)是非像素图像,则无法正确执行解码过程。