AI图像编辑与风格迁移完全教程

教程简介

本教程全面讲解AI图像编辑与风格迁移的核心技术,涵盖Inpainting图像修复、超分辨率增强、Neural Style/AdaIN风格迁移、ControlNet精确编辑、IP-Adapter风格保持、图像融合与合成、背景替换与物体移除、批量处理工作流等核心内容,帮助开发者构建AI图像编辑工具。

AI图像编辑与风格迁移完全教程

1. AI图像编辑技术演进

AI图像编辑经历了从传统图像处理到深度生成模型的跨越。核心技术路线可以概括为四个阶段:

第一阶段:传统图像处理 基于像素级操作(滤波、直方图变换、形态学运算),能力有限但计算成本低。

第二阶段:GAN时代(2014-2021) 以StyleGAN、Pix2Pix、CycleGAN为代表,首次实现了高质量的图像生成和风格转换,但可控性不足。

第三阶段:Diffusion模型时代(2021-2023) Stable Diffusion、DALL-E 2、Midjourney引领了扩散模型革命,图像质量和多样性大幅提升。

第四阶段:可控生成时代(2023-至今) ControlNet、IP-Adapter、InstructPix2Pix等技术实现了精确的空间控制、风格控制和语义编辑,AI图像编辑进入实用化阶段。

核心概念速览

# AI图像编辑的核心技术栈
IMAGE_EDITING_STACK = {
    '生成基础': ['VAE', 'UNet', 'DiT', 'Flow Matching'],
    '条件控制': ['Text Conditioning', 'ControlNet', 'T2I-Adapter'],
    '风格控制': ['LoRA', 'IP-Adapter', 'Style Aligned'],
    '编辑技术': ['Inpainting', 'Img2Img', 'InstructPix2Pix'],
    '后处理': ['Super Resolution', 'Face Restoration', 'Colorization'],
    '推理加速': ['LCM', 'SDXL Turbo', 'Consistency Models'],
}

2. 图像修复(Inpainting)技术详解

图像修复是AI图像编辑中最基础也最实用的技术之一,用于填充图像中被遮罩的区域。

传统修复 vs AI修复

import numpy as np
from typing import Tuple, Optional

class InpaintingPipeline:
    """
    图像修复管线
    支持多种后端:OpenCV传统方法、Stable Diffusion Inpainting
    """

    def __init__(self, method: str = 'diffusion'):
        self.method = method

    def preprocess_mask(
        self,
        mask: np.ndarray,
        dilate_kernel: int = 5,
        blur_radius: int = 3,
    ) -> np.ndarray:
        """
        预处理遮罩:膨胀 + 模糊,使修复边缘更自然
        """
        import cv2

        # 确保遮罩是二值图
        if len(mask.shape) == 3:
            mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)

        _, binary = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)

        # 膨胀操作,扩大修复区域以覆盖边缘
        kernel = np.ones((dilate_kernel, dilate_kernel), np.uint8)
        dilated = cv2.dilate(binary, kernel, iterations=1)

        # 高斯模糊,使遮罩边缘柔和
        blurred = cv2.GaussianBlur(
            dilated, (blur_radius * 2 + 1, blur_radius * 2 + 1), 0
        )

        return blurred

    def prepare_inpainting_input(
        self,
        image: np.ndarray,
        mask: np.ndarray,
        target_size: Tuple[int, int] = (512, 512),
    ) -> dict:
        """
        准备修复模型的输入数据
        """
        import cv2

        h, w = image.shape[:2]

        # 将图像和遮罩调整到目标尺寸
        image_resized = cv2.resize(image, target_size)
        mask_resized = cv2.resize(mask, target_size)

        # 归一化
        image_norm = image_resized.astype(np.float32) / 255.0
        mask_norm = mask_resized.astype(np.float32) / 255.0

        # 将遮罩区域置零(模型输入格式)
        masked_image = image_norm * (1 - mask_norm[..., np.newaxis])

        return {
            'image': image_norm,
            'mask': mask_norm,
            'masked_image': masked_image,
            'original_size': (h, w),
        }


class DiffusionInpainter:
    """
    基于Stable Diffusion的图像修复
    使用diffusers库实现高质量语义修复
    """

    def __init__(self, model_id: str = "runwayml/stable-diffusion-inpainting"):
        self.model_id = model_id
        self.pipe = None

    def load(self):
        """加载修复模型"""
        from diffusers import StableDiffusionInpaintPipeline
        import torch

        self.pipe = StableDiffusionInpaintPipeline.from_pretrained(
            self.model_id,
            torch_dtype=torch.float16,
            safety_checker=None,
        ).to("cuda")
        # 启用内存优化
        self.pipe.enable_attention_slicing()

    def inpaint(
        self,
        image,
        mask,
        prompt: str,
        negative_prompt: str = "blurry, low quality, artifacts",
        num_inference_steps: int = 30,
        guidance_scale: float = 7.5,
        seed: Optional[int] = None,
    ):
        """
        执行修复

        参数:
            image: PIL Image - 原始图像
            mask: PIL Image - 遮罩(白色=需要修复的区域)
            prompt: 文本提示 - 描述期望生成的内容
            negative_prompt: 负向提示
            num_inference_steps: 推理步数
            guidance_scale: 引导强度
            seed: 随机种子(用于可复现)
        """
        import torch

        generator = None
        if seed is not None:
            generator = torch.Generator(device="cuda").manual_seed(seed)

        result = self.pipe(
            prompt=prompt,
            image=image,
            mask_image=mask,
            negative_prompt=negative_prompt,
            num_inference_steps=num_inference_steps,
            guidance_scale=guidance_scale,
            generator=generator,
        )

        return result.images[0]

蒙版生成策略

实际应用中,遮罩往往不是手动绘制的,而是通过算法自动生成:

