Python爬虫数据增强:用DeOldify自动上色爬取的历史图片

你有没有想过,那些尘封在历史网站里的黑白老照片,如果能恢复色彩,会是什么样子?对于做数字人文研究或者需要历史图像数据集的朋友来说,这往往是个难题。手动上色?工作量巨大,不现实。直接使用黑白图训练AI模型?效果又可能大打折扣。

最近我在做一个关于近代城市风貌变迁的项目,需要大量高质量的彩色历史图片。我发现,把Python爬虫和DeOldify这个AI上色神器结合起来,可以搭建一条全自动的“数据增强流水线”。简单来说,就是让爬虫去网上“搬砖”,把黑白老照片批量抓回来,然后自动交给DeOldify“上色”,最后得到一套崭新的彩色数据集。整个过程几乎不用人工干预,效率提升了好几个量级。

今天,我就来分享一下这个结合了数据采集和AI处理的实战方案,希望能给有类似需求的朋友一些启发。

1. 场景与痛点:为什么需要自动化上色?

在做历史研究、文化保护或者训练视觉AI模型时,我们常常受限于历史影像资料的匮乏和质量。很多珍贵的档案、报纸、书籍插图都是黑白的,直接使用这些数据存在几个明显的痛点:

  • 信息缺失:色彩是重要的信息维度。黑白照片丢失了建筑的原色、服饰的纹样、自然景观的层次,这会影响研究的深度和AI模型学习的特征。
  • 数据集质量低:直接用黑白图训练的图像识别、分类模型,其泛化能力在面对真实彩色世界时可能会打折扣。
  • 人工处理成本高:传统的人工上色或修复需要专业的美术功底,耗时耗力,无法应对大规模数据处理的诉求。
  • 数据获取分散:有价值的历史图片分散在各个博物馆、档案馆、图书馆的网站上,手动一张张下载整理,效率极低。

我遇到的正是这些问题。我需要一个能自动从多个公开资源库抓取图片,并批量将其转为可信彩色图像的工具链。Python爬虫负责解决“获取”的问题,而DeOldify则解决了“增强”的问题。

2. 技术方案选型:为什么是DeOldify?

给黑白照片上色的AI模型不止一个,比如也有基于GAN的模型。我选择DeOldify,主要是基于它在真实感易用性上的平衡。

DeOldify的核心优势在于它生成的色彩通常比较自然、克制,不容易出现大面积色块溢出或过于艳丽的“塑料感”,这对于历史照片的修复尤为重要。它追求的是“可信”而非“炫技”。

从技术实现角度看,对于我们这个数据管道,DeOldify有几个好处:

  1. 有成熟的预训练模型:开箱即用,不需要我们自己从头收集数据训练,这对于快速启动项目至关重要。
  2. 提供多种接口:既有本地部署的库,也有封装好的工具函数,方便我们集成到Python自动化脚本中。
  3. 效果相对稳定:在人物、风景、建筑等常见历史题材上表现可靠,减少了后期人工筛选的工作量。

当然,它也不是万能的。对于某些特定场景(如极低分辨率、严重损坏的图片),效果可能不尽如人意。但对于从正规档案网站爬取的质量尚可的扫描件,它的表现足够胜任。

3. 构建自动化管道:从爬取到上色的全流程

整个管道的思路很清晰:爬虫获取图片列表和链接 -> 下载原始黑白图片 -> 调用DeOldify处理每张图片 -> 保存并整理结果。下面我们拆解每一步。

3.1 第一步:编写针对性爬虫抓取图片

爬虫部分的目标是精准、高效、友好地从目标网站获取图片。这里以某个假设的历史图片库为例。

import requests
from bs4 import BeautifulSoup
import os
import time
from urllib.parse import urljoin

