一句话,AI帮你P图!Qwen-Image-Edit本地部署教程:能转能改能加字

一、模型介绍

Qwen-Image-Edit基于 20B Qwen-Image 模型,成功地将 Qwen-Image 独特的文本渲染能力扩展到了图像编辑任务中,实现了精确的文本编辑。此外,Qwen-Image-Edit 同时将输入图像馈送到 Qwen2.5-VL(用于视觉语义控制)和 VAE 编码器(用于视觉外观控制),从而在语义和外观编辑方面都具备了能力。

  • 语义和外观编辑 :Qwen-Image-Edit 支持低级视觉外观编辑(例如添加、删除或修改元素,要求图像的其他区域完全不变)和高级视觉语义编辑(例如 IP 创建、对象旋转和风格转换,允许整体像素变化同时保持语义一致性)。
  • 精确文本编辑 :Qwen-Image-Edit 支持双语(中文和英文)文本编辑,允许直接在图像中添加、删除和修改文本,同时保留原始字体、大小和样式。
  • 强大的基准性能 :在多个公共基准上的评估表明,Qwen-Image-Edit 在图像编辑任务中达到了最先进的(SOTA)性能,确立了其作为强大基础模型的地位。

二、模型部署

快速部署及使用方法,请通过文末卡片进入算家云, 参考"镜像社区"。

基础环境最低配置推荐

环境名称 版本信息
Ubuntu 22.04.4 LTS
Python 3.12
CUDA 12.4
NVIDIA Corporation RTX 4090 * 4

注:该模型支持多卡与单卡

1.更新基础软件包、配置镜像源

查看系统版本信息

bash 复制代码
#查看系统的版本信息,包括 ID(如 ubuntu、centos 等)、版本号、名称、版本号 ID 等
cat /etc/os-release

更新软件包列表

csharp 复制代码
#更新软件列表
apt-get update

配置国内镜像源(阿里云)

具体而言,vim 指令编辑文件 sources.list

bash 复制代码
#编辑源列表文件
vim  /etc/apt/sources.list

"i" 进入编辑模式,将如下内容插入至 sources.list 文件中

arduino 复制代码
deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse

最后,按 "esc" 键退出编辑模式,输入 :wq 命令并按下 "enter" 键便可保存并退出 sources.list 文件

2.创建虚拟环境

创建虚拟环境

ini 复制代码
#创建名为Qwen-Image的虚拟环境,python版本:3.12
conda create -n Qwen-Image python=3.12

激活虚拟环境

复制代码
conda activate Qwen-Image

3.克隆项目

创建 Qwen-Image 文件夹

bash 复制代码
#创建Qwen-Image文件夹
mkdir Qwen-Image

Qwen-Image-Edit模型基于我们的 20B Qwen-Image 模型,故可以直接克隆,github(QwenLM/Qwen-Image:Qwen-Image 是一个强大的图像生成基础模型,能够进行复杂的文本渲染和精确的图像编辑。)中克隆项目代码文件至该目录

bash 复制代码
#进入Qwen-Image目录
cd Qwen-Image
#克隆仓库
git clone https://github.com/QwenLM/Qwen-Image.git

4.下载依赖

requirements.txt 文件

复制代码
pip install -r requirements.txt

文件内容:

arduino 复制代码
git+https://github.com/huggingface/diffusers.git
transformers
accelerate
safetensors
sentencepiece
dashscope

5.模型下载

转到魔塔社区官网下载模型文件:Qwen-Image · 模型库

进入堡垒机使用命令行下载完整模型库

bash 复制代码
#启用代理
source /data/models_and_datasets/Script/SJ-proxy.sh && proxy_on
#下载命令
modelscope download --model Qwen/Qwen-Image-Edit --local_dir /data/models_and_datasets/LargeModel/Qwen-Image-Edit

返回实例终端输入命令检查模型是否下载

bash 复制代码
cd /root/sj-data/LargeModel/Qwen-Image-Edit/Qwen/Qwen-Image-Edit

三、web 页面启动

Qwen-Image-Edit模型没有发布web页面相关代码,自己创建一个app.py文件