class MaskGenerator:
    """自动遮罩生成器"""

    @staticmethod
    def from_bbox(image: np.ndarray, bbox: tuple, padding: int = 10) -> np.ndarray:
        """从边界框生成遮罩"""
        import cv2
        h, w = image.shape[:2]
        mask = np.zeros((h, w), dtype=np.uint8)
        x1, y1, x2, y2 = bbox
        x1 = max(0, x1 - padding)
        y1 = max(0, y1 - padding)
        x2 = min(w, x2 + padding)
        y2 = min(h, y2 + padding)
        mask[y1:y2, x1:x2] = 255
        return mask

    @staticmethod
    def from_segmentation(
        image: np.ndarray,
        target_class: str,
        model_name: str = "sam2"
    ) -> np.ndarray:
        """基于语义分割生成遮罩"""
        # 使用SAM2或语义分割模型
        # 这里展示概念框架
        import cv2
        h, w = image.shape[:2]
        mask = np.zeros((h, w), dtype=np.uint8)
        # 实际中调用分割模型获取目标区域的mask
        return mask

    @staticmethod
    def expand_mask(mask: np.ndarray, pixels: int = 15) -> np.ndarray:
        """扩展遮罩区域,确保覆盖边缘过渡"""
        import cv2
        kernel = np.ones((pixels, pixels), np.uint8)
        return cv2.dilate(mask, kernel, iterations=1)

3. 图像超分辨率增强

超分辨率(Super Resolution)将低分辨率图像放大并补充细节,是图像增强的核心技术。

Real-ESRGAN 实战

class SuperResolutionPipeline:
    """
    图像超分辨率管线
    支持多种放大模型和后处理
    """

    def __init__(self, scale: int = 4, model_type: str = 'realesrgan'):
        self.scale = scale
        self.model_type = model_type
        self.model = None

    def load_model(self):
        """加载超分模型"""
        if self.model_type == 'realesrgan':
            self._load_realesrgan()
        elif self.model_type == 'swinir':
            self._load_swinir()

    def _load_realesrgan(self):
        """加载Real-ESRGAN模型"""
        from realesrgan import RealESRGANer
        from basicsr.archs.rrdbnet_arch import RRDBNet

        model = RRDBNet(
            num_in_ch=3, num_out_ch=3, num_feat=64,
            num_block=23, num_grow_ch=32, scale=4
        )
        self.model = RealESRGANer(
            scale=4,
            model_path='weights/RealESRGAN_x4plus.pth',
            model=model,
            half=True,  # 使用FP16加速
            tile=400,    # 分块处理大图,避免显存溢出
            tile_pad=10,
        )

    def enhance(
        self,
        image: np.ndarray,
        denoise_strength: float = 0.5,
        outscale: Optional[int] = None,
    ) -> np.ndarray:
        """
        执行超分辨率增强

        参数:
            image: 输入图像 (H, W, C) BGR格式
            denoise_strength: 去噪强度 (0-1)
            outscale: 最终输出的放大倍数
        """
        import cv2

        target_scale = outscale or self.scale

        # Real-ESRGAN推理
        output, _ = self.model.enhance(image, outscale=target_scale)

        # 可选:后处理去噪
        if denoise_strength > 0:
            output = self._denoise(output, denoise_strength)

        return output

    @staticmethod
    def _denoise(image: np.ndarray, strength: float) -> np.ndarray:
        """轻度去噪后处理"""
        import cv2
        h = int(strength * 10)
        if h <= 0:
            return image
        return cv2.fastNlMeansDenoisingColored(image, None, h, h, 7, 21)

    @staticmethod
    def assess_quality(original: np.ndarray, enhanced: np.ndarray) -> dict:
        """评估超分质量(无参考指标)"""
        import cv2

        # 计算拉普拉斯方差(清晰度指标)
        def laplacian_variance(img):
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img
            return cv2.Laplacian(gray, cv2.CV_64F).var()

        # 计算图像信息熵
        def entropy(img):
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img
            hist = cv2.calcHist([gray], [0], None, [256], [0, 256])
            hist = hist / hist.sum()
            hist = hist[hist > 0]
            return -np.sum(hist * np.log2(hist))

        return {
            'original_sharpness': round(laplacian_variance(original), 2),
            'enhanced_sharpness': round(laplacian_variance(enhanced), 2),
            'original_entropy': round(entropy(original), 2),
            'enhanced_entropy': round(entropy(enhanced), 2),
            'improvement_ratio': round(
                laplacian_variance(enhanced) / max(laplacian_variance(original), 1e-6), 2
            ),
        }

4. 风格迁移算法

风格迁移将一幅图像的艺术风格应用到另一幅图像上,同时保留内容结构。

Neural Style Transfer(经典方法)

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import models, transforms

