QT图形界面集成Qwen-Image-Edit-F2P模型开发实战
最近在做一个桌面端应用项目,需要把AI图像生成能力集成进去。客户要求很简单:用户上传一张人脸照片,然后能生成各种风格的全身照。听起来像是那种“一键变装”的玩法,但背后其实挺有意思的。
我调研了一圈,发现Qwen-Image-Edit-F2P这个模型挺适合的。它专门做“人脸到照片”的生成,效果不错,而且社区生态也挺活跃。不过问题来了:怎么让普通用户也能方便地用上这个能力?总不能让他们去折腾命令行或者ComfyUI工作流吧。
所以我就想,能不能用QT做个图形界面,把整个流程包装成一个桌面应用。这样用户点点按钮就能用,不用懂什么模型部署、参数调整。今天就跟大家分享一下这个开发过程,如果你也想做类似的应用,可以参考一下。
1. 项目整体思路
先说说我的整体设计思路。这个应用的核心功能其实就三步:
- 用户上传一张人脸照片
- 输入想要的风格描述(比如“穿着红色礼服在巴黎铁塔前”)
- 点击生成,等一会儿就能看到结果
听起来简单,但背后要处理的事情不少。比如照片上传后要自动裁剪人脸区域,要调用模型生成,还要把结果显示出来。我打算用QT做界面,Python做后端逻辑,中间通过进程调用的方式连接。
为什么选QT?主要是因为它跨平台,Windows、macOS、Linux都能用,而且界面开发相对成熟。Python那边就用diffusers库来调用Qwen模型,这样两边都能用自己擅长的工具。
2. 环境准备与依赖安装
开始之前,得先把环境搭好。我用的Python 3.10,这个版本比较稳定。QT这边,我选择了PySide6,它是QT的Python绑定,用起来比原生的C++ QT要方便一些。
先安装Python依赖:
# 基础依赖 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install diffusers transformers accelerate pillow # QT相关 pip install PySide6 opencv-python这里有个小细节:如果你用NVIDIA显卡,记得装对应CUDA版本的PyTorch。我这边用的是CUDA 11.8,所以加了--index-url参数。如果不用GPU,装CPU版本也行,就是生成速度会慢一些。
QT的安装比较简单,PySide6一条命令就搞定了。OpenCV主要是用来做人脸检测和裁剪的,后面会用到。
模型文件需要单独下载。Qwen-Image-Edit-F2P其实是在Qwen-Image-Edit基础上加了LoRA,所以需要下载几个文件:
- 基础模型:Qwen/Qwen-Image-Edit-2509
- F2P LoRA:Qwen-Image-Edit-F2P.safetensors
- 文本编码器:qwen_2.5_vl_7b_fp8_scaled.safetensors
- VAE:qwen_image_vae.safetensors
这些文件加起来大概20GB左右,第一次运行时会自动下载,但建议提前下好放到指定目录,避免等待时间太长。
3. QT界面设计与实现
界面设计我用了QT Designer,可视化拖拽组件,然后生成.ui文件,再用代码加载。这样界面和逻辑能分开,维护起来方便。
先看看主窗口的布局设计:
import sys from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QTextEdit, QFileDialog, QProgressBar, QGroupBox) from PySide6.QtCore import Qt, QThread, Signal from PySide6.QtGui import QPixmap, QImage import cv2 from PIL import Image import numpy as np class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("AI写真生成器") self.setGeometry(100, 100, 900, 700) # 中央部件 central_widget = QWidget() self.setCentralWidget(central_widget) # 主布局 main_layout = QVBoxLayout(central_widget) # 上传区域 upload_group = QGroupBox("上传人脸照片") upload_layout = QHBoxLayout() self.upload_btn = QPushButton("选择照片") self.upload_btn.clicked.connect(self.select_image) self.image_label = QLabel("未选择照片") self.image_label.setFixedSize(300, 300) self.image_label.setStyleSheet("border: 2px dashed #ccc;") self.image_label.setAlignment(Qt.AlignCenter) upload_layout.addWidget(self.upload_btn) upload_layout.addWidget(self.image_label) upload_group.setLayout(upload_layout) main_layout.addWidget(upload_group) # 提示词区域 prompt_group = QGroupBox("风格描述") prompt_layout = QVBoxLayout() self.prompt_edit = QTextEdit() self.prompt_edit.setPlaceholderText("例如:穿着红色礼服在巴黎铁塔前,优雅的姿势,专业摄影") self.prompt_edit.setMaximumHeight(100) # 预设提示词按钮 preset_layout = QHBoxLayout() presets = [ "夏日花海:穿着黄色连衣裙站在花田中,背景是五颜六色的花朵", "古风侠女:淡绿色古装,手执长剑,立于古风长廊", "都市时尚:黑色皮夹克和蓝色牛仔裤,工业风建筑背景" ] for preset in presets: btn = QPushButton(preset[:15] + "...") btn.clicked.connect(lambda checked, p=preset: self.prompt_edit.setText(p)) preset_layout.addWidget(btn) prompt_layout.addWidget(self.prompt_edit) prompt_layout.addLayout(preset_layout) prompt_group.setLayout(prompt_layout) main_layout.addWidget(prompt_group) # 生成区域 generate_group = QGroupBox("生成控制") generate_layout = QHBoxLayout() self.generate_btn = QPushButton("开始生成") self.generate_btn.clicked.connect(self.start_generation) self.generate_btn.setEnabled(False) self.progress_bar = QProgressBar() self.progress_bar.setVisible(False) generate_layout.addWidget(self.generate_btn) generate_layout.addWidget(self.progress_bar) generate_group.setLayout(generate_layout) main_layout.addWidget(generate_group) # 结果区域 result_group = QGroupBox("生成结果") result_layout = QHBoxLayout() self.result_label = QLabel("等待生成...") self.result_label.setFixedSize(512, 512) self.result_label.setStyleSheet("border: 2px solid #ccc;") self.result_label.setAlignment(Qt.AlignCenter) result_layout.addWidget(self.result_label) result_group.setLayout(result_layout) main_layout.addWidget(result_group) # 状态栏 self.status_label = QLabel("就绪") self.statusBar().addWidget(self.status_label) # 初始化变量 self.input_image = None self.face_image = None def select_image(self): """选择图片文件""" file_path, _ = QFileDialog.getOpenFileName( self, "选择人脸照片", "", "图片文件 (*.jpg *.jpeg *.png *.bmp)" ) if file_path: # 显示原图 pixmap = QPixmap(file_path) scaled_pixmap = pixmap.scaled(300, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.image_label.setPixmap(scaled_pixmap) # 加载并检测人脸 self.input_image = Image.open(file_path).convert("RGB") self.detect_and_crop_face() self.generate_btn.setEnabled(True) self.status_label.setText("照片已加载,可以开始生成") def detect_and_crop_face(self): """检测并裁剪人脸区域""" if self.input_image is None: return # 转换为OpenCV格式 cv_image = cv2.cvtColor(np.array(self.input_image), cv2.COLOR_RGB2BGR) # 使用OpenCV的人脸检测器 face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) gray = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY) faces = face_cascade.detectMultiScale(gray, 1.1, 4) if len(faces) > 0: # 取最大的人脸 x, y, w, h = max(faces, key=lambda rect: rect[2] * rect[3]) # 扩大裁剪区域,确保包含完整脸部 padding = int(max(w, h) * 0.2) x = max(0, x - padding) y = max(0, y - padding) w = min(cv_image.shape[1] - x, w + 2 * padding) h = min(cv_image.shape[0] - y, h + 2 * padding) # 裁剪并保存 face_crop = cv_image[y:y+h, x:x+w] self.face_image = Image.fromarray(cv2.cvtColor(face_crop, cv2.COLOR_BGR2RGB)) # 显示裁剪后的人脸 face_qimage = QImage(face_crop.data, face_crop.shape[1], face_crop.shape[0], face_crop.shape[1] * 3, QImage.Format_BGR888) face_pixmap = QPixmap.fromImage(face_qimage) scaled_face = face_pixmap.scaled(150, 150, Qt.KeepAspectRatio, Qt.SmoothTransformation) # 在原图标签上叠加显示裁剪区域 self.image_label.setText("") self.image_label.setPixmap(scaled_face) self.status_label.setText(f"检测到人脸,已裁剪为 {w}x{h} 像素") else: self.status_label.setText("未检测到人脸,请重新选择照片") self.generate_btn.setEnabled(False) def start_generation(self): """开始生成图片""" if self.face_image is None or not self.prompt_edit.toPlainText().strip(): self.status_label.setText("请先上传照片并输入描述") return self.generate_btn.setEnabled(False) self.progress_bar.setVisible(True) self.progress_bar.setValue(0) self.status_label.setText("正在生成...") # 启动生成线程 self.worker = GenerationWorker(self.face_image, self.prompt_edit.toPlainText()) self.worker.progress_signal.connect(self.update_progress) self.worker.result_signal.connect(self.show_result) self.worker.finished_signal.connect(self.generation_finished) self.worker.start() def update_progress(self, value): """更新进度条""" self.progress_bar.setValue(value) def show_result(self, image_path): """显示生成结果""" pixmap = QPixmap(image_path) scaled_pixmap = pixmap.scaled(512, 512, Qt.KeepAspectRatio, Qt.SmoothTransformation) self.result_label.setPixmap(scaled_pixmap) def generation_finished(self, success): """生成完成""" self.generate_btn.setEnabled(True) self.progress_bar.setVisible(False) if success: self.status_label.setText("生成完成!") else: self.status_label.setText("生成失败,请重试")这个界面分了四个主要区域:上传照片、输入描述、控制按钮、显示结果。我加了一些预设提示词按钮,用户可以直接点选,不用自己打字。人脸检测用了OpenCV的Haar级联分类器,虽然简单但效果还行,大部分正面人脸都能检测到。
4. 模型调用与生成逻辑
界面做好了,接下来是核心的生成逻辑。我单独写了一个工作线程,避免生成过程中界面卡死。
import torch from diffusers import QwenImageEditPlusPipeline from PIL import Image import os import tempfile class GenerationWorker(QThread): progress_signal = Signal(int) result_signal = Signal(str) finished_signal = Signal(bool) def __init__(self, face_image, prompt): super().__init__() self.face_image = face_image self.prompt = prompt self.pipeline = None def run(self): try: self.progress_signal.emit(10) # 加载模型(第一次运行会比较慢) if self.pipeline is None: self.load_model() self.progress_signal.emit(30) # 准备输入 # F2P模型需要正面提示词和负面提示词 negative_prompt = "低分辨率,低画质,肢体畸形,手指畸形,画面过饱和,蜡像感,人脸无细节,过度光滑,画面具有AI感" # 构建输入 inputs = { "image": [self.face_image], "prompt": self.prompt, "negative_prompt": negative_prompt, "generator": torch.manual_seed(int(torch.rand(1).item() * 1000)), "true_cfg_scale": 4.0, "num_inference_steps": 40, "guidance_scale": 1.0, "num_images_per_prompt": 1, "height": 1024, "width": 768 } self.progress_signal.emit(50) # 生成图片 with torch.inference_mode(): output = self.pipeline(**inputs) generated_image = output.images[0] self.progress_signal.emit(80) # 保存结果 temp_dir = tempfile.gettempdir() output_path = os.path.join(temp_dir, f"generated_{os.getpid()}.png") generated_image.save(output_path) self.progress_signal.emit(100) self.result_signal.emit(output_path) self.finished_signal.emit(True) except Exception as e: print(f"生成失败: {e}") self.finished_signal.emit(False) def load_model(self): """加载模型管道""" try: # 这里加载基础模型 self.pipeline = QwenImageEditPlusPipeline.from_pretrained( "Qwen/Qwen-Image-Edit-2509", torch_dtype=torch.bfloat16 ) # 加载F2P LoRA lora_path = "path/to/your/Qwen-Image-Edit-F2P.safetensors" if os.path.exists(lora_path): self.pipeline.load_lora_weights(lora_path) # 移到GPU if torch.cuda.is_available(): self.pipeline.to("cuda") self.pipeline.set_progress_bar_config(disable=True) except Exception as e: print(f"模型加载失败: {e}") raise # 应用启动 if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())这里有几个关键点:
模型加载:第一次运行时会下载模型,建议提前下载好。LoRA权重需要单独加载,这样才能实现人脸保持功能。
参数设置:
true_cfg_scale设为4.0效果比较好,num_inference_steps用40步平衡速度和质量。尺寸我设了1024x768,这个比例适合全身照。负面提示词:加了中文的负面提示词,能避免一些常见问题,比如手指畸形、AI感太重等。
种子随机:每次生成用随机种子,这样同样的输入能出不同的结果,增加多样性。
5. 实际效果与优化建议
我测试了几个场景,效果还挺有意思的。上传一张证件照,输入“穿着白色婚纱在教堂前”,能生成挺自然的婚纱照。换古风描述,也能出相应的效果。
不过在实际使用中,我也发现了一些可以优化的地方:
人脸检测的改进OpenCV的检测器对侧脸、遮挡脸效果不太好。可以考虑换用更先进的检测器,比如MTCNN或者RetinaFace。或者干脆让用户手动调整裁剪框,给更多控制权。
生成速度优化在RTX 4060上,生成一张图大概要15-20秒。如果想更快,可以考虑:
- 用更少的推理步数(比如20步)
- 启用xformers加速
- 使用量化版的模型
提示词优化普通用户可能不知道怎么写好的提示词。可以做个提示词助手,根据用户输入的关键词自动补充细节。比如用户输入“海滩”,自动加上“阳光、沙滩、海浪”等元素。
批量处理功能很多用户可能想一次生成多张不同风格的照片。可以加个批量模式,上传一张脸,输入多个提示词,一次性生成所有结果。
6. 打包与部署
开发完了,怎么让用户用上呢?需要打包成可执行文件。
我用PyInstaller来打包,写个spec文件:
# app.spec a = Analysis( ['main.py'], pathex=[], binaries=[], datas=[ ('models/*', 'models'), ('ui/*.ui', 'ui') ], hiddenimports=[ 'PIL._imaging', 'PIL._imagingft', 'torch', 'torchvision', 'diffusers', 'transformers', 'accelerate' ], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], noarchive=False, ) pyz = PYZ(a.pure) exe = EXE( pyz, a.scripts, a.binaries, a.datas, [], name='AI写真生成器', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, console=False, # 不显示控制台窗口 icon='icon.ico' )打包命令:
pyinstaller app.spec打包后大概有2-3GB,主要是模型文件占空间。如果嫌太大,可以考虑让用户第一次运行时下载模型,或者提供精简版模型。
7. 总结
整体做下来,感觉QT集成AI模型这条路是可行的。用户不用懂技术细节,点点按钮就能用上先进的AI能力。虽然中间有些坑要踩,比如模型加载、内存管理、线程同步等,但解决后体验还不错。
这个项目还有很多可以扩展的地方。比如加入风格选择器,预设几种流行风格;或者加个历史记录功能,保存用户生成过的图片;甚至可以做社交分享,让用户把作品分享出去。
如果你也想做类似的应用,建议先从简单功能开始,跑通整个流程后再慢慢加特性。模型方面,Qwen系列更新挺快的,可以关注社区动态,及时用上新版本。
技术总是在进步,但最终还是要服务于实际需求。把复杂的技术包装成简单的工具,让更多人能用上,这才是最有价值的事情。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。