2025.10.21实验二_AI插图生成平台

实验二:AI插图生成平台

核心代码

1. 图像生成服务 (services/image_service.py)

import os
import base64
from datetime import datetime
from dotenv import load_dotenv
from utils.aliyun_client import AliyunClient
import requests
import json

# 加载环境变量
load_dotenv()

def generate_image(prompt, style=None, size=None):
    """
    使用阿里云百炼平台的图像生成服务创建故事插图
    
    Args:
        prompt: 图像描述提示词
        style: 图像风格
        size: 图像尺寸
    
    Returns:
        dict: 包含图像信息的字典
    """
    # 确保uploads目录存在
    uploads_dir = os.path.abspath(os.getenv('UPLOAD_FOLDER', './uploads'))
    os.makedirs(uploads_dir, exist_ok=True)
    
    # 构建优化后的提示词
    optimized_prompt = _optimize_prompt_for_children_illustration(prompt, style)
    
    print(f"🎨 原始提示词: {prompt}")
    print(f"🎨 优化后提示词: {optimized_prompt}")
    
    # 创建阿里云客户端
    client = AliyunClient()
    
    try:
        # 设置图像参数
        image_params = {
            "model": "wan2.5-t2i-preview",
            "n": 1,
            "size": size or "1024x1024"
        }
        
        # 调用图像生成API
        print("🔄 调用阿里云图像生成API...")
        result = client.generate_image(optimized_prompt, **image_params)
        
        print(f"✅ 图像生成API返回结果: {result}")
        
        # 处理API响应,尝试多种可能的数据结构
        image_data = None
        
        # 检查不同的响应结构
        if 'results' in result:
            if isinstance(result['results'], list) and len(result['results']) > 0:
                # 检查 results.data 或 results.url 或 results.output
                for item in result['results']:
                    if 'data' in item and item['data']:
                        # 处理base64编码的图像数据
                        image_data = item['data']
                        break
                    elif 'url' in item and item['url']:
                        # 处理URL形式的图像数据
                        try:
                            response = requests.get(item['url'], timeout=10)
                            if response.status_code == 200:
                                image_data = base64.b64encode(response.content).decode('utf-8')
                                break
                        except Exception as e:
                            print(f"⚠️ 下载图像失败: {e}")
                    elif 'output' in item and 'images' in item['output'] and item['output']['images']:
                        # 处理output.images形式的图像数据
                        image_data = item['output']['images'][0]
                        break
        elif 'output' in result and 'images' in result['output']:
            # 直接从output.images获取
            if result['output']['images']:
                image_data = result['output']['images'][0]
        elif 'data' in result and result['data']:
            # 直接从data字段获取
            image_data = result['data']
        elif 'url' in result and result['url']:
            # 直接从url字段获取
            try:
                response = requests.get(result['url'], timeout=10)
                if response.status_code == 200:
                    image_data = base64.b64encode(response.content).decode('utf-8')
            except Exception as e:
                print(f"⚠️ 下载图像失败: {e}")
        
        # 如果没有找到有效的图像数据,使用备用方案
        if not image_data:
            print("❌ 未找到有效的图像数据,使用备用方案")
            image_info = _generate_fallback_image(prompt, uploads_dir)
            return image_info
        
        # 确保image_data是base64编码的
        if not image_data.startswith('data:image') and not image_data.startswith('iVBORw0KGgo'):
            # 如果不是标准的data URL,可能需要添加前缀
            if 'http' in image_data:
                # 如果是URL,尝试下载
                try:
                    response = requests.get(image_data, timeout=10)
                    if response.status_code == 200:
                        image_data = base64.b64encode(response.content).decode('utf-8')
                except Exception as e:
                    print(f"⚠️ 下载图像失败: {e}")
                    image_info = _generate_fallback_image(prompt, uploads_dir)
                    return image_info
            else:
                # 如果不是URL,尝试直接使用base64数据
                pass
        
        # 清理base64数据
        if image_data.startswith('data:image'):
            # 移除data:image前缀
            image_data = image_data.split(',')[1]
        
        try:
            # 解码base64数据
            image_bytes = base64.b64decode(image_data)
            
            # 生成文件名
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            image_filename = f"story_illustration_{timestamp}.png"
            image_path = os.path.join(uploads_dir, image_filename)
            
            # 保存图像文件
            with open(image_path, 'wb') as f:
                f.write(image_bytes)
            
            print(f"✅ 图像保存成功: {image_path}")
            
            # 返回图像信息
            return {
                'filename': image_filename,
                'path': image_path,
                'url': f"/uploads/{image_filename}",
                'prompt': prompt,
                'style': style,
                'size': size or "1024x1024"
            }
            
        except Exception as e:
            print(f"❌ 图像处理失败: {e}")
            # 使用备用方案
            image_info = _generate_fallback_image(prompt, uploads_dir)
            return image_info
            
    except Exception as e:
        error_msg = str(e)
        print(f"❌ 图像生成异常: {error_msg}")
        
        # 特殊处理API频率限制错误
        if "429" in error_msg or "Too Many Requests" in error_msg:
            raise Exception("图像生成服务暂时繁忙,请稍后再试。建议等待1-2分钟后再进行图像生成。")
        elif "400" in error_msg and "InvalidParameter" in error_msg:
            raise Exception("图像生成参数错误,请检查提示词内容。")
        else:
            # 使用备用方案
            image_info = _generate_fallback_image(prompt, uploads_dir)
            return image_info