class NeuralStyleTransfer:
    """
    经典神经风格迁移
    基于Gatys等人的方法,通过优化像素实现风格迁移
    """

    def __init__(self, device: str = 'cuda'):
        self.device = device
        self.vgg = models.vgg19(pretrained=True).features.to(device).eval()

        # 内容层和风格层的选择
        self.content_layers = ['conv_4']
        self.style_layers = ['conv_1', 'conv_2', 'conv_3', 'conv_4', 'conv_5']

        # 冻结VGG参数
        for param in self.vgg.parameters():
            param.requires_grad_(False)

    def gram_matrix(self, tensor: torch.Tensor) -> torch.Tensor:
        """计算Gram矩阵,用于表示风格特征"""
        b, c, h, w = tensor.size()
        features = tensor.view(b, c, h * w)
        gram = torch.bmm(features, features.transpose(1, 2))
        return gram / (c * h * w)

    def extract_features(self, image: torch.Tensor) -> dict:
        """提取中间层特征"""
        features = {}
        x = image
        layer_names = []

        for name, layer in self.vgg._modules.items():
            x = layer(x)
            if isinstance(layer, nn.Conv2d):
                layer_name = f'conv_{len([n for n in layer_names if "conv" in n]) + 1}'
                layer_names.append(layer_name)
                features[layer_name] = x
            elif isinstance(layer, nn.ReLU):
                layer_names.append(f'relu_{len(layer_names)}')
            elif isinstance(layer, nn.MaxPool2d):
                layer_names.append(f'pool_{len(layer_names)}')

        return features

    def transfer(
        self,
        content_image: torch.Tensor,
        style_image: torch.Tensor,
        num_steps: int = 300,
        content_weight: float = 1.0,
        style_weight: float = 1e6,
        tv_weight: float = 1e-5,
    ) -> torch.Tensor:
        """
        执行风格迁移

        参数:
            content_image: 内容图像 (1, 3, H, W)
            style_image: 风格图像 (1, 3, H, W)
            num_steps: 优化迭代次数
            content_weight: 内容损失权重
            style_weight: 风格损失权重
            tv_weight: 全变分正则化权重(减少噪声)
        """
        # 用内容图像初始化目标图像
        target = content_image.clone().requires_grad_(True)
        optimizer = optim.Adam([target], lr=0.01)

        # 提取内容和风格特征
        content_features = self.extract_features(content_image)
        style_features = self.extract_features(style_image)

        # 预计算风格Gram矩阵
        style_grams = {
            layer: self.gram_matrix(style_features[layer])
            for layer in self.style_layers
        }

        for step in range(num_steps):
            optimizer.zero_grad()

            target_features = self.extract_features(target)

            # 内容损失
            content_loss = 0
            for layer in self.content_layers:
                content_loss += nn.functional.mse_loss(
                    target_features[layer], content_features[layer]
                )

            # 风格损失
            style_loss = 0
            for layer in self.style_layers:
                target_gram = self.gram_matrix(target_features[layer])
                style_loss += nn.functional.mse_loss(
                    target_gram, style_grams[layer]
                )

            # 全变分正则化(平滑噪声)
            tv_loss = (
                torch.sum(torch.abs(target[:, :, :, :-1] - target[:, :, :, 1:])) +
                torch.sum(torch.abs(target[:, :, :-1, :] - target[:, :, 1:, :]))
            )

            # 总损失
            total_loss = (
                content_weight * content_loss +
                style_weight * style_loss +
                tv_weight * tv_loss
            )

            total_loss.backward()
            optimizer.step()

            if (step + 1) % 50 == 0:
                print(
                    f"Step {step+1}/{num_steps} | "
                    f"Content: {content_loss.item():.4f} | "
                    f"Style: {style_loss.item():.4f} | "
                    f"TV: {tv_loss.item():.4f}"
                )

        return target.detach()

AdaIN风格迁移(快速方法)

class AdaINStyleTransfer(nn.Module):
    """
    Adaptive Instance Normalization (AdaIN) 风格迁移
    Huang & Belongie, 2017
    实时风格迁移,推理速度远快于优化方法
    """

    def __init__(self, encoder_weights: str = 'vgg'):
        super().__init__()
        # 编码器(VGG特征提取器)
        self.encoder = self._build_encoder()
        # 解码器(将特征重建为图像)
        self.decoder = self._build_decoder()

    @staticmethod
    def adain(content_feat: torch.Tensor, style_feat: torch.Tensor) -> torch.Tensor:
        """
        AdaIN核心操作:
        将内容特征的均值和方差对齐到风格特征的统计量
        """
        size = content_feat.size()

        # 计算内容特征的统计量
        content_mean = content_feat.mean(dim=[2, 3], keepdim=True)
        content_std = content_feat.std(dim=[2, 3], keepdim=True) + 1e-6

        # 计算风格特征的统计量
        style_mean = style_feat.mean(dim=[2, 3], keepdim=True)
        style_std = style_feat.std(dim=[2, 3], keepdim=True) + 1e-6

        # 归一化内容特征 + 应用风格统计量
        normalized = (content_feat - content_mean) / content_std
        return normalized * style_std + style_mean

    def forward(
        self,
        content: torch.Tensor,
        style: torch.Tensor,
        alpha: float = 1.0,
    ) -> torch.Tensor:
        """
        前向推理

        参数:
            content: 内容图像 (B, 3, H, W)
            style: 风格图像 (B, 3, H, W)
            alpha: 风格强度 (0-1),0=纯内容,1=纯风格
        """
        # 提取特征
        content_feat = self.encoder(content)
        style_feat = self.encoder(style)

        # AdaIN变换
        transferred = self.adain(content_feat, style_feat)

        # 混合原始内容特征和风格化特征
        blended = alpha * transferred + (1 - alpha) * content_feat

        # 解码器重建图像
        output = self.decoder(blended)
        return output

    def _build_encoder(self):
        """构建VGG编码器(只取前几层)"""
        from torchvision.models import vgg19
        vgg = vgg19(pretrained=True).features
        # 取到relu4_1
        encoder = nn.Sequential(*list(vgg.children())[:21])
        for param in encoder.parameters():
            param.requires_grad_(False)
        return encoder

    def _build_decoder(self):
        """构建解码器(编码器的逆结构)"""
        return nn.Sequential(
            nn.Conv2d(512, 256, 3, 1, 1, padding_mode='reflect'),
            nn.ReLU(inplace=True),
            nn.Upsample(scale_factor=2, mode='nearest'),
            nn.Conv2d(256, 256, 3, 1, 1, padding_mode='reflect'),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, 3, 1, 1, padding_mode='reflect'),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, 3, 1, 1, padding_mode='reflect'),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 128, 3, 1, 1, padding_mode='reflect'),
            nn.ReLU(inplace=True),
            nn.Upsample(scale_factor=2, mode='nearest'),
            nn.Conv2d(128, 128, 3, 1, 1, padding_mode='reflect'),
            nn.ReLU(inplace=True),
            nn.Conv2d(128, 64, 3, 1, 1, padding_mode='reflect'),
            nn.ReLU(inplace=True),
            nn.Upsample(scale_factor=2, mode='nearest'),
            nn.Conv2d(64, 64, 3, 1, 1, padding_mode='reflect'),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 3, 3, 1, 1, padding_mode='reflect'),
        )

5. ControlNet精确编辑控制

ControlNet通过在预训练扩散模型上添加空间条件控制,实现了对生成过程的精确控制,是当前最强大的可控图像生成方案。

ControlNet核心原理

import torch
import torch.nn as nn

