基于ModelScope的AI图片生成器:完整源码与深度解析
本文介绍了一个基于Python和ModelScope API的AI图片生成工具,该工具采用Tkinter构建用户界面,支持Qwen/Qwen-Image和Liudef/XB_F.1_MIX两种先进模型。文章详细展示了该工具的功能特点,包括直观的参数设置界面、双模型支持、精细的参数控制、原图查看功能以及任务状态监控等。同时提供了完整的源代码实现,涵盖图片生成、任务提交、状态跟踪和结果显示等核心功能模
·
基于ModelScope的AI图片生成器:完整源码与深度解析
本文将完整展示一个基于Python和ModelScope API的AI图片生成工具源码,让你轻松将文字描述转化为惊艳的视觉艺术作品!
引言:AI图像生成技术的新突破
近年来,AI图像生成技术取得了令人瞩目的进展。本工具利用ModelScope平台强大的AI模型,结合Python的Tkinter GUI框架,打造了一个简单易用且功能强大的图片生成应用程序。无论你是设计师、开发者还是AI爱好者,都能通过这个工具快速生成高质量图像。
功能亮点
- 直观的用户界面:简洁明了的参数设置区域和图片预览区域
- 双模型支持:支持Qwen/Qwen-Image和Liudef/XB_F.1_MIX两种先进模型
- 参数精细控制:可调整图像尺寸、采样步数、引导系数等关键参数
- 原图查看功能:点击预览图即可查看高分辨率原图
- 任务状态监控:实时显示任务提交、排队、生成等状态
- 自动命名保存:生成的图片按序号自动命名保存
完整源代码实现
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, Frame, Label, Spinbox
import threading
import os
import time
import requests
import json
from PIL import Image, ImageTk
from io import BytesIO
import queue
import datetime
# 全局配置
base_url = 'https://api-inference.modelscope.cn/'
DEFAULT_API_KEY = "平台获取key"
MODELS = ["Qwen/Qwen-Image", "Liudef/XB_F.1_MIX"]
DEFAULT_NEGATIVE_PROMPT = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"
STATUS_MESSAGES = {
"SUBMITTED": "任务已提交",
"QUEUED": "任务排队中",
"RUNNING": "图片生成中",
"SUCCEED": "生成成功!",
"FAILED": "生成失败"
}
class ImageViewer(tk.Toplevel):
"""用于显示原分辨率图片的窗口"""
def __init__(self, parent, image_path):
super().__init__(parent)
self.title("原分辨率图片")
self.geometry("800x600")
# 创建滚动区域
canvas = tk.Canvas(self)
scroll_y = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
scroll_x = ttk.Scrollbar(self, orient="horizontal", command=canvas.xview)
canvas.configure(yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set)
scroll_x.pack(side="bottom", fill="x")
scroll_y.pack(side="right", fill="y")
canvas.pack(side="left", fill="both", expand=True)
# 创建框架用于包含图片
frame = ttk.Frame(canvas)
canvas.create_window((0, 0), window=frame, anchor="nw")
# 加载并显示图片
try:
img = Image.open(image_path)
photo = ImageTk.PhotoImage(img)
img_label = ttk.Label(frame, image=photo)
img_label.image = photo # 保持引用
img_label.pack(padx=10, pady=10)
# 更新滚动区域
frame.update_idletasks()
canvas.config(scrollregion=canvas.bbox("all"))
except Exception as e:
messagebox.showerror("错误", f"无法加载图片: {str(e)}")
# 绑定窗口大小变化事件
self.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
class ImageGeneratorApp:
def __init__(self, root):
self.root = root
root.title("AI 图片生成器")
root.geometry("1200x700") # 更宽的窗口以适应左右布局
root.resizable(True, True)
# 初始化图片计数器
self.image_counter = self.get_next_image_number()
self.current_image = None
self.status_queue = queue.Queue()
self.running = False
self.start_time = None
self.current_image_path = None # 当前显示的图片路径
self.create_widgets()
self.root.after(100, self.process_queue)
def get_next_image_number(self):
"""获取下一个可用的图片序号"""
count = 1
while os.path.exists(f"result_image_{count:03d}.jpg"):
count += 1
return count
def create_widgets(self):
# 创建主框架 - 左右布局
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 左侧参数面板
left_frame = ttk.LabelFrame(main_frame, text="生成参数", padding=10)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, padx=(0, 10), pady=5)
# API Key 输入
api_frame = ttk.Frame(left_frame)
api_frame.pack(fill=tk.X, pady=5)
ttk.Label(api_frame, text="API Key:").pack(anchor=tk.W)
self.api_key_var = tk.StringVar(value=DEFAULT_API_KEY)
api_entry = ttk.Entry(api_frame, textvariable=self.api_key_var, width=50)
api_entry.pack(fill=tk.X, pady=(0, 10))
# 模型选择
model_frame = ttk.Frame(left_frame)
model_frame.pack(fill=tk.X, pady=5)
ttk.Label(model_frame, text="选择模型:").pack(anchor=tk.W)
self.model_var = tk.StringVar(value=MODELS[0])
model_dropdown = ttk.Combobox(model_frame, textvariable=self.model_var,
values=MODELS, state="readonly", width=40)
model_dropdown.pack(fill=tk.X, pady=(0, 10))
# 提示词输入
prompt_frame = ttk.Frame(left_frame)
prompt_frame.pack(fill=tk.BOTH, expand=True, pady=5)
ttk.Label(prompt_frame, text="正向提示词:").pack(anchor=tk.W)
self.prompt_text = scrolledtext.ScrolledText(prompt_frame, wrap=tk.WORD,
height=5, font=("Arial", 10))
self.prompt_text.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
self.prompt_text.insert(tk.END, "A golden cat")
# 负向提示词
neg_prompt_frame = ttk.Frame(left_frame)
neg_prompt_frame.pack(fill=tk.BOTH, expand=True, pady=5)
ttk.Label(neg_prompt_frame, text="负向提示词 (可选):").pack(anchor=tk.W)
self.neg_prompt_text = scrolledtext.ScrolledText(neg_prompt_frame, wrap=tk.WORD,
height=3, font=("Arial", 10))
self.neg_prompt_text.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
self.neg_prompt_text.insert(tk.END, DEFAULT_NEGATIVE_PROMPT)
# 高级参数设置
params_frame = ttk.LabelFrame(left_frame, text="高级参数", padding=10)
params_frame.pack(fill=tk.X, pady=5)
# 图像尺寸设置
size_frame = ttk.Frame(params_frame)
size_frame.pack(fill=tk.X, pady=5)
ttk.Label(size_frame, text="图像尺寸:").grid(row=0, column=0, sticky=tk.W)
self.width_var = tk.IntVar(value=1024)
ttk.Label(size_frame, text="宽:").grid(row=0, column=1, padx=(10, 2))
width_spin = ttk.Spinbox(size_frame, from_=64, to=2048, increment=64,
textvariable=self.width_var, width=6)
width_spin.grid(row=0, column=2)
ttk.Label(size_frame, text="高:").grid(row=0, column=3, padx=(10, 2))
self.height_var = tk.IntVar(value=1024)
height_spin = ttk.Spinbox(size_frame, from_=64, to=2048, increment=64,
textvariable=self.height_var, width=6)
height_spin.grid(row=0, column=4)
# 采样步数
steps_frame = ttk.Frame(params_frame)
steps_frame.pack(fill=tk.X, pady=5)
ttk.Label(steps_frame, text="采样步数:").grid(row=0, column=0, sticky=tk.W)
self.steps_var = tk.IntVar(value=30)
steps_spin = ttk.Spinbox(steps_frame, from_=1, to=100, increment=1,
textvariable=self.steps_var, width=8)
steps_spin.grid(row=0, column=1, padx=5)
# 引导系数
guidance_frame = ttk.Frame(params_frame)
guidance_frame.pack(fill=tk.X, pady=5)
ttk.Label(guidance_frame, text="引导系数:").grid(row=0, column=0, sticky=tk.W)
self.guidance_var = tk.DoubleVar(value=3.5)
guidance_spin = ttk.Spinbox(guidance_frame, from_=1.5, to=20.0, increment=0.1,
textvariable=self.guidance_var, width=8, format="%.1f")
guidance_spin.grid(row=0, column=1, padx=5)
# 状态和操作区域
action_frame = ttk.Frame(left_frame)
action_frame.pack(fill=tk.X, pady=10)
self.generate_btn = ttk.Button(action_frame, text="生成图片",
command=self.start_generation, width=15)
self.generate_btn.pack(side=tk.LEFT, padx=5)
self.progress = ttk.Progressbar(action_frame, mode='indeterminate')
self.progress.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
# 计时器显示
time_frame = ttk.Frame(left_frame)
time_frame.pack(fill=tk.X, pady=5)
ttk.Label(time_frame, text="生成耗时:").pack(side=tk.LEFT, padx=5)
self.time_var = tk.StringVar(value="00:00")
time_label = ttk.Label(time_frame, textvariable=self.time_var,
font=("Arial", 10), foreground="green")
time_label.pack(side=tk.LEFT, padx=5)
# 状态显示
status_frame = ttk.Frame(left_frame)
status_frame.pack(fill=tk.X, pady=5)
self.status_var = tk.StringVar(value="就绪")
status_label = ttk.Label(status_frame, textvariable=self.status_var,
font=("Arial", 10, "bold"), foreground="blue", wraplength=300)
status_label.pack(anchor=tk.W)
# 右侧图片显示区域
right_frame = ttk.LabelFrame(main_frame, text="图片预览", padding=10)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, pady=5)
# 图片显示区域
self.image_frame = ttk.Frame(right_frame)
self.image_frame.pack(fill=tk.BOTH, expand=True)
# 初始提示
self.image_label = ttk.Label(self.image_frame, text="图片将在此处显示\n(点击图片可查看原分辨率)",
anchor=tk.CENTER, justify=tk.CENTER,
background="#f0f0f0", font=("Arial", 12))
self.image_label.pack(fill=tk.BOTH, expand=True)
# 添加点击事件绑定
self.image_label.bind("<Button-1>", self.show_original_image)
def show_original_image(self, event=None):
"""显示原分辨率图片"""
if self.current_image_path:
ImageViewer(self.root, self.current_image_path)
def start_generation(self):
"""开始生成图片"""
if self.running:
messagebox.showwarning("警告", "当前已有任务正在运行")
return
prompt = self.prompt_text.get("1.0", tk.END).strip()
if not prompt:
messagebox.showerror("错误", "请输入提示词")
return
# 获取所有参数
width = self.width_var.get()
height = self.height_var.get()
steps = self.steps_var.get()
guidance = self.guidance_var.get()
negative_prompt = self.neg_prompt_text.get("1.0", tk.END).strip()
# 验证参数
if width < 64 or width > 2048 or height < 64 or height > 2048:
messagebox.showerror("错误", "图像尺寸必须在64-2048像素之间")
return
if steps < 1 or steps > 100:
messagebox.showerror("错误", "采样步数必须在1-100之间")
return
if guidance < 1.5 or guidance > 20.0:
messagebox.showerror("错误", "引导系数必须在1.5-20.0之间")
return
self.running = True
self.start_time = time.time()
self.generate_btn.config(state=tk.DISABLED)
self.progress.start(10)
self.status_var.set("提交任务...")
self.time_var.set("00:00")
# 启动计时器更新
self.update_timer()
# 启动生成线程
threading.Thread(
target=self.generate_image,
args=(self.api_key_var.get(), self.model_var.get(), prompt,
negative_prompt, f"{width}x{height}", steps, guidance),
daemon=True
).start()
def update_timer(self):
"""更新计时器显示"""
if self.running and self.start_time:
elapsed = time.time() - self.start_time
mins, secs = divmod(int(elapsed), 60)
self.time_var.set(f"{mins:02d}:{secs:02d}")
if self.running:
self.root.after(1000, self.update_timer)
def generate_image(self, api_key, model, prompt, negative_prompt, size, steps, guidance):
"""生成图片的线程函数"""
self.status_queue.put(("SUBMITTED", "任务已提交"))
try:
# 提交任务
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
# 构建请求数据
request_data = {
"model": model,
"prompt": prompt,
"size": size,
"steps": steps,
"guidance": guidance
}
# 添加负向提示词(如果非空)
if negative_prompt:
request_data["negative_prompt"] = negative_prompt
response = requests.post(
f"{base_url}v1/images/generations",
headers={**headers, "X-ModelScope-Async-Mode": "true"},
data=json.dumps(request_data, ensure_ascii=False).encode('utf-8')
)
response.raise_for_status()
task_id = response.json()["task_id"]
self.status_queue.put(("QUEUED", f"任务ID: {task_id}"))
# 轮询任务状态
while True:
result = requests.get(
f"{base_url}v1/tasks/{task_id}",
headers={**headers, "X-ModelScope-Task-Type": "image_generation"},
)
result.raise_for_status()
data = result.json()
status = data["task_status"]
self.status_queue.put((status, STATUS_MESSAGES.get(status, status)))
if status == "SUCCEED":
# 保存图片
filename = f"result_image_{self.image_counter:03d}.jpg"
self.image_counter += 1
img_response = requests.get(data["output_images"][0])
img_response.raise_for_status()
with open(filename, "wb") as f:
f.write(img_response.content)
# 记录当前图片路径
self.current_image_path = filename
# 计算总耗时
total_time = time.time() - self.start_time
mins, secs = divmod(int(total_time), 60)
# 在UI中显示图片
self.status_queue.put(("IMAGE", (filename, total_time)))
self.status_queue.put(("TIME", f"总耗时: {mins}分{secs}秒"))
break
elif status == "FAILED":
# 计算总耗时
total_time = time.time() - self.start_time
mins, secs = divmod(int(total_time), 60)
self.status_queue.put(("ERROR", f"图片生成失败 (耗时: {mins}分{secs}秒)"))
break
time.sleep(3)
except Exception as e:
# 计算总耗时
total_time = time.time() - self.start_time
mins, secs = divmod(int(total_time), 60)
self.status_queue.put(("ERROR", f"发生错误: {str(e)} (耗时: {mins}分{secs}秒)"))
finally:
self.status_queue.put(("DONE", ""))
def process_queue(self):
"""处理状态队列中的消息"""
try:
while not self.status_queue.empty():
status, message = self.status_queue.get_nowait()
if status == "IMAGE":
filename, total_time = message
self.show_image(filename)
mins, secs = divmod(int(total_time), 60)
self.status_var.set(f"图片已保存为: {filename}")
self.time_var.set(f"{mins:02d}:{secs:02d}")
elif status == "TIME":
self.time_var.set(message)
elif status == "ERROR":
self.status_var.set(message)
messagebox.showerror("错误", message)
elif status == "DONE":
self.running = False
self.generate_btn.config(state=tk.NORMAL)
self.progress.stop()
else:
self.status_var.set(message)
except queue.Empty:
pass
self.root.after(100, self.process_queue)
def show_image(self, image_path):
"""在UI中显示图片"""
try:
# 清除之前的图片
for widget in self.image_frame.winfo_children():
widget.destroy()
# 创建新的图片标签
img = Image.open(image_path)
# 调整图片大小以适应界面
max_size = (650, 450) # 更大的显示区域
img.thumbnail(max_size, Image.LANCZOS)
photo = ImageTk.PhotoImage(img)
self.image_label = ttk.Label(self.image_frame, image=photo)
self.image_label.image = photo # 保持引用
self.image_label.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 添加点击提示
tip_label = ttk.Label(self.image_frame,
text="点击图片查看原分辨率",
foreground="gray")
tip_label.pack(side=tk.BOTTOM, pady=(0, 10))
# 绑定点击事件
self.image_label.bind("<Button-1>", self.show_original_image)
except Exception as e:
self.status_queue.put(("ERROR", f"无法显示图片: {str(e)}"))
if __name__ == "__main__":
root = tk.Tk()
app = ImageGeneratorApp(root)
root.mainloop()
技术实现解析
1. 核心架构设计
该应用采用经典的MVC(模型-视图-控制器)架构:
- 模型层:负责与ModelScope API交互,处理图像生成逻辑
- 视图层:基于Tkinter构建的GUI界面
- 控制器层:协调用户操作与后台任务
2. 关键技术实现
异步任务处理与状态监控
def generate_image(self, api_key, model, prompt, negative_prompt, size, steps, guidance):
# 提交任务
response = requests.post(...)
# 获取任务ID
task_id = response.json()["task_id"]
# 轮询任务状态
while True:
result = requests.get(f"{base_url}v1/tasks/{task_id}", ...)
status = result.json()["task_status"]
if status == "SUCCEED":
# 下载并保存图片
img_response = requests.get(data["output_images"][0])
with open(filename, "wb") as f:
f.write(img_response.content)
break
多线程处理与队列通信
# 启动生成线程
threading.Thread(
target=self.generate_image,
args=(...),
daemon=True
).start()
# 队列处理
def process_queue(self):
while not self.status_queue.empty():
status, message = self.status_queue.get_nowait()
# 处理不同状态消息
高分辨率图片查看器
class ImageViewer(tk.Toplevel):
def __init__(self, parent, image_path):
# 创建带滚动条的画布
canvas = tk.Canvas(self)
scroll_y = ttk.Scrollbar(...)
scroll_x = ttk.Scrollbar(...)
# 加载并显示图片
img = Image.open(image_path)
photo = ImageTk.PhotoImage(img)
img_label = ttk.Label(..., image=photo)
参数验证与错误处理
def start_generation(self):
# 验证提示词是否为空
if not prompt:
messagebox.showerror("错误", "请输入提示词")
return
# 验证图像尺寸范围
if width < 64 or width > 2048 or height < 64 or height > 2048:
messagebox.showerror("错误", "图像尺寸必须在64-2048像素之间")
return
# 验证其他参数范围
...
3. 界面设计亮点
程序采用左右分栏设计:
- 左侧参数区:集中所有控制选项,分组明确
- 右侧预览区:大尺寸图片显示区域,支持点击查看原图
# 主界面布局
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 左侧参数面板
left_frame = ttk.LabelFrame(main_frame, text="生成参数", padding=10)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, padx=(0, 10), pady=5)
# 右侧图片显示区域
right_frame = ttk.LabelFrame(main_frame, text="图片预览", padding=10)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, pady=5)
使用指南
1. 运行环境准备
pip install tkinter pillow requests
2. 使用步骤
- 运行程序:
python image_generator.py
- 在API Key输入框填入你的ModelScope API密钥(默认提供测试密钥)
- 选择要使用的AI模型
- 在"正向提示词"区域输入图像描述
- 调整图像尺寸、采样步数等参数(可选)
- 点击"生成图片"按钮
- 在右侧预览区查看生成的图片
- 点击图片查看高分辨率原图
3. 参数说明
参数名称 | 默认值 | 范围 | 说明 |
---|---|---|---|
图像尺寸 | 1024x1024 | 64-2048 | 控制生成图片的分辨率 |
采样步数 | 30 | 1-100 | 值越高图片质量越好,生成时间越长 |
引导系数 | 3.5 | 1.5-20.0 | 控制生成图片与提示词的匹配度 |
负向提示词 | 预置值 | 任意文本 | 排除不希望出现的元素 |
应用示例与效果展示
示例1:生成金色猫咪
- 正向提示词:
A golden cat
- 模型:Qwen/Qwen-Image
- 尺寸:1024x1024
- 效果:生成一只金色毛发的猫咪,细节丰富,毛发质感逼真
示例2:生成科幻城市景观
- 正向提示词:
Futuristic cityscape at sunset, cyberpunk style
- 负向提示词:
blurry, low quality, deformed buildings
- 模型:Liudef/XB_F.1_MIX
- 尺寸:1024x768
- 效果:生成赛博朋克风格的未来城市,霓虹灯光效果突出
使用技巧与最佳实践
-
提示词优化技巧:
- 使用具体描述:“明亮的蓝色眼睛” 比 “好看的眼睛” 更好
- 组合多个概念:“星空下的中世纪城堡”
- 添加艺术风格:“梵高风格的向日葵田野”
-
参数调整建议:
# 人像建议参数 self.width_var.set(768) self.height_var.set(1024) self.steps_var.set(40) self.guidance_var.set(5.0) # 风景建议参数 self.width_var.set(1024) self.height_var.set(768) self.steps_var.set(35) self.guidance_var.set(4.0)
-
负向提示词的重要性:
DEFAULT_NEGATIVE_PROMPT = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry"
使用负向提示词可以显著提升生成图片的质量
总结与扩展建议
本工具通过简洁的GUI界面封装了ModelScope强大的图像生成API,使AI图像创作变得更加容易。通过调整提示词和参数,你可以生成各种风格的高质量图像。
扩展建议:
- 添加模型切换功能:支持更多ModelScope上的图像生成模型
- 实现历史记录查看:保存每次生成的参数和结果
- 添加图片编辑功能:裁剪、滤镜、调整亮度等
- 实现批量生成:一次性生成多张图片供选择
# 添加模型扩展示例
MODELS = ["Qwen/Qwen-Image",
"Liudef/XB_F.1_MIX",
"AI-ModelScope/Stable-Diffusion-v1-5",
"AI-ModelScope/DALL-E-3"]
希望这个工具能激发你的创造力!如果你有任何改进建议或使用心得,欢迎在评论区分享交流。
更多推荐
所有评论(0)