资讯动态

Caffe图片转LMDB:一键脚本生成与避坑指南

发布时间:2026/10/13 14:33:18 来源:尧图企业网站定制
简介新版Caffe图片转LMDB直接运行脚本重点优化了对多分类大于10类数据的转换支持面向需要快速制作Caffe训练数据的深度学习初学者与算法工程师可作为此前旧版资源的直接替代。资源压缩包共13个文件、40KB内部以5个sh脚本为核心分别负责生成训练与测试图片列表、创建LMDB数据集、计算均值文件等关键环节同时附有3个txt输出、2个运行日志便于核对执行结果以及说明文档和均值计算、样本转换等辅助工具目录中还包含训练与测试LMDB结果目录结构清晰。目前已有354人学习下载。使用者可拿到一套开箱即用的数据预处理流程从图片列表生成、数据格式转换到均值计算无需额外修改即可完成训练前准备配合输出列表与运行日志还能快速定位转换过程中的问题特别适合正在搭建卷积神经网络图像分类任务的读者参考。1. 把图片变成Caffe的LMDB为什么卡住你的不是模型而是数据Caffe的官方训练流程里模型结构、预训练权重都好找唯独“自己的图片”这一步没人替你写一堆jpg分散在几十个类别文件夹里官方工具convert_imageset只能吃列表文件列表文件又要你手写。很多新手在这里反复试错——手写txt漏行、拼接路径出错、标签从1还是从0开始搞不清。这个直接运行版脚本解决的就是这个卡点自动扫描目录、生成train.txt/test.txt、调用convert_imageset一次跑出两个LMDB。适合两类人第一次用Caffe做图像分类的入门者以及想稳定复现训练流程、不想每次手动拼路径的从业者。读完你可以直接拿自己的数据目录跑通也知道每个参数改哪个。2. 理解LMDB与txt的关系为什么convert_imageset不直接读文件夹2.1 为什么Caffe偏要用LMDB而不是直接读jpgCaffe的数据层可以配ImageData直接按txt路径去读图片、解码、做预处理。听起来简单实际训练时你会发现CPU占用不高但GPU一直在等数据因为每轮迭代都要现读现解文件IO和JPEG解码开销完全暴露在训练流水线上。LMDB本质是一个内存映射的键值数据库把图片的原始字节作为value把样本ID作为key训练时按key随机取value省去了文件系统寻址和重复解码的开销尤其在小文件数量几万几十万时优势明显。这也是convert_imageset存在的意义它不负责扫描文件夹只按txt逐行把图片塞进DB。新版Caffe虽然还保留ImageData层但官方示例和大多数实战配置都默认走LMDB。理解这层关系你就知道为什么必须先生成train.txt/test.txt——这份txt是Caffe数据准备流程的“索引”convert_imageset本身没有任何目录扫描和类别识别能力它只认你给它的行。我见过有人因为data层一直报错怀疑是模型写错了最后发现是txt里最后一行没有换行符导致最后一张图没被读进去。这类问题在LMDB方案里出现概率更低但txt本身的格式仍然决定一切。在继续之前先明确一个关键点convert_imageset不只是打包原始图片它还会按你传入的resize参数把图片统一缩放后再写入DB。也就是说你最终训练时从LMDB取出来的数据已经是统一边长、统一通道数的数据不需要数据层再实时resize。这解释了为什么Caffe的prototxt里只要写crop、mirror而不需要写resize——resize早在转换阶段完成了。新接触Caffe的人常把resize写进数据层和转换参数重复虽然不一定报错但会无谓增加预处理时间。还有一点值得单独说LMDB目录里的data.mdb会随样本量增大而变大但它是按页分配的如果你的图片尺寸差别很大实际占用空间可能比原始样本总和还大因为每张图都带一份原始字节。转换前统一resize也是在帮你控制DB体积256x256的jpg平均几十KB到一两百KB比原图动辄几MB节省太多。2.2 train.txt/test.txt的标准格式与标签起点一个标准的列表文件长这样class_01/0231.jpg 0 class_01/0342.jpg 0 class_02/0110.jpg 1 class_02/0899.jpg 1左边是相对于ROOTFOLDER的图片路径右边是类别标签的整数。这里有两个硬性约定第一路径里不要出现空格否则convert_imageset按空格拆分时会把你路径切成好几段第二标签必须是从0开始连续递增的整数而不是你肉眼看到的“第1类、第2类”。Caffe的SoftmaxWithLoss层内部按标签做one-hot下标从0开始如果类别从1编号第一类样本会把梯度算到第二个输出通道上训练时loss能降但acc永远不对位。还要注意编码问题。txt文件请用UTF-8无BOM保存如果带BOMBOM会附着在第一个文件路径前面导致第一张图打不开。这个隐藏问题在Windows上最常见用Python的open(xxx, w)默认不带BOM但如果你手动用记事本另存为容易带上。我一般不在txt里写任何中文注释类别信息都在目录名里txt只需要路径和标签。如果只是临时验证数据流程也可以用shell一行生成txt但不推荐用作正式脚本find /path/to/data -name *.jpg | awk -F/ {print $0, c} train.txt这种写法有两个隐患标签顺序完全依赖find的遍历顺序换到不同文件系统就变另外它没法做train/test比例切分只能把所有图片放进同一个txt。所以我后面的“直接运行版”会用一段可控的Python脚本把标签映射写死按类名字母序生成稳定得多。下面这张表能帮你快速判断该选哪种数据喂法配置方式读取速度生成数据耗时是否适合大训练集入门坑ImageData直接读图慢受文件IO影响无需生成适合小数据或调试路径写错会静默跳过LMDB预打包快随机访问稳定需要一次转换生产训练主力转换脚本参数多还要注意txt里的路径是“相对路径”不是绝对路径。convert_imageset有三个入参ROOTFOLDER、LISTFILE、DB_NAME。它内部会把ROOTFOLDER和txt每一行的前半部分拼接起来找图。如果ROOTFOLDER传错路径拼接出来不存在那图片会被跳过但脚本不一定明确报错只会在终端刷Could not open or find file。很多人看到这个刷屏直接关掉窗口没数过具体跳过了多少张这是后面训练样本不足的根源。所以第5章会把这类静默问题单独列出来讲。2.3 convert_imageset的常用参数与新版变化convert_imageset本身是Caffe自带的一个命令行工具位置在编译输出目录的tools下。新版Caffe用CMake构建后可执行文件在build/tools/convert_imageset老式Makefile构建也会生成类似路径。你可以在终端里不带任何参数运行它看看当前这个版本的参数提示。它有三个必填位置参数顺序是ROOTFOLDER、LISTFILE、DB_NAME。可选参数最常见的是这几个参数名作用典型值踩坑点--resize_width缩放后的目标宽度256不设则按原图大小编码--resize_height缩放后的目标高度256和高宽比不一致会拉伸--shuffle写入前打乱样本顺序无值不加会导致同类连续出现--backend数据库类型lmdb老脚本常写成leveldb--encoded是否存储压缩编码后的图片无值默认存原字节打开后DB更小--gray是否转灰度图无值分类任务一般不设新版Caffe对参数风格的统一比较严格统一使用--resize_width256这种长横线加等号写法老版教程里常见的-resize_width 256在某些构建版本上不再识别。如果你看到unrecognized option先别怀疑Caffe坏了把参数改成新版写法再试。还有一点要注意--backend的值只有--backendlmdb或--backendleveldb两者之间选新版默认lmdb但你写出来更明确也方便以后排查。3. 直接运行版脚本一键生成train.txt、test.txt和两个LMDB3.1 脚本主体代码直接运行版的意思是你只需要传入数据根目录脚本自动完成扫描类别、写txt、切分train/test、调用convert_imageset生成两个LMDB。我一般把脚本命名为make_lmdb.py放在Caffe工程目录外也可以只要convert_imageset在PATH里。先贴完整代码#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import random import shutil import subprocess import sys VALID_EXTS (.jpg, .jpeg, .png, .bmp) def list_samples(data_root): 扫描 data_root 下的类别目录返回 [(相对路径, 标签)] samples [] classes sorted([d for d in os.listdir(data_root) if os.path.isdir(os.path.join(data_root, d))]) for label, clazz in enumerate(classes): class_dir os.path.join(data_root, clazz) for fname in os.listdir(class_dir): if fname.lower().endswith(VALID_EXTS): rel_path os.path.join(clazz, fname) samples.append((rel_path, label)) return samples def write_txt(samples, txt_path): 按 convert_imageset 要求的格式写列表文件 with open(txt_path, w) as f: for rel_path, label in samples: f.write({0} {1}\n.format(rel_path, label)) def convert_to_lmdb(data_root, txt_path, db_path, resize256, shuffleTrue): 调用 Caffe 的 convert_imageset 生成一个 LMDB if os.path.exists(db_path): shutil.rmtree(db_path) # 防止旧 LMDB 锁死 cmd [ convert_imageset, --resize_width{}.format(resize), --resize_height{}.format(resize), --backendlmdb, ] if shuffle: cmd.append(--shuffle) cmd [data_root, txt_path, db_path] print([CMD], .join(cmd)) subprocess.check_call(cmd) def main(): data_root sys.argv[1] if len(sys.argv) 1 else ./data train_ratio float(sys.argv[2]) if len(sys.argv) 2 else 0.8 resize int(sys.argv[3]) if len(sys.argv) 3 else 256 random.seed(42) # 固定随机种子结果可复现 samples list_samples(data_root) random.shuffle(samples) # 打乱后切分避免某个类全部进 test cut int(len(samples) * train_ratio) train_samples samples[:cut] test_samples samples[cut:] write_txt(train_samples, train.txt) write_txt(test_samples, test.txt) convert_to_lmdb(data_root, train.txt, train_lmdb, resize, shuffleTrue) convert_to_lmdb(data_root, test.txt, test_lmdb, resize, shuffleTrue) print(train: {}, test: {}.format(len(train_samples), len(test_samples))) if __name__ __main__: main()这段脚本的逻辑分成四步先按类名字母序扫出全量样本标签从0开始编号再做shuffle和数据切分然后写入train.txt/test.txt最后调用convert_imageset生成DB。关键设计是把标签映射和路径扫描放在Python里而不是依赖find遍历顺序这样同一份数据无论在哪台机器上跑生成的txt和标签顺序都是一致的。输出里的[CMD]行是你实际执行的convert_imageset完整命令方便复制下来单独跑也方便排错。convert_to_lmdb里有一行容易被忽略生成前检查目标db_path是否存在存在就shutil.rmtree。原因很现实convert_imageset不会像覆盖普通文件那样直接重建LMDB如果上次运行中断或DB已经存在再次执行会卡在创建新事务上表现就是进程僵住或报找不到环境。先删再建是最省心的做法代价只是多花几十秒删除时间换来的是一次干净可复现的执行。脚本的main部分接受三个命令行参数第一个是数据根目录第二个是训练集比例第三个是resize边长。如果数据量小不希望每一次都重新划分train/test可以把划分结果落在固定txt上脚本加个--no-shuffle开关用于重新生成DB时保持原有txt不变。我没有把它写进主流程是因为实际训练调参时大家最怕的就是train/test集合在多次尝试中间偷偷变化导致指标不可比。固定随机种子至少保证每次划分一致。3.2 参数说明resize、shuffle、backend到底改哪个先说resize。resize256是常见默认值因为很多预训练模型输入是224x224或227x227转换时先放到256给数据层的随机裁剪留一点余量。如果你明确知道自己模型的crop_size就是224也可以直接设resize224DB会小一点但会失去随机裁剪带来的空间扰动。千万不要设成原始图片尺寸除非你的数据已经全部是统一大小否则DB巨大且训练时每次都要读取大图再裁剪浪费内存带宽。通俗说LMDB里存的图越大训练时每个batch从内存到显存的拷贝量越大一个看似不起眼的参数能把训练速度拖慢一半。再说shuffle。convert_imageset的--shuffle是对DB内部样本顺序做打乱这个必须加。不加的话LMDB里的样本按txt原始顺序排列训练时每个batch内部几乎都是同一类梯度会在方向上震荡最常见表现是loss像锯齿一样高低跳几个epoch都不降。我在脚本里同时做了Python层面的shuffle和convert层面的shuffle双保险。有些实现只做前者认为txt顺序已经打乱DB自然就是乱的实际上Caffe读取DB时是顺序游标txt顺序直接决定DB顺序所以两边的shuffle都有意义。再说backend。这里固定用lmdb主要是兼容性和生态原因。leveldb在并发访问和跨平台文件拷贝上问题更多新版Caffe训练时的data_param配置也默认写backend: LMDB。如果你的脚本是从某份老代码里抄来的把--backendleveldb改成--backendlmdb同时prototxt里也要对应改否则数据源类型不匹配会直接报Unknown database backend。最后是路径。convert_imageset是否在PATH里决定了你是直接写命令名还是写全路径。新版Caffe编译后常见做法是运行前执行export PATH$HOME/caffe/build/tools:$PATH如果你用CMake构建路径同样是build/tools。确认方式是用which convert_imageset。找不到时有人会被各种“环境变量”“LD_LIBRARY_PATH”的报错绕晕其实问题就是PATH没加进去。3.3 生成的LMDB怎么接到Caffe数据层脚本生成的train_lmdb和test_lmdb要能被Caffe的Data层识别。prototxt里的数据层通常长这样layers { name: data type: Data top: data top: label transform_param { crop_size: 227 mean_value: 104 mean_value: 117 mean_value: 123 mirror: true } data_param { source: train_lmdb batch_size: 64 backend: LMDB } }source指向LMDB目录名backend必须是LMDBtransform_param里不需要写resize因为resize已经在转换阶段做完了。这里比较常见的一个坑是source路径如果prototxt在models目录下而LMDB在项目根目录路径要写相对路径或绝对路径写错会报Data layer initialization failed。建议把所有LMDB和prototxt放在同一个工程目录下省去一堆相对路径问题。4. 在真实数据上跑通脚本目录组织、命令参数与结果验证4.1 标准目录组织与一条命令跑通脚本要求的数据目录组织非常传统就是“根目录/类别名/图片”。例如data/ class_a/ 0001.jpg 0002.jpg class_b/ 0501.png class_c/ 1001.jpg类别名可以随意但不要包含空格也尽量不要用纯数字命名否则后面分析标签时容易混淆。假设你已经把数据整理成这样执行export PATH$HOME/caffe/build/tools:$PATH python3 make_lmdb.py ./data 0.8 256正常情况下会看到类似下面的输出[CMD] convert_imageset --resize_width256 --resize_height256 --backendlmdb --shuffle ./data train.txt train_lmdb [CMD] convert_imageset --resize_width256 --resize_height256 --backendlmdb --shuffle ./data test.txt test_lmdb train: 2400, test: 600这里的类别数和样本数是根据目录自动统计的不用在脚本里写死。如果输出和你预期的数量对不上先别急检查脚本目录里是不是有隐藏文件或者某些子目录是空的。转换结束后目录下应该有这些文件文件/目录作用train.txt训练列表一行一图片test.txt测试列表train_lmdb/训练LMDB目录test_lmdb/测试LMDB目录LMDB是一个目录不是单个文件。目录里的data.mdb和lock.mdb是核心文件lock.mdb在LMDB打开时会生成训练结束后可能还在这正常。如果你要把训练数据迁移到别的机器直接拷整个train_lmdb目录即可不用管txt。4.2 不急着训练先验证文本和DB是否对得上很多新手跑完生成脚本就急着去改prototxt结果训练报错又回头查数据。我会先花20秒做验证。第一步看txt行数是否等于图片数wc -l train.txt test.txt第二步看前几行和末尾几行确认路径格式和标签范围head -n 5 train.txt tail -n 5 train.txt如果某一行出现以/data/开头的绝对路径说明脚本拼接时出错了ROOTFOLDER应该只包含前缀txt里只需要相对路径。第三步用Python读一次LMDB统计key数量确认与txt行数一致import lmdb env lmdb.open(train_lmdb, readonlyTrue) with env.begin() as txn: cursor txn.cursor() count sum(1 for _ in cursor) print(train_lmdb count:, count)这里需要安装python-lmdb包。如果LMDB数量比txt少说明有图片在转换时被OpenCV跳过原因是图片格式损坏或文件后缀与真实编码不一致。常见的是png文件内容实际是jpg后缀被改成了.png。convert_imageset遇到解码失败会跳过样本但不会中止整个转换所以总数对不上往往不容易察觉这个验证步骤非常值得养成习惯。还可以顺便看一眼key的格式。Caffe写入LMDB时key是类似00000000_0001.jpg的字符串前面是样本序号后面是原文件名。如果你在检查时发现序号不连续比如0、1、2、4缺少3那说明编号为3的图片在解码时失败了和上面总数对不上是同一个问题。4.3 最容易忽略的类别分布检查验证LMDB数量还不够还要验证每个类别是否都进入了train和test。如果某个类别只有几张图而随机切分时全部落进了traintest就完全没有这一类测试acc会虚高等部署到真实场景遇到这个类就直接崩。最直接的检查回到txt层面因为txt里的第二列就是标签而且标签和LMDB里的datum是对应的。用一行awk就能完成awk {print $2} train.txt | sort | uniq -c运行后你会看到每个标签下的样本数。如果发现某个类缺失重新生成txt时可以先按类别分层抽样而不是整体shuffle。分层抽样的做法是遍历每个类各自随机分train/test再合并写入两个txt。这种做法在类别极不均衡时更稳如果你手里的数据每个类都几百张以上整体shuffle影响不大。5. 避坑记录LMDB生成过程中最常见的5个翻车现场与修复方法以下五条是实际调试中遇到频率最高的坑每一条都值得在跑脚本前通读。前三条会直接影响训练曲线后两条会影响脚本的可重复性很多问题表面上看是训练时的事故根源其实都在数据转换这段。5.1 为什么txt里有600行LMDB里只有598条现象awk统计txt行数是600用Python遍历LMDB只有598个key但convert_imageset全程没有任何报错。原因LMDB转换过程中OpenCV读图失败时默认跳过该样本而不是抛出异常。被跳过的图片大多是文件后缀被改过比如把网络下载的webp直接改成.jpg或者小图被截断损坏。解决写完train.txt后先做一次可读性预检。常见做法是在Python脚本里用OpenCV逐个读一遍把读失败的图片路径单独输出import cv2, os with open(train.txt) as f: for line in f: path, _ line.strip().split() if cv2.imread(path) is None: print(bad image:, path)把bad image对应的文件复制出来用工具重新转成标准jpg或删除。如果图片本身是灰度图不需要特殊处理Caffe会按3通道读入。如果图片是调色板PNG也没问题但如果是CMYK的jpg颜色会错乱最好统一转成RGB。这个预检可以放在生成脚本里也可以单独跑看你的数据源脏到什么程度。5.2 标签从1开始还是从0开始为什么网络不报错但acc永远不对现象训练时loss下降正常但val accuracy始终停留在50%左右像是网络没学会任何类别。原因很多人把类别编号直接写成1、2、3而Caffe的SoftmaxWithLoss层从0开始索引。第一类样本的标签是1网络却把它的梯度算在第1个输出通道上而第0通道没有任何正样本整个类的对应关系全部错位。解决脚本里用enumerate(classes)生成标签从0开始连续递增。如果你的txt已经生成且标签从1开始最快修复方法是重新跑一遍生成脚本。如果还想保留原来txt可以用一行awk整体减1awk {printf %s %d\n, $1, $2-1} train.txt train_fixed.txt注意检查原始标签里有没有0如果有0那这套数据就混合了两种规范不能简单减1。另外如果你的模型输出类别数是N标签恰好用了1到NSoftmaxWithLoss虽然不报错但最后一类样本会落到索引N上超出输出通道运行时会直接越界。这类问题最隐蔽的地方在于它不影响loss曲线数值波动只会让最终精度虚假或错乱。5.3 没加shuffle为什么loss像锯齿一样忽高忽低现象前几个batch loss一路走低然后突然跳到很高再走低整个训练过程像锯齿模型迟迟不收敛。原因如果图片按类别顺序写进txtLMDB里的样本也按类别排列。batch size为64时一个batch里全是同一个类网络在这个batch上更新完下个batch又换成另一个类梯度方向来回变loss自然震荡。这不是学习率的问题你就算把学习率调得再小也只能缓解不能根治。解决在convert_imageset的调用参数里加上--shuffle让DB内部样本顺序被打乱。脚本里convert_to_lmdb已经默认加了这个参数如果你是手动执行的务必检查命令行。加完shuffle后即使txt顺序没改DB顺序也会被打乱。经验是加完shuffle后如果loss曲线还是锯齿再考虑改学习率和batch_size。5.4 路径里有空格转换出的样本数量正常但标签错位现象某些样本的路径包含空格转换完成后LMDB数量对得上但训练时loss不下降检查key发现value顺序错乱标签跟图片对不上。原因convert_imageset解析txt时按空格拆分字段路径my dog/001.jpg 3会被拆成my和dog/001.jpg 3两部分后续拼接路径自然失败或者更隐蔽的是路径被截断后读到了另一张图。一旦读到别的图标签还是一样所以DB数量不少但内容已经乱了。解决最省事的办法是在整理数据时就把所有目录名和文件名里的空格替换成下划线。如果数据已经换不过来可以在生成txt前做一次统一替换find data -name * * -exec rename s/ /_/g {} 如果系统没有rename命令就写一段Python os.rename遍历替换。这个坑在Windows和macOS上更常见因为这些系统的用户目录名经常带空格所以数据根目录路径也尽量不要带空格。判断依据很简单执行head train.txt如果第一行路径里有空格立即停手处理。5.5 重复运行脚本时卡住或报LMDB已存在现象第一次运行正常第二次运行同一脚本进程停在创建DB阶段或者直接报错说无法创建事务删除目录后又能跑通。原因convert_imageset不会主动覆盖已有LMDBLMDB在同一时刻只能被一个进程以写模式操作。上次运行残留的lock.mdb或未提交事务会让新进程认为数据库正在被占用。解决在调用convert_imageset之前先删除目标目录。脚本里的shutil.rmtree(db_path)就是为此设计的。如果转换过程中途被你CtrlC打断目录可能残留再次运行脚本前也可以手动rm -rf train_lmdb test_lmdb注意LMDB目录被占用时rm可能会失败先关掉所有正在读取它的进程包括正在跑的Caffe训练和Python调试进程再删除。还有一个容易被忽略的场景验证LMDB的Python脚本没有正常关闭环境时会把lock.mdb留在内存映射里导致后续转换卡住。我在写验证代码时习惯用with上下文确保环境总是关闭。6. 进阶玩法验证LMDB内容与两个让训练更稳的小改动验证LMDB内容除了数key数量还可以检查value大小是否合理。用Python读取每一个value并打印前几个字节看你存进去的到底是原始jpg还是解码后的raw数据。convert_imageset默认不启用--encoded时value是图片原始字节你会看到JPEG的FF D8头或PNG的89 50 4E 47。如果看到的是BGR排列的裸像素说明你的编译版本默认存了解码数据DB会明显更大。了解这一点能避免疑惑。第一个小改动是把resize和crop分开。转换时resize到256数据层随机crop到227配合mirror翻折变相扩充样本多样性。这个组合比直接resize到227再训练更稳也是主流图像分类训练的标准做法。如果你的模型是自建的小网络crop_size可以设得更小比如96训练稳定性会显著提升。第二个小改动是生成均值文件。训练前用官方工具compute_image_mean把LMDB内容累加出每个通道的均值compute_image_mean train_lmdb mean.binaryproto将mean.binaryproto复制到prototxt所在目录在transform_param里配置mean_file: mean.binaryproto。新版Caffe也支持用npy均值但binaryproto兼容性更好老模型直接用。均值文件必须在训练前生成如果数据更新了要重新生成否则模型会学着去补偿不再匹配的均值导致val指标虚高。我一开始也跳过LMDB校验总想着“转换脚本就这么简单不可能出错”后来被label错位坑了一整天。从那以后养成习惯生成完先数一遍LMDB数量和txt行数再抽查每个类别的样本量20秒的成本换两小时的安心。这几个小改动做完你的数据准备流程基本就接近生产级别了把脚本和txt固化下来下一次换数据集只需要改根目录路径。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