好的,话不多说,我们直接开始
图像处理在各个方面的应用都是十分广泛的,这里不再一一列举
但是图像处理的第一步永远是图像的读入
我们在本篇文章将会讲解读入的方式
如果你对图像处理很有兴趣但是这篇文章又没有从头去讲解,可以去阅读我之前的文章
图像处理与图像分析—图像的读入(C语言)_c语言读取各种图像的函数-CSDN博客
图像的规格
这里就绕不开一个关键词—图片位深度
什么是图片位深度呢?
位深度是指在记录数字图像的颜色时,计算机实际上是用每个像素需要的位深度来表示的。计算机之所以能够显示颜色,是采用了一种称作“位”( bit ) 的记数单位来记录所表示颜色的数据。当这些数据按照一定的编排方式被记录在计算机中,就构成了一个数字图像的计算机文件。**“位”( bit )是计算机存储器里的最小单元,它用来记录每一个像素颜色的值。图像的色彩越丰富,“位”就越多。**每一个像素在计算机中所使用的这种位数就是“位深度”。
术语很多,看起来并不是很好理解
我们接着往下看
黑白二色的图像是数字图像中最简单的一种,它只有黑、白两种颜色,也就是说它的每个像素只有1位颜色,位深度是1,用2的一次幂来表示;考虑到位深度平均分给R, G, B和Alpha,而只有RGB可以相互组合成颜色。所以4位颜色的图,它的位深度是4,只有2的4次幂种颜色,(即16种颜色或16种灰度等级 )。8位颜色的图,位深度就是8,用2的8次幂表示,它含有256种颜色 ( 或256种灰度等级 )。24位颜色可称之为真彩色,位深度是24,它能组合成2的24次幂种颜色,即:16777216种颜色 ( 或称千万种颜色 ),超过了人眼能够分辨的颜色数量。当我们用24位来记录颜色时,实际上是以2^(8×3),即红、绿、蓝 ( RGB ) 三基色各以2的8次幂,256种颜色而存在的,三色组合就形成一千六百万种颜色。
我们来总结一下,其实也很简单
位深度就是每一个像素块在计算机内存储的大小
只有黑白色
那么我们怎么储存?
很简单,每一块像素之中0表示黑,1表示白
在计算机中一个bit就可以储存一个像素点
也就是一位图像
八位灰度图像
这幅图就和上面的明显不同了,黑色有了深浅变化,那么从全黑到全白,我们一共分为256种不同深度的灰色,然后放在计算机中储存
那么每个像素块需要多大的储存空间呢?
没错,我们需要8个bit — 2的8次方刚好就等于256
这就是八位图像
二十四位图像
彩色的米老鼠我就不用啦,这次用海绵宝宝举例子
其实我们在日常生活中见到的最多的图像都是彩色的图像,这些图像是怎么存储的呢?
我们绕不开一个概念—混色效应
好的,在彩色图像的存储中,我们只需要每个像素存红绿蓝三个颜色的灰度值就行,那也就是说,每个像素要存三个八位灰度值,一共就是—24个bit
也就是24位的彩色图像
图像的读入
我们读取图像也会用8位图像和24位图像来读取
首先来看八位灰度图像
八位灰度图像的读入
先分析函数需求
- 传入文件地址
- 传入图像的长和宽
- 返回图像的储存空间
先来看上次写的代码
int main()
{
const char* filename = "E:/code/IDP-Learning-Journey/images/ME.bmp";
FILE* file = fopen(filename, "rb");
uint8_t bmpHeader[54];
fread(bmpHeader, 1, 54, file);
int width = *(int*)&bmpHeader[18]; // 宽度信息位于偏移量为 18 的位置
int height = *(int*)&bmpHeader[22]; // 高度信息位于偏移量为 22 的位置
uint8_t* imageData = (uint8_t*)malloc(width * height); // 每个像素点占用 3 个字节(BGR)
fseek(file, 54, SEEK_SET); // 跳过 BMP 文件头
fread(imageData, 1, width * height, file); // 每个像素点占用 3 个字节(BGR)
F1(imageData, width, height);
fclose(file);
free(imageData);
return 0;
}
很草率但是也能达到需求,其实有很多地方可以继续优化,但是只能读取24位的数据
我们来写成函数读取8位灰度图像试试
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
uint8_t* readGrayScaleBMP(const char* filename, int* width, int* height) {
FILE* file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Error opening file %s\n", filename);
return NULL;
}
// 读取BMP文件头部信息
uint8_t bmpHeader[54];
fread(bmpHeader, 1, 54, file);
// 从文件头部提取图像宽度和高度信息
*width = *(int*)&bmpHeader[18];
*height = *(int*)&bmpHeader[22];
// 分配存储图像数据的内存
uint8_t* imageData = (uint8_t*)malloc(*width * *height);
if (!imageData) {
fprintf(stderr, "内存分配失败\n");
fclose(file);
return NULL;
}
// 计算调色板的大小
int paletteSize = *(int*)&bmpHeader[46];
if (paletteSize == 0)
paletteSize = 256;
// 读取调色板数据
uint8_t palette[1024];
fread(palette, 1, paletteSize * 4, file);
// 读取图像数据
fseek(file, *(int*)&bmpHeader[10], SEEK_SET);
fread(imageData, 1, *width * *height, file);
fclose(file);
return imageData;
}
好的,这样一个函数应该就可以正常实现功能了
我们来试一下功能,当然得写一个写入的函数
八位图像写入
直接上代码
// 将8位灰度图像数据保存为BMP文件
void saveGrayScaleBMP(const char* filename, const uint8_t* imageData, int width, int height) {
FILE* file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Error creating file %s\n", filename);
return;
}
// BMP文件头部信息
uint8_t bmpHeader[54] = {
0x42, 0x4D, // 文件类型标识 "BM"
0x36, 0x00, 0x0C, 0x00, // 文件大小(以字节为单位,此处假设图像数据大小不超过4GB)
0x00, 0x00, // 保留字段
0x00, 0x00, // 保留字段
0x36, 0x00, 0x00, 0x00, // 位图数据偏移(以字节为单位)
0x28, 0x00, 0x00, 0x00, // 位图信息头大小(40字节)
0x00, 0x00, 0x00, 0x00, // 图像宽度
0x00, 0x00, 0x00, 0x00, // 图像高度
0x01, 0x00, // 目标设备的级别(此处为1,不压缩)
0x08, 0x00, // 每个像素的位数(8位)
0x00, 0x00, 0x00, 0x00, // 压缩类型(此处为不压缩)
0x00, 0x00, 0x00, 0x00, // 图像数据大小(以字节为单位,此处为0,表示不压缩)
0x00, 0x00, 0x00, 0x00, // 水平分辨率(像素/米,此处为0,表示未知)
0x00, 0x00, 0x00, 0x00, // 垂直分辨率(像素/米,此处为0,表示未知)
0x00, 0x00, 0x00, 0x00, // 使用的颜色索引数(0表示使用所有调色板项)
0x00, 0x00, 0x00, 0x00 // 重要的颜色索引数(0表示所有颜色都重要)
};
// 更新BMP文件头部信息中的宽度和高度
*(int*)&bmpHeader[18] = width;
*(int*)&bmpHeader[22] = height;
// 写入BMP文件头部信息
fwrite(bmpHeader, 1, 54, file);
// 写入调色板数据
for (int i = 0; i < 256; i++) {
fputc(i, file); // 蓝色分量
fputc(i, file); // 绿色分量
fputc(i, file); // 红色分量
fputc(0, file); // 保留字节
}
// 写入图像数据
fwrite(imageData, 1, width * height, file);
fclose(file);
}
那么带上之前的试一下效果
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
uint8_t* readGrayScaleBMP(const char* filename, int* width, int* height) {
FILE* file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Error opening file %s\n", filename);
return NULL;
}
// 读取BMP文件头部信息
uint8_t bmpHeader[54];
fread(bmpHeader, 1, 54, file);
// 从文件头部提取图像宽度和高度信息
*width = *(int*)&bmpHeader[18];
*height = *(int*)&bmpHeader[22];
// 分配存储图像数据的内存
uint8_t* imageData = (uint8_t*)malloc(*width * *height);
if (!imageData) {
fprintf(stderr, "内存分配失败\n");
fclose(file);
return NULL;
}
// 计算调色板的大小
int paletteSize = *(int*)&bmpHeader[46];
if (paletteSize == 0)
paletteSize = 256;
// 读取调色板数据
uint8_t palette[1024];
fread(palette, 1, paletteSize * 4, file);
// 读取图像数据
fseek(file, *(int*)&bmpHeader[10], SEEK_SET);
fread(imageData, 1, *width * *height, file);
fclose(file);
return imageData;
}
// 将8位灰度图像数据保存为BMP文件
void saveGrayScaleBMP(const char* filename, const uint8_t* imageData, int width, int height) {
FILE* file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Error creating file %s\n", filename);
return;
}
// BMP文件头部信息
uint8_t bmpHeader[54] = {
0x42, 0x4D, // 文件类型标识 "BM"
0x36, 0x00, 0x0C, 0x00, // 文件大小(以字节为单位,此处假设图像数据大小不超过4GB)
0x00, 0x00, // 保留字段
0x00, 0x00, // 保留字段
0x36, 0x00, 0x00, 0x00, // 位图数据偏移(以字节为单位)
0x28, 0x00, 0x00, 0x00, // 位图信息头大小(40字节)
0x00, 0x00, 0x00, 0x00, // 图像宽度
0x00, 0x00, 0x00, 0x00, // 图像高度
0x01, 0x00, // 目标设备的级别(此处为1,不压缩)
0x08, 0x00, // 每个像素的位数(8位)
0x00, 0x00, 0x00, 0x00, // 压缩类型(此处为不压缩)
0x00, 0x00, 0x00, 0x00, // 图像数据大小(以字节为单位,此处为0,表示不压缩)
0x00, 0x00, 0x00, 0x00, // 水平分辨率(像素/米,此处为0,表示未知)
0x00, 0x00, 0x00, 0x00, // 垂直分辨率(像素/米,此处为0,表示未知)
0x00, 0x00, 0x00, 0x00, // 使用的颜色索引数(0表示使用所有调色板项)
0x00, 0x00, 0x00, 0x00 // 重要的颜色索引数(0表示所有颜色都重要)
};
// 更新BMP文件头部信息中的宽度和高度
*(int*)&bmpHeader[18] = width;
*(int*)&bmpHeader[22] = height;
// 写入BMP文件头部信息
fwrite(bmpHeader, 1, 54, file);
// 写入调色板数据
for (int i = 0; i < 256; i++) {
fputc(i, file); // 蓝色分量
fputc(i, file); // 绿色分量
fputc(i, file); // 红色分量
fputc(0, file); // 保留字节
}
// 写入图像数据
fwrite(imageData, 1, width * height, file);
fclose(file);
}
int main() {
const char* grayScaleFilename = "gray_scale.bmp";
int grayScaleWidth, grayScaleHeight;
// 读取8位灰度图像
uint8_t* grayScaleImageData = readGrayScaleBMP(grayScaleFilename, &grayScaleWidth, &grayScaleHeight);
if (!grayScaleImageData) {
fprintf(stderr, "Failed to read gray scale BMP image\n");
return 1;
}
// 执行对灰度图像的操作
// 保存8位灰度图像数据为BMP文件
saveGrayScaleBMP("out.bmp", grayScaleImageData, grayScaleWidth, grayScaleHeight);
free(grayScaleImageData);
return 0;
}
好的,难得的成功,24位图像实际上是要比8位简单的
24位图像的读取
直接上代码
// 读取24位彩色图像的BMP文件
uint8_t* readColorBMP(const char* filename, int* width, int* height) {
FILE* file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Error opening file %s\n", filename);
return NULL;
}
// 读取BMP文件头部信息
uint8_t bmpHeader[54];
fread(bmpHeader, 1, 54, file);
// 从文件头部提取图像宽度和高度信息
*width = *(int*)&bmpHeader[18];
*height = *(int*)&bmpHeader[22];
// 分配存储图像数据的内存
uint8_t* imageData = (uint8_t*)malloc(*width * *height * 3);
if (!imageData) {
fprintf(stderr, "Memory allocation failed\n");
fclose(file);
return NULL;
}
// 读取图像数据
fseek(file, *(int*)&bmpHeader[10], SEEK_SET);
fread(imageData, 1, *width * *height * 3, file);
fclose(file);
return imageData;
}
这次连调色板都用不上,直接就可以读取
我们再来写一个写入函数看看效果
24位图像的写入
直接来写一个代码
void saveColorBMP(const char* filename, const uint8_t* imageData, int width, int height) {
FILE* file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Error creating file %s\n", filename);
return;
}
// BMP文件头部信息
uint8_t bmpHeader[54] = {
0x42, 0x4D, // 文件类型标识 "BM"
0x00, 0x00, 0x00, 0x00, // 文件大小(占位,稍后计算)
0x00, 0x00, // 保留字段
0x00, 0x00, // 保留字段
0x36, 0x00, 0x00, 0x00, // 位图数据偏移(以字节为单位)
0x28, 0x00, 0x00, 0x00, // 位图信息头大小(40字节)
0x00, 0x00, 0x00, 0x00, // 图像宽度
0x00, 0x00, 0x00, 0x00, // 图像高度
0x01, 0x00, // 目标设备的级别(此处为1,不压缩)
0x18, 0x00, // 每个像素的位数(24位)
0x00, 0x00, 0x00, 0x00, // 压缩类型(此处为不压缩)
0x00, 0x00, 0x00, 0x00, // 图像数据大小(占位,稍后计算)
0x00, 0x00, 0x00, 0x00, // 水平分辨率(像素/米,此处为0,表示未知)
0x00, 0x00, 0x00, 0x00, // 垂直分辨率(像素/米,此处为0,表示未知)
0x00, 0x00, 0x00, 0x00, // 使用的颜色索引数(0表示使用所有调色板项)
0x00, 0x00, 0x00, 0x00 // 重要的颜色索引数(0表示所有颜色都重要)
};
// 更新BMP文件头部信息中的宽度和高度
*(int*)&bmpHeader[18] = width;
*(int*)&bmpHeader[22] = height;
// 计算图像数据大小
uint32_t imageDataSize = width * height * 3 + 54; // 加上文件头部大小
bmpHeader[2] = (uint8_t)(imageDataSize & 0xFF);
bmpHeader[3] = (uint8_t)((imageDataSize >> 8) & 0xFF);
bmpHeader[4] = (uint8_t)((imageDataSize >> 16) & 0xFF);
bmpHeader[5] = (uint8_t)((imageDataSize >> 24) & 0xFF);
// 写入BMP文件头部信息
fwrite(bmpHeader, 1, 54, file);
// 写入图像数据
fwrite(imageData, width * height * 3, 1, file);
fclose(file);
}
我们来看看实现效果
再次修改尝试
经过测试,只有这张图片不行,目前还没有找到原因
总结
好的,那么我们这次介绍了图像的读取和写入
是图像处理的基础
出现的问题找到原因后会及时更新博客
感谢大家的阅读
源码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
uint8_t* readGrayScaleBMP(const char* filename, int* width, int* height) {
FILE* file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Error opening file %s\n", filename);
return NULL;
}
// 读取BMP文件头部信息
uint8_t bmpHeader[54];
fread(bmpHeader, 1, 54, file);
// 从文件头部提取图像宽度和高度信息
*width = *(int*)&bmpHeader[18];
*height = *(int*)&bmpHeader[22];
// 分配存储图像数据的内存
uint8_t* imageData = (uint8_t*)malloc(*width * *height);
if (!imageData) {
fprintf(stderr, "内存分配失败\n");
fclose(file);
return NULL;
}
// 计算调色板的大小
int paletteSize = *(int*)&bmpHeader[46];
if (paletteSize == 0)
paletteSize = 256;
// 读取调色板数据
uint8_t palette[1024];
fread(palette, 1, paletteSize * 4, file);
// 读取图像数据
fseek(file, *(int*)&bmpHeader[10], SEEK_SET);
fread(imageData, 1, *width * *height, file);
fclose(file);
return imageData;
}
// 将8位灰度图像数据保存为BMP文件
void saveGrayScaleBMP(const char* filename, const uint8_t* imageData, int width, int height) {
FILE* file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Error creating file %s\n", filename);
return;
}
// BMP文件头部信息
uint8_t bmpHeader[54] = {
0x42, 0x4D, // 文件类型标识 "BM"
0x36, 0x00, 0x0C, 0x00, // 文件大小(以字节为单位,此处假设图像数据大小不超过4GB)
0x00, 0x00, // 保留字段
0x00, 0x00, // 保留字段
0x36, 0x00, 0x00, 0x00, // 位图数据偏移(以字节为单位)
0x28, 0x00, 0x00, 0x00, // 位图信息头大小(40字节)
0x00, 0x00, 0x00, 0x00, // 图像宽度
0x00, 0x00, 0x00, 0x00, // 图像高度
0x01, 0x00, // 目标设备的级别(此处为1,不压缩)
0x08, 0x00, // 每个像素的位数(8位)
0x00, 0x00, 0x00, 0x00, // 压缩类型(此处为不压缩)
0x00, 0x00, 0x00, 0x00, // 图像数据大小(以字节为单位,此处为0,表示不压缩)
0x00, 0x00, 0x00, 0x00, // 水平分辨率(像素/米,此处为0,表示未知)
0x00, 0x00, 0x00, 0x00, // 垂直分辨率(像素/米,此处为0,表示未知)
0x00, 0x00, 0x00, 0x00, // 使用的颜色索引数(0表示使用所有调色板项)
0x00, 0x00, 0x00, 0x00 // 重要的颜色索引数(0表示所有颜色都重要)
};
// 更新BMP文件头部信息中的宽度和高度
*(int*)&bmpHeader[18] = width;
*(int*)&bmpHeader[22] = height;
// 写入BMP文件头部信息
fwrite(bmpHeader, 1, 54, file);
// 写入调色板数据
for (int i = 0; i < 256; i++) {
fputc(i, file); // 蓝色分量
fputc(i, file); // 绿色分量
fputc(i, file); // 红色分量
fputc(0, file); // 保留字节
}
// 写入图像数据
fwrite(imageData, 1, width * height, file);
fclose(file);
}
// 读取24位彩色图像的BMP文件
uint8_t* readColorBMP(const char* filename, int* width, int* height) {
FILE* file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "Error opening file %s\n", filename);
return NULL;
}
// 读取BMP文件头部信息
uint8_t bmpHeader[54];
fread(bmpHeader, 1, 54, file);
// 从文件头部提取图像宽度和高度信息
*width = *(int*)&bmpHeader[18];
*height = *(int*)&bmpHeader[22];
// 分配存储图像数据的内存
uint8_t* imageData = (uint8_t*)malloc(*width * *height * 3);
if (!imageData) {
fprintf(stderr, "Memory allocation failed\n");
fclose(file);
return NULL;
}
// 读取图像数据
fseek(file, *(int*)&bmpHeader[10], SEEK_SET);
fread(imageData, 1, *width * *height * 3, file);
fclose(file);
return imageData;
}
void saveColorBMP(const char* filename, const uint8_t* imageData, int width, int height) {
FILE* file = fopen(filename, "wb");
if (!file) {
fprintf(stderr, "Error creating file %s\n", filename);
return;
}
// BMP文件头部信息
uint8_t bmpHeader[54] = {
0x42, 0x4D, // 文件类型标识 "BM"
0x00, 0x00, 0x00, 0x00, // 文件大小(占位,稍后计算)
0x00, 0x00, // 保留字段
0x00, 0x00, // 保留字段
0x36, 0x00, 0x00, 0x00, // 位图数据偏移(以字节为单位)
0x28, 0x00, 0x00, 0x00, // 位图信息头大小(40字节)
0x00, 0x00, 0x00, 0x00, // 图像宽度
0x00, 0x00, 0x00, 0x00, // 图像高度
0x01, 0x00, // 目标设备的级别(此处为1,不压缩)
0x18, 0x00, // 每个像素的位数(24位)
0x00, 0x00, 0x00, 0x00, // 压缩类型(此处为不压缩)
0x00, 0x00, 0x00, 0x00, // 图像数据大小(占位,稍后计算)
0x00, 0x00, 0x00, 0x00, // 水平分辨率(像素/米,此处为0,表示未知)
0x00, 0x00, 0x00, 0x00, // 垂直分辨率(像素/米,此处为0,表示未知)
0x00, 0x00, 0x00, 0x00, // 使用的颜色索引数(0表示使用所有调色板项)
0x00, 0x00, 0x00, 0x00 // 重要的颜色索引数(0表示所有颜色都重要)
};
// 更新BMP文件头部信息中的宽度和高度
*(int*)&bmpHeader[18] = width;
*(int*)&bmpHeader[22] = height;
// 计算图像数据大小
uint32_t imageDataSize = width * height * 3 + 54; // 加上文件头部大小
bmpHeader[2] = (uint8_t)(imageDataSize & 0xFF);
bmpHeader[3] = (uint8_t)((imageDataSize >> 8) & 0xFF);
bmpHeader[4] = (uint8_t)((imageDataSize >> 16) & 0xFF);
bmpHeader[5] = (uint8_t)((imageDataSize >> 24) & 0xFF);
// 写入BMP文件头部信息
fwrite(bmpHeader, 1, 54, file);
// 写入图像数据
fwrite(imageData, width * height * 3, 1, file);
fclose(file);
}
int main() {
const char* grayScaleFilename = "gray_scale.bmp";
const char* colorFilename = "FXT12111.bmp";
int grayScaleWidth, grayScaleHeight;
int width, height;
// 读取8位灰度图像
uint8_t* grayScaleImageData = readGrayScaleBMP(grayScaleFilename, &grayScaleWidth, &grayScaleHeight);
if (!grayScaleImageData) {
fprintf(stderr, "Failed to read gray scale BMP image\n");
return 1;
}
// 读取24位彩色图像
uint8_t* colorImageData = readColorBMP(colorFilename, &width, &height);
if (!colorImageData) {
fprintf(stderr, "Failed to read color BMP image\n");
return 1;
}
// 执行对灰度图像的操作
// 保存8位灰度图像数据为BMP文件
saveGrayScaleBMP("out.bmp", grayScaleImageData, grayScaleWidth, grayScaleHeight);
// 保存24位灰度图像数据为BMP文件
saveColorBMP("outcolor.bmp", colorImageData, width, height);
free(grayScaleImageData);
free(colorImageData);
return 0;
}