Skip to content

使用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一下:

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

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

sh
conda create -n modelscope python=3.8
conda activate modelscope

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

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

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

可以使用清华源加速:

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

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

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

sh
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常用的包

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

验证环境正确安装

使用分词模型进行分词:

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

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

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

测试大模型

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

python
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])
sh
conda activate modelscope
python test.py

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

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

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

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

生成基本逻辑代码

提示

http
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脚本用来生成封面图并返回文件名:

python
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有两种写法:

yaml
## 写法1
tags: [标签1, 标签2]
yaml
## 写法2
tags: 
  - 标签1
  - 标签2

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

解决提取标签的问题

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

提示

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

回复

json
{
    "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提供的代码。

python
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来得更快,而且更符合需求。

转载请注明出处https://bananaoven.com/articles/28084.html | 香蕉微波炉
分享许可方式知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
重大发现:转载注明原文网址的同学刚买了彩票就中奖,刚写完代码就跑通,刚转身就遇到了真爱。