目录
🧠 前言
🧾 我的需求
🔧 实现过程(按功能一步步来)
🚶♂️ Step 1:基本图像生成界面
🗃️ Step 2:保存图片并显示历史记录
📏 Step 3:优化长提示词显示和添加删除功能
🧹 Step 4:解决新生成图片删除失败
⚠️ 遇到的问题总结
✅ 最终效果展示
🧩 总结 & 收获
🧠 前言
本科课设做过一个人脸面部表情识别系统,当时是用Pyqt5库实现的图形可视化界面,最近我尝试结合 PyQt5 和 Stable Diffusion 模型开发一个图像生成工具,目标是实现一个带有图形界面的系统,能够输入提示词生成图像、自动保存并按序命名、查看历史记录并支持删除功能。在开发过程中,我逐步实现了功能,也遇到了一些问题,最终在优化下达到了预期效果。本文将记录我的需求、实现过程、遇到的问题以及最终成果。
这一篇是在先前部署Stable Diffusion V1.5的基础上,加入了图形界面显示优化,部署可看从零搭建这篇,搭建完即可链接本篇:
深度学习项目记录·Stable Diffusion从零搭建、复现笔记-CSDN博客
从全灰到清晰图像:我的 Stable Diffusion 多尺度优化学习记录-CSDN博客
这里初始图像生成迭代步数我设置为30步,img2img优化迭代步数设置为50步
可能不好理解为什么实际运行生成图片过程中是15步?其实大概知道是微调受strength参数影响,50*0.3=15,这里我查了仔细解释,如下:
在 img2img(图像到图像)模式中,实际执行的迭代步数通常会受到 strength 参数的影响,而不是直接等于 num_inference_steps。
第一,strength 参数的作用:
- strength(这里是0.3)控制从初始图像 init_image 到生成新图像的“变化程度”。
- 它的取值范围是0到1:
- strength=0:完全保留初始图像,不做任何改变。
- strength=1:完全忽略初始图像,等同于从头生成(text-to-image)。
- strength=0.3:表示保留70%的初始图像特征,只对30%的内容进行调整。
第二,为什么是15步而不是50步?
- 在 img2img 模式中,初始图像 init_image 已经提供了大部分结构,模型不需要从完全随机的噪声开始生成。因此,strength=0.3 表示只需要对图像进行轻微调整,实际迭代步数被缩减为 50 * 0.3 = 15。
- 这是一个优化机制,避免浪费计算资源。
第三,总结
- 第一行(30/30):对应 pipe(prompt, ..., num_inference_steps=30),完整的30步生成。
- 第二行(15/15):对应 img2img_pipe(..., num_inference_steps=50, strength=0.3),实际步数被 strength=0.3 缩减为15步。
- 原因:img2img 模式的 strength 参数调整了实际迭代步数,以适配从已有图像开始的优化过程。
正文开始:
🧾 我的需求
-
生成逻辑
-
输入提示词 → 点击生成 → 得到一张图像并保存,文件名按序递增(如 image_001.png, image_002.png)
-
生成完成后暂停,等待下一次手动点击“开始生成”。
-
-
交互界面
-
提供“清空提示词”按钮,方便输入新提示词。
-
历史记录列表显示生成的图像,格式为:
image_001.png 描述: A beautiful sunset
-
-
历史管理
-
长提示词能完整显示,不被截断。
-
每张图片支持删除功能,点击图片名称后可删除对应文件。
-
🔧 实现过程(按功能一步步来)
🚶♂️ Step 1:基本图像生成界面
目标:实现一个简单的 PyQt5 界面,能够输入提示词并生成图像,仅显示在界面上,不保存。
主要通过导入PyQt5库实现:
实现要点:
-
使用 StableDiffusionPipeline 和 StableDiffusionImg2ImgPipeline 生成图像。
-
创建 PyQt5 界面,包含 QLineEdit(输入提示词)、QPushButton(开始生成)、QLabel(显示图像)
-
将生成过程放入 QThread,通过信号机制
pyqtSignal
将生成的图像传回主线程显示。(生成图像是个耗时操作,用QThread
异步处理)
用到的 Python 原理和技术: - 多线程(QThread):避免生成图像时阻塞主界面,使用 pyqtSignal 传递结果。
- GUI 布局(QVBoxLayout, QHBoxLayout):组织界面元素。
- 图像处理(PIL 到 QPixmap):将生成的 PIL 图像转换为 PyQt5 可显示的格式。
class ImageGenerationThread(QThread):
finished = pyqtSignal(object)
def run(self):
image = self.pipe(self.prompt).images[0]
self.finished.emit(image)
def generate_image(self):
self.thread = ImageGenerationThread(prompt, save_path)
self.thread.finished.connect(self.show_image)
self.thread.start()
def show_image(self, image):
image.save("temp.png")
pixmap = QPixmap("temp.png")
self.image_label.setPixmap(pixmap)
🗃️ Step 2:保存图片并显示历史记录
目标:在生成图像后自动保存,并用列表显示图片名称和提示词,支持点击查看。
实现要点:
- 添加保存路径和文件名生成逻辑(image_001.png 等)。
- 使用 QListWidget 显示历史记录,每张图片占两行:文件名和描述。
- 实现点击列表项显示对应图像的功能。
用到的 Python 原理和技术:
- 文件操作(os.path, os.makedirs):创建目录并保存图像。
- 列表控件(QListWidget):存储和显示图片名及描述,使用 addItem 添加条目。
- 字典(dict):用 self.history_data 存储文件名和提示词的映射,便于查看时获取描述。
- 事件处理(itemClicked):绑定点击事件,加载并显示选中的图像。
def generate_image(self):
filename = f"image_{self.count:03d}.png"
save_path = os.path.join(self.save_dir, filename)
self.thread = ImageGenerationThread(prompt, save_path)
self.thread.finished.connect(self.on_generation_finished)
def on_generation_finished(self, image, save_path, prompt):
pixmap = QPixmap(save_path)
self.image_label.setPixmap(pixmap)
filename = os.path.basename(save_path)
self.history_list.addItem(filename)
self.history_list.addItem(f"描述: {prompt}")
self.history_data[filename] = prompt
self.count += 1
📏 Step 3:优化长提示词显示和添加删除功能
目标:解决长提示词截断问题,并为每张图片添加删除按钮。
实现要点:
-
设置
QListWidget
宽度、开启自动换行; -
新增“删除图像”按钮,绑定删除逻辑;
-
删除时移除对应列表项,弹确认框防误删。
用到的 Python 原理和技术:
- 控件属性调整(setMaximumWidth, setWordWrap):增加宽度并启用自动换行。
- 文件删除(os.remove):删除磁盘上的图片文件。
- 列表操作(takeItem):从 QListWidget 中移除条目。
- 对话框(QMessageBox):提供删除确认提示。
self.history_list.setMaximumWidth(400)
self.history_list.setWordWrap(True)
def delete_selected_image(self):
filename = self.history_list.selectedItems()[0].text()
image_path = os.path.join(self.save_dir, filename)
os.remove(image_path)
row = self.history_list.row(selected_item)
self.history_list.takeItem(row + 1) # 描述
self.history_list.takeItem(row) # 文件名
🧹 Step 4:解决新生成图片删除失败
现象:已有图片可以删,但刚生成的删不了,会报错 [WinError 2]
。
原因:生成线程或图像显示时还占着这个文件的资源,没释放。
解决办法:
-
生成完成后手动释放图像对象;
-
删除前清空当前显示的图像;
-
加一波
gc.collect()
触发垃圾回收。
用到的 Python 原理和技术:
- 垃圾回收(gc.collect):强制释放内存中的图像对象。
- 资源管理(del, clear):显式删除图像对象并清除 QLabel 显示。
- 异常处理(try-except):捕获删除时的错误并显示。
def on_generation_finished(self, image, save_path, prompt):
# 显示图像 & 加入历史列表 ...
del image
gc.collect()
self.image_label.clear()
def delete_selected_image(self):
if self.image_label.pixmap():
self.image_label.clear()
os.remove(image_path)
⚠️ 遇到的问题总结
问题 | 描述 | 解决方法 |
---|---|---|
❌ 无法保存图像 | 最初没写保存逻辑 | 手动加保存路径 + 文件名管理 |
❌ 提示词显示不完整 | 长提示词在历史列表中只显示一行 | 增加宽度 + 开启 setWordWrap(True) |
❌ 新图片删不掉 | 报错 [WinError 2] 文件被占用 | 显式释放图像资源 + 清空 QLabel + 垃圾回收 |
✅ 最终效果展示
-
生成 + 保存
- 输入提示词,点击“开始生成”,生成一张图像并保存(如 image_001.png),完成后暂停。
- 清空提示词后输入新提示词,点击生成,保存为 image_002.png,文件名依次递增。
-
历史记录显示
-
历史列表中,文件名和提示词分两行显示,长提示词自动换行,例如:
image_001.png 描述: A beautiful sunset over the mountains with a long description that wraps image_002.png 描述: A cute kitten playing with a ball
-
-
删除功能完善
-
选中历史中的图片文件名,点“删除”按钮,会弹窗确认;
-
删除后界面实时刷新,文件从磁盘也会消失;删除后:
-
新生成的图片也能正常删了!删除001.png
下方显示已删除图像+图像名
生成测试:This guy is wearing a short-sleeve T-shirt with pure color patterns. The T-shirt is with cotton fabric and its neckline is v-shape. The pants this guy wears is of long length. The pants are with cotton fabric and solid color patterns
这位男士穿着一件纯色图案的短袖 T 恤。T恤为棉质面料,领口呈 V 形。这位男士穿着一条长裤。裤子为纯棉面料,纯色图案。
效果一般,测试2:A little girl is sitting in front of a large painted rainbow .
测试3:White dog playing with a red ball on the shore near the water .
关闭系统界面,图像已保存到本地
-
🧩 总结 & 收获
这个小项目让我逐步实践了以下内容:
-
PyQt5 界面搭建;
-
多线程 + 信号机制避免卡顿;
-
图像处理、自动保存、文件管理;
-
资源释放与垃圾回收(真香);
-
控件交互与用户体验优化。
最关键的是,从一开始的功能设想到实际落地、再到解决 bug 完善体验,完整走了一遍闭环,非常适合练手或当项目展示。
如果你也想做个图像生成小工具类似系统,可以直接参考我这套逻辑(Pyqt5+生成模型)。