scratch lenet(1): 读写 pgm 图像文件

scratch lenet(1): 读写 pgm 图像文件

1. 目的

最近在 github 上关注了 LLM 的流行库 llama.cpp 和 whisper.cpp 的作者 Georgi Gerganov, 简称 gg 哥。 通过 gg哥关注到了 deepdream 算法作者开源的 deepdream_c 代码。完全用 C89 写成的 deepdream, 编译只需要1秒钟, Windows 和 Linux 都能运行。完全不依赖 PyTorch 和 OpenCV 等框架, 连 C++ 都没使用,非常克制, 可移植性非常高。这个风格和 gg 哥的风格有点像的: 喜欢单一的 .vimrc 文件, 写 ggml 主要放在一个16000行的文件中。打算按照这种风格, 用 C99 标准,不借助外部库, 实现 lenet。

实现过程中难免遇到不熟悉的内容, 因为这是一种“全都自己造”的风格; 没关系, 我会尝试逐一弄懂, 以博客形式分享出来。

这是这系列的第一篇, 分享的是 pgm 图像的读写。用 pgm 格式的原因是, 项目规模非常小的时候, BMP 的编解码都显得过于复杂, 目前只需要灰度图的前提下, pgm 足够使用, 而 Linux KDE 下的默认图像查看器, 完全可以查看 pgm 格式。

2. pgm 格式介绍

2.1 概要

pgm 里的 g 表示 gray, 是灰度图, 数据范围通常是 0 到 255。

pgm 文件,以二进制格式进行存储, 不过它的内容其实是 文本 + 二进制混合的:meta 信息是文本, 图像像素内容是二进制。

2.2 meta 信息

第一行是 P5 两个字符。
第二行是空格分隔的两个整数, 分别表示图像宽度,高度。
第三行是一个整数, 表示像素最大取值, 通常是255。

这些信息都是文本格式写入, 也就是用 fprintf 写入, fscanf 读取。也可以用记事本查看 .pgm 文件。

2.3 像素内容

用二进制形式存储, row-major。
也就是说, 用 fread 读取, 用 fwrite 写入。

3. 创建 .pgm 文件

我的开发环境是 Ubuntu 22.04, KDE 桌面, 也可以叫做 “KUbuntu”. 不过我认为 KUbuntu 的叫法很奇怪,Ubuntu 并不限制你用什么桌面, 安装了 KDE 之后也可以安装 Cinamon, XFCE 等桌面, 如果安装的不是 Ubuntu 而是 OpenSude, Manjaro 等 Linux 发行版, 也可以安装 KDE。

我使用 KDE 里的 KolourPaint 这个绘图软件,制作一张手写数字“3”的图像:

在这里插入图片描述

保存图像, 选择 .pgm 格式:
在这里插入图片描述

4. 使用C语言读取 .pgm 灰度图文件

4.1 实现

简单起见, 我们直接读取刚刚用 KolourPaint 生成的 3.pgm 文件。你也可以直接下载:
3.pgm:一张绘图图,格式为pgm

uchar g_image[784];
void read_pgm_image()
{
    FILE* fin = fopen("3.pgm", "rb");
    char magic[3];
    int width, height;
    int nscan = fscanf(fin, "%2s\n%d %d\n255\n", magic, &width, &height);

    if (nscan == 3 && magic[0] == 'P' && magic[1] == '5')
    {
        fread(g_image, 784, 1, fin);
    }
    fclose(fin);
}

如果打算读取其他 .pgm 文件, 可以自行重构, 其中 784 等于 28 * 28, 是图像大小。

4.2 解释

FILE* fin = fopen("3.pgm", "rb"); 是以二进制格式打开文件 3.pgm.

读取第一行的 P5 两个字符时, 使用了

char magic[3];

而不是

char magic[2];

原因是避免内存越界, 具体分析见 Cracking C++(13): 读取不超过n个字符

int nscan = fscanf(fin, "%2s\n%d %d\n255\n", magic, &width, &height); 是读取 meta 信息,是以文本方式读取。

fread(g_image, 784, 1, fin); 是读取像素内容, 是按二进制格式读取。

5. 使用C语言保存 .pgm 灰度图文件

和读取过程是配套的,代码如下

void write_pgm_image(uchar* image, int width, int height, const char* filename)
{
    FILE* fout = fopen(filename, "wb");
    fprintf(fout, "P5\n%d %d\n255\n", width, height);
    fwrite(image, width * height, 1, fout);
    fclose(fout);
}
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值