图像读写并不是图像处理的核心,仅仅作为调试工具, 是一种手段而非目的。
图像文件格式的选择
正因如此,对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 datafread(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 memoryfree(ppm_data);free(grayImg.plane[0]);return 0;
}
查看 lena.pgm, 符合预期