def _optimize_prompt_for_children_illustration(prompt, style=None):
    """
    优化提示词以生成适合儿童的插图
    
    Args:
        prompt: 原始提示词
        style: 图像风格
    
    Returns:
        str: 优化后的提示词
    """
    # 基础提示词模板
    base_prompt = "儿童插画,风格可爱,明亮温暖的色彩,清晰的线条,卡通风格,适合儿童绘本,高清细节,"
    
    # 风格映射
    style_map = {
        'cartoon': "迪士尼卡通风格,色彩鲜艳,角色圆润可爱",
        'watercolor': "水彩画风格,柔和的色彩过渡,轻盈透明",
        'flat': "扁平化设计,简约干净,明亮色调",
        'anime': "日式动漫风格,大眼睛角色,明亮色彩",
        'sketch': "素描风格,线条清晰,黑白为主"
    }
    
    # 添加风格描述
    if style and style in style_map:
        base_prompt += style_map[style] + ", "
    
    # 添加用户提示词
    base_prompt += prompt
    
    # 添加额外要求
    base_prompt += ", 适合3-12岁儿童,无恐怖元素,积极向上,温馨友好"
    
    return base_prompt

def _generate_fallback_image(prompt, uploads_dir):
    """
    生成备用图像(当AI图像生成失败时使用)
    
    Args:
        prompt: 原始提示词
        uploads_dir: 上传目录
    
    Returns:
        dict: 包含图像信息的字典
    """
    print("🔄 生成备用图像...")
    
    # 生成文件名
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    image_filename = f"fallback_image_{timestamp}.svg"
    image_path = os.path.join(uploads_dir, image_filename)
    
    # 创建简单的SVG图像
    # 提取主题关键词用于图像描述
    theme_summary = prompt[:30] + "..." if len(prompt) > 30 else prompt
    
    svg_content = f'''
    <svg width="500" height="500" xmlns="http://www.w3.org/2000/svg">
      <!-- 背景 -->
      <rect width="500" height="500" fill="#e6f7ff"/>
      
      <!-- 中心圆 -->
      <circle cx="250" cy="250" r="200" fill="#f0f9ff" stroke="#91d5ff" stroke-width="4"/>
      
      <!-- 图标 -->
      <text x="250" y="200" font-family="Arial, sans-serif" font-size="80" text-anchor="middle" fill="#69c0ff">📚</text>
      
      <!-- 文本 -->
      <text x="250" y="300" font-family="Arial, sans-serif" font-size="20" text-anchor="middle" fill="#1890ff">故事插图</text>
      <text x="250" y="340" font-family="Arial, sans-serif" font-size="14" text-anchor="middle" fill="#40a9ff">{theme_summary}</text>
      <text x="250" y="380" font-family="Arial, sans-serif" font-size="12" text-anchor="middle" fill="#91d5ff">AI图像生成暂不可用</text>
      
      <!-- 装饰元素 -->
      <circle cx="100" cy="100" r="20" fill="#fff2e8" stroke="#ffbb96" stroke-width="2"/>
      <circle cx="400" cy="100" r="15" fill="#f6ffed" stroke="#b7eb8f" stroke-width="2"/>
      <circle cx="100" cy="400" r="18" fill="#fff1f0" stroke="#ffadd2" stroke-width="2"/>
      <circle cx="400" cy="400" r="12" fill="#f0f5ff" stroke="#adc6ff" stroke-width="2"/>
    </svg>
    '''
    
    # 保存SVG文件
    with open(image_path, 'w', encoding='utf-8') as f:
        f.write(svg_content.strip())
    
    print(f"✅ 备用图像生成成功: {image_path}")
    
    # 返回图像信息
    return {
        'filename': image_filename,
        'path': image_path,
        'url': f"/uploads/{image_filename}",
        'prompt': prompt,
        'style': 'fallback',
        'size': '500x500'
    }