python 复制代码
import os
import sys
import logging
import traceback
from datetime import datetime

# 设置日志
def setup_logging():
    # 创建日志目录
    log_dir = "/Qwen-Image/logs"
    os.makedirs(log_dir, exist_ok=True)
  
    # 创建日志文件名(带时间戳)
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    log_file = os.path.join(log_dir, f"qwen_image_edit_{timestamp}.log")
  
    # 配置日志
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler(log_file, encoding='utf-8'),
            logging.StreamHandler(sys.stdout)
        ]
    )
  
    return logging.getLogger(__name__)

# 初始化日志
logger = setup_logging()

# 环境变量设置
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True,max_split_size_mb:128")
os.environ.setdefault("CUDA_LAUNCH_BLOCKING", "0")
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")

# 记录环境信息
logger.info("=" * 60)
logger.info("启动 Qwen-Image-Edit 应用")
logger.info("=" * 60)
logger.info(f"Python版本: {sys.version}")
logger.info(f"工作目录: {os.getcwd()}")

try:
    import torch
    import gradio as gr
    from PIL import Image
    from diffusers import DiffusionPipeline
  
    logger.info(f"PyTorch版本: {torch.__version__}")
    logger.info(f"CUDA可用: {torch.cuda.is_available()}")
  
    if torch.cuda.is_available():
        logger.info(f"GPU数量: {torch.cuda.device_count()}")
        for i in range(torch.cuda.device_count()):
            props = torch.cuda.get_device_properties(i)
            logger.info(f"GPU {i}: {props.name}, 显存: {props.total_memory/1024**3:.2f} GiB")
  
except ImportError as e:
    logger.error(f"导入依赖失败: {e}")
    logger.error("请确保已安装所有依赖: torch, gradio, Pillow, diffusers")
    sys.exit(1)

# 兼容不同 diffusers 版本的 Qwen Image Edit 入口
try:
    from diffusers import QwenImageEditPipeline
    _HAS_QWEN_EDIT = True
    logger.info("成功导入 QwenImageEditPipeline")
except Exception as e:
    _HAS_QWEN_EDIT = False
    logger.warning(f"无法导入 QwenImageEditPipeline: {e}")
    logger.warning("将使用通用的 DiffusionPipeline")

# -------------------------
# 实用函数
# -------------------------
def _gpu_summary() -> str:
    if not torch.cuda.is_available():
        return "CUDA 不可用,使用 CPU"
    parts = []
    for i in range(torch.cuda.device_count()):
        props = torch.cuda.get_device_properties(i)
        parts.append(f"[{i}] {props.name} {props.total_memory/1024**3:.2f} GiB")
    return " | ".join(parts)

def _free_cuda():
    if torch.cuda.is_available():
        torch.cuda.empty_cache()
        torch.cuda.synchronize()

def _pick_dtype():
    if torch.cuda.is_available():
        dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
        logger.info(f"选择数据类型: {dtype}")
        return dtype
    logger.info("使用CPU模式,数据类型: float32")
    return torch.float32

