c++对大量图片进行序列化和反序列化

220 天前
 h3xz
工作中开发的软件有这样一个需求:
程序用了某种算法对大量图片(约有 9 千多张,每张约 4M )进行了处理。我该怎么将这所有的图片以数组的形式序列化为一个文件夹,当从硬盘将这些图片读回内存时可以随机访问图片,以便用户第二次打开。
2163 次点击
所在节点    程序员
34 条回复
xytysingle
220 天前
用 base64 编码图片为字符串,比如一行一个编号,日期,图片的 base64 字符串
bertonzh
220 天前
.img 文件
knva
220 天前
还是实现个文件系统简单。
kokutou
220 天前
啥都无所谓的话。。。直接丢数据库?
feirisu
220 天前
大概懂了,就是游戏资源的打包方式呗,其设计目的就是不让用户修改,又能快速定位资源,格式也简单。

但是单文件过大建议考虑分包。

通常格式是:
固定自定义格式头,包含多少个文件[int]等
资源名长度[int]、资源名[char*]、资源尺寸[int]、资源内容[byte*]

读取时可以直接打开文件,用文件句柄偏移的方式可以快速构建索引以及针对对应的偏移取出数据。
GeruzoniAnsasu
220 天前
数据库中存索引和 meta info ,图片文件一律改名成 ID ,完毕。

IO 不够就把文件丢进分布式文件系统里从网络读
mioktiar56
220 天前
我怀疑你是微信上找我的那个人

内存映射即可解决
feirisu
220 天前
就依照我#25 写的,可以直接让 chatgpt 给你封装一套打包加拆包的类。
wxf666
219 天前
@h3xz 那就存 SQLite 数据库呗。。

优点:

- 单文件
- 可随机读写
- 依赖库只有几百 KB
- 有原生 C/C++ 接口
- 可存一系列数据(编号、日期、数据、图片文件本身、……),单个数据最大 2GB


写了 10GB 共 2500 条数据(每条包含 4MB 图片及数据),
再测试下,随机读取图片速度(测试前,已用 RamMap 清空系统文件缓存):

- 机械盘:27 条数据/秒( 150 MB/s 顺序读取,0.65 MB/s 随机 4K 读取)
- 内存盘:323 条数据/秒( 6754 MB/s 顺序读取,310 MB/s 随机 4K 读取,感觉明显没吃满 IO )
rpWQTyfsAjMCKgPA
219 天前
@h3xz 谷歌的 protobuf 。你这个需求和深度学习里准备训练数据十分类似:都是要把图片和一些 meta 信息(标注)序列化到硬盘上方便 retrieval 。
h3xz
219 天前
@feirisu 今天测试了一下,我存了 1000 张 4M/张 的图片到 SQLite 中,为什么显示 db 文件占了 11 个 G 呀
h3xz
219 天前
@wxf666 今天测试了一下,我存了 1000 张 4M/张 的图片到 SQLite 中,为什么显示 db 文件占了 11 个 G 呀
ma46
219 天前
@h3xz 你是不是直接存了图片的矩阵数据, 因为你看到的文件大小其实是图像压缩编码后的大小
wxf666
219 天前
@h3xz 不知道你是怎么存的。我写个示例,你按情况改了后,执行看看?


1. 准备 SQLite 环境

原因:
① 不想在这儿写 C/C++,太冗长。
② 后文的脚本,需要用到 dll 没有的,仅命令行版本才有的 readfile 函数。

步骤:
① 打开 https://sqlite.org/download.html
② 找到 sqlite-tools-win-x64-3450200.zip (当前版本)并下载(若 Linux/MacOS 找对应平台的)
③ 解压,得到 sqlite3.exe


2. 准备(存数据的) SQL 文件

参考以下内容,按你自己情况更改后,用 UTF-8 编码,保存为 test.sql 。

```sql
-- 把默认页大小 4KB 改为 64KB 。因为你大部分数据都很大,一次读取便加载更多数据,能提速
PRAGMA page_size = 65536;

CREATE TABLE image (
id INTEGER PRIMARY KEY,
-- 假设你通过名字来定位图片及其他数据。UNIQUE 既创建了索引,也保证名字唯一
name TEXT NOT NULL UNIQUE,
-- 注意,默认生成的时间,是 UTC 时间,比中国慢 8 个小时
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
extra_data JSON,
data BLOB NOT NULL
);

BEGIN;
-- 省略 id ,SQLite 则会自动填写为最大 ID + 1 。
-- 单引号内的 \ 不用转义。若要表示 ',双写即可。如 'Kai''Sa.jpg'。
-- 文件路径不要包含中文,因为 SQLite 会把 UTF8 字符串,原样调用系统 API ,然而 Windows 会以为是 GBK 编码。。
-- 如果文件路径一定包含中文,请保证其它字符串(如 name 列)没有中文,然后 SQL 文件转为 GBK 编码。
INSERT INTO image (name, data) VALUES ('aaa.jpg', readfile('C:\aaa.jpg'));
INSERT INTO image (name, data) VALUES ('bbb.jpg', readfile('C:\bbb.jpg'));
INSERT INTO image (name, extra_data, data) VALUES ('ccc.jpg', '{"width": 123, "height": 456}', readfile('C:\ccc.jpg'));
COMMIT;

-- 可选:碎片整理数据库文件,并去除冗余空间,达到瘦身紧实的效果。
-- VACUUM;

SELECT printf('写入了 %d 个文件,共 %d 字节。', COUNT(*), SUM(LENGTH(data))) FROM image;
```


3. 执行

```shell
sqlite3.exe images.db < test.sql
```

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1031297

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX