图像读写并不是图像处理的核心,仅仅作为调试工具, 是一种手段而非目的。
图像文件格式的选择
正因如此,对gray和rgb图像的读写,存在多种方法。 最常见的三种图像文件格式:
- bmp
- png
- jpg
实际上有更简单的方式:pgm(存储gray图像),ppm(存储gray图像)。
选择 pgm 和 ppm 图像文件格式, 纯粹是因为编解码更为简单, 实现起来更容易。
获取 .ppm 和 .pgm 样例图像文件
https://graphics.stanford.edu/~jowens/223b/examples.html
提供了 lena.ppm, lena.pgm 等图像。
读写 .ppm 和 .pgm 图像文件
代码源自网络,稍加整理。放在 test.cpp 里。
/// @brief Save GRAY image to .pgm file
static void savePGM(const char* filename, int width, int height, uint8_t* image)
{
FILE* fout = fopen(filename, "wb");
fprintf(fout, "P5\n%d %d\n255\n", width, height);
fwrite(image, width * height, 1, fout);
fclose(fout);
}
/// @brief Load GRAY image from .pgm file
/// The caller should release the memory
static uint8_t* loadPGM(const char* filename, int* out_width, int* out_height)
{
FILE* fin = fopen(filename, "rb");
char magic[3];
int width, height;
int nscan = fscanf(fin, "%2s\n%d %d\n255\n", magic, &width, &height);
*out_width = width;
*out_height = height;
uint8_t* image = nullptr;
if (nscan == 3 && magic[0] == 'P' && magic[1] == '5')
{
image = (uint8_t*) malloc(width * height);
fread(image, width * height, 1, fin);
}
fclose(fin);
return image;
}
/// @brief Save RGB image to .ppm file
static void savePPM(const char* filename, int width, int height, unsigned char* data)
{
FILE* fp;
char header[20];
fp = fopen(filename, "wb");
// 写图片格式、宽高、最大像素值
fprintf(fp,"P6\n%d %d\n255\n", width, height);
// 写RGB数据
fwrite(data, width*height*3, 1, fp);
fclose(fp);
}
/// @brief Load RGB image from .ppm file
/// The caller should release the memory
static uint8_t* loadPPM(const char* filename, int* out_width, int* out_height)
{
char header[1024];
FILE* fp = NULL;
int line = 0;
fp = fopen(filename, "rb");
// 读取图片格式(例如:"P6")
// 高宽在第二行非注释数据
while(line < 2){
fgets(header, 1024, fp);
if(header[0] != '#'){
++line;
}
}
int width, height;
sscanf(header,"%d %d\n", &width, &height);
*out_width = width;
*out_height = height;
// 获取最大像素值
fgets(header, 20, fp);
size_t buf_size = height * width * 3;
uint8_t* data = (uint8_t*) malloc(buf_size);
// get rgb data
fread(data, buf_size, 1, fp);
fclose(fp);
return data;
}
投入使用
调试是一种手段。 上述 ppm/pgm 图像的读写, 是用于 naivecv 第一个图像处理函数:rgb 转 gray。
NCV_Status ncvConvertColorRGBtoGRAY(const NCV_Image* srcImg, NCV_Image* dstImg);
api 设计
enum {
NCV_STATUS_OK = 0,
NCV_STATUS_INVALID_PARAM = 1
};
typedef int NCV_Status;
typedef enum NCV_PixelFormat {
NCV_PIXFMT_GRAY,
NCV_PIXFMT_RGB,
} NCV_PixelFormat;
typedef struct NCV_Image {
NCV_PixelFormat format;
int width;
int height;
uint8_t* plane[4];
int pitch[4];
} NCV_Image;
具体实现
具体实现是细节, 这里忽略。
测试
这里贴出测试代码:
int main()
{
const char* filename = "/Users/zz/data/lena.ppm";
int width, height;
uint8_t* ppm_data = loadPPM(filename, &width, &height);
// 使用读取到的图像数据进行后续处理
NCV_Image rgbImg;
rgbImg.format = NCV_PIXFMT_RGB;
rgbImg.height = height;
rgbImg.width = width;
rgbImg.pitch[0] = width * 3;
rgbImg.plane[0] = ppm_data;
NCV_Image grayImg;
grayImg.format = NCV_PIXFMT_GRAY;
grayImg.height = height;
grayImg.width = width;
grayImg.pitch[0] = width;
grayImg.plane[0] = (uint8_t*) malloc(height * width);
ncvConvertColorRGBtoGRAY(&rgbImg, &grayImg);
savePGM("lena.pgm", width, height, grayImg.plane[0]);
savePPM("lena.ppm", width, height, rgbImg.plane[0]);
// free memory
free(ppm_data);
free(grayImg.plane[0]);
return 0;
}
查看 lena.pgm, 符合预期