# -------------------------
# 模型初始化
# -------------------------
def init_pipeline(
    model_name: str = "/root/sj-data/LargeModel/Qwen-Image-Edit/Qwen/Qwen-Image-Edit",
    prefer_balanced: bool = True,
):
    logger.info(f"开始初始化模型: {model_name}")
    logger.info(f"GPU状态: {_gpu_summary()}")
  
    dtype = _pick_dtype()
    n_gpu = torch.cuda.device_count() if torch.cuda.is_available() else 0

    max_mem = None
    if n_gpu >= 2:
        max_mem = {i: "22GiB" for i in range(n_gpu)}
        max_mem["cpu"] = "64GiB"
        logger.info(f"设置多GPU内存限制: {max_mem}")

    def _common_post_init(pipe: DiffusionPipeline):
        logger.info("执行通用后初始化步骤")
  
        # 显存优化开关
        if hasattr(pipe, "enable_attention_slicing"):
            pipe.enable_attention_slicing(slice_size="max")
            logger.info("启用注意力切片")
  
        if hasattr(pipe, "enable_vae_slicing"):
            pipe.enable_vae_slicing()
            logger.info("启用VAE切片")
  
        if hasattr(pipe, "enable_vae_tiling"):
            pipe.enable_vae_tiling()
            logger.info("启用VAE平铺")
  
        if hasattr(pipe, "set_progress_bar_config"):
            pipe.set_progress_bar_config(disable=False)
            logger.info("启用进度条")
  
        # flash/Math SDP
        try:
            torch.backends.cuda.matmul.allow_tf32 = True
            torch.backends.cuda.enable_flash_sdp(True)
            torch.backends.cuda.enable_mem_efficient_sdp(True)
            torch.backends.cuda.enable_math_sdp(True)
            logger.info("启用CUDA优化设置")
        except Exception as e:
            logger.warning(f"CUDA优化设置失败: {e}")
  
        return pipe

    common_kwargs = dict(
        pretrained_model_name_or_path=model_name,
        torch_dtype=dtype,
        safety_checker=None,
    )

    # 多卡均衡
    if n_gpu >= 2 and prefer_balanced:
        try:
            logger.info("尝试 device_map='balanced' + max_memory 多卡均衡切分")
            pipe = (QwenImageEditPipeline if _HAS_QWEN_EDIT else DiffusionPipeline).from_pretrained(
                **common_kwargs,
                device_map="balanced",
                max_memory=max_mem,
            )
            _common_post_init(pipe)
            logger.info("初始化成功:balanced")
            return pipe, "balanced"
        except Exception as e:
            logger.error(f"balanced 失败:{e}")
            _free_cuda()

    # 多卡自动切分
    if n_gpu >= 2:
        try:
            logger.info("尝试 device_map='auto' 多卡自动切分")
            pipe = (QwenImageEditPipeline if _HAS_QWEN_EDIT else DiffusionPipeline).from_pretrained(
                **common_kwargs,
                device_map="auto",
                max_memory=max_mem,
            )
            _common_post_init(pipe)
            logger.info("初始化成功:auto")
            return pipe, "auto"
        except Exception as e:
            logger.error(f"auto 失败:{e}")
            _free_cuda()

    # 单卡 + CPU卸载
    try:
        logger.info("尝试单卡/CPU 初始化 + 顺序卸载")
        pipe = (QwenImageEditPipeline if _HAS_QWEN_EDIT else DiffusionPipeline).from_pretrained(
            **common_kwargs,
            device_map={"": "cpu"},
        )
        _common_post_init(pipe)
        if hasattr(pipe, "enable_sequential_cpu_offload"):
            pipe.enable_sequential_cpu_offload()
            logger.info("启用顺序CPU卸载")
        else:
            pipe.enable_model_cpu_offload()
            logger.info("启用模型CPU卸载")
        logger.info("初始化成功:sequential_cpu_offload")
        return pipe, "sequential_cpu_offload"
    except Exception as e:
        logger.error(f"sequential_cpu_offload 失败:{e}")
        logger.error(traceback.format_exc())
        raise RuntimeError("所有初始化策略均失败")

