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')
避坑指南:我曾经在处理一批卫星图像时,发现转换后图片颜色不对。排查了半天,原来是 PNG 有透明度通道(RGBA),直接保存成 JPG 会丢失 Alpha 通道。所以代码里我加了 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')
注意:OpenCV 的 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')
个人经验:我曾经接手一个项目,10 万张图片里有 3000 多张是损坏的。如果不提前清洗,训练到一半模型就崩了。所以,清洗这一步,绝对不能省

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 信息')
注意:不是所有图片都有 EXIF 信息。比如从网上下载的图片、经过压缩处理的图片,EXIF 可能被清空。另外,隐私问题也要注意——我建议在公开数据集中,务必清除 GPS 等敏感信息

3.4 本章知识体系总览

下面这张图,帮你梳理本章的核心内容。你可以把它当作一个“速查地图”。

数据预处理工具(上)知识体系 图像格式转换 • PIL:简单、直观 • OpenCV:快速、功能强 • 常见转换:PNG→JPG • 注意:BGR/RGB 顺序 • 质量参数控制 批量重命名与清洗 • 命名规范:前缀_序号 • 避免特殊字符 • 删除空文件 • 删除损坏图片 • 排序保证顺序 元数据提取 • EXIF:拍摄参数 • GPS:经纬度坐标 • 文件信息:大小/时间 • 隐私保护:清除敏感信息 • 批量提取脚本 核心目标:让数据“干净、规范、可用” 格式统一 → 命名规范 → 清洗脏数据 → 提取元数据 推荐工具组合:PIL + OpenCV + os + ExifRead

3.5 避坑总结

最后,我把自己踩过的坑总结一下,希望能帮你少走弯路:

  • 格式转换时注意颜色通道:PIL 是 RGB,OpenCV 是 BGR。混用前一定要转换。
  • 重命名前先备份:我曾经一次性把 5000 张图片的名字改乱了,恢复起来非常痛苦。建议先复制一份再操作。
  • 清洗时不要只删不记:删除损坏图片后,最好记录一下文件名和原因。方便后续排查。
  • 元数据提取要考虑隐私:如果数据要公开,记得清除 GPS 和拍摄者信息。

好了,这一章的内容就到这里。数据预处理是标注工作的“地基”,地基打牢了,后面的工作才能顺利。下一章我们会继续聊数据增强和标注格式转换,到时候见。


专注资料整理