class HistoricalImageCrawler:
    def __init__(self, base_url, save_dir='./raw_images'):
        self.base_url = base_url
        self.save_dir = save_dir
        os.makedirs(save_dir, exist_ok=True)
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (历史研究数据采集脚本)'
        }

    def fetch_image_links(self, list_page_url):
        """从列表页解析出所有图片详情页链接"""
        try:
            resp = requests.get(list_page_url, headers=self.headers, timeout=10)
            resp.raise_for_status()
            soup = BeautifulSoup(resp.text, 'html.parser')
            
            # 假设详情页链接在 class='item-link' 的a标签里
            detail_links = []
            for a_tag in soup.find_all('a', class_='item-link', href=True):
                full_url = urljoin(self.base_url, a_tag['href'])
                detail_links.append(full_url)
            return detail_links
        except Exception as e:
            print(f"获取列表页失败 {list_page_url}: {e}")
            return []

    def download_image(self, detail_page_url):
        """访问详情页,找到并下载高清图片"""
        try:
            resp = requests.get(detail_page_url, headers=self.headers, timeout=10)
            resp.raise_for_status()
            soup = BeautifulSoup(resp.text, 'html.parser')
            
            # 假设高清图片在 meta 标签的 og:image 属性中,或某个特定img标签
            img_tag = soup.find('meta', property='og:image')
            if img_tag and img_tag.get('content'):
                img_url = urljoin(detail_page_url, img_tag['content'])
            else:
                # 备选方案:找最大的img标签
                img_tag = soup.find('img', {'class': 'main-photo'})
                if not img_tag:
                    return None
                img_url = urljoin(detail_page_url, img_tag['src'])
            
            # 下载图片
            img_data = requests.get(img_url, headers=self.headers, timeout=15).content
            # 生成文件名,可以用详情页的标题或ID
            file_name = os.path.basename(img_url.split('?')[0]) or f"image_{int(time.time())}.jpg"
            save_path = os.path.join(self.save_dir, file_name)
            
            with open(save_path, 'wb') as f:
                f.write(img_data)
            print(f"下载成功: {save_path}")
            return save_path
        except Exception as e:
            print(f"处理详情页失败 {detail_page_url}: {e}")
            return None

    def crawl(self, start_page, max_pages=5):
        """主爬取流程"""
        all_downloaded = []
        for page in range(1, max_pages + 1):
            print(f"正在抓取第 {page} 页...")
            list_url = f"{start_page}?page={page}"  # 根据实际网站结构调整
            detail_links = self.fetch_image_links(list_url)
            
            for link in detail_links:
                saved_path = self.download_image(link)
                if saved_path:
                    all_downloaded.append(saved_path)
                time.sleep(1)  # 礼貌性延迟,避免对服务器造成压力
            time.sleep(2)
        return all_downloaded

# 使用示例
if __name__ == '__main__':
    crawler = HistoricalImageCrawler(base_url="https://example-archive.org")
    downloaded_images = crawler.crawl(start_page="https://example-archive.org/photos", max_pages=3)
    print(f"总共下载了 {len(downloaded_images)} 张图片。")

关键点说明

  • 遵守规则:爬虫的User-Agent要声明用途,并设置合理的延迟(time.sleep),尊重网站的robots.txt
  • 健壮性:代码中加入了异常处理,确保某个页面或图片失败时,流程不会完全中断。
  • 灵活性:解析逻辑(fetch_image_linksdownload_image中的选择器)需要根据目标网站的实际HTML结构进行调整。这里只是示例。

3.2 第二步:集成DeOldify进行批量上色

下载好一批黑白图片后,接下来就是调用DeOldify来上色。这里我们使用DeOldify的Python库进行本地处理。首先需要安装环境。

# 这是一个简化的环境准备步骤,实际可能需要根据DeOldify官方文档调整
git clone https://github.com/jantic/DeOldify.git
cd DeOldify
pip install -r requirements.txt
# 下载预训练模型文件到指定目录

然后,我们可以编写一个处理脚本:

