實驗二: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格式的備用圖像
本文章為轉載內容,我們尊重原作者對文章享有的著作權。如有內容錯誤或侵權問題,歡迎原作者聯繫我們進行內容更正或刪除文章。