使用ChatGPT和StableDiffusion给Hexo文章添加封面图

使用ChatGPT和StableDiffusion给Hexo文章添加封面图

最近想添加封面图,想尝试使用StableDiffusion的AIGC自动生成封面图。但是,国内chatGPT很容易被封,像DALL·E这种又没法注册,国内百度的文言一格等未开放API,所以只有使用开源大模型了。

类似github管理代码仓库一样,比较有名的管理开源大模型的网站主要有2个:

考虑到第一次使用,学习曲线需要比较平缓才好入门,我使用国内的ModelScope来做大模型HelloWorld。最终决定使用阿里达摩院提供的中文StableDiffusion-通用领域这个大模型进行生成。

M2芯片本地安装环境

官方提供了一个环境配置教程,但是没有看到任何有关M2芯片的提示,于是我转而采用了CSDN上的教程Mac M1 安装 modelscope 深度学习库避免踩坑。

安装ANACONDA

anaconda有点像是python界的docker,可以安装很多个版本的python环境不打架。

anaconda_download.jpg

创建modelscope python环境

安装完成后可能还不能正常使用conda命令,需要先source一下:

1
source {安装的路径}/anaconda3/bin/activate

然后就可以创建modelscope自己的python环境了,使用3.8版本:

1
2
conda create -n modelscope python=3.8
conda activate modelscope

官方文档是3.7,但是CSDN上说python3.7和python3.9有问题,所以用3.8。

执行完activate之后,你会发现命令行前面会多一个(modelscope)这样的前缀,说明你进入了modelscope这个环境。

在modelscope环境下,下载Pytorch框架包

可以使用清华源加速:

1
pip3 install torch torchvision torchaudio -i https://pypi.tuna.tsinghua.edu.cn/simple

在modelscope环境下,下载tensorflow框架包

tensorflow似乎就会有mac芯片兼容性问题,因此直接采用CSDN上的解法:

1
GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=1 GRPC_PYTHON_BUILD_SYSTEM_ZLIB=1  python -m pip install tensorflow-macos -i https://pypi.tuna.tsinghua.edu.cn/simple

安装ModelScope常用的包

1
pip install "modelscope[cv,nlp,multi-modal,science]" -f https://modelscope.oss-cn-beijing.aliyuncs.com/releases/repo.html

验证环境正确安装

使用分词模型进行分词:

1
python -c "from modelscope.pipelines import pipeline;print(pipeline('word-segmentation')('今天天气不错,适合 出去游玩'))"

如果环境正确,就能成功对这句话进行分词:

1
{'output': ['今天', '天气', '不错', ',', '适合', '出去', '游玩']}

测试大模型

根据中文StableDiffusion-文本生成图像-通用领域,我们可以进行模型测试:

1
2
3
4
5
6
7
8
9
10
import torch
import cv2
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks

task = Tasks.text_to_image_synthesis
model_id = 'damo/multi-modal_chinese_stable_diffusion_v1.0'
pipe = pipeline(task=task, model=model_id, torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32)
output = pipe({'text': '明亮插画 人工智能 chatGPT', 'num_inference_steps': 50, 'guidance_scale': 7.5, 'negative_prompt':'模糊的'})
cv2.imwrite('result.png', output['output_imgs'][0])
1
2
conda activate modelscope
python test.py

期间会下载很久大模型(而且不能中断,中断就得重新来,这点我还没去仔细研究),下载完成后,就会根据你的提示词生成图片,我这里的提示词是明亮插画 人工智能 chatGPT。这里即使没有GPU也可以的,它会使用CPU去跑,生成一张图片大约需要4分钟左右。

然后你就可以得到一张图片:
generated_image.png

这张图默认是512x512像素的,我查了一下,似乎并不能生成非正方形的图像,不过即使是正方形,hexo的cover也能自动调整显示尺寸。另外有一个遗留问题,就是图片比较模糊,暂时没有解决方法。大概率是因为使用了贫穷的CPU进行跑图的关系。

使用ChatGPT帮我在博客里注入cover标签

生成基本逻辑代码

提示