class ControlNetConditionEncoder:
    """
    ControlNet条件编码器
    将不同类型的控制信号转化为统一的条件嵌入
    """

    CONDITION_TYPES = {
        'canny': '边缘检测图',
        'depth': '深度图',
        'pose': '人体姿态骨架',
        'segmentation': '语义分割图',
        'normal': '法线图',
        'scribble': '涂鸦/线稿',
        'lineart': '线稿',
        'mlsd': '直线检测',
        'shuffle': '内容随机打乱',
        'tile': '分块超分',
    }

    def __init__(self):
        self.preprocessors = {}

    def register_preprocessor(self, condition_type: str, processor):
        self.preprocessors[condition_type] = processor

    def encode(self, image, condition_type: str, **kwargs):
        """将图像转换为指定类型的条件图"""
        if condition_type not in self.preprocessors:
            raise ValueError(
                f"未知条件类型: {condition_type},"
                f"支持: {list(self.preprocessors.keys())}"
            )
        return self.preprocessors[condition_type](image, **kwargs)


class CannyEdgePreprocessor:
    """Canny边缘检测预处理器"""

    def __init__(self, low_threshold: int = 100, high_threshold: int = 200):
        self.low = low_threshold
        self.high = high_threshold

    def __call__(self, image, **kwargs):
        import cv2
        low = kwargs.get('low_threshold', self.low)
        high = kwargs.get('high_threshold', self.high)

        if len(image.shape) == 3:
            gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        else:
            gray = image

        edges = cv2.Canny(gray, low, high)
        return edges


class DepthMapPreprocessor:
    """深度图预处理器(使用MiDaS)"""

    def __init__(self):
        self.model = None

    def load(self):
        """加载MiDaS深度估计模型"""
        model_type = "DPT_Large"
        self.model = torch.hub.load("intel-isl/MiDaS", model_type)
        self.model.eval()
        self.transform = torch.hub.load("intel-isl/MiDaS", "transforms")
        self.transform = self.transform.dpt_transform

    def __call__(self, image, **kwargs):
        import cv2
        input_batch = self.transform(image).unsqueeze(0)
        with torch.no_grad():
            prediction = self.model(input_batch)
            prediction = torch.nn.functional.interpolate(
                prediction.unsqueeze(1),
                size=image.shape[:2],
                mode="bicubic",
                align_corners=False,
            ).squeeze()
        depth_map = prediction.cpu().numpy()
        # 归一化到0-255
        depth_map = (
            (depth_map - depth_map.min()) /
            (depth_map.max() - depth_map.min()) * 255
        ).astype(np.uint8)
        return depth_map


def build_controlnet_pipeline():
    """构建完整的ControlNet推理管线"""
    from diffusers import (
        StableDiffusionControlNetPipeline,
        ControlNetModel,
        UniPCMultistepScheduler,
    )
    import torch

    # 加载ControlNet模型
    controlnet = ControlNetModel.from_pretrained(
        "lllyasviel/sd-controlnet-canny",
        torch_dtype=torch.float16,
    )

    # 构建管线
    pipe = StableDiffusionControlNetPipeline.from_pretrained(
        "runwayml/stable-diffusion-v1-5",
        controlnet=controlnet,
        torch_dtype=torch.float16,
    )

    # 使用更快的调度器
    pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
    pipe.enable_model_cpu_offload()

    return pipe

多ControlNet联合控制

def multi_controlnet_example():
    """
    多ControlNet联合使用示例
    同时使用边缘图和深度图控制生成
    """
    from diffusers import (
        StableDiffusionControlNetPipeline,
        ControlNetModel,
    )
    import torch

    # 加载多个ControlNet
    controlnet_canny = ControlNetModel.from_pretrained(
        "lllyasviel/sd-controlnet-canny", torch_dtype=torch.float16
    )
    controlnet_depth = ControlNetModel.from_pretrained(
        "lllyasviel/sd-controlnet-depth", torch_dtype=torch.float16
    )

    # 构建多条件管线
    pipe = StableDiffusionControlNetPipeline.from_pretrained(
        "runwayml/stable-diffusion-v1-5",
        controlnet=[controlnet_canny, controlnet_depth],  # 列表形式
        torch_dtype=torch.float16,
    )
    pipe.enable_model_cpu_offload()

    return pipe

6. IP-Adapter风格保持生成

IP-Adapter通过解耦的交叉注意力机制,将参考图像的风格和内容信息注入扩散模型,实现"以图生图"的同时保持风格一致性。

class IPAdapterPipeline:
    """
    IP-Adapter管线
    支持风格迁移和风格保持生成
    """

    def __init__(self, base_model: str = "runwayml/stable-diffusion-v1-5"):
        self.base_model = base_model
        self.pipe = None

    def load(
        self,
        ip_adapter_model: str = "h94/IP-Adapter",
        image_encoder_path: str = "h94/IP-Adapter/models/image_encoder",
        subfolder: str = "models",
        weight_name: str = "ip-adapter_sd15.bin",
    ):
        """加载IP-Adapter管线"""
        from diffusers import StableDiffusionPipeline
        from transformers import CLIPVisionModelWithProjection
        import torch

        # 加载基础管线
        self.pipe = StableDiffusionPipeline.from_pretrained(
            self.base_model, torch_dtype=torch.float16
        )

        # 加载图像编码器
        image_encoder = CLIPVisionModelWithProjection.from_pretrained(
            image_encoder_path
        ).to("cuda", dtype=torch.float16)

        # 注入IP-Adapter
        self.pipe.load_ip_adapter(
            ip_adapter_model,
            subfolder=subfolder,
            weight_name=weight_name,
        )
        self.pipe.set_ip_adapter_scale(0.6)  # 风格影响强度

        self.pipe.to("cuda")

    def generate(
        self,
        reference_image,
        prompt: str,
        negative_prompt: str = "low quality, blurry",
        ip_scale: float = 0.6,
        num_images: int = 1,
        seed: int = 42,
    ):
        """
        基于参考图像生成新图像

        参数:
            reference_image: 参考风格图像 (PIL Image)
            prompt: 文本提示
            ip_scale: IP-Adapter影响强度 (0-1)
            num_images: 生成数量
        """
        import torch

        self.pipe.set_ip_adapter_scale(ip_scale)

        generator = torch.Generator(device="cuda").manual_seed(seed)

        images = self.pipe(
            prompt=prompt,
            ip_adapter_image=reference_image,
            negative_prompt=negative_prompt,
            num_images_per_prompt=num_images,
            num_inference_steps=30,
            generator=generator,
        ).images

        return images