# -------------------------
# 图像编辑
# -------------------------
def edit_image(
    pipe: DiffusionPipeline,
    prompt: str,
    image_path: str,
    negative_prompt: str = "",
    true_cfg_scale: float = 4.0,
    guidance_scale: float = 3.5,
    num_inference_steps: int = 25,
    seed: int = 42,
):
    logger.info(f"开始图像编辑: prompt='{prompt}', 图片路径='{image_path}'")
    logger.info(f"参数: true_cfg_scale={true_cfg_scale}, guidance_scale={guidance_scale}, steps={num_inference_steps}, seed={seed}")
  
    if image_path is None or image_path == "":
        error_msg = "请先上传一张图片。"
        logger.error(error_msg)
        raise gr.Error(error_msg)

    zh_magic = "超清, 4K, 电影级构图"
    if any("\u4e00" <= ch <= "\u9fff" for ch in prompt):
        prompt = f"{prompt} {zh_magic}"
        logger.info(f"添加中文优化提示: {prompt}")

    try:
        image = Image.open(image_path).convert("RGB")
        logger.info(f"成功加载图片: {image.size}")
    except Exception as e:
        error_msg = f"无法加载图片: {e}"
        logger.error(error_msg)
        raise gr.Error(error_msg)

    generator = None
    if seed is not None and int(seed) >= 0:
        device_for_gen = "cuda" if torch.cuda.is_available() else "cpu"
        generator = torch.Generator(device=device_for_gen).manual_seed(int(seed))
        logger.info(f"设置随机种子: {seed}")

    is_cuda = torch.cuda.is_available()
    amp_dtype = torch.bfloat16 if (is_cuda and torch.cuda.is_bf16_supported()) else (torch.float16 if is_cuda else torch.float32)
    logger.info(f"使用精度: {amp_dtype}")

    from contextlib import nullcontext
    try:
        logger.info("开始推理过程")
        _free_cuda()
        start_time = datetime.now()
  
        with torch.inference_mode():
            ctx = torch.autocast(device_type="cuda", dtype=amp_dtype) if is_cuda else nullcontext()
            with ctx:
                out = pipe(
                    image=image,
                    prompt=prompt,
                    negative_prompt=negative_prompt or None,
                    true_cfg_scale=true_cfg_scale,
                    guidance_scale=guidance_scale,
                    num_inference_steps=int(num_inference_steps),
                    generator=generator,
                )
  
        end_time = datetime.now()
        duration = (end_time - start_time).total_seconds()
        logger.info(f"推理完成,耗时: {duration:.2f}秒")
  
        return out.images[0]
  
    except torch.cuda.OutOfMemoryError:
        error_msg = "显存不足。请降低步数/强度或使用更小图片。"
        logger.error(error_msg)
        _free_cuda()
        raise gr.Error(error_msg)
  
    except Exception as e:
        error_msg = f"生成失败: {str(e)}"
        logger.error(error_msg)
        logger.error(traceback.format_exc())
        raise gr.Error(error_msg)

# -------------------------
# Gradio UI
# -------------------------
def launch_gradio():
    try:
        model_path = os.environ.get("QWEN_IMAGE_EDIT_PATH", "/root/sj-data/LargeModel/Qwen-Image-Edit/Qwen/Qwen-Image-Edit")
        logger.info(f"模型路径: {model_path}")
  
        pipe, strat = init_pipeline(model_path)
        logger.info(f"模型初始化完成,策略: {strat}")

        with gr.Blocks(
            title="Qwen-Image-Edit 图像编辑器",
            theme=gr.themes.Soft(primary_hue="blue", secondary_hue="gray")
        ) as demo:
            # 标题
            gr.Markdown("# 🎨 Qwen-Image-Edit 图像编辑器")
            gr.Markdown("多GPU优化 · 高效显存管理 · 快速图像编辑")

            with gr.Row():
                # 左侧输入区域
                with gr.Column(scale=1):
                    with gr.Group():
                        gr.Markdown("### 📝 输入设置")
            
                        # 提示词输入
                        prompt = gr.Textbox(
                            label="提示词(支持中文)", 
                            placeholder="例如:兔子毛色变为黑色",
                            lines=2
                        )
            
                        # 负面提示词
                        negative_prompt = gr.Textbox(
                            label="负面提示词(可选)", 
                            placeholder="请输入您不需要出现的内容",
                            lines=2
                        )
            
                        # 图片上传
                        input_image = gr.Image(
                            label="上传待编辑图片", 
                            type="filepath", 
                            height=200
                        )
        
                    # 高级设置
                    with gr.Accordion("⚙️ 高级设置", open=False):
                        true_cfg_scale = gr.Slider(
                            label="True CFG 强度", 
                            minimum=1.0, 
                            maximum=10.0, 
                            value=4.0, 
                            step=0.5,
                            info="数值越高越贴近提示词"
                        )
                        guidance_scale = gr.Slider(
                            label="引导强度", 
                            minimum=1.0, 
                            maximum=10.0, 
                            value=3.5, 
                            step=0.1
                        )
                        num_inference_steps = gr.Slider(
                            label="迭代步数", 
                            minimum=10, 
                            maximum=50, 
                            value=25, 
                            step=5
                        )
                        seed = gr.Number(
                            label="随机种子", 
                            value=42, 
                            precision=0, 
                            step=1
                        )
        
                    # 生成按钮
                    generate_btn = gr.Button(
                        "🚀 开始生成", 
                        variant="primary", 
                        size="lg"
                    )

                # 右侧输出区域
                with gr.Column(scale=1):
                    with gr.Group():
                        gr.Markdown("### 🖼️ 编辑结果")
                        output_image = gr.Image(
                            type="pil", 
                            interactive=False, 
                            height=400,
                            label="生成结果"
                        )
        
                    # 状态显示
                    status_text = gr.Textbox(
                        label="状态", 
                        value="等待生成...", 
                        interactive=False
                    )

            # 生成函数
            def run_generation(prompt, image_path, negative_prompt, true_cfg_scale, guidance_scale, num_inference_steps, seed):
                try:
                    logger.info("用户点击生成按钮")
                    yield "正在处理图像...", None
        
                    result = edit_image(
                        pipe=pipe, 
                        prompt=prompt, 
                        image_path=image_path, 
                        negative_prompt=negative_prompt,
                        true_cfg_scale=true_cfg_scale, 
                        guidance_scale=guidance_scale, 
                        num_inference_steps=num_inference_steps,
                        seed=seed
                    )
        
                    logger.info("图像生成成功")
                    yield "生成完成!", result
        
                except Exception as e:
                    error_msg = f"生成过程中出错: {str(e)}"
                    logger.error(error_msg)
                    logger.error(traceback.format_exc())
                    yield error_msg, None

            # 绑定事件
            generate_btn.click(
                fn=run_generation,
                inputs=[prompt, input_image, negative_prompt, true_cfg_scale, guidance_scale, num_inference_steps, seed],
                outputs=[status_text, output_image]
            )

        # 启动设置
        try:
            demo.queue(max_size=2)
        except TypeError:
            demo.queue()
  
        logger.info("启动 Gradio 服务...")
        demo.launch(
            server_name="0.0.0.0", 
            server_port=8080, 
            share=False,
            show_error=True
        )
  
    except Exception as e:
        logger.error(f"应用启动失败: {e}")
        logger.error(traceback.format_exc())
        raise

