.\main.exe a.png
// 读取和写入图像的库
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <cmath>
using namespace std;
// 读取一个图像文件并将其存储在变量image中,返回图像的宽、高和颜色通道数。
bool read_image(const string &filename, vector<unsigned char> &image, int &width, int &height, int &channels)
{
unsigned char *data = stbi_load(filename.c_str(), &width, &height, &channels, 0); // 使用stb_image库中的stbi_load函数读取图像文件
if (data == nullptr)
{
cerr << "Failed to read image: " << filename << endl;
return false;
}
image.resize(width * height * channels); // 计算图像的总数据量
memcpy(image.data(), data, image.size()); // 复制
stbi_image_free(data); // 释放内存
return true;
}
// 将图像保存为PNG格式的图像文件。
bool save_image(const string &filename, const vector<unsigned char> &image, int width, int height, int channels)
{
if (stbi_write_png(filename.c_str(), width, height, channels, image.data(), width * channels) == 0)
{
cerr << "Failed to save image: " << filename << endl;
return false;
}
return true;
}
// 将压缩图像数据保存为二进制文件。
bool save_compressed_image(const string &filename, const vector<unsigned char> &compressed_image)
{
ofstream ofs(filename, ios::binary); // 以二进制打开文件
if (!ofs)
{
cerr << "Failed to save compressed image: " << filename << endl;
return false;
}
ofs.write(reinterpret_cast<const char *>(compressed_image.data()), compressed_image.size()); // 将压缩后的图像数据写入文件
return true;
}
// 从一个二进制文件中读取压缩的图像数据
bool read_compressed_image(const string &filename, vector<unsigned char> &compressed_image)
{
ifstream ifs(filename, ios::binary); // 以二进制打开文件
if (!ifs)
{
cerr << "Failed to read compressed image: " << filename << endl;
return false;
}
// assign函数会清空compressed_image的现有内容,并将从文件读取的数据添加到向量的末尾
compressed_image.assign((istreambuf_iterator<char>(ifs)), istreambuf_iterator<char>());
return true;
}
// RLE压缩算法
vector<unsigned char> rle_compress(const vector<unsigned char> &image, int width, int height, int channels)
{
cout << "Compressing image..." << endl;
// 创建一个vector来存储压缩后的图像数据
vector<unsigned char> compressed_image;
for (int i = 0; i < image.size(); i += channels)
{
int count = 1;
// 检查当前像素块与后续的像素块是否相同,并且计数不超过255
while (i + count * channels < image.size() && memcmp(&image[i], &image[i + count * channels], channels) == 0 && count < 255)
{
count++;
}
compressed_image.push_back(count); // 将重复的像素块数添加到压缩后的图像数据中
// 将重复像素块的一个像素数据添加到压缩后的图像数据中
for (int j = 0; j < channels; j++)
{
compressed_image.push_back(image[i + j]);
}
i += (count - 1) * channels; // 更新索引以跳过已经处理的重复像素块
}
cout << "Done." << endl;
return compressed_image; // 返回压缩后的图像
}
// RLE解压缩,将压缩的图像数据恢复为原始形式
vector<unsigned char> rle_decompress(const vector<unsigned char> &compressed_image, int width, int height, int channels)
{
vector<unsigned char> image; // 创建一个vector来存储解压后的图像数据
for (size_t i = 0; i < compressed_image.size(); i += (channels + 1))
{
int count = compressed_image[i]; // 读取当前像素块的重复计数
// 根据计数,重复添加该像素块的像素数据到解压后的图像数据中
for (int j = 0; j < count; j++)
{
for (int k = 0; k < channels; k++) // 对于每个像素块,添加其所有通道的数据
{
image.push_back(compressed_image[i + 1 + k]);
}
}
}
return image;
}
// 计算熵,熵是信息不确定性的度量,对于图像而言,熵越高,图像信息越丰富,越难压缩
double calculate_entropy(const vector<unsigned char> &image, int width, int height, int channels)
{
vector<int> histogram(256, 0); // 创建一个大小为256的直方图数组,用于统计0 - 255范围内每个像素值出现的次数
for (int i = 0; i < width * height * channels; i++) // 遍历图像的每个像素
{
histogram[image[i]]++;
}
double entropy = 0;
for (int i = 0; i < 256; i++) // 遍历直方图,计算熵
{
if (histogram[i] == 0)
{
continue;
}
// 概率p是像素值出现的次数除以总像素数
double p = static_cast<double>(histogram[i]) / (width * height * channels);
entropy -= p * log2(p); // 计算熵
}
return entropy;
}
// 计算RLE压缩后的平均码长,用于评估无损数据压缩算法的效果
double calculate_average_code_length(const vector<unsigned char> &image, int width, int height, int channels)
{
vector<int> histogram(256, 0);
for (int i = 0; i < width * height * channels; i++)
{
histogram[image[i]]++;
}
double average_code_length = 0;
for (int i = 0; i < 256; i++)
{
if (histogram[i] == 0)
{
continue;
}
double p = static_cast<double>(histogram[i]) / (width * height * channels);
// 累加所有像素值的期望码长得到平均码长
average_code_length += p * (1 + log2(1 / p));
}
return average_code_length;
}
int main(int argc, char **argv)
{
if (argc != 2) // 检查命令行参数的数量是否为2,程序名本身和一个文件名
{
cerr << "Usage: " << argv[0] << " <filename>" << endl;
return -1;
}
string filename = argv[1]; // 将命令行参数中的文件名赋值给字符串变量filename
vector<unsigned char> image; // 存储图像数据
int width, height, channels;
if (!read_image(filename, image, width, height, channels)) // 调用read_image函数读取图像文件
{
return -1;
}
// 调用rle_compress函数对图像进行行程编码压缩,并将结果存储在compressed_image中
vector<unsigned char> compressed_image = rle_compress(image, width, height, channels);
// 调用save_compressed_image函数保存压缩后的图像
if (!save_compressed_image("compressed_" + filename, compressed_image))
{
return -1;
}
// 调用rle_decompress函数对压缩后的图像进行解压缩,并将结果存储在decompressed_image中
vector<unsigned char> decompressed_image = rle_decompress(compressed_image, width, height, channels);
// 调用save_image函数保存解压缩后的图像
if (!save_image("decompressed_" + filename, decompressed_image, width, height, channels))
{
return -1;
}
double entropy = calculate_entropy(image, width, height, channels); // 计算熵
double average_code_length = calculate_average_code_length(image, width, height, channels); // 计算平均编码长度
cout << "Entropy: " << entropy << endl;
cout << "Average code length: " << average_code_length << endl;
// 计算编码效率,等于熵除以平均码长
cout << "Coding efficiency: " << entropy / average_code_length << endl;
return 0;
}
或
需要配置opencv环境
// 读取和写入图像的库
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
// 包含OpenCV头文件
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <cmath>
using namespace std;
using namespace cv;
// 读取一个图像文件并将其存储在变量image中,返回图像的宽、高和颜色通道数。
bool read_image(const string &filename, vector<unsigned char> &image, int &width, int &height, int &channels)
{
unsigned char *data = stbi_load(filename.c_str(), &width, &height, &channels, 0); // 使用stb_image库中的stbi_load函数读取图像文件
if (data == nullptr)
{
cerr << "Failed to read image: " << filename << endl;
return false;
}
image.resize(width * height * channels); // 计算图像的总数据量
memcpy(image.data(), data, image.size()); // 复制
stbi_image_free(data); // 释放内存
return true;
}
// 将图像保存为PNG格式的图像文件。
bool save_image(const string &filename, const vector<unsigned char> &image, int width, int height, int channels)
{
if (stbi_write_png(filename.c_str(), width, height, channels, image.data(), width * channels) == 0)
{
cerr << "Failed to save image: " << filename << endl;
return false;
}
return true;
}
// 将压缩图像数据保存为二进制文件。
bool save_compressed_image(const string &filename, const vector<unsigned char> &compressed_image)
{
ofstream ofs(filename, ios::binary); // 以二进制打开文件
if (!ofs)
{
cerr << "Failed to save compressed image: " << filename << endl;
return false;
}
ofs.write(reinterpret_cast<const char *>(compressed_image.data()), compressed_image.size()); // 将压缩后的图像数据写入文件
return true;
}
// 从一个二进制文件中读取压缩的图像数据
bool read_compressed_image(const string &filename, vector<unsigned char> &compressed_image)
{
ifstream ifs(filename, ios::binary); // 以二进制打开文件
if (!ifs)
{
cerr << "Failed to read compressed image: " << filename << endl;
return false;
}
// assign函数会清空compressed_image的现有内容,并将从文件读取的数据添加到向量的末尾
compressed_image.assign((istreambuf_iterator<char>(ifs)), istreambuf_iterator<char>());
return true;
}
// RLE压缩算法
vector<unsigned char> rle_compress(const vector<unsigned char> &image, int width, int height, int channels)
{
cout << "Compressing image..." << endl;
// 创建一个vector来存储压缩后的图像数据
vector<unsigned char> compressed_image;
for (int i = 0; i < image.size(); i += channels)
{
int count = 1;
// 检查当前像素块与后续的像素块是否相同,并且计数不超过255
while (i + count * channels < image.size() && memcmp(&image[i], &image[i + count * channels], channels) == 0 && count < 255)
{
count++;
}
compressed_image.push_back(count); // 将重复的像素块数添加到压缩后的图像数据中
// 将重复像素块的一个像素数据添加到压缩后的图像数据中
for (int j = 0; j < channels; j++)
{
compressed_image.push_back(image[i + j]);
}
i += (count - 1) * channels; // 更新索引以跳过已经处理的重复像素块
}
cout << "Done." << endl;
return compressed_image; // 返回压缩后的图像
}
// RLE解压缩,将压缩的图像数据恢复为原始形式
vector<unsigned char> rle_decompress(const vector<unsigned char> &compressed_image, int width, int height, int channels)
{
vector<unsigned char> image; // 创建一个vector来存储解压后的图像数据
for (size_t i = 0; i < compressed_image.size(); i += (channels + 1))
{
int count = compressed_image[i]; // 读取当前像素块的重复计数
// 根据计数,重复添加该像素块的像素数据到解压后的图像数据中
for (int j = 0; j < count; j++)
{
for (int k = 0; k < channels; k++) // 对于每个像素块,添加其所有通道的数据
{
image.push_back(compressed_image[i + 1 + k]);
}
}
}
return image;
}
// 计算熵,熵是信息不确定性的度量,对于图像而言,熵越高,图像信息越丰富,越难压缩
double calculate_entropy(const vector<unsigned char> &image, int width, int height, int channels)
{
vector<int> histogram(256, 0); // 创建一个大小为256的直方图数组,用于统计0 - 255范围内每个像素值出现的次数
for (int i = 0; i < width * height * channels; i++) // 遍历图像的每个像素
{
histogram[image[i]]++;
}
double entropy = 0;
for (int i = 0; i < 256; i++) // 遍历直方图,计算熵
{
if (histogram[i] == 0)
{
continue;
}
// 概率p是像素值出现的次数除以总像素数
double p = static_cast<double>(histogram[i]) / (width * height * channels);
entropy -= p * log2(p); // 计算熵
}
return entropy;
}
// 计算RLE压缩后的平均码长,用于评估无损数据压缩算法的效果
double calculate_average_code_length(const vector<unsigned char> &image, int width, int height, int channels)
{
vector<int> histogram(256, 0);
for (int i = 0; i < width * height * channels; i++)
{
histogram[image[i]]++;
}
double average_code_length = 0;
for (int i = 0; i < 256; i++)
{
if (histogram[i] == 0)
{
continue;
}
double p = static_cast<double>(histogram[i]) / (width * height * channels);
// 累加所有像素值的期望码长得到平均码长
average_code_length += p * (1 + log2(1 / p));
}
return average_code_length;
}
int main()
{
string filename = "a.png";
vector<unsigned char> image;
int width, height, channels;
// 读取图像文件
if (!read_image(filename, image, width, height, channels))
{
return -1;
}
// 将vector转换为Mat以便使用OpenCV显示
Mat originalImage(height, width, CV_8UC(channels), image.data());
// 压缩图像
vector<unsigned char> compressed_image = rle_compress(image, width, height, channels);
// 解压缩图像
vector<unsigned char> decompressed_image = rle_decompress(compressed_image, width, height, channels);
// 将解压缩后的vector转换为Mat以便使用OpenCV显示
Mat decompressedMat(height, width, CV_8UC(channels), decompressed_image.data());
// 使用OpenCV显示图像
namedWindow("Original Image", WINDOW_NORMAL);
imshow("Original Image", originalImage);
namedWindow("Decompressed Image", WINDOW_NORMAL);
imshow("Decompressed Image", decompressedMat);
// 等待按键
waitKey(0);
// 计算熵和平均编码长度
double entropy = calculate_entropy(image, width, height, channels); // 计算熵
double average_code_length = calculate_average_code_length(image, width, height, channels); // 计算平均编码长度
cout << "Entropy: " << entropy << endl;
cout << "Average code length: " << average_code_length << endl;
// 计算编码效率,等于熵除以平均码长
cout << "Coding efficiency: " << entropy / average_code_length << endl;
return 0;
}