import torch
from deoldify import device
from deoldify.device_id import DeviceId
from deoldify.visualize import get_image_colorizer

import os
from PIL import Image
import glob

class ImageColorizer:
    def __init__(self, model_path='./models/ColorizeArtistic_gen.pth'):
        # 设置设备,如果有GPU会快很多
        device.set(device=DeviceId.GPU0 if torch.cuda.is_available() else DeviceId.CPU)
        self.colorizer = get_image_colorizer(artistic=True)  # artistic模型通常色彩更生动

    def colorize_single(self, image_path, output_path=None, render_factor=35):
        """对单张图片进行上色
        render_factor: 渲染因子,值越大细节越多但可能引入噪声,通常20-40之间
        """
        try:
            if output_path is None:
                base, ext = os.path.splitext(image_path)
                output_path = f"{base}_colorized{ext}"
            
            # 调用DeOldify上色
            result = self.colorizer.get_transformed_image(
                path=image_path, 
                render_factor=render_factor,
                watermarked=False
            )
            if result is not None:
                result.save(output_path)
                print(f"上色完成: {output_path}")
                return output_path
            else:
                print(f"上色失败: {image_path}")
                return None
        except Exception as e:
            print(f"处理图片时出错 {image_path}: {e}")
            return None

    def colorize_batch(self, input_dir, output_dir, file_pattern="*.jpg", render_factor=35):
        """批量处理一个目录下的所有图片"""
        os.makedirs(output_dir, exist_ok=True)
        image_paths = glob.glob(os.path.join(input_dir, file_pattern))
        
        colorized_paths = []
        for img_path in image_paths:
            file_name = os.path.basename(img_path)
            out_path = os.path.join(output_dir, f"colorized_{file_name}")
            result = self.colorize_single(img_path, out_path, render_factor)
            if result:
                colorized_paths.append(result)
        print(f"批量处理完成,成功处理 {len(colorized_paths)}/{len(image_paths)} 张图片。")
        return colorized_paths

# 使用示例:连接爬虫和上色器
if __name__ == '__main__':
    # 假设我们已经用爬虫下载了图片到 './raw_images'
    raw_image_dir = './raw_images'
    
    # 初始化上色器(首次运行会下载模型,需要一点时间)
    print("正在初始化DeOldify上色器...")
    colorizer = ImageColorizer()
    
    # 设置输出目录
    colorized_dir = './colorized_images'
    
    # 开始批量上色
    print("开始批量上色处理...")
    processed_images = colorizer.colorize_batch(
        input_dir=raw_image_dir,
        output_dir=colorized_dir,
        file_pattern="*.jpg",
        render_factor=32  # 可以调整这个参数看效果
    )

关键点说明

  • 渲染因子render_factor是一个重要参数。数值小(如15)色彩柔和但可能模糊;数值大(如40)细节更多但可能产生噪点。需要根据原始图片质量微调。
  • 性能:如果有NVIDIA GPU,处理速度会快很多。批量处理时,注意GPU内存是否足够。
  • 模型选择:DeOldify通常提供“Artistic”(艺术化)和“Stable”(稳定)两种风格的模型。对于历史照片,“Stable”可能更保守真实,但“Artistic”有时效果更惊艳。可以都试试。

3.3 第三步:组装完整管道与后期处理

将前两步串联起来,并增加一些实用功能,就形成了完整管道。

import json
import hashlib

