# JoyAI-Image-Edit

[JoyAI-Image](https://github.com/jd-opensource/JoyAI-Image) 是一个用于图像理解、文生图以及指令引导图像编辑的统一多模态基础模型。它将一个 8B 多模态大语言模型（MLLM）与一个 16B 多模态扩散 Transformer（MMDiT）相结合。JoyAI-Image 的核心原则是实现理解、生成与编辑之间的闭环协作。

JoyAI-Image-Edit 支持通用图像编辑，以及包括对象移动、对象旋转和相机控制在内的空间编辑功能。

| 模型 | 描述 | 下载 |
|:-----:|:-----------:|:--------:|
| JoyAI-Image-Edit | 通过精确且可控的空间操作实现指令引导的图像编辑 | [Hugging Face](https://huggingface.co/jdopensource/JoyAI-Image-Edit-Diffusers) |

```python
import torch
from diffusers import JoyImageEditPipeline
from diffusers.utils import load_image

pipeline = JoyImageEditPipeline.from_pretrained(
    "jdopensource/JoyAI-Image-Edit-Diffusers", dtype=torch.bfloat16
)
pipeline.to("cuda")  # or "mps", "xpu", "cpu"

image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg")
prompt = "Add wings to the astronaut."

output = pipeline(
    image=image,
    prompt=prompt,
    num_inference_steps=40,
    guidance_scale=4.0,
    generator=torch.Generator("cuda").manual_seed(0),
).images[0]
output.save("joyimage_edit_output.png")
```

## 空间编辑

JoyAI-Image 支持三种空间编辑提示词模式：**Object Move**、**Object Rotation** 和 **Camera Control**。为了获得最佳效果，请尽可能严格遵循下方的提示词模板。有关更多信息，请参阅 [SpatialEdit](https://github.com/EasonXiao-888/SpatialEdit)。

### 对象移动

将目标对象移动到输入图像中由红框标记的指定区域。

```text
Move the <object> into the red box and finally remove the red box.
```

### 物体旋转

将对象旋转到特定的标准视图。支持的 `<view>` 值：`front`、`right`、`left`、`rear`、`front right`、`front left`、`rear right`、`rear left`。

```text
Rotate the <object> to show the <view> side view.
```

### 相机控制

在保持 3D 场景不变的情况下，更改相机视角。

```text
Move the camera.
- Camera rotation: Yaw {y_rotation}°, Pitch {p_rotation}°.
- Camera zoom: in/out/unchanged.
- Keep the 3D scene static; only change the viewpoint.
```

## JoyImageEditPipeline[[diffusers.JoyImageEditPipeline]]

#### diffusers.JoyImageEditPipeline[[diffusers.JoyImageEditPipeline]]

```python
diffusers.JoyImageEditPipeline(scheduler: FlowMatchEulerDiscreteScheduler, vae: AutoencoderKLWan, text_encoder: Qwen3VLForConditionalGeneration, tokenizer: Qwen2Tokenizer, transformer: JoyImageEditTransformer3DModel, processor: Qwen3VLProcessor, text_token_max_length: int = 2048)
```

[源代码](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/joyimage/pipeline_joyimage_edit.py#L100)

使用 JoyImage 架构进行图像编辑的扩散流水线。

该流水线通过 Qwen3-VL 文本编码器对文本和图像条件进行编码，使用 3-D transformer 对潜变量进行去噪，并通过 WAN VAE 解码结果。

模型卸载顺序：text_encoder -> transformer -> vae。

#### __调用__[[diffusers.JoyImageEditPipeline.__call__]]

```python
__call__(image: typing.Union[PIL.Image.Image, numpy.ndarray, torch.Tensor, list[PIL.Image.Image], list[numpy.ndarray], list[torch.Tensor], NoneType] = None, prompt: str | list[str] = None, height: int | None = None, width: int | None = None, num_inference_steps: int = 40, timesteps: typing.List[int] = None, sigmas: typing.List[float] = None, guidance_scale: float = 4.0, negative_prompt: typing.Union[str, typing.List[str], NoneType] = None, num_images_per_prompt: typing.Optional[int] = 1, generator: typing.Union[torch.Generator, typing.List[torch.Generator], NoneType] = None, latents: typing.Optional[torch.Tensor] = None, prompt_embeds: typing.Optional[torch.Tensor] = None, prompt_embeds_mask: typing.Optional[torch.Tensor] = None, negative_prompt_embeds: typing.Optional[torch.Tensor] = None, negative_prompt_embeds_mask: typing.Optional[torch.Tensor] = None, output_type: typing.Optional[str] = 'pil', return_dict: bool = True, callback_on_step_end: typing.Union[typing.Callable[[int, int, typing.Dict], NoneType], diffusers.callbacks.PipelineCallback, diffusers.callbacks.MultiPipelineCallbacks, NoneType] = None, callback_on_step_end_tensor_inputs: typing.List[str] = ['latents'], max_sequence_length: int = 4096, enable_denormalization: bool = True)
```

[源代码](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/joyimage/pipeline_joyimage_edit.py#L600)

**参数：**

prompt (*str* 或 *List[str]*)：用于引导生成的提示词或提示词列表。

height (*int*) : 生成输出的高度（以像素为单位）。

width (*int*)：生成输出的宽度（以像素为单位）。

image (*PipelineImageInput*，*可选*)：用于条件控制的参考图像。提供该图像时，流水线将以 `num_items=2` 运行于图像编辑模式。

num_inference_steps (*int*，*optional*，默认值为 40)：去噪步数。步数越多，通常质量越高，但推理速度会变慢。

timesteps (*List[int]*, *可选*) : 用于去噪过程的自定义时间步。如果提供，`num_inference_steps` 将根据列表长度推断得出。

sigmas (*List[float]*, *optional*) : 用于去噪过程的自定义 sigmas。与 `timesteps` 互斥。

guidance_scale (*float*，*optional*，默认为 4.0)：Classifier-free guidance 的缩放系数。

negative_prompt (*str* 或 *List[str]*，*可选*)：用于抑制不期望内容的负面提示词。

num_images_per_prompt (*int*，*optional*，默认为 1)：每个提示词生成的样本数量。

generator (*torch.Generator* 或 *List[torch.Generator]*，*可选*)：用于确定性采样的随机数生成器。

latents (*torch.Tensor*, *可选*) : 目标槽位的预生成噪声潜变量。若未提供，则从高斯分布中采样。可用于从特定的起始噪声张量开始生成。

prompt_embeds (*torch.Tensor*, *可选*) : 预计算的提示嵌入。提供此参数时，可以省略 `prompt`。

prompt_embeds_mask (*torch.Tensor*, *可选*) : `prompt_embeds` 的注意力掩码。

negative_prompt_embeds (*torch.Tensor*，*可选*)：预计算的负提示嵌入。

negative_prompt_embeds_mask (*torch.Tensor*, *可选*) : `negative_prompt_embeds` 的注意力掩码。

output_type (*str*，*可选*，默认为 `"pil"`)：输出格式。传入 `"latent"` 以返回原始潜在表示。

return_dict (*bool*，*可选*，默认为 *True*)：是否返回 [JoyImageEditPipelineOutput](/docs/diffusers/main/zh/api/pipelines/joyimage_edit#diffusers.JoyImageEditPipelineOutput) 或普通张量。

callback_on_step_end (*Callable*、*PipelineCallback*、*MultiPipelineCallbacks*、*optional*)：在每个去噪步骤结束时调用的回调，其签名为 `(self, step: int, timestep: int, callback_kwargs: Dict)`。

callback_on_step_end_tensor_inputs (*List[str]*，*可选*，默认为 `["latents"]`)：包含在 `callback_kwargs` 中用于 `callback_on_step_end` 的张量键。

max_sequence_length (*int*，*optional*，默认为 4096)：用于提示编码的最大序列长度。

enable_denormalization (*bool*，*可选*，默认为 *True*)：在 VAE 解码之前对潜变量进行反归一化。

**返回：** [*~pipelines.joyimage.JoyImageEditPipelineOutput*] 或 *torch.Tensor*

如果 `return_dict` 为 `True`，则返回一个包含生成图像的 pipeline 输出对象。否则，直接返回图像张量。

根据参考图像和文本提示生成编辑后的图像。

示例：
```python
>>> import torch
>>> from diffusers import JoyImageEditPipeline
>>> from diffusers.utils import load_image

>>> model_id = "jdopensource/JoyAI-Image-Edit-Diffusers"
>>> pipe = JoyImageEditPipeline.from_pretrained(model_id, torch_dtype=torch.bfloat16)
>>> pipe.to("cuda")

>>> image = load_image("https://huggingface.co/datasets/diffusers/docs-images/resolve/main/astronaut.jpg")
>>> output = pipe(
...     image=image,  # pass an image for editing; omit for text-to-image generation
...     prompt="Add wings to the astronaut.",
...     num_inference_steps=40,
...     guidance_scale=4.0,
...     generator=torch.manual_seed(0),
... )
>>> output.images[0].save("joyimage_edit.png")
```

#### check_inputs[[diffusers.JoyImageEditPipeline.check_inputs]]

```python
check_inputs(prompt, height, width, negative_prompt = None, prompt_embeds = None, negative_prompt_embeds = None, prompt_embeds_mask = None, negative_prompt_embeds_mask = None, callback_on_step_end_tensor_inputs = None)
```

[源代码](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/joyimage/pipeline_joyimage_edit.py#L409)

**引发：** ``ValueError``

- ``ValueError`` -- 当参数组合无效时抛出。

在前向传播之前验证流水线输入。

#### denormalize_latents[[diffusers.JoyImageEditPipeline.denormalize_latents]]

```python
denormalize_latents(latent: Tensor)
```

[源代码](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/joyimage/pipeline_joyimage_edit.py#L476)

**参数：**

latent : 归一化的潜在张量。

**返回值：**

`vae.decode` 所期望的尺度下的潜在张量。

反转 `normalize_latents` 以恢复原始的潜在变量尺度。

#### encode_prompt[[diffusers.JoyImageEditPipeline.encode_prompt]]

```python
encode_prompt(prompt: typing.Union[str, typing.List[str]], device: typing.Optional[torch.device] = None, num_images_per_prompt: int = 1, prompt_embeds: typing.Optional[torch.Tensor] = None, prompt_embeds_mask: typing.Optional[torch.Tensor] = None, max_sequence_length: int = 1024, template_type: str = 'image')
```

[源代码](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/joyimage/pipeline_joyimage_edit.py#L364)

**参数：**

prompt : 提示字符串或提示字符串列表。

device : 目标设备。

num_images_per_prompt : 每个提示词生成的输出数量。

prompt_embeds : 预计算的提示嵌入。

prompt_embeds_mask : 预计算嵌入的注意力掩码。

max_sequence_length : 最大输出序列长度。

template_type : 提示模板键（`"image"` 或 `"multiple_images"`）。

**返回值：**

(prompt_embeds, prompt_embeds_mask) 的元组。

将文本提示编码为嵌入（仅文本路径）。

预计算的 `prompt_embeds` 会完全绕过编码过程。

#### encode_prompt_multiple_images[[diffusers.JoyImageEditPipeline.encode_prompt_multiple_images]]

```python
encode_prompt_multiple_images(prompt: typing.Union[str, typing.List[str]], device: typing.Optional[torch.device] = None, num_images_per_prompt: int = 1, images: typing.Optional[torch.Tensor] = None, prompt_embeds: typing.Optional[torch.Tensor] = None, prompt_embeds_mask: typing.Optional[torch.Tensor] = None, template_type: typing.Optional[str] = 'multiple_images', max_sequence_length: typing.Optional[int] = None)
```

[源代码](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/joyimage/pipeline_joyimage_edit.py#L286)

**参数：**

prompt : 提示字符串，其中可选地包含 `&amp;lt;image>\n` 标记。

device : 目标设备。

num_images_per_prompt : 每个提示词生成的输出数量。

images : 与内联图像标记对应的像素张量。

prompt_embeds : 预计算的提示嵌入。

prompt_embeds_mask : 预计算嵌入的注意力掩码。

template_type : 必须为 `"multiple_images"`。

max_sequence_length : 如果设置，则将输出截断为此长度（保留最后 `max_sequence_length` 个 token）。

**返回值：**

(prompt_embeds, prompt_embeds_mask) 的元组。

通过 Qwen 处理器对包含内联图像标记的提示进行编码。

每个提示字符串中的 `&amp;lt;image>\n` 占位符在输入多模态编码器之前，会被替换为 Qwen 视觉特殊标记。

#### normalize_latents[[diffusers.JoyImageEditPipeline.normalize_latents]]

```python
normalize_latents(latent: Tensor)
```

[源代码](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/joyimage/pipeline_joyimage_edit.py#L447)

**参数：**

latent : 来自 `vae.encode` 的原始潜在张量。

**返回值：**

归一化的潜在张量。

使用 VAE 配置中的逐通道统计量对潜变量进行归一化。

Uses 当 VAE 暴露 `latents_mean` 和 `latents_std` 时，使用 (latent - mean) / std；否则回退到使用 `scaling_factor` 进行缩放。

#### prepare_latents[[diffusers.JoyImageEditPipeline.prepare_latents]]

```python
prepare_latents(batch_size: int, num_channels_latents: int, height: int, width: int, video_length: int, dtype: dtype, device: device, generator: typing.Union[torch.Generator, typing.List[torch.Generator], NoneType], latents: typing.Optional[torch.Tensor] = None, image: typing.Optional[typing.List[PIL.Image.Image]] = None, enable_denormalization: bool = True)
```

[源代码](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/joyimage/pipeline_joyimage_edit.py#L502)

**参数：**

batch_size : 批次中的样本数量。

num_channels_latents : 来自 transformer 配置的潜在通道维度。

height : 空间高度（以像素为单位）。

width : 空间宽度（以像素为单位）。

video_length : 帧数（图像推理时为 1）。

dtype : 潜在张量的浮点数据类型。

device : 目标设备。

generator : 用于可复现采样的随机数生成器。

latents : 可选的用户提供的目标槽位的初始噪声。当为 `None` 时，将采样随机噪声。

image : 可选的 PIL 参考图像列表，用于 VAE 编码作为条件槽位。

enable_denormalization : 是否对编码后的参考潜变量进行归一化。

**返回值：**

`(latents, image_latents)` 元组，其中 `latents` 的形状为 `(B, 1, C, T, H', W')`，`image_latents` 的形状为 `(B, N_ref, C, T, H', W')`；若未提供参考图像，则为 `None`。

**引发：** `ValueError`

- `ValueError` -- 如果 `generator` 是一个列表，且其长度与 `batch_size` 不同。

为去噪循环准备初始含噪潜在张量。

## JoyImageEditPipelineOutput[[diffusers.JoyImageEditPipelineOutput]]

#### diffusers.JoyImageEditPipelineOutput[[diffusers.JoyImageEditPipelineOutput]]

```python
diffusers.JoyImageEditPipelineOutput(images: typing.Union[typing.List[PIL.Image.Image], numpy.ndarray])
```

[源代码](https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/joyimage/pipeline_output.py#L11)

JoyImageEdit 生成管线的输出类。

