图纸归档场景里,一千多张 BMP 底图,单张平均约 6MB,要求转成 PNG 并尽量控制在 2MB 以内。BMP 是真彩原样存储,1920×1080 图像光数据就是 1920×1080×3≈6.2MB;PNG 用 deflate 无损压缩,工程图、扫描件、图标类内容通常能压到原来的 1/5 到 1/3,实测 6MB 可到 1MB 出头。透明底图、无损保存、文字线条或纯色块内容优先 PNG;照片噪点多、允许轻微损失则 JPEG 或 WebP 更合适。下面按可落地程度整理 Pillow 脚本、os.walk 递归脚本、IrfanView、XnConvert、ImageMagick 五种方式,并附常见问题排查。
方法一:Pillow 单目录批量转换
先装 Pillow:
验证是否装好:- from PIL import Image
- print(Image.__version__)
复制代码
pip 慢可换国内镜像:- pip install pillow -i https://pypi.tuna.tsinghua.edu.cn/simple
复制代码
如果电脑有多个 Python 版本,先用 python --version 和 pip --version 确认两者一致,否则会出现 pip 装在一个版本、脚本跑在另一个版本,最终报 ModuleNotFoundError。
最简单目录批量转换脚本如下,逻辑是遍历输入目录,把 .bmp 文件用 Pillow 打开,再以 PNG 格式另存,文件名保持原样只替换扩展名:- import os
- from PIL import Image
- def convert_bmp_to_png(input_dir, output_dir=None):
- if output_dir is None:
- output_dir = input_dir
- os.makedirs(output_dir, exist_ok=True)
- for filename in os.listdir(input_dir):
- if filename.lower().endswith('.bmp'):
- bmp_path = os.path.join(input_dir, filename)
- png_filename = os.path.splitext(filename)[0] + '.png'
- png_path = os.path.join(output_dir, png_filename)
- with Image.open(bmp_path) as img:
- img.save(png_path, format='PNG')
- print(f'转换完成: {filename} -> {png_filename}')
- if __name__ == '__main__':
- convert_bmp_to_png('图片文件夹路径')
复制代码
两个细节值得注意:filename.lower().endswith('.bmp') 兼容 .BMP、.Bmp 等大小写;with 语句自动管理文件句柄,处理几百个文件时不易出现句柄泄漏和内存占用持续上涨。
img.save 里可调 optimize 和 compress_level:- img.save(png_path, format='PNG', optimize=True, compress_level=9)
复制代码
optimize=True 会在写入前多花时间尝试更优压缩,纯色区域多的图实测体积还能再小 10% 左右。compress_level 取值 0 到 9,9 压缩最狠、速度最慢,0 完全不压缩。大批量转换时 5 到 7 性价比较高,压缩率与 9 相差不大但速度更快。原文实测工程图用 compress_level=7 加 optimize=True,平均每张约 0.3 秒,一千张约五分钟,体积从平均 6MB 压到约 1.2MB。特殊文件体积偏大时,再单独用 compress_level=9 重转即可。32 位 BMP 若带透明信息,存 PNG 会保留 alpha;普通 24 位 BMP 存 PNG 则为 RGB,无透明通道。
方法二:os.walk 递归并保留目录结构
BMP 常分布在多层子目录中,例如项目名、归档日期、图号分层。单层 os.listdir 只能处理当前目录,嵌套目录需要递归。用 os.walk 可以一次遍历所有子目录,输出目录结构与输入一一对应:- import os
- from PIL import Image
- def batch_convert(src_root, dst_root):
- for dirpath, dirnames, filenames in os.walk(src_root):
- relative_path = os.path.relpath(dirpath, src_root)
- target_dir = os.path.join(dst_root, relative_path)
- os.makedirs(target_dir, exist_ok=True)
- for filename in filenames:
- if filename.lower().endswith('.bmp'):
- src_file = os.path.join(dirpath, filename)
- dst_file = os.path.join(target_dir, os.path.splitext(filename)[0] + '.png')
- with Image.open(src_file) as img:
- img.save(dst_file, format='PNG', optimize=True, compress_level=7)
- print(f'转换完成: {src_file} -> {dst_file}')
- if __name__ == '__main__':
- batch_convert('原始目录', '输出目录')
复制代码
os.walk 返回的三个值:dirpath 是当前目录路径,dirnames 是当前目录下子目录列表,filenames 是当前目录下文件列表。os.path.relpath 算出当前目录相对根目录的路径,再拼到输出目录,就能保持原目录树。
Windows 下中文、空格、特殊字符路径不要手工拼字符串,统一用 os.path.join,它会按系统选择分隔符,跨平台更稳。文件名带括号、井号时,用 os.path.splitext 去掉扩展名后再拼 .png 是安全的。但要注意:文件名有多个点号时,splitext 只去掉最后一个点号后的部分,例如 2024.06.15 图纸.bmp 会变成 2024.06.15 图纸.png。跑批量前,先用包含中文路径、空格路径的小目录试跑,再上全量数据。
方法三与四:IrfanView 和 XnConvert
不想写代码时,IrfanView 是 Windows 上轻量选择。菜单 File 下的 Batch Conversion,快捷键 F7。步骤是:左侧加入 BMP 文件;下方 Output directory 指定输出目录;Output format 选 PNG;点 Start Batch。默认输出文件名沿用原文件名,只把扩展名改成 .png。勾选 Use advanced options 可以改文件名生成规则,例如加前缀、加自增序号。高级选项里还能统一输出尺寸、自动裁边或加白边,并设置 PNG 压缩参数。界面较老且默认英文菜单,但功能稳定。
XnConvert 支持 Windows、macOS、Linux,界面更直观。左侧 Input 标签页拖入 BMP 或 Add files;中间 Actions 默认空着,只转格式无需添加动作;右侧 Output 标签页的 Format 选 PNG,在 Folder 指定输出目录,点 Convert 执行。Format 旁设置小图标里可调 PNG 压缩级别、隔行扫描、颜色位数。一般压缩级别选 6 或 7 较均衡;网页用途建议关掉隔行扫描,体积更小、加载更快。Actions 标签页适合叠加自动色阶、缩放、水印、边框、旋转、锐化等处理,并按顺序一次跑完。输出命名也可用 img_{index}.png 这类模板。
方法五:ImageMagick 命令行
装了 ImageMagick 后,单条命令即可批量转换:- mogrify -format png *.bmp
复制代码
mogrify 适合批量场景,默认把 PNG 输出到当前目录,原 BMP 保留不动。输出到其他目录加 -path:- mogrify -path output_folder -format png *.bmp
复制代码
调整 PNG 压缩级别用 -quality,这里不是 JPEG 质量百分比,而是压缩级别 0 到 100,值越低压缩越强、耗时越长,一般设 80 左右较均衡:- mogrify -path output_folder -format png -quality 80 *.bmp
复制代码
转格式同时统一缩放到最长边 1920:- mogrify -path output_folder -resize 1920x1920 -format png *.bmp
复制代码
文件量很大时,在 Linux 或 macOS 下可配合 xargs 并行处理:- find . -name '*.bmp' -print0 | xargs -0 -P 8 -I {} convert {} {}.png
复制代码
Windows 下可用 PowerShell 或开多个命令行窗口分批处理。命令行方式可复制、可版本管理,适合服务器环境和海量文件。
常见问题排查
转出来 PNG 背景是黑色:常见于 32 位 BMP 的 alpha 通道数据质量差。Pillow 会把 alpha 原样搬到 PNG,显示时像黑底。需要透明就确认原图 alpha 是否可靠;不需要透明就强制合成到白底:- from PIL import Image
- with Image.open(src_file) as img:
- if img.mode == 'RGBA':
- background = Image.new('RGB', img.size, (255, 255, 255))
- background.paste(img, mask=img.split()[3])
- background.save(dst_file, format='PNG')
- else:
- img.save(dst_file, format='PNG')
复制代码
转换后文件反而变大:原图颜色杂乱、缺少可压缩重复数据,或把 8 位灰度图存成 32 位 RGBA PNG 都会导致体积暴涨。先检查原图色彩模式:- from PIL import Image
- img = Image.open('xxx.bmp')
- print(img.mode)
复制代码
若是 L 模式说明是灰度图,存 PNG 默认就是灰度 PNG,体积较小。RGB 但内容是黑白线稿,可先转灰度再保存。没有透明需求时不要输出多余 alpha 通道。
PNG 在文件夹里不显示缩略图:多半是系统缩略图缓存损坏,或注册表 PNG 缩略图预览被第三方软件禁用。可右键文件夹进入属性、自定义、优化此文件夹,把类型改成图片,并确认资源管理器查看选项没有勾选始终显示图标、从不显示缩略图;必要时清理缩略图缓存并重启资源管理器。
判断文件是不是伪装成 BMP:用十六进制编辑器看文件头。BMP 头两个字节固定是 42 4D,即 ASCII 的 BM;PNG 头八个字节是 89 50 4E 47 0D 0A 1A 0A。脚本读取报错时先查文件头,避免把 JPEG、GIF 混在 .bmp 里导致误判。
输出 512x512、200KB 以内图标时,先把图缩到 512x512,保存 PNG 时把压缩级别拉到最高;简单图形通常能压到 200KB 以内。若内容噪点多仍超标,应考虑换 JPEG 或重新设计素材,而不是继续硬压。
选择思路:有 Python 环境、要处理几百上千张、需要定制处理或保留目录结构,用 Pillow 脚本;多层嵌套归档用 os.walk 递归脚本;Windows 临时小批量用 IrfanView;给非技术同事操作说明用 XnConvert;服务器、命令行环境或海量文件用 ImageMagick。排查时优先从文件头、色彩模式、输出参数三个方向定位,大多数 BMP 转 PNG 问题都能收敛。 |