class HistoricalImageEnhancementPipeline:
    def __init__(self, crawler, colorizer):
        self.crawler = crawler
        self.colorizer = colorizer
        self.metadata = []  # 用于记录每张图片的元数据

    def run(self, start_url, max_pages=3):
        print("=== 阶段1:爬取原始图片 ===")
        raw_images = self.crawler.crawl(start_url, max_pages=max_pages)
        
        print(f"\n=== 阶段2:批量AI上色 ===")
        colorized_images = self.colorizer.colorize_batch(
            self.crawler.save_dir, 
            './enhanced_final'
        )
        
        # 简单的元数据记录(例如,记录原始文件和上色后文件的对应关系)
        for raw, color in zip(raw_images, colorized_images):
            self.metadata.append({
                'source': raw,
                'enhanced': color,
                'file_hash': self._calculate_file_hash(raw)  # 用于去重或标识
            })
        
        # 将元数据保存为JSON,方便后续管理
        with open('./pipeline_metadata.json', 'w') as f:
            json.dump(self.metadata, f, indent=2)
        print(f"\n=== 流程结束!元数据已保存。===")
        return colorized_images

    def _calculate_file_hash(self, filepath):
        """计算文件哈希值,用于唯一标识"""
        with open(filepath, 'rb') as f:
            return hashlib.md5(f.read()).hexdigest()

# 主程序入口
if __name__ == '__main__':
    # 初始化组件
    my_crawler = HistoricalImageCrawler(base_url="https://example-archive.org", save_dir='./raw_historical')
    my_colorizer = ImageColorizer()
    
    # 初始化并运行管道
    pipeline = HistoricalImageEnhancementPipeline(my_crawler, my_colorizer)
    final_images = pipeline.run(start_url="https://example-archive.org/collection/1920s", max_pages=2)
    
    print(f"成功生成 {len(final_images)} 张增强后的彩色历史图片。")

这个管道还加入了简单的元数据管理,记录了原始文件和生成文件的对应关系,这对于后续的数据集整理和溯源非常重要。

4. 实际效果与优化建议

跑通整个流程后,我得到了一批上色后的历史图片。整体来看,DeOldify对建筑、街道、自然风景的上色效果非常出色,色彩还原显得合理且富有时代感。例如,砖墙呈现出暗红色,天空是淡蓝色,树木是深浅不一的绿色,看起来非常自然。

当然,过程中也遇到一些需要调整的地方:

  • 人脸肤色:对于某些人物特写,肤色有时会偏黄或偏红,可能需要针对性地调整render_factor或尝试不同的预训练模型。
  • 复杂纹理:对于极其模糊或破损严重的原图,上色后可能会产生一些不规则的色斑。这时,可以在上色前尝试用传统图像处理算法(如锐化、降噪)进行轻度预处理。
  • 批量处理效率:如果图片数量成千上万,需要考虑任务队列(如Celery)和分布式处理,避免单机内存或显存不足。
  • 结果审核:目前流程是全自动的,对于关键项目,建议加入一个“人工审核”环节,快速浏览生成结果,剔除明显失败(如全灰、色彩严重错乱)的个案。

一个更进阶的用法是,可以将这个管道产出的高质量彩色图像,作为训练数据,去微调一个专属的“历史影像上色模型”,可能在某些特定领域(如某个城市的旧照)获得比通用模型更好的效果。

5. 总结

把Python爬虫和DeOldify组合起来,相当于打造了一个“历史图片焕新工厂”。爬虫负责源源不断地输送原材料(黑白老照片),而DeOldify则是一个不知疲倦的AI画师,自动为它们填充上合理的色彩。

这套方案的价值在于它的自动化可扩展性。一旦脚本写好,你就可以用它来处理成千上万张图片,极大地解放了人力。获取到的彩色数据集,无论是用于学术研究、文化展示,还是作为训练数据喂给其他AI模型(如图像分类、风格迁移),其价值都远高于原始的黑白数据集。

实际操作中,最关键的两步是针对目标网站编写健壮的爬虫根据图片质量调整DeOldify的参数。如果效果不理想,多试试不同的render_factor,或者换用artistic=False的稳定模式,往往能有改善。

技术本身不是目的,用它来解决实际问题才是。希望这个“爬虫+AI”的搭配思路,能帮你打开一扇窗,更高效地挖掘和利用那些沉睡在数字角落里的历史宝藏。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