1
2
3
4
5
6
7
8
9
10
POST https://api.openai.com/v1/chat/completions
{
"model": "gpt-3.5-turbo",
"messages": [
{
"role": "user",
"content": "用python3完成如下功能,注意是macOS操作系统:我有一堆hexo博客文件,格式是markdown。如果文件内容头部有类似'cover: /image/123.jpg'的注解,说明有封面图了,跳过;如果文件内容头部没有类似注解,则调用test.py为其生成一张图片到image目录下,并插入注解到内容中"
}
]
}

回复

以下是一种实现方式,其中假设markdown文件都存放在post文件夹下,test.py脚本用来生成封面图并返回文件名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import os
import subprocess


# 函数: 判断markdown文件中是否包含封面注解
def has_cover(path):
with open(path, 'r') as f:
lines = f.readlines()
for line in lines:
if line.startswith('cover:'):
return True
return False


# 函数: 为markdown文件生成封面图并返回文件名
def generate_cover(path):
# 调用test.py脚本生成封面图
cmd = f'python3 test.py {path}'
filename = subprocess.check_output(cmd, shell=True).decode('utf-8').strip()

# 将封面注解插入markdown文件中
with open(path, 'r+') as f:
lines = f.readlines()
lines.insert(2, f'cover: /image/{filename}\\n')
f.seek(0)
f.writelines(lines)
return filename


# 遍历所有markdown文件
for filename in os.listdir('post'):
if filename.endswith('.md'):
path = os.path.join('post', filename)
# 判断是否已经有封面注解
if not has_cover(path):
# 生成封面图并插入注解
generate_cover(path)

需要注意的是,test.py脚本需要自己编写,用于生成封面图并返回文件名。另外,上述代码中默认在博客根目录下建立image文件夹,用于存放生成的封面图。如果需要修改文件夹名或路径,可以修改代码中对应的部分。

分析

近乎完美,逻辑清晰,语法正确,而且对于不熟悉python的我来说,很多地方我都想不到的,比如:

  • {filename}字符串竟然可以直接这样注入
  • subprocess竟然可以运行command命令
  • 我想的是完全解析整个文件,但它的思路是line.startswith('cover:'),以及lines.insert

不过有个缺憾,也怪我没有提示清楚,就是hexo的markdown头部,是用yaml写的,我想利用tags来作为提示词,但是yaml有两种写法:

1
2
3
4
5
6
7
# 写法1
tags: [标签1, 标签2]

# 写法2
tags:
- 标签1
- 标签2

我的习惯是第1种写法,但是abbr这个hexo插件在注入abbrlink时会自动改为第2种写法,这就导致两种写法都存在,都得解析。如果直接用line.startswith就不太好搞。

解决提取标签的问题

于是我又问了一下chatGPT思路,如何提取标签?

提示

1
2
3
4
5
6
7
8
9
{
"model": "gpt-3.5-turbo",
"messages": [
{
"role": "user",
"content": "用python3完成如下功能,注意是macOS操作系统:有一个markdown文件,需要提取tags内容,提取为python数组。其格式可能为'tags:\n- 平行宇宙\n- 量子力学',也可能为'tags:['平行宇宙', '量子力学']'"
}
]
}

回复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"id": "chatcmpl-7Gkdb9UAEWK0sdP8TQGzeYNlEolyn",
"object": "chat.completion",
"created": 1684226879,
"model": "gpt-3.5-turbo-0301",
"usage": {
"prompt_tokens": 84,
"completion_tokens": 367,
"total_tokens": 451
},
"choices": [
{
"message": {
"role": "assistant",
"content": "可以使用正则表达式来提取tags内容,代码如下:\n\n```python\nimport re\n\nwith open('example.md', 'r') as f:\n content = f.read()\n tags_match = re.search(r'^tags:([\\s\\S]*?)(?=^\\w)', content, re.M)\n if tags_match:\n tags_str = tags_match.group(1)\n tags = re.findall(r'[\\w\\s]+', tags_str)\n tags = [tag.strip() for tag in tags]\n print(tags)\n```\n\n这里的正则表达式`r'^tags:([\\s\\S]*?)(?=^\\w)'`的含义是:从文本开头匹配'tags:',然后匹配任意非空白字符和空白字符0到无限次,非贪婪模式,直到遇到一个以字母或数字开头的行为止。\n\n如果查找成功,则会得到一个含有tags内容的字符串,例如' - 平行宇宙\\n - 量子力学\\n'或\"['平行宇宙', '量子力学']\"。接下来,使用正则表达式`r'[\\w\\s]+'`提取出所有的单词和空格,再通过循环去除空格并添加到一个数组中即可。最后将数组打印出来即可。\n\n注:假设markdown文件中只含有一组tags内容。如果含有多组tags内容,只提取第一组。如果不含有tags内容,则不会打印任何东西。"
},
"finish_reason": "stop",
"index": 0
}
]
}

