您现在的位置是:首页 >技术交流 >提取图片RGB三通道数据+用RGB恢复原始图片网站首页技术交流
提取图片RGB三通道数据+用RGB恢复原始图片
简介提取图片RGB三通道数据+用RGB恢复原始图片
提取图片的RGB三通道数据+用RGB恢复原始图片
功能实现:
- 提取出一张图片的R、G、B三个通道的值并且将其分别转换为十进制数据存储到R.txt、G.txt、B.txt文档中
- 再将R.txt、G.txt、B.txt里的十进制数据恢复成图片形式,用以验证
代码已开源到github上
提取图片的RGB三通道数据
使用的是libjpeg库,关于如何在QT中配置libjpeg库的环境,请看我这篇博客
- 首先定义输入图片和输出文件名
char* input_filename = "C:/Users/10858/Desktop/1.jpg";
char* output_filename_R = "C:/Users/10858/Desktop/output_R.txt";
char* output_filename_G = "C:/Users/10858/Desktop/output_G.txt";
char* output_filename_B = "C:/Users/10858/Desktop/output_B.txt";
- 打开输入文件并读取JPEG图像数据
FILE* infile = fopen(input_filename, "rb");
if (!infile) {
printf("无法打开文件: %s
", input_filename);
return 1;
}
- 存储在内存中
fseek(infile, 0, SEEK_END); //文件指针定位到文件结尾位置
size_t size = ftell(infile); //读取文件大小
fseek(infile, 0, SEEK_SET); //文件指针回到文件开始位置
unsigned char* jpegData = new unsigned char[size]; //在堆上分配一个内存块
fread(jpegData, size, 1, infile); //数据读取到刚才分配的内存块中
- 初始化JPEG解压对象
- jpeg_decompress_struct 结构体:用来保存JPEG解压缩的相关参数
其中jpeg_mem_src()函数的作用是将大小为size的jpegData内存中的数据拷贝到cinfo.src结构体;内存的位置和大小会储存在结构体中的指针里,这样JPEG解压缩就可以直接从内存中读取JPEG图像数据进行解压缩操作了 - jpeg_error_mgr结构体:设置libjpeg解压缩时的错误处理程序
struct jpeg_decompress_struct cinfo;
jpeg_create_decompress(&cinfo); //创建解压缩对象
jpeg_mem_src(&cinfo, jpegData, size); //设置JPEG解压缩的源数据
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
- 读取JPEG文件头
使用libjpeg库提供的jpeg_read_header函数来读取JPEG文件头信息,该函数会解析JPEG文件头,读取其中的基本信息(包括图像的宽度、高度、压缩类型等),并将其存储在jpeg_decompress_struct结构体中
int ret = jpeg_read_header(&cinfo, TRUE);
if (ret != JPEG_HEADER_OK) {
fprintf(stderr, "Error: jpeg_read_header returned %d
", ret);
jpeg_destroy_decompress(&cinfo); //释放资源
fclose(infile);
return 1;
}
- 开始解压缩JPEG文件
- 图像的像素数据的每一行的末尾会添加一些额外的字节,以确保下一行的像素数据起始地址是按照特定的字节对齐方式进行的。这个额外的字节被称为“填充字节”
- 扫描行跨度指的是一行像素数据(不包括填充字节)在内存中所占用的字节数。它通常由每行像素数和每个像素所占用的字节数计算得出,即 扫描行跨度 = 每行像素数 × 每个像素所占用的字节数
- row_pointer是一个指向buffer中当前行数据的指针。cinfo.output_scanline是 jpeg_decompress_struct 结构体中的成员变量,用于记录已经解压的扫描行数量,在处理完每一行数据后,该变量的值会自动加 1。buffer 是指向存储整个图像数据的缓冲区的指针,row 变量则表示当前正在处理的行数。因为 每个像素由 R、G、B 三个分量组成,每个分量占用一个字节,所以一行数据占用 width * 3 个字节,所以row * width * 3 就能计算出当前行的位置,而row * width * 3仅仅是当前行数据在整张图片中的起始位置,因此要加上buffer
- jpeg_read_scanlines() 函数用来扫描行数据,存储到 row_pointer 指向的内存地址中,第三个参数 1 表示每次读取一行
//开始解压缩JPEG文件
jpeg_start_decompress(&cinfo);
//获取图像宽度和高度
int width = cinfo.output_width;
int height = cinfo.output_height;
//扫描行跨度:计算一行像素所需的字节数
int row_stride = width * cinfo.output_components;
//为解压后的图像分配内存
unsigned char* buffer = new unsigned char[width * height * 3];
//读取解压后的图像数据
unsigned char* row_pointer;
int row = 0;
while (cinfo.output_scanline < height) {
row_pointer = buffer + (row * width * 3);
jpeg_read_scanlines(&cinfo, &row_pointer, 1);
row++;
}
//结束解压缩JPEG文件
jpeg_finish_decompress(&cinfo);
- 结束解压缩
//结束解压缩JPEG文件
jpeg_finish_decompress(&cinfo);
//关闭输入文件
fclose(infile);
- 将RGB格式数据存储到txt文件中
创建一个输出流对象outfile_X,后续将向该文件中写入数据。static_cast (int)用于将字节类型转换为整型
ofstream outfile_R(output_filename_R);
if (!outfile_R.is_open()) {
printf("无法打开文件: %s
", output_filename_R);
return 1;
}
ofstream outfile_G(output_filename_G);
if (!outfile_G.is_open()) {
printf("无法打开文件: %s
", output_filename_G);
return 1;
}
ofstream outfile_B(output_filename_B);
if (!outfile_B.is_open()) {
printf("无法打开文件: %s
", output_filename_B);
return 1;
}
//逐个像素地写入数据
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
// 计算当前像素在缓冲区中的位置
int pos = (i * width + j) * 3;
// 将RGB三个分量的值写入txt文件中
outfile_R << static_cast<int>(buffer[pos]) << endl;
outfile_G << static_cast<int>(buffer[pos + 1]) << endl;
outfile_B << static_cast<int>(buffer[pos + 2]) << endl;
}
}
- 关闭释放
outfile_R.close();
outfile_G.close();
outfile_B.close();
//释放内存
delete[] buffer;
//清除JPEG解压缩对象
jpeg_destroy_decompress(&cinfo);
输出结果:
测试图片:
用RGB恢复原始图片
为了验证R、G、B的数据是否正确,需要用这些数据恢复出原始图片
- 先要读取三个文件中的十进制数据,得到三个整数列表:r_data、g_data和b_data
with open('C:/Users/10858/Desktop/output_R.txt') as f:
r_data = [int(x) for x in f.read().split()]
with open('C:/Users/10858/Desktop/output_G.txt') as f:
g_data = [int(x) for x in f.read().split()]
with open('C:/Users/10858/Desktop/output_B.txt') as f:
b_data = [int(x) for x in f.read().split()]
- 将数据填充到新建的空白图片中
# 创建空白的图片
img = Image.new('RGB', (width, height))
# 填充图片
for y in range(height):
for x in range(width):
r = r_data[y * width + x]
g = g_data[y * width + x]
b = b_data[y * width + x]
img.putpixel((x, y), (r, g, b))
恢复结果:
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。