class StyleConsistentGenerator:
    """
    风格一致性生成器
    使用IP-Adapter确保批量生成的图像保持统一风格
    """

    def __init__(self, ip_adapter: IPAdapterPipeline):
        self.ip_adapter = ip_adapter
        self.style_reference = None

    def set_style(self, reference_image, scale: float = 0.6):
        """设定全局风格参考"""
        self.style_reference = reference_image
        self.ip_adapter.pipe.set_ip_adapter_scale(scale)

    def generate_batch(
        self,
        prompts: list,
        output_dir: str = './output',
        seed_base: int = 100,
    ) -> list:
        """
        批量生成风格一致的图像

        参数:
            prompts: 文本提示列表
            output_dir: 输出目录
            seed_base: 基础随机种子(不同prompt用不同seed但保持可控)
        """
        import os
        os.makedirs(output_dir, exist_ok=True)

        results = []
        for i, prompt in enumerate(prompts):
            seed = seed_base + i
            images = self.ip_adapter.generate(
                reference_image=self.style_reference,
                prompt=prompt,
                seed=seed,
            )
            for j, img in enumerate(images):
                path = os.path.join(output_dir, f"style_gen_{i}_{j}.png")
                img.save(path)
                results.append(path)

        return results

7. 图像融合与合成技术

图像融合将多张图像无缝组合,关键技术包括前景提取、颜色匹配和边界融合。

