3、数据预处理工具(上):图像格式转换、批量重命名与清洗、元数据提取
大家好,欢迎来到数据预处理工具的上半部分。
说实话,做数据标注这么多年,我见过太多团队在预处理环节翻车。有的因为格式不对,模型训练直接报错;有的因为文件名乱码,标注结果对不上原始图。这些坑,我几乎都踩过一遍。
今天我们就来聊聊图像格式转换、批量重命名与清洗,还有元数据提取。这三件事,是数据预处理的“基本功”。
3.1 图像格式转换:PIL 与 OpenCV 的实战选择
图像格式转换,说白了就是把图片从一种格式变成另一种。比如从 PNG 转成 JPG,或者从 BMP 转成 TIFF。
你可能会问:“直接用画图软件转不就行了?” 嗯,一张两张可以,但当你面对几万张图片时,手动操作就是灾难。
3.1.1 为什么需要格式转换?
- 模型要求:很多深度学习框架对输入格式有硬性要求。比如 YOLO 系列通常用 JPG,而医学影像常用 DICOM 或 TIFF。
- 存储优化:PNG 无损但体积大,JPG 有损但体积小。我有个项目,原始数据全是 PNG,一张 5MB,10 万张就是 500GB。转成 JPG 后,直接降到 50GB。
- 兼容性:某些标注工具只支持特定格式。比如 LabelImg 对 TIFF 支持不好,我建议统一转成 JPG 或 PNG。
3.1.2 PIL 与 OpenCV 的核心差异
| 对比项 | PIL (Pillow) | OpenCV |
|---|---|---|
| 安装 | pip install Pillow | pip install opencv-python |
| 读取格式 | RGB 顺序 | BGR 顺序(注意!) |
| 性能 | 中等,适合小批量 | 快,适合大批量 |
| 功能丰富度 | 基础操作够用 | 图像处理功能极强 |
| 典型场景 | 格式转换、缩略图 | 批量处理、实时处理 |
我个人习惯是:简单格式转换用 PIL,复杂处理用 OpenCV。为什么?PIL 的 API 更直观,不容易出错。而 OpenCV 虽然快,但 BGR 顺序这个坑,我刚开始做时栽过好几次。
3.1.3 代码实战:批量格式转换
下面这段代码,是我在项目中反复使用的模板。它能把一个文件夹里的所有 PNG 转成 JPG。
from PIL import Image
import os
def batch_convert_png_to_jpg(input_dir, output_dir, quality=85):
"""
批量将 PNG 转换为 JPG
:param input_dir: 输入文件夹
:param output_dir: 输出文件夹
:param quality: JPG 质量,默认 85
"""
if not os.path.exists(output_dir):
os.makedirs(output_dir)
for filename in os.listdir(input_dir):
if filename.lower().endswith('.png'):
img_path = os.path.join(input_dir, filename)
img = Image.open(img_path)
# 转换为 RGB(PNG 可能有 RGBA 通道)
if img.mode == 'RGBA':
img = img.convert('RGB')
# 保存为 JPG
new_filename = filename.replace('.png', '.jpg')
output_path = os.path.join(output_dir, new_filename)
img.save(output_path, 'JPEG', quality=quality)
print(f'已转换: {filename} -> {new_filename}')
# 使用示例
batch_convert_png_to_jpg('./raw_images', './converted_images')
if img.mode == 'RGBA': img = img.convert('RGB') 这一步。
3.1.4 OpenCV 版本(适合大批量)
import cv2
import os
def batch_convert_with_opencv(input_dir, output_dir):
if not os.path.exists(output_dir):
os.makedirs(output_dir)
for filename in os.listdir(input_dir):
if filename.lower().endswith(('.png', '.bmp')):
img_path = os.path.join(input_dir, filename)
# OpenCV 读取为 BGR
img = cv2.imread(img_path)
# 直接保存为 JPG
new_filename = os.path.splitext(filename)[0] + '.jpg'
output_path = os.path.join(output_dir, new_filename)
cv2.imwrite(output_path, img, [cv2.IMWRITE_JPEG_QUALITY, 90])
print(f'已转换: {filename} -> {new_filename}')
batch_convert_with_opencv('./raw_images', './converted_images')
imread 默认返回 BGR 顺序。如果你后续要用 PIL 或其他 RGB 工具处理,记得用 cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 转换一下。这个坑,我刚开始做时至少浪费了 2 小时。
3.2 批量重命名与清洗:让文件名“规规矩矩”
数据标注中,文件名就是数据的“身份证”。如果身份证乱七八糟,后续的标注匹配、模型训练都会出问题。
我见过最离谱的命名:IMG_20210315_093847.jpg、照片 (2).png、新建文件夹/未命名.bmp…… 嗯,这种数据,标注工具直接罢工。
3.2.1 命名规范建议
- 统一格式:建议使用
类别_序号_日期或项目名_序号。比如cat_0001_20240301.jpg。 - 避免特殊字符:空格、中文、括号、& 等符号,能不用就不用。很多标注工具对中文支持不好。
- 序号补零:用
0001而不是1,方便排序。
3.2.2 批量重命名实战
import os
import re
def batch_rename(input_dir, prefix='img', start_num=1, digits=4):
"""
批量重命名文件
:param input_dir: 文件夹路径
:param prefix: 文件名前缀
:param start_num: 起始编号
:param digits: 编号位数
"""
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
files.sort() # 排序,保证顺序
for i, filename in enumerate(files, start=start_num):
# 获取文件扩展名
ext = os.path.splitext(filename)[1].lower()
# 只处理图片文件
if ext not in ['.jpg', '.jpeg', '.png', '.bmp', '.tiff']:
continue
# 新文件名:前缀_序号.扩展名
new_name = f"{prefix}_{str(i).zfill(digits)}{ext}"
old_path = os.path.join(input_dir, filename)
new_path = os.path.join(input_dir, new_name)
os.rename(old_path, new_path)
print(f'重命名: {filename} -> {new_name}')
# 使用示例
batch_rename('./raw_images', prefix='cat', start_num=1, digits=4)
files.sort() 按字母序排,这样能保证顺序不乱。
3.2.3 数据清洗:删除“脏数据”
数据清洗,就是去掉那些不该出现在数据集里的文件。比如损坏的图片、空文件、非图片文件等。
import os
from PIL import Image
def clean_images(input_dir):
"""
清洗图片文件夹:删除损坏、空文件、非图片文件
"""
removed_count = 0
for filename in os.listdir(input_dir):
file_path = os.path.join(input_dir, filename)
if not os.path.isfile(file_path):
continue
# 检查文件大小
if os.path.getsize(file_path) == 0:
os.remove(file_path)
print(f'删除空文件: {filename}')
removed_count += 1
continue
# 尝试用 PIL 打开,检查是否损坏
try:
with Image.open(file_path) as img:
img.verify() # 验证图片完整性
except (IOError, SyntaxError) as e:
os.remove(file_path)
print(f'删除损坏图片: {filename}, 错误: {e}')
removed_count += 1
print(f'清洗完成,共删除 {removed_count} 个文件')
clean_images('./raw_images')
3.3 元数据提取:从图片里“挖”出隐藏信息
元数据,就是图片的“身份证信息”。比如拍摄时间、相机型号、GPS 坐标、分辨率等。这些信息,有时候比图片本身还重要。
3.3.1 常见的元数据字段
| 字段 | 说明 | 提取工具 |
|---|---|---|
| EXIF | 拍摄参数(光圈、快门、ISO) | PIL、ExifRead |
| GPS | 经纬度、海拔 | PIL、piexif |
| 文件信息 | 分辨率、文件大小、创建时间 | os、PIL |
| IPTC | 关键词、标题、作者 | pyexiv2 |
3.3.2 用 PIL 提取 EXIF 信息
from PIL import Image
from PIL.ExifTags import TAGS
def extract_exif(image_path):
"""
提取图片的 EXIF 元数据
"""
img = Image.open(image_path)
exif_data = img._getexif()
if exif_data is None:
print('该图片没有 EXIF 信息')
return {}
exif_dict = {}
for tag_id, value in exif_data.items():
tag_name = TAGS.get(tag_id, tag_id)
exif_dict[tag_name] = value
return exif_dict
# 使用示例
exif_info = extract_exif('./sample.jpg')
for key, value in exif_info.items():
print(f'{key}: {value}')
3.3.3 提取 GPS 坐标(实用场景)
我有个项目是做地理图像标注,需要从图片里提取 GPS 坐标。下面这段代码,能帮你把经纬度提取出来。
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
def get_gps_coordinates(image_path):
"""
从图片中提取 GPS 坐标
"""
img = Image.open(image_path)
exif_data = img._getexif()
if not exif_data:
return None
gps_info = {}
for tag_id, value in exif_data.items():
tag_name = TAGS.get(tag_id, tag_id)
if tag_name == 'GPSInfo':
for gps_tag_id, gps_value in value.items():
gps_tag_name = GPSTAGS.get(gps_tag_id, gps_tag_id)
gps_info[gps_tag_name] = gps_value
# 转换经纬度格式(度分秒 -> 十进制)
def dms_to_decimal(dms_tuple):
degrees, minutes, seconds = dms_tuple
return degrees + minutes / 60.0 + seconds / 3600.0
if 'GPSLatitude' in gps_info and 'GPSLongitude' in gps_info:
lat = dms_to_decimal(gps_info['GPSLatitude'])
lon = dms_to_decimal(gps_info['GPSLongitude'])
return lat, lon
return None
# 使用示例
coords = get_gps_coordinates('./sample.jpg')
if coords:
print(f'GPS 坐标: 纬度 {coords[0]}, 经度 {coords[1]}')
else:
print('未找到 GPS 信息')
3.4 本章知识体系总览
下面这张图,帮你梳理本章的核心内容。你可以把它当作一个“速查地图”。
3.5 避坑总结
最后,我把自己踩过的坑总结一下,希望能帮你少走弯路:
- 格式转换时注意颜色通道:PIL 是 RGB,OpenCV 是 BGR。混用前一定要转换。
- 重命名前先备份:我曾经一次性把 5000 张图片的名字改乱了,恢复起来非常痛苦。建议先复制一份再操作。
- 清洗时不要只删不记:删除损坏图片后,最好记录一下文件名和原因。方便后续排查。
- 元数据提取要考虑隐私:如果数据要公开,记得清除 GPS 和拍摄者信息。
好了,这一章的内容就到这里。数据预处理是标注工作的“地基”,地基打牢了,后面的工作才能顺利。下一章我们会继续聊数据增强和标注格式转换,到时候见。