学术投稿、期刊论文对图片DPI有严格要求,多数期刊要求300dpi甚至600dpi,而ArcGIS截图、屏幕截图默认仅为72或96dpi。手动逐张修改费时费力,PS动作批量处理也常卡顿。本文提供一个纯Python批量修改图片DPI的解决方案,基于Pillow库实现,支持JPG/PNG/TIFF等格式,并详解参数含义与踩坑点。
一、DPI与像素的关系
DPI(Dots Per Inch)是打印分辨率标识,修改DPI不会改变图片的像素尺寸,只影响打印时的物理尺寸(物理尺寸 = 像素尺寸 / DPI)。例如,一张3000×2400像素的图,设置为300dpi时打印尺寸为10×8英寸;若设为600dpi则打印尺寸缩小为5×4英寸,但像素不变。因此,仅修改DPI元数据不会改变文件体积,仅改变打印/出版属性。
二、核心实现:单图片DPI修改
Pillow的Image.save()方法直接支持dpi参数,几行代码即可完成:
- from PIL import Image
- with Image.open('input.jpg') as img:
- img.save('output.jpg', dpi=(600, 600), quality=95)
复制代码
参数说明:
- dpi:元组(x, y),建议x和y相同。对于JPG,设置dpi即可写入EXIF元数据;PNG也支持dpi元数据(部分读取器可能忽略)。
- quality(仅JPG):1-100整数,推荐95,在文件大小和画质间平衡较好。
- subsampling=0:在JPG保存时禁用4:2:0色度子采样,可避免文字边缘模糊,适合插图。
三、批量处理脚本(带错误处理与格式过滤)
以下脚本遍历输入文件夹,自动跳过非图片文件,支持JPG/JPEG/PNG,批量修改DPI并保持原文件名输出到目标文件夹:
- from PIL import Image
- import os
- INPUT_FOLDER = r".\input" # 原图存放路径
- OUTPUT_FOLDER = r".\output" # 输出路径
- TARGET_DPI = (600, 600) # 目标DPI
- os.makedirs(OUTPUT_FOLDER, exist_ok=True)
- extensions = ('.png', '.jpg', '.jpeg')
- count_ok = count_skip = 0
- for filename in os.listdir(INPUT_FOLDER):
- filepath = os.path.join(INPUT_FOLDER, filename)
- if not os.path.isfile(filepath):
- continue
- ext = os.path.splitext(filename)[1].lower()
- if ext not in extensions:
- count_skip += 1
- continue
- try:
- with Image.open(filepath) as img:
- params = {'dpi': TARGET_DPI}
- if ext in ('.jpg', '.jpeg'):
- params['quality'] = 95
- params['subsampling'] = 0
- img.save(os.path.join(OUTPUT_FOLDER, filename), **params)
- count_ok += 1
- except Exception as e:
- count_skip += 1
- print(f'处理失败: {filename} | {e}')
- print(f'成功: {count_ok} 张,跳过: {count_skip} 个')
复制代码
四、使用步骤
1. 安装Pillow:pip install Pillow
2. 在脚本同级目录新建input和output文件夹。
3. 将待处理图片放入input文件夹,运行脚本。
4. 处理后的图片出现在output文件夹,右键属性→详细信息可查看DPI变化。
五、常见踩坑与解决办法
1. JPG保存后变糊:未设置quality或过低,请显式设为95,并添加subsampling=0。
2. 文件扩展名大小写:.JPG等大写后缀,脚本中使用.lower()转换即可。
3. 图片被其他程序占用(如PS打开):关闭占用程序再运行。
4. TIFF格式支持:在extensions元组中添加('.tiff', '.tif'),Pillow可处理TIFF DPI元数据。
六、高级扩展:两种DPI修改模式
除了仅修改元数据,有时需要同时重采样像素以保持打印物理尺寸一致。以下区分两种模式并提供函数实现。
模式1:仅修改DPI元数据(不改变像素尺寸)
适用于已有像素足够大、只需更新打印标识的场景。注意:PNG保存时可能丢失dpi,可转为JPEG或TIFF。
- def batch_set_dpi_metadata(input_dir, output_dir, target_dpi=300, extensions=('.jpg', '.jpeg', '.png', '.tiff', '.bmp')):
- import os
- from PIL import Image
- os.makedirs(output_dir, exist_ok=True)
- for fname in os.listdir(input_dir):
- if not fname.lower().endswith(extensions):
- continue
- src = os.path.join(input_dir, fname)
- dst = os.path.join(output_dir, fname)
- with Image.open(src) as img:
- if img.format == 'PNG' and dst.lower().endswith('.png'):
- dst = os.path.splitext(dst)[0] + '.jpg'
- img = img.convert('RGB')
- img.save(dst, dpi=(target_dpi, target_dpi))
复制代码
模式2:按目标DPI重采样图像(改变像素尺寸)
若原图像素过小,仅改DPI会导致打印物理尺寸过小,需按公式“新像素 = 原像素 × (目标DPI / 原始DPI)”进行缩放。此处使用LANCZOS高质量重采样。
- def batch_resize_by_dpi(input_dir, output_dir, target_dpi=300):
- import os
- from PIL import Image
- os.makedirs(output_dir, exist_ok=True)
- for fname in os.listdir(input_dir):
- if not fname.lower().endswith(('.jpg', '.jpeg', '.png', '.tiff')):
- continue
- src = os.path.join(input_dir, fname)
- dst = os.path.join(output_dir, fname)
- with Image.open(src) as img:
- src_dpi = img.info.get('dpi', (72, 72))[0]
- if src_dpi == target_dpi:
- img.save(dst)
- continue
- ratio = target_dpi / src_dpi
- new_size = (int(img.width * ratio), int(img.height * ratio))
- resized = img.resize(new_size, Image.Resampling.LANCZOS)
- resized.save(dst, dpi=(target_dpi, target_dpi))
复制代码
七、终极方案:通过piexif直接修改JPEG的EXIF DPI标签
有些期刊要求精确写入EXIF中的XResolution/YResolution,使用piexif库可绕过Pillow的封装,更可靠。
- import os, piexif
- from PIL import Image
- def batch_set_dpi_exif(input_dir, output_dir, target_dpi=300):
- os.makedirs(output_dir, exist_ok=True)
- for fname in os.listdir(input_dir):
- if not fname.lower().endswith(('.jpg', '.jpeg')):
- continue
- src = os.path.join(input_dir, fname)
- dst = os.path.join(output_dir, fname)
- img = Image.open(src)
- try:
- exif = piexif.load(img.info.get('exif', b''))
- except:
- exif = {'0th': {}, 'Exif': {}, 'GPS': {}, '1st': {}}
- exif['0th'][piexif.ImageIFD.XResolution] = (target_dpi, 1)
- exif['0th'][piexif.ImageIFD.YResolution] = (target_dpi, 1)
- exif['0th'][piexif.ImageIFD.ResolutionUnit] = 2
- exif_bytes = piexif.dump(exif)
- img.save(dst, exif=exif_bytes)
复制代码
总结:本文提供的Python脚本可直接用于学术投稿前的DPI批量修改,核心在于Pillow的save方法。根据需求选择仅改元数据或重采样,JPEG注意quality和subsampling参数,PNG建议转JPEG以保留DPI信息。实测处理几百张图片仅需数秒,显著提升效率。 |