# -------------------------
# 异常处理
# -------------------------
def handle_exception(exc_type, exc_value, exc_traceback):
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return
  
    logger.error("未捕获的异常:", exc_info=(exc_type, exc_value, exc_traceback))

# 设置全局异常处理
sys.excepthook = handle_exception

# -------------------------
# 入口
# -------------------------
if __name__ == "__main__":
    try:
        launch_gradio()
    except Exception as e:
        logger.error(f"应用执行失败: {e}")
        logger.error(traceback.format_exc())

页面展示

用户还可以根据需求,设置高级设置中的各个参数,要达到提高图像质量、缩短生成时间等。

相关推荐
Moshow郑锴11 分钟前
实践题:智能客服机器人设计
人工智能·机器人·智能客服
2501_9248895538 分钟前
商超高峰客流统计误差↓75%!陌讯多模态融合算法在智慧零售的实战解析
大数据·人工智能·算法·计算机视觉·零售
维基框架1 小时前
维基框架 (Wiki Framework) 1.1.0 版本发布 提供多模型AI辅助开发
人工智能
西猫雷婶2 小时前
神经网络|(十二)概率论基础知识-先验/后验/似然概率基本概念
人工智能·神经网络·机器学习·回归·概率论
墨风如雪2 小时前
P图终结者?阿里通义新作,一句话让文字和像素俯首称臣
aigc
居7然3 小时前
大模型微调面试题全解析:从概念到实战
人工智能·微调
haidizym3 小时前
质谱数据分析环节体系整理
大数据·人工智能·数据分析·ai4s
Godspeed Zhao4 小时前
Tesla自动驾驶域控制器产品(AutoPilot HW)的系统化梳理
人工智能·机器学习·自动驾驶
fsnine4 小时前
机器学习案例——预测矿物类型(模型训练)
人工智能·机器学习
数据知道4 小时前
机器翻译60天修炼专栏介绍和目录
人工智能·自然语言处理·机器翻译