分析

正则的思路很好,不过我还是觉得不太可靠,毕竟文本中也可能出现tags:xxx这种被匹配出来,虽然也有方法去除,但可靠性不太高,不想给自己挖坑。

最终脚本

最终,我还是自己采用了yaml解析的方式解决这个问题。不过仍然参考了大量chatGPT提供的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import os
import torch
import cv2
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
import yaml

# 解析博客文件
def parse_blog(blog_md_file):
with open(blog_md_file, 'r', encoding='utf-8') as f:
raw_data = f.read()
pattern = '---'
blog_data = {}
search_result = list(pattern_search(raw_data, pattern))
if len(search_result) >= 2:
blog_info_str = raw_data[search_result[0] + len(pattern): search_result[1]]
blog_data = yaml.load(blog_info_str, Loader=yaml.SafeLoader)
blog_data['content'] = raw_data[search_result[1] + len(pattern):]
return blog_data

# 按指定格式切分内容
def pattern_search(string, pattern):
index = 0
while index < len(string)-len(pattern):
index = string.find(pattern, index, len(string))
if index == -1:
break
yield index
index += len(pattern)-1


# 函数: 为markdown文件生成封面
def generate_cover(path, blog_data):
# 使用stable diffusion生成封面图
task = Tasks.text_to_image_synthesis
model_id = 'damo/multi-modal_chinese_stable_diffusion_v1.0'
pipe = pipeline(task=task, model=model_id, torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32)
text = "明亮插画 " + " ".join(blog_data.get("tags"))
cover_path = '/cover/' + str(blog_data.get("abbrlink")) + ".png"
print("generating cover for " + path + ", prompt is " + text + ", image name is: " + cover_path)
output = pipe({'text': text, 'num_inference_steps': 50, 'guidance_scale': 7.5, 'negative_prompt': '模糊的'})
cv2.imwrite("source" + cover_path, output['output_imgs'][0])

# 将封面注解插入markdown文件中
with open(path, 'r+') as f:
lines = f.readlines()
lines.insert(2, f"cover: {cover_path}\n")
f.seek(0)
f.writelines(lines)
return filename

if __name__ == '__main__':
# 遍历所有markdown文件
for filename in os.listdir('source/_posts'):
if filename.endswith('.md'):
path = os.path.join('source/_posts', filename)
blog_data = parse_blog(path)
# 判断是否已经有封面注解
if not ('cover' in blog_data.keys()):
# 生成封面图并插入注解
filename = generate_cover(path, blog_data)

总结

这次尝试让我熟悉了大模型和chatGPT的使用。我之前一直觉得chatGPT不太能辅助写代码,但是这次深刻体会到的一点是,chatGPT不太能辅助你写你擅长语言的熟悉的代码。如果让chatGPT写Java,我能挑出一百个毛病。但是如果让它写python,我会惊叹原来还能这样写、原来还有这个库、原来还有这个函数!它会加速你实现目标的过程,无需担心编程语法。另外,就算是Java,我也不熟悉文件操作之类的日常不会使用的库,相比于网上搜索,直接问chatGPT来得更快,而且更符合需求。

使用ChatGPT和StableDiffusion给Hexo文章添加封面图

https://www.bananaoven.com/posts/28084/

作者

浅雾

发布于

2023-05-13

更新于

2023-11-05

许可协议

评论