2. 图像生成API路由 (routes/image_routes.py)

from flask import Blueprint, request, jsonify

# 创建图像相关的蓝图
bp = Blueprint('image', __name__)

# 导入图像生成服务
from services.image_service import generate_image

# 图像生成路由
@bp.route('/generate', methods=['POST'])
def generate_image_route():
    try:
        # 获取请求数据
        data = request.json
        
        # 验证必要的参数
        if not data or 'prompt' not in data:
            return jsonify({'error': '缺少必要的提示词参数'}), 400
        
        # 获取参数
        prompt = data['prompt']
        style = data.get('style', 'cartoon')  # 默认卡通风格
        size = data.get('size', '1024x1024')  # 默认尺寸
        
        # 调用图像生成服务
        image_data = generate_image(prompt, style, size)
        
        # 返回成功响应
        return jsonify(image_data), 200
        
    except Exception as e:
        # 记录错误并返回错误响应
        print(f"❌ 图像生成API错误: {e}")
        return jsonify({'error': f'生成图像时发生错误: {str(e)}'}), 500

功能说明

图像生成服务

  • 核心功能:使用阿里云百炼平台的图像生成API创建故事插图
  • 提示词优化:针对儿童插画特点优化提示词,支持多种艺术风格
  • 多来源数据提取:灵活处理不同格式的API响应(base64、URL、嵌套结构)
  • 错误处理:完善的异常捕获和备用图像生成机制
  • 图像保存:将生成的图像保存到本地文件系统

备用图像生成

  • 应急方案:当API调用失败时,自动生成SVG格式的备用图像
  • 内容关联:备用图像包含故事主题摘要,保持与原始请求的关联性
  • 友好提示:清晰指示这是备用图像,避免用户困惑

API路由

  • 生成图像:POST /image/generate,接收提示词、风格和尺寸参数
  • 参数验证:确保必要参数存在,设置合理默认值
  • 错误响应:统一的错误处理和状态码返回

技术栈

  • Python 3.12
  • Flask Web框架
  • 阿里云百炼平台图像生成API
  • SVG生成(备用方案)
  • Base64编码处理
  • 异常处理与容错机制

使用方法

  1. 确保配置了阿里云API密钥(在.env文件中)
  2. 调用 /image/generate 端点,传入故事相关的提示词
  3. 可选指定图像风格和尺寸
  4. API返回生成的图像信息,包括文件名、路径和URL

与Story模型的集成

  • 生成的图像URL可以保存到Story模型的image_url字段
  • 支持为每个故事关联配套插图
  • 与实验一的故事生成服务协同工作

特色功能

  • 多样化风格支持:卡通、水彩、扁平化、动漫等多种艺术风格
  • 儿童友好优化:专为儿童设计的插图风格,确保内容健康正面
  • 多级容错机制:API失败时的备用方案,保证服务可用性
  • 灵活的图像格式:支持PNG格式的AI生成图像和SVG格式的备用图像
posted @ 2025-12-28 23:15  ysd666  阅读(17)  评论(0)    收藏  举报