手把手教你用Python处理SynOld老照片数据集:从合并图到LQ/GT分离的完整流程 手把手教你用Python处理SynOld老照片数据集从合并图到LQ/GT分离的完整流程老照片修复技术近年来在计算机视觉领域备受关注而高质量的数据集是训练优秀模型的基础。SynOld作为老照片修复领域常用的数据集之一采用了独特的左右拼接格式存储低质量(LQ)和高质量(GT)图像对。这种存储方式虽然节省空间但在实际训练前需要进行预处理。本文将详细介绍如何使用Python处理这类特殊格式的数据集从基础的分割操作到完整的工程化解决方案。1. 理解SynOld数据集的结构与特点SynOld数据集包含500对训练图像和200对测试图像每对图像由低质量版本(LQ)和高质量版本(GT)水平拼接而成。这种存储方式在图像修复领域并不常见但却有其独特的优势空间效率合并存储减少了文件数量降低了文件系统开销对齐保证拼接后的图像天然保证了LQ和GT的像素级对齐防错设计避免了单独存储可能导致的文件丢失或错配问题然而这种格式也带来了使用上的不便大多数图像修复框架期望LQ和GT分别存储在不同文件夹中直接使用合并图像会浪费计算资源处理不需要的区域批量数据增强操作难以针对性地应用于LQ或GT部分理解这些特点后我们需要设计一个可靠的分割流程将合并图像还原为标准格式同时保持原始数据的完整性。2. 基础分割脚本解析与实现下面是一个完整的Python脚本用于将SynOld的合并图像分割为独立的LQ和GT图像import os from PIL import Image def split_synold_images(input_dir, output_dir): 将SynOld格式的合并图像分割为独立的LQ和GT图像 参数: input_dir (str): 包含合并图像的输入目录路径 output_dir (str): 分割后图像的输出目录路径 # 创建LQ和GT子目录 lq_dir os.path.join(output_dir, LQ) gt_dir os.path.join(output_dir, GT) os.makedirs(lq_dir, exist_okTrue) os.makedirs(gt_dir, exist_okTrue) # 处理目录中的每个JPEG文件 for filename in os.listdir(input_dir): if filename.lower().endswith((.jpg, .jpeg, .png)): img_path os.path.join(input_dir, filename) try: with Image.open(img_path) as img: width, height img.size # 验证图像宽度是否为偶数 if width % 2 ! 0: print(f警告: {filename} 的宽度不是偶数无法均分) continue # 分割左半部分(LQ)和右半部分(GT) lq_img img.crop((0, 0, width//2, height)) gt_img img.crop((width//2, 0, width, height)) # 保存分割后的图像 base_name os.path.splitext(filename)[0] lq_img.save(os.path.join(lq_dir, f{base_name}_LQ.jpg)) gt_img.save(os.path.join(gt_dir, f{base_name}_GT.jpg)) except Exception as e: print(f处理 {filename} 时出错: {str(e)})这个脚本的核心功能包括目录结构创建自动建立LQ和GT子目录图像分割逻辑使用Pillow库加载图像计算图像宽度并验证可分割性使用crop方法精确提取左右两部分错误处理机制检查图像宽度是否为偶数捕获并报告处理过程中的异常文件命名规范保留原始文件名并添加_LQ/_GT后缀提示在实际应用中建议添加日志记录功能而非直接打印便于后期问题排查。3. 工程化扩展批量处理与验证基础脚本满足了单个目录的处理需求但在实际项目中我们通常需要更完善的解决方案。以下是几个关键的工程化扩展点3.1 多目录批量处理对于包含多个子集的数据集我们可以扩展脚本以支持批量处理def batch_process_synold(root_dir, output_base): 批量处理包含多个子集的SynOld数据集 参数: root_dir (str): 包含train/test等子集的根目录 output_base (str): 输出文件的基础目录 for subset in [train, test, val]: input_dir os.path.join(root_dir, subset) if os.path.exists(input_dir): output_dir os.path.join(output_base, subset) split_synold_images(input_dir, output_dir)3.2 数据完整性验证分割后的数据需要验证以确保处理过程没有引入错误def validate_split_results(output_dir): 验证分割结果的完整性和一致性 参数: output_dir (str): 包含LQ和GT子目录的输出目录 lq_dir os.path.join(output_dir, LQ) gt_dir os.path.join(output_dir, GT) lq_files set(f.replace(_LQ.jpg, ) for f in os.listdir(lq_dir)) gt_files set(f.replace(_GT.jpg, ) for f in os.listdir(gt_dir)) # 检查文件数量是否匹配 if len(lq_files) ! len(gt_files): print(f警告: LQ({len(lq_files)})和GT({len(gt_files)})文件数量不匹配) # 检查文件名是否一一对应 missing_in_gt lq_files - gt_files missing_in_lq gt_files - lq_files if missing_in_gt: print(fGT中缺少对应文件: {missing_in_gt}) if missing_in_lq: print(fLQ中缺少对应文件: {missing_in_lq})3.3 性能优化与并行处理对于大型数据集我们可以采用多进程加速处理from multiprocessing import Pool def parallel_split_images(input_dir, output_dir, workers4): 使用多进程并行处理图像分割 参数: input_dir (str): 输入目录路径 output_dir (str): 输出目录路径 workers (int): 并行工作进程数 # 创建输出目录 os.makedirs(os.path.join(output_dir, LQ), exist_okTrue) os.makedirs(os.path.join(output_dir, GT), exist_okTrue) # 准备任务列表 tasks [(f, input_dir, output_dir) for f in os.listdir(input_dir) if f.lower().endswith((.jpg, .jpeg, .png))] # 使用进程池处理 with Pool(workers) as p: p.starmap(process_single_image, tasks) def process_single_image(filename, input_dir, output_dir): 处理单个图像文件的辅助函数 try: img_path os.path.join(input_dir, filename) with Image.open(img_path) as img: width, height img.size if width % 2 ! 0: return lq_img img.crop((0, 0, width//2, height)) gt_img img.crop((width//2, 0, width, height)) base_name os.path.splitext(filename)[0] lq_img.save(os.path.join(output_dir, LQ, f{base_name}_LQ.jpg)) gt_img.save(os.path.join(output_dir, GT, f{base_name}_GT.jpg)) except Exception as e: print(f处理 {filename} 时出错: {str(e)})4. 通用化处理方案与最佳实践虽然上述方案针对SynOld数据集但我们可以抽象出通用模式来处理类似格式的数据集4.1 支持多种拼接方式扩展脚本以支持垂直拼接或其他分割比例def split_image_with_mode(img, modehorizontal, ratio0.5): 通用图像分割函数支持多种拼接方式 参数: img (PIL.Image): 待分割的图像 mode (str): 拼接模式 (horizontal|vertical) ratio (float): 分割比例 (0-1) 返回: tuple: (第一部分图像, 第二部分图像) width, height img.size if mode horizontal: split_pos int(width * ratio) return (img.crop((0, 0, split_pos, height)), img.crop((split_pos, 0, width, height))) else: split_pos int(height * ratio) return (img.crop((0, 0, width, split_pos)), img.crop((0, split_pos, width, height)))4.2 配置文件驱动处理使用JSON配置文件定义处理规则提高灵活性{ dataset_name: SynOld, input_structure: { train: path/to/train, test: path/to/test }, split_params: { mode: horizontal, ratio: 0.5, naming: { part1_suffix: _LQ, part2_suffix: _GT } } }对应的Python处理代码import json def process_with_config(config_file): 根据配置文件处理数据集 with open(config_file) as f: config json.load(f) for subset, input_dir in config[input_structure].items(): output_dir os.path.join(processed, config[dataset_name], subset) os.makedirs(output_dir, exist_okTrue) split_params config[split_params] process_directory(input_dir, output_dir, split_params) def process_directory(input_dir, output_dir, params): 根据参数处理单个目录 part1_dir os.path.join(output_dir, part1) part2_dir os.path.join(output_dir, part2) os.makedirs(part1_dir, exist_okTrue) os.makedirs(part2_dir, exist_okTrue) for filename in os.listdir(input_dir): if filename.lower().endswith((.jpg, .jpeg, .png)): img_path os.path.join(input_dir, filename) with Image.open(img_path) as img: part1, part2 split_image_with_mode( img, params[mode], params[ratio]) base_name os.path.splitext(filename)[0] part1.save(os.path.join(part1_dir, f{base_name}{params[naming][part1_suffix]}.jpg)) part2.save(os.path.join(part2_dir, f{base_name}{params[naming][part2_suffix]}.jpg))4.3 质量检查与可视化在处理完成后建议进行质量检查随机抽样检查人工查看部分分割结果指标验证计算分割前后图像的统计特性是否一致差异可视化生成对比图帮助发现问题def visualize_split_results(lq_path, gt_path, output_path): 生成分割结果的可视化对比图 参数: lq_path (str): LQ图像路径 gt_path (str): GT图像路径 output_path (str): 输出图像路径 lq_img Image.open(lq_path) gt_img Image.open(gt_path) # 创建对比图像 width, height lq_img.size result Image.new(RGB, (width*2, height)) result.paste(lq_img, (0, 0)) result.paste(gt_img, (width, 0)) # 添加分隔线和标签 draw ImageDraw.Draw(result) draw.line([(width, 0), (width, height)], fillred, width2) draw.text((10, 10), LQ, fillwhite) draw.text((width10, 10), GT, fillwhite) result.save(output_path)5. 实际应用中的注意事项在处理SynOld或其他类似数据集时有几个关键点需要特别注意文件命名一致性确保LQ和GT文件的对应关系明确避免特殊字符和空格在文件名中考虑使用UUID等唯一标识符而非简单序号图像格式处理统一转换为标准格式(如JPEG或PNG)注意颜色空间的一致性(RGB vs. Grayscale)处理可能的Alpha通道问题存储优化考虑使用压缩格式平衡质量和大小对于大型数据集可以考虑HDF5等高效存储格式建立合理的目录结构便于版本管理元数据保留保留原始图像的EXIF等信息考虑创建处理日志记录操作历史添加README说明处理流程和参数版本控制对原始数据和处理后的数据分别进行版本管理使用checksum验证数据完整性考虑使用数据注册表跟踪不同版本在实际项目中我曾遇到过因文件名包含特殊字符导致处理失败的情况后来统一采用了简单的字母数字组合命名方案。另一个常见问题是图像格式不一致有些是JPEG有些是PNG最佳实践是在处理前统一转换格式。