文章目录
Datawhale AI夏令营第四期 Task2: 精读代码,实战进阶
探探前沿:了解一下 AI生图技术 的能力&局限
了解AI生图的重要性
AI生图使用得当可以产生精美的图片
但也有不法分子用图片制造虚假新闻,造成社会动荡
对所有人来说,定期关注AI生图的最新能力情况都十分重要:
- 对于普通人来说,可以避免被常见的AI生图场景欺骗,偶尔也可以通过相关工具绘图
- 对于创作者来说,通过AI生图的工具可以提效,快速制作自己所需要的内容
- 对于技术人来说,了解AI生图的能力的玩法,可以更好地针对自己的业务进行开发和使用,甚至攻克难题开发更实用的工具
回顾AI生图的历史
最早的AI生图可追溯到20世纪70年代,当时由艺术家哈罗德·科恩(Harold Cohen)发明AARON,可通过机械臂输出作画。
现代的AI生图模型大多基于深度神经网络基础上训练,最早可追溯到2012年吴恩达训练出的能生成“猫脸”的模型。
它使用卷积神经网络(CNN)训练,证明了深度学习模型能够学习到图像的复杂特征。
2015年,谷歌推出了“深梦”(Deep Dream)图像生成工具,类似一个高级滤镜,可以基于给定的图片生成梦幻版图片——
2021 年 1 月 OpenAI 推出DALL-E模型(一个深度学习算法模型,是GPT-3 语言处理模型的一个衍生版本),能直接从文本提示“按需创造”风格多样的图形设计——
在当时,就已经被一些媒体评价为:“ 秒杀50%的设计行业打工人应该是没有问题的,而且是质量和速度双重意义上的“秒杀” ”。
通过学习大量画家的作品,AI生图模型 往往可以照猫画虎绘制出类似的画作,在2022年8月,AI生图真正走进了大众的视野,让各个领域无法忽视。
当时让AI生图破圈的是AI绘画作品《太空歌剧院》,该作品在美国科罗拉多州举办的新兴数字艺术家竞赛中获得了比赛“数字艺术/数字修饰照片”类别一等奖,引起了当时“艺术家们 Not Happy”的社会舆论。
AI 画图的挑战
稍微接触过AI画图的朋友们都知道AI画不好手,现在也有了一些优化方案,训练时增加手部细节的标签,或者在生成时使用ControlNet等技术。
Part1:磨刀准备一一认识通义千问
通义千问链接
在接下来的代码解读环节,你有任何不理解的代码块,都可以粘贴到对话框中去询问通义千问,将他当作你的私人助教进行提问。
Part2:精读baseline——从零入门AI生图
我们的baseline文生图代码框架如下
如下是baseline的完整代码:
!pip install simple-aesthetics-predictor
!pip install -v -e data-juicer
!pip uninstall pytorch-lightning -y
!pip install peft lightning pandas torchvision
!pip install -e DiffSynth-Studio
from modelscope.msdatasets import MsDataset
ds = MsDataset.load(
'AI-ModelScope/lowres_anime',
subset_name='default',
split='train',
cache_dir="/mnt/workspace/kolors/data"
)
import json, os
from data_juicer.utils.mm_utils import SpecialTokens
from tqdm import tqdm
os.makedirs("./data/lora_dataset/train", exist_ok=True)
os.makedirs("./data/data-juicer/input", exist_ok=True)
with open("./data/data-juicer/input/metadata.jsonl", "w") as f:
for data_id, data in enumerate(tqdm(ds)):
image = data["image"].convert("RGB")
image.save(f"/mnt/workspace/kolors/data/lora_dataset/train/{data_id}.jpg")
metadata = {"text": "二次元", "image": [f"/mnt/workspace/kolors/data/lora_dataset/train/{data_id}.jpg"]}
f.write(json.dumps(metadata))
f.write("\n")
data_juicer_config = """
# global parameters
project_name: 'data-process'
dataset_path: './data/data-juicer/input/metadata.jsonl' # path to your dataset directory or file
np: 4 # number of subprocess to process your dataset
text_keys: 'text'
image_key: 'image'
image_special_token: '<__dj__image>'
export_path: './data/data-juicer/output/result.jsonl'
# process schedule
# a list of several process operators with their arguments
process:
- image_shape_filter:
min_width: 1024
min_height: 1024
any_or_all: any
- image_aspect_ratio_filter:
min_ratio: 0.5
max_ratio: 2.0
any_or_all: any
"""
with open("data/data-juicer/data_juicer_config.yaml", "w") as file:
file.write(data_juicer_config.strip())
!dj-process --config data/data-juicer/data_juicer_config.yaml
import pandas as pd
import os, json
from PIL import Image
from tqdm import tqdm
texts, file_names = [], []
os.makedirs("./data/data-juicer/output/images", exist_ok=True)
with open("./data/data-juicer/output/result.jsonl", "r") as f:
for line in tqdm(f):
metadata = json.loads(line)
texts.append(metadata["text"])
file_names.append(metadata["image"][0])
df = pd.DataFrame({"text": texts, "file_name": file_names})
df.to_csv("./data/data-juicer/output/result.csv", index=False)
df
from transformers import CLIPProcessor, CLIPModel
import torch
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
images = [Image.open(img_path) for img_path in df["file_name"]]
inputs = processor(text=df["text"].tolist(), images=images, return_tensors="pt", padding=True)
outputs = model(**inputs)
logits_per_image = outputs.logits_per_image # this is the image-text similarity score
probs = logits_per_image.softmax(dim=1) # we can take the softmax to get the probabilities
probs
from torch.utils.data import Dataset, DataLoader
class CustomDataset(Dataset):
def __init__(self, df, processor):
self.texts = df["text"].tolist()
self.images = [Image.open(img_path) for img_path in df["file_name"]]
self.processor = processor
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
inputs = self.processor(text=self.texts[idx], images=self.images[idx], return_tensors="pt", padding=True)
return inputs
dataset = CustomDataset(df, processor)
dataloader = DataLoader(dataset, batch_size=8)
for batch in dataloader:
outputs = model(**batch)
logits_per_image = outputs.logits_per_image
probs = logits_per_image.softmax(dim=1)
print(probs)
import torch
from diffusers import StableDiffusionPipeline
torch.manual_seed(1)
pipe = StableDiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v-1-4", torch_dtype=torch.float16)
pipe = pipe.to("cuda")
prompt = "二次元,一个紫色长发小女孩穿着粉色吊带漏肩连衣裙,在练习室练习唱歌,手持话筒"
negative_prompt = "丑陋、变形、嘈杂、模糊、低对比度"
guidance_scale = 4
num_inference_steps = 50
image = pipe(
prompt=prompt,
negative_prompt=negative_prompt,
guidance_scale=guidance_scale,
num_inference_steps=num_inference_steps,
height=1024,
width=1024,
).images[0]
image.save("example_image.png")
image
from PIL import Image
torch.manual_seed(1)
image = pipe(
prompt="二次元,日系动漫,演唱会的观众席,人山人海,一个紫色短发小女孩穿着粉色吊带漏肩连衣裙坐在演唱会的观众席,舞台上衣着华丽的歌星们在唱歌",
negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
cfg_scale=4,
num_inference_steps=50, height=1024, width=1024,
)
image.save("1.jpg")
torch.manual_seed(1)
image = pipe(
prompt="二次元,一个紫色短发小女孩穿着粉色吊带漏肩连衣裙坐在演唱会的观众席,露出憧憬的神情",
negative_prompt="丑陋、变形、嘈杂、模糊、低对比度,色情擦边",
cfg_scale=4,
num_inference_steps=50, height=1024, width=1024,
)
image.save("2.jpg")
torch.manual_seed(2)
image = pipe(
prompt="二次元,一个紫色短发小女孩穿着粉色吊带漏肩连衣裙坐在演唱会的观众席,露出憧憬的神情",
negative_prompt="丑陋、变形、嘈杂、模糊、低对比度,色情擦边",
cfg_scale=4,
num_inference_steps=50, height=1024, width=1024,
)
image.save("3.jpg")
torch.manual_seed(5)
image = pipe(
prompt="二次元,一个紫色短发小女孩穿着粉色吊带漏肩连衣裙,对着流星许愿,闭着眼睛,十指交叉,侧面",
negative_prompt="丑陋、变形、嘈杂、模糊、低对比度,扭曲的手指,多余的手指",
cfg_scale=4,
num_inference_steps=50, height=1024, width=1024,
)
image.save("4.jpg")
torch.manual_seed(0)
image = pipe(
prompt="二次元,一个紫色中等长度头发小女孩穿着粉色吊带漏肩连衣裙,在练习室练习唱歌",
negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
cfg_scale=4,
num_inference_steps=50, height=1024, width=1024,
)
image.save("5.jpg")
torch.manual_seed(1)
image = pipe(
prompt="二次元,一个紫色长发小女孩穿着粉色吊带漏肩连衣裙,在练习室练习唱歌,手持话筒",
negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
cfg_scale=4,
num_inference_steps=50, height=1024, width=1024,
)
image.save("6.jpg")
torch.manual_seed(7)
image = pipe(
prompt="二次元,紫色长发少女,穿着黑色连衣裙,试衣间,心情忐忑",
negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
cfg_scale=4,
num_inference_steps=50, height=1024, width=1024,
)
image.save("7.jpg")
torch.manual_seed(0)
image = pipe(
prompt="二次元,紫色长发少女,穿着黑色礼服,连衣裙,在台上唱歌",
negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
cfg_scale=4,
num_inference_steps=50, height=1024, width=1024,
)
image.save("8.jpg")
import numpy as np
from PIL import Image
images = [np.array(Image.open(f"{i}.jpg")) for i in range(1, 9)]
image = np.concatenate([
np.concatenate(images[0:2], axis=1),
np.concatenate(images[2:4], axis=1),
np.concatenate(images[4:6], axis=1),
np.concatenate(images[6:8], axis=1),
], axis=0)
image = Image.fromarray(image).resize((1024, 2048))
image
1. 分析代码的主体架构
我们在通义千问对话框中输入:
你是一个优秀的python开发工程师,现在我们需要你帮我们分析这个代码的主体框架,你需要把代码按照工作流分成几部分,用中文回答我的问题。代码如下:
{此处把上面baseline的完整代码粘贴进来}
下面是通义千问的回答:
这段代码可以分为几个主要的部分来理解:
第一部分:环境准备和依赖安装
这部分代码负责安装或卸载一些必要的Python包。
!pip install simple-aesthetics-predictor
!pip install -v -e data-juicer
!pip uninstall pytorch-lightning -y
!pip install peft lightning pandas torchvision
!pip install -e DiffSynth-Studio
第二部分:数据集加载与处理
这部分代码从ModelScope中加载了一个名为lowres_anime
的数据集,并将其保存到指定目录。接着创建了一个包含图像路径和标签的JSONL文件。
from modelscope.msdatasets import MsDataset
ds = MsDataset.load(
'AI-ModelScope/lowres_anime',
subset_name='default',
split='train',
cache_dir="/mnt/workspace/kolors/data"
)
# ... (后续代码省略)
第三部分:使用Data-Juicer进行数据过滤
这一部分定义了数据处理配置,并利用Data-Juicer对数据进行了过滤,包括尺寸和宽高比等。
data_juicer_config = """
# global parameters
project_name: 'data-process'
dataset_path: './data/data-juicer/input/metadata.jsonl' # path to your dataset directory or file
np: 4 # number of subprocess to process your dataset
text_keys: 'text'
image_key: 'image'
image_special_token: '<__dj__image>'
export_path: './data/data-juicer/output/result.jsonl'
# process schedule
# a list of several process operators with their arguments
process:
- image_shape_filter:
min_width: 1024
min_height: 1024
any_or_all: any
- image_aspect_ratio_filter:
min_ratio: 0.5
max_ratio: 2.0
any_or_all: any
"""
# ... (后续代码省略)
第四部分:构建DataFrame并进行CLIP相似性计算
这里使用Pandas创建了一个DataFrame,并利用CLIP模型计算了图像和文本之间的相似度。
import pandas as pd
import os, json
from PIL import Image
from tqdm import tqdm
# ... (后续代码省略)
from transformers import CLIPProcessor, CLIPModel
import torch
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
# ... (后续代码省略)
第五部分:创建自定义数据集和数据加载器
这部分定义了一个自定义的PyTorch数据集类,并创建了一个数据加载器用于批量处理数据。
from torch.utils.data import Dataset, DataLoader
class CustomDataset(Dataset):
def __init__(self, df, processor):
self.texts = df["text"].tolist()
self.images = [Image.open(img_path) for img_path in df["file_name"]]
self.processor = processor
# ... (后续代码省略)
dataset = CustomDataset(df, processor)
dataloader = DataLoader(dataset, batch_size=8)
# ... (后续代码省略)
第六部分:生成图像
这部分代码使用StableDiffusionPipeline
根据不同的提示(prompts)生成了一系列二次元风格的图像。
import torch
from diffusers import StableDiffusionPipeline
torch.manual_seed(1)
pipe = StableDiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v-1-4", torch_dtype=torch.float16)
pipe = pipe.to("cuda")
# ... (后续代码省略)
第七部分:图像拼接
最后,将生成的图像拼接到一起形成一张大图。
import numpy as np
from PIL import Image
images = [np.array(Image.open(f"{i}.jpg")) for i in range(1, 9)]
image = np.concatenate([
np.concatenate(images[0:2], axis=1),
np.concatenate(images[2:4], axis=1),
np.concatenate(images[4:6], axis=1),
np.concatenate(images[6:8], axis=1),
], axis=0)
image = Image.fromarray(image).resize((1024, 2048))
image
以上就是代码的主要组成部分及其功能概述。如果有任何具体细节或问题需要进一步解释,请随时告诉我。
2. 逐行解释代码
继续在对话框向与通义千问提问,输入:
你是一个优秀的python开发工程师,现在我们需要你帮我们逐行分析这个代码,用中文回答我的问题。
{这些粘贴进你不明白的代码}
3. 代码还有疑问?继续追问
遵循一个“哪里不明白问哪里”的原则,像你跟一个不厌其烦的老师请教问题一样。
Part3:实战演练一一基于话剧的连环画制作
让通义千问帮我们生成提示词
在对话框中输入下面的文本(可以根据自己的需求进行修改):
你是一个文生图专家,我们现在要做一个实战项目,就是要编排一个文生图话剧
话剧由8张场景图片生成,你需要输出每张图片的生图提示词
具体的场景图片
1、女主是一名刺客,故意接近男主
2、与男主嬉笑玩乐
3、男主深深地爱上了女主
4、男主发誓要保护女主一辈子
5、女主被坏人抓走了
6、男主孤身一人前去救女主
7、男主中了圈套
8、女主并不爱男主,男主悲痛欲绝
生图提示词要求
1、风格为古风
2、根据场景确定是使用全身还是上半身
3、人物描述
4、场景描述
5、做啥事情
例子:
古风,水墨画,一个黑色长发少女,坐在小溪边,抚摸着秀发,深思,上半身,红色长裙
结合通义千问的回答,并自己进行了一些调整,得到场景表格
图片编号 | 场景描述 | 正向提示词 | 反向提示词 |
---|---|---|---|
图片1 | 女主是一名刺客,故意接近男主 | 古风,细腻笔触,一个黑色长发少女,在一座古典园林,竹林轻摇,眼神锐利,全身,红色长裙 | 丑陋,变形,嘈杂,模糊,低对比度 |
图片2 | 女主与男主嬉笑玩乐 | 古风,细腻笔触.黑发少女,坐在花园里,全身,穿着红色长裙 | 丑陋,变形,嘈杂,模糊,低对比度 |
图片3 | 男主深深地爱上了女主 | 二次元,古风,柔和色调,男主角深情地望着女主角,月下凉亭,微风吹过,男主角握着女主角的手,眼中满是柔情,上半身 | 丑陋,变形,嘈杂,模糊,低对比度 |
图片4 | 男主发誓要保护女主一辈子 | 二次元,古风,细腻笔触,庄严色彩,男主角单膝跪地,女主,红色长裙,上半身,特写 | 丑陋,变形,嘈杂,模糊,低对比度 |
图片5 | 女主被坏人抓走了 | 二次元,古风,紧张氛围,在一条昏暗的小巷中,女主角试图挣脱,表情惊恐 ,全身 | 丑陋,变形,嘈杂,模糊,低对比度 |
图片6 | 男主孤身一人前去救女主 | 二次元,古风,阴沉色调,男主角手持长剑,目光坚定,夜晚的荒郊野外,月光如水,男主角疾步前行,准备营救,全身 | 丑陋,变形,嘈杂,模糊,低对比度 |
图片7 | 男主中了圈套 | 二次元,古风,暗黑背景,男主角受伤,表情疼痛,全身,黑暗的森林,全身 | 丑陋,变形,嘈杂,模糊,低对比度 |
图片8 | 女主并不爱男主,男主悲痛欲绝 | 二次元,古风,悲伤色彩,男主角泪流满面,雨夜中的破败小屋,男主角跪倒在地,手扶着门框,上半身 | 丑陋,变形,嘈杂,模糊,低对比度 |
使用baseline代码生成图片
女主是一名刺客,故意接近男主 | 女主与男主嬉笑玩乐 |
---|---|
男主深深地爱上了女主 | 男主发誓要保护女主一辈子 |
女主被坏人抓走了 | 男主孤身一人前去救女主 |
男主中了圈套 | 女主并不爱男主,男主悲痛欲绝 |
个人感觉画的比较好的是图1、2、6,图1、2单体照片比较精致,图6非常符合意境,生成代码如下:
torch.manual_seed(0)
image = pipe(
prompt="古风,细腻笔触,一个黑色长发少女,在一座古典园林,竹林轻摇,眼神锐利,全身,红色长裙",
negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
cfg_scale=4,
num_inference_steps=50, height=1024, width=1024,
)
image.save("1.jpg")
torch.manual_seed(112)
image = pipe(
prompt="古风,细腻笔触.黑发少女,坐在花园里,全身,穿着红色长裙",
negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
cfg_scale=4,
num_inference_steps=50, height=1024, width=1024,
)
image.save("2.jpg")
torch.manual_seed(1)
image = pipe(
prompt="二次元,古风,阴沉色调,男主角手持长剑,目光坚定,夜晚的荒郊野外,月光如水,男主角疾步前行,准备营救,全身",
negative_prompt="丑陋、变形、嘈杂、模糊、低对比度",
cfg_scale=4,
num_inference_steps=50, height=1024, width=1024,
)
image.save("6.jpg")
Part4:更多的选择一一浅尝scepter webui
魔搭体验网址
我切换了模型为SD1.5,大家可以换用不同的模型,使用相同的参数生成图片,选择自己喜欢的模型。
我的提示词和参数设置
生成图片如下:
哈哈似乎效果不是很理想,大家可以调整prompt和其他参数,或者换用其他模型多多进行尝试,好的图片是需要不断试错的!