实验二: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编码处理
- 异常处理与容错机制
使用方法
- 确保配置了阿里云API密钥(在.env文件中)
- 调用 /image/generate 端点,传入故事相关的提示词
- 可选指定图像风格和尺寸
- API返回生成的图像信息,包括文件名、路径和URL
与Story模型的集成
- 生成的图像URL可以保存到Story模型的image_url字段
- 支持为每个故事关联配套插图
- 与实验一的故事生成服务协同工作
特色功能
- 多样化风格支持:卡通、水彩、扁平化、动漫等多种艺术风格
- 儿童友好优化:专为儿童设计的插图风格,确保内容健康正面
- 多级容错机制:API失败时的备用方案,保证服务可用性
- 灵活的图像格式:支持PNG格式的AI生成图像和SVG格式的备用图像