class ImageCompositor:
    """
    图像合成引擎
    支持前景-背景融合、多图拼接和颜色协调
    """

    @staticmethod
    def alpha_blend(
        foreground: np.ndarray,
        background: np.ndarray,
        mask: np.ndarray,
        position: tuple = (0, 0),
    ) -> np.ndarray:
        """
        Alpha通道融合

        参数:
            foreground: 前景图像 (H, W, 3)
            background: 背景图像 (H, W, 3)
            mask: 前景遮罩 (H, W),0-255
            position: 前景在背景中的放置位置 (x, y)
        """
        import cv2

        bg = background.copy()
        fh, fw = foreground.shape[:2]
        bh, bw = bg.shape[:2]
        px, py = position

        # 计算实际放置区域
        x1 = max(0, px)
        y1 = max(0, py)
        x2 = min(bw, px + fw)
        y2 = min(bh, py + fh)

        # 计算前景裁剪区域
        fx1 = x1 - px
        fy1 = y1 - py
        fx2 = fx1 + (x2 - x1)
        fy2 = fy1 + (y2 - y1)

        if x2 <= x1 or y2 <= y1:
            return bg

        # 获取前景和遮罩的对应区域
        fg_roi = foreground[fy1:fy2, fx1:fx2]
        mask_roi = mask[fy1:fy2, fx1:fx2]

        # 归一化遮罩到0-1
        alpha = mask_roi.astype(np.float32) / 255.0
        if len(alpha.shape) == 2:
            alpha = alpha[..., np.newaxis]

        # 融合
        bg_roi = bg[y1:y2, x1:x2]
        blended = fg_roi * alpha + bg_roi * (1 - alpha)
        bg[y1:y2, x1:x2] = blended.astype(np.uint8)

        return bg

    @staticmethod
    def color_transfer(source: np.ndarray, target: np.ndarray) -> np.ndarray:
        """
        颜色迁移:将目标图像的颜色分布迁移到源图像
        Reinhard颜色迁移算法
        """
        import cv2

        # 转换到LAB颜色空间
        src_lab = cv2.cvtColor(source, cv2.COLOR_BGR2LAB).astype(np.float32)
        tgt_lab = cv2.cvtColor(target, cv2.COLOR_BGR2LAB).astype(np.float32)

        # 计算统计量
        src_mean, src_std = src_lab.mean(axis=(0, 1)), src_lab.std(axis=(0, 1))
        tgt_mean, tgt_std = tgt_lab.mean(axis=(0, 1)), tgt_lab.std(axis=(0, 1))

        # 逐通道归一化 + 迁移
        result = (src_lab - src_mean) / (src_std + 1e-6) * tgt_std + tgt_mean
        result = np.clip(result, 0, 255).astype(np.uint8)

        return cv2.cvtColor(result, cv2.COLOR_LAB2BGR)

    @staticmethod
    def poisson_blend(
        foreground: np.ndarray,
        background: np.ndarray,
        mask: np.ndarray,
        position: tuple,
        seamless: bool = True,
    ) -> np.ndarray:
        """
        泊松融合 - 实现无缝的颜色协调合成
        使用OpenCV的seamlessClone
        """
        import cv2

        # 计算前景中心点
        fh, fw = foreground.shape[:2]
        center = (position[0] + fw // 2, position[1] + fh // 2)

        if seamless:
            # 使用泊松融合(自然融合边界)
            result = cv2.seamlessClone(
                foreground, background, mask, center, cv2.MIXED_CLONE
            )
        else:
            result = cv2.seamlessClone(
                foreground, background, mask, center, cv2.NORMAL_CLONE
            )

        return result

8. 背景替换与物体移除

基于分割的背景替换

class BackgroundReplacer:
    """
    背景替换引擎
    使用SAM/语义分割进行前景提取,然后替换背景
    """

    def __init__(self, segmentation_model: str = 'u2net'):
        self.model_type = segmentation_model
        self.model = None

    def load(self):
        """加载分割模型"""
        if self.model_type == 'u2net':
            self._load_u2net()
        elif self.model_type == 'sam2':
            self._load_sam2()

    def extract_foreground(
        self,
        image: np.ndarray,
        refine_edges: bool = True,
    ) -> tuple:
        """
        提取前景和遮罩

        返回: (foreground, mask)
        """
        import cv2

        # 模型推理获取粗略遮罩
        mask = self._predict_mask(image)

        if refine_edges:
            # 边缘精修
            mask = self._refine_edge(image, mask)

        # 应用遮罩提取前景
        foreground = cv2.bitwise_and(image, image, mask=mask)

        return foreground, mask

    def replace_background(
        self,
        image: np.ndarray,
        new_background: np.ndarray,
        mask: np.ndarray = None,
        edge_feather: int = 5,
    ) -> np.ndarray:
        """
        替换图像背景

        参数:
            image: 原始图像
            new_background: 新背景图像
            mask: 前景遮罩(如不提供则自动计算)
            edge_feather: 边缘羽化像素数
        """
        import cv2

        if mask is None:
            _, mask = self.extract_foreground(image)

        # 调整背景尺寸
        h, w = image.shape[:2]
        bg_resized = cv2.resize(new_background, (w, h))

        # 边缘羽化处理
        if edge_feather > 0:
            kernel = np.ones((edge_feather, edge_feather), np.float32) / (edge_feather ** 2)
            mask_float = mask.astype(np.float32) / 255.0
            mask_blurred = cv2.filter2D(mask_float, -1, kernel)
            mask_blurred = np.clip(mask_blurred * 255, 0, 255).astype(np.uint8)
        else:
            mask_blurred = mask

        # Alpha混合
        alpha = mask_blurred.astype(np.float32) / 255.0
        alpha = alpha[..., np.newaxis]

        result = image * alpha + bg_resized * (1 - alpha)
        return result.astype(np.uint8)

    def _predict_mask(self, image: np.ndarray) -> np.ndarray:
        """使用模型预测前景遮罩"""
        # 实际实现中调用U2Net/SAM等模型
        import cv2
        h, w = image.shape[:2]
        # 这里返回一个模拟遮罩
        mask = np.zeros((h, w), dtype=np.uint8)
        mask[h//4:3*h//4, w//4:3*w//4] = 255
        return mask

    @staticmethod
    def _refine_edge(image: np.ndarray, mask: np.ndarray) -> np.ndarray:
        """使用GrabCut精修边缘"""
        import cv2
        bgd_model = np.zeros((1, 65), np.float64)
        fgd_model = np.zeros((1, 65), np.float64)

        # 将mask转为GrabCut格式
        grabcut_mask = np.where(mask > 127, cv2.GC_FGD, cv2.GC_BGD).astype(np.uint8)

        try:
            cv2.grabCut(
                image, grabcut_mask, None,
                bgd_model, fgd_model, 5, cv2.GC_INIT_WITH_MASK
            )
            refined = np.where(
                (grabcut_mask == cv2.GC_FGD) | (grabcut_mask == cv2.GC_PR_FGD),
                255, 0
            ).astype(np.uint8)
        except cv2.error:
            refined = mask

        return refined


class ObjectRemover:
    """
    物体移除器
    结合检测模型和修复模型实现自动物体移除
    """

    def __init__(self, detection_model=None, inpainting_model=None):
        self.detector = detection_model
        self.inpainter = inpainting_model

    def remove_by_class(
        self,
        image: np.ndarray,
        target_classes: list,
        padding: int = 15,
        prompt: str = "",
    ) -> np.ndarray:
        """
        移除图像中指定类别的物体

        参数:
            image: 输入图像
            target_classes: 要移除的物体类别列表
            padding: 遮罩扩展像素
            prompt: 修复提示(留空则使用上下文填充)
        """
        import cv2

        # 检测目标物体
        detections = self._detect_objects(image, target_classes)

        if not detections:
            return image.copy()

        # 生成合并遮罩
        mask = np.zeros(image.shape[:2], dtype=np.uint8)
        for det in detections:
            x1, y1, x2, y2 = det['bbox']
            x1 = max(0, x1 - padding)
            y1 = max(0, y1 - padding)
            x2 = min(image.shape[1], x2 + padding)
            y2 = min(image.shape[0], y2 + padding)
            mask[y1:y2, x1:x2] = 255

        # 执行修复
        result = self.inpainter.inpaint(
            image=image,
            mask=mask,
            prompt=prompt or "clean background, natural scene",
        )

        return result

    def _detect_objects(self, image: np.ndarray, target_classes: list) -> list:
        """检测图像中的目标物体"""
        # 实际实现中使用YOLO/DETR等检测模型
        return []

9. 批量图像处理工作流

生产环境中,往往需要处理成百上千张图像。设计高效的批处理工作流至关重要。

import asyncio
import os
from dataclasses import dataclass
from typing import List, Callable, Dict, Optional
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path

@dataclass
class ProcessingTask:
    """图像处理任务"""
    task_id: str
    input_path: str
    output_path: str
    operation: str
    params: Dict = None
    status: str = 'pending'
    result: Optional[Dict] = None
    error: Optional[str] = None

class BatchImageProcessor:
    """
    批量图像处理引擎
    支持任务队列、并发控制、进度跟踪和错误恢复
    """

    def __init__(self, max_workers: int = 4, output_dir: str = './output'):
        self.max_workers = max_workers
        self.output_dir = output_dir
        self.operations: Dict[str, Callable] = {}
        self.task_queue: List[ProcessingTask] = []
        self.completed: List[ProcessingTask] = []
        self.failed: List[ProcessingTask] = []

        os.makedirs(output_dir, exist_ok=True)

    def register_operation(self, name: str, fn: Callable):
        """注册图像处理操作"""
        self.operations[name] = fn

    def add_task(self, task: ProcessingTask):
        """添加处理任务"""
        self.task_queue.append(task)

    def add_batch(
        self,
        input_dir: str,
        operation: str,
        params: Dict = None,
        output_suffix: str = '_processed',
        extensions: tuple = ('.jpg', '.jpeg', '.png', '.webp'),
    ):
        """批量添加目录中的图像任务"""
        input_path = Path(input_dir)
        for img_file in input_path.iterdir():
            if img_file.suffix.lower() in extensions:
                output_name = f"{img_file.stem}{output_suffix}{img_file.suffix}"
                self.add_task(ProcessingTask(
                    task_id=f"task_{len(self.task_queue)}",
                    input_path=str(img_file),
                    output_path=os.path.join(self.output_dir, output_name),
                    operation=operation,
                    params=params or {},
                ))

    async def process_all(self, progress_callback: Callable = None) -> Dict:
        """
        并发处理所有任务

        参数:
            progress_callback: 进度回调函数 (completed, total, current_task)
        """
        total = len(self.task_queue)
        semaphore = asyncio.Semaphore(self.max_workers)
        completed_count = 0

        async def process_one(task: ProcessingTask):
            nonlocal completed_count
            async with semaphore:
                try:
                    task.status = 'processing'
                    op_fn = self.operations.get(task.operation)
                    if not op_fn:
                        raise ValueError(f"未知操作: {task.operation}")

                    # 在线程池中执行CPU密集操作
                    loop = asyncio.get_event_loop()
                    result = await loop.run_in_executor(
                        None, op_fn, task.input_path, task.output_path, task.params
                    )

                    task.status = 'completed'
                    task.result = result
                    self.completed.append(task)

                except Exception as e:
                    task.status = 'failed'
                    task.error = str(e)
                    self.failed.append(task)

                completed_count += 1
                if progress_callback:
                    progress_callback(completed_count, total, task)

        # 并发执行
        tasks = [process_one(t) for t in self.task_queue]
        await asyncio.gather(*tasks)

        return {
            'total': total,
            'completed': len(self.completed),
            'failed': len(self.failed),
            'success_rate': f"{len(self.completed)/total*100:.1f}%",
        }

    def get_failure_report(self) -> str:
        """生成失败任务报告"""
        if not self.failed:
            return "所有任务执行成功 ✓"

        lines = [f"失败任务报告 ({len(self.failed)} 个):\n"]
        for task in self.failed:
            lines.append(f"  [{task.task_id}] {task.input_path}")
            lines.append(f"    操作: {task.operation}")
            lines.append(f"    错误: {task.error}")
            lines.append("")
        return '\n'.join(lines)


# 使用示例
def demo_batch_processing():
    """批量处理工作流示例"""

    processor = BatchImageProcessor(
        max_workers=4,
        output_dir='./processed_images'
    )

    # 注册处理操作
    def super_res_operation(input_path, output_path, params):
        import cv2
        # 实际调用超分模型
        img = cv2.imread(input_path)
        # 模拟处理
        scale = params.get('scale', 2)
        h, w = img.shape[:2]
        result = cv2.resize(img, (w * scale, h * scale), interpolation=cv2.INTER_LANCZOS4)
        cv2.imwrite(output_path, result)
        return {'input_size': (h, w), 'output_size': result.shape[:2]}

    def style_transfer_operation(input_path, output_path, params):
        import cv2
        # 实际调用风格迁移模型
        img = cv2.imread(input_path)
        cv2.imwrite(output_path, img)  # 模拟
        return {'status': 'styled'}

    processor.register_operation('super_resolution', super_res_operation)
    processor.register_operation('style_transfer', style_transfer_operation)

    # 添加批量任务
    processor.add_batch(
        input_dir='./input_images',
        operation='super_resolution',
        params={'scale': 2},
    )

    # 进度回调
    def on_progress(completed, total, current):
        print(f"[{completed}/{total}] {current.task_id} - {current.status}")

    # 执行处理
    loop = asyncio.new_event_loop()
    result = loop.run_until_complete(
        processor.process_all(progress_callback=on_progress)
    )

    print(f"\n处理完成: {result}")
    print(processor.get_failure_report())

10. 实战案例:AI图像编辑工具开发

将上述技术整合为一个可交互的图像编辑工具。

import gradio as gr
from pathlib import Path

class AIImageEditor:
    """
    基于Gradio的AI图像编辑工具
    集成修复、超分、风格迁移、背景替换等功能
    """

    def __init__(self):
        self.inpainter = None
        self.super_res = None
        self.style_transfer = None
        self.bg_replacer = None

    def load_models(self):
        """加载所有模型"""
        print("加载修复模型...")
        # self.inpainter = DiffusionInpainter()
        # self.inpainter.load()

        print("加载超分模型...")
        # self.super_res = SuperResolutionPipeline()
        # self.super_res.load_model()

        print("加载风格迁移模型...")
        # self.style_transfer = AdaINStyleTransfer()

        print("加载背景替换模型...")
        # self.bg_replacer = BackgroundReplacer()
        # self.bg_replacer.load()

        print("所有模型加载完成 ✓")

    def build_ui(self) -> gr.Blocks:
        """构建Gradio界面"""
        with gr.Blocks(title="AI图像编辑工具", theme=gr.themes.Soft()) as app:
            gr.Markdown("# 🎨 AI图像编辑工具")

            with gr.Tabs():
                # Tab 1: 图像修复
                with gr.Tab("🖌️ 图像修复"):
                    with gr.Row():
                        inpaint_image = gr.Image(label="原始图像", type="numpy")
                        inpaint_mask = gr.Image(label="遮罩(白色=修复区域)", type="numpy")
                    inpaint_prompt = gr.Textbox(
                        label="修复提示",
                        placeholder="描述期望生成的内容...",
                    )
                    inpaint_btn = gr.Button("执行修复", variant="primary")
                    inpaint_output = gr.Image(label="修复结果")

                # Tab 2: 超分辨率
                with gr.Tab("🔍 超分辨率"):
                    sr_input = gr.Image(label="输入图像", type="numpy")
                    sr_scale = gr.Slider(
                        minimum=2, maximum=8, value=4, step=1,
                        label="放大倍数"
                    )
                    sr_btn = gr.Button("执行放大", variant="primary")
                    sr_output = gr.Image(label="放大结果")

                # Tab 3: 风格迁移
                with gr.Tab("🎭 风格迁移"):
                    with gr.Row():
                        style_content = gr.Image(label="内容图像", type="numpy")
                        style_ref = gr.Image(label="风格参考", type="numpy")
                    style_strength = gr.Slider(
                        minimum=0, maximum=1, value=0.6, step=0.05,
                        label="风格强度"
                    )
                    style_btn = gr.Button("执行风格迁移", variant="primary")
                    style_output = gr.Image(label="迁移结果")

                # Tab 4: 背景替换
                with gr.Tab("🌄 背景替换"):
                    with gr.Row():
                        bg_input = gr.Image(label="原始图像", type="numpy")
                        bg_new = gr.Image(label="新背景", type="numpy")
                    bg_btn = gr.Button("替换背景", variant="primary")
                    bg_output = gr.Image(label="替换结果")

            # 绑定事件
            inpaint_btn.click(
                fn=self._do_inpaint,
                inputs=[inpaint_image, inpaint_mask, inpaint_prompt],
                outputs=inpaint_output,
            )
            sr_btn.click(
                fn=self._do_super_res,
                inputs=[sr_input, sr_scale],
                outputs=sr_output,
            )
            style_btn.click(
                fn=self._do_style_transfer,
                inputs=[style_content, style_ref, style_strength],
                outputs=style_output,
            )
            bg_btn.click(
                fn=self._do_bg_replace,
                inputs=[bg_input, bg_new],
                outputs=bg_output,
            )

        return app

    def _do_inpaint(self, image, mask, prompt):
        if image is None or mask is None:
            return None
        # 实际调用self.inpainter
        return image  # placeholder

    def _do_super_res(self, image, scale):
        if image is None:
            return None
        return image  # placeholder

    def _do_style_transfer(self, content, style, strength):
        if content is None or style is None:
            return None
        return content  # placeholder

    def _do_bg_replace(self, image, background):
        if image is None or background is None:
            return None
        return image  # placeholder

    def launch(self, port: int = 7860, share: bool = False):
        """启动Web服务"""
        app = self.build_ui()
        app.launch(server_port=port, share=share)


# 启动
if __name__ == '__main__':
    editor = AIImageEditor()
    editor.load_models()
    editor.launch(port=7860)

11. 商业应用场景与最佳实践

电商场景

商品主图优化

  • 自动背景替换:将商品放到纯色或场景化背景中
  • 超分辨率增强:将低清供应商图片提升到高清标准
  • 批量水印和风格统一

实现要点:

class EcommerceImagePipeline:
    """电商图像批处理管线"""

    def __init__(self):
        self.bg_replacer = BackgroundReplacer()
        self.super_res = SuperResolutionPipeline(scale=2)

    def process_product_image(
        self,
        image_path: str,
        background_style: str = 'white',
    ) -> str:
        """
        单张商品图处理流程:
        1. 背景替换
        2. 超分辨率增强
        3. 裁剪和尺寸标准化
        """
        import cv2
        img = cv2.imread(image_path)

        # 步骤1:背景替换
        if background_style == 'white':
            bg = np.ones_like(img) * 255
        else:
            bg = self._get_scene_background(background_style)

        _, mask = self.bg_replacer.extract_foreground(img)
        result = self.bg_replacer.replace_background(img, bg, mask)

        # 步骤2:超分辨率
        result = self.super_res.enhance(result)

        # 步骤3:标准化尺寸
        result = self._resize_to_standard(result, (1000, 1000))

        return result

    def _resize_to_standard(self, img, target_size):
        import cv2
        h, w = img.shape[:2]
        scale = min(target_size[0] / w, target_size[1] / h)
        new_w, new_h = int(w * scale), int(h * scale)
        resized = cv2.resize(img, (new_w, new_h))
        # 居中填充
        canvas = np.ones((target_size[1], target_size[0], 3), dtype=np.uint8) * 255
        x_off = (target_size[0] - new_w) // 2
        y_off = (target_size[1] - new_h) // 2
        canvas[y_off:y_off+new_h, x_off:x_off+new_w] = resized
        return canvas

    def _get_scene_background(self, style):
        # 从预设库或AI生成获取场景背景
        pass

设计与创意场景

品牌视觉一致性

  • 使用IP-Adapter保持品牌风格统一
  • 批量生成营销素材(海报、Banner、社交媒体图)
  • A/B测试不同风格版本

影视与游戏

场景概念设计

  • 用ControlNet草图快速生成概念图
  • 角色一致性生成(同一角色不同场景)
  • 纹理和材质生成

最佳实践清单

  1. 质量控制:建立自动化质量评估管线,过滤低质量输出
  2. 内容审核:对AI生成内容进行安全审核,避免不当内容
  3. 版权合规:使用许可明确的模型和训练数据,避免侵权风险
  4. 成本优化
    • 使用LCM/Turbo模型减少推理步数
    • 合理使用GPU批处理提高吞吐
    • 缓存常用风格和模型权重
  5. 用户体验:提供实时预览、撤销/重做、参数微调等交互能力
  6. 可扩展性:采用插件架构,方便添加新的AI能力模块

技术选型建议

场景 推荐方案 推理速度 质量
实时风格迁移 AdaIN / CycleGAN <100ms
高质量修复 SD Inpainting 2-5s
精确可控生成 ControlNet + SD 3-8s
风格一致性 IP-Adapter 3-5s
快速放大 Real-ESRGAN <1s 中高
高质量放大 SwinIR / HAT 2-5s 极高
前景提取 SAM2 / U2Net <1s

部署架构参考

用户请求 → API Gateway → 任务队列(Redis/RabbitMQ)
                              ↓
                    Worker Pool (GPU集群)
                    ├── 修复Worker
                    ├── 超分Worker
                    ├── 风格Worker
                    └── 分割Worker
                              ↓
                    结果存储(S3/MinIO) → CDN → 用户

关键指标监控:

  • P50/P95延迟:不同操作的响应时间
  • GPU利用率:目标>70%
  • 任务成功率:目标>99%
  • 排队深度:防止任务堆积

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录