国风AI绘画进阶:LiuJuan模型生成图片的导出、命名与元数据嵌入教程
第一次用LiuJuan模型生成出那张惊艳的国风人像时,瞬间就被击中了——汉服少女衣袂飘飘,水墨背景意境悠远,一切都恰到好处。但快感过后,问题很快冒出来:生成的图片散落在WebUI的临时文件夹里,文件名是毫无意义的随机字符,连自己都记不清哪张图用了什么提示词。
这就像一个画家创作了无数作品,却把所有画作堆在角落,没有签名、没有日期、没有创作笔记。时间一长,连自己都分不清先后顺序。对于AI绘画来说,这个问题更突出——一天可能生成上百张图片,没有一套好的管理方法,这些精心创作的作品很快就会变成数字垃圾。
今天这篇文章,就专攻这个“创作后”的问题。从生成到管理的完整闭环,下面一步步拆解。核心痛点有三个:
- 怎么把图片安全保存下来?总不能每次都靠浏览器截图吧。
- 怎么给大量图片起个好名字?“output_001.jpg”这种名字等于没名字。
- 怎么把生成信息“烙”进图片里?让提示词、参数等信息永远跟着图片走。
无论你是国风艺术创作者、设计师,还是AI绘画爱好者,掌握这些技巧都能让你的创作流程更专业高效。更重要的是,这些方法都不需要你成为编程专家,跟着步骤来就行。
1. 引言:从创作到归档,你的国风作品需要系统化管理
(原文引言部分已包含在上文,此处保留章节标题作为结构。下面继续按原文章节展开。)
2. 准备工作:确认你的LiuJuan模型已就绪
在开始管理图片之前,得先确保模型服务正常运行。如果你已经能正常生成图片,可以快速浏览这节;如果是第一次接触,建议跟着操作一遍。
2.1 检查服务状态
首先,确认LiuJuan模型服务是否正常启动。打开终端或SSH连接到你的服务器,输入以下命令查看服务日志:
# 查看服务运行状态
ps aux | grep -E "(gradio|fastapi)" | grep -v grep
# 或者查看端口占用情况
netstat -tlnp | grep -E "(7860|8000)"
如果看到7860端口(WebUI)或8000端口(API)正在监听,说明服务运行正常。
2.2 访问Web界面生成测试图片
服务正常启动后,打开浏览器访问Web界面。根据你的部署方式,访问地址可能是:
https://你的服务器IP:7860(如果使用Gradio WebUI)- 或者通过平台提供的WEB访问入口
进入界面后,快速生成一张测试图片,确保一切正常:
- 选择模型版本:在下拉菜单中选择“LiuJuan20260223Zimage_25”(第25轮训练,风格最稳定)
- 输入提示词:在提示词框中输入“oriental beauty, elegant Chinese woman, flowing hanfu dress”
- 设置参数:分辨率选择768x768,推理步数20,CFG Scale设为7.5
- 生成图片:点击生成按钮,等待6-10秒
如果能看到生成的国风人像图片,说明模型工作正常,可以继续下一步。
2.3 找到图片存储位置
不同的部署方式,图片的默认保存位置可能不同。常见的位置包括:
# 检查可能的存储路径
ls -la /tmp/gradio/ # Gradio的临时缓存目录
ls -la /root/.cache/gradio/ # 另一个可能的缓存位置
ls -la /root/workspace/outputs/ # 自定义输出目录
如果你找不到图片,可以在WebUI界面上寻找“保存”或“下载”按钮,点击后查看浏览器下载的文件保存到了哪里。
3. 基础操作:单张图片的保存与导出
假设你已经生成了一张满意的图片,它正显示在Web界面上。怎么把它变成你电脑里的一个文件呢?这里有几种方法,从简单到专业,按需选择。
3.1 浏览器直接保存(最简方法)
这是最不需要技术背景的方法,适合偶尔生成一两张图片的用户。
操作步骤:
- 在Web界面生成的图片上,右键点击
- 选择“图片另存为”(Chrome/Edge)或“图像另存为”(Firefox)
- 选择你想要保存的文件夹
- 给图片起个有意义的名字,比如“汉服少女_水墨背景.jpg”
- 点击保存
优点:操作简单,无需任何技术知识,立即生效。
缺点:每次都要手动操作,生成多了很麻烦;文件名需要手动输入,容易混乱或重复;没有保存生成时的参数信息(提示词、模型版本等);图片质量可能受浏览器缩放影响。
3.2 使用界面下载功能(如果有)
有些WebUI界面会提供专门的下载按钮。如果LiuJuan的界面有的话,通常会在图片下方看到“Download”、“保存”或下载图标(⬇️)。点击后浏览器会自动下载图片文件,这种方式保存的图片通常是原始分辨率,质量更好。
小技巧:下载前可以修改文件名。大多数浏览器在下载时会弹出保存对话框,你可以在那里修改文件名。建议使用有意义的命名,比如“国风仕女图_20250315_01.jpg”。
3.3 从服务器文件系统直接获取(推荐给进阶用户)
如果你有服务器权限,并且知道图片生成后保存在服务器的哪个目录,这是最可靠的方法。
第一步:找到图片存储路径
# 方法1:查找最近修改的图片文件
find /tmp /root -name "*.jpg" -o -name "*.png" | xargs ls -lt | head -10
# 方法2:查看Gradio的缓存目录(常见位置)
ls -la /tmp/gradio/* 2>/dev/null | head -20
# 方法3:如果使用FastAPI服务,可能在这里
ls -la /root/workspace/outputs/ 2>/dev/null
第二步:复制图片到本地
# 方法A:使用scp命令(从本地终端执行)
# 将服务器上的图片复制到本地当前目录
scp username@你的服务器IP:/tmp/gradio/生成的图片.jpg .
# 方法B:使用SFTP客户端(如FileZilla)
# 连接服务器后,直接拖拽文件到本地
# 方法C:打包下载(图片较多时)
# 在服务器上打包
cd /tmp/gradio/
tar -czf liujuan_images.tar.gz *.jpg *.png
# 然后下载压缩包
scp username@你的服务器IP:/tmp/gradio/liujuan_images.tar.gz .
第三步:清理临时文件(可选)
# 清理7天前的临时文件
find /tmp/gradio -name "*.jpg" -mtime +7 -delete
find /tmp/gradio -name "*.png" -mtime +7 -delete
4. 进阶技巧:批量导出与智能命名
当你开始大量使用LiuJuan模型进行创作时,手动一张张保存和命名就不现实了。这时候需要一些自动化工具来提高效率。
4.1 批量导出所有生成图片
假设你已经找到了图片的存储目录,比如 /tmp/gradio/,可以用简单的命令批量处理。
方法一:使用命令行工具批量复制
# 创建专门的存储目录
mkdir -p ~/liujuan_collection/$(date +%Y%m%d)
# 复制所有jpg和png文件到新目录
cp /tmp/gradio/*.jpg ~/liujuan_collection/$(date +%Y%m%d)/
cp /tmp/gradio/*.png ~/liujuan_collection/$(date +%Y%m%d)/
# 统计复制了多少文件
echo "已复制 $(ls ~/liujuan_collection/$(date +%Y%m%d)/*.jpg 2>/dev/null | wc -l) 个jpg文件"
echo "已复制 $(ls ~/liujuan_collection/$(date +%Y%m%d)/*.png 2>/dev/null | wc -l) 个png文件"
方法二:使用Python脚本批量处理
如果你需要更精细的控制,比如只导出特定大小的图片,或者按照时间筛选,可以写一个简单的Python脚本:
import os
import shutil
from datetime import datetime, timedelta
# 配置路径
source_dir = "/tmp/gradio/" # 源目录(图片生成位置)
target_base = "/home/user/liujuan_collection/" # 目标基础目录
# 按日期创建子目录
today = datetime.now().strftime("%Y%m%d")
target_dir = os.path.join(target_base, today)
os.makedirs(target_dir, exist_ok=True)
print(f"源目录: {source_dir}")
print(f"目标目录: {target_dir}")
# 获取所有图片文件
image_files = []
for ext in ['.jpg', '.jpeg', '.png', '.webp']:
image_files.extend([f for f in os.listdir(source_dir) if f.lower().endswith(ext)])
print(f"找到 {len(image_files)} 个图片文件")
# 批量复制
copied_count = 0
for filename in image_files:
source_path = os.path.join(source_dir, filename)
target_path = os.path.join(target_dir, filename)
# 检查文件大小(避免复制空文件或损坏文件)
if os.path.getsize(source_path) > 1024: # 大于1KB
shutil.copy2(source_path, target_path)
copied_count += 1
print(f"已复制: {filename}")
else:
print(f"跳过小文件: {filename}")
print(f"\n批量导出完成!共复制 {copied_count} 个文件到 {target_dir}")
# 可选:清理源目录(谨慎操作!)
# confirm = input("\n是否清理源目录?(y/n): ")
# if confirm.lower() == 'y':
# for filename in image_files:
# os.remove(os.path.join(source_dir, filename))
# print("源目录已清理")
4.2 给图片批量起有意义的名字
“gradio_temp_image_12345.jpg”这种名字几乎没有任何信息量。好的命名应该能让你一眼就知道图片内容,甚至一年后还能回忆起当时的创作意图。
命名原则:
- 信息丰富:包含主题、风格、日期等关键信息
- 保持一致性:所有图片使用相同的命名规则
- 便于排序:按时间或序号排列时能保持正确顺序
- 避免特殊字符:只用字母、数字、下划线和连字符
命名方案示例:
| 方案 | 格式示例 | 优点 | 缺点 |
|---|---|---|---|
| 主题+日期+序号 | hanfu_20250315_001.jpg | 直观,便于按主题查找 | 主题需要手动判断 |
| 日期+时间+主题 | 20250315_143022_liujuan_cute.jpg | 精确到秒,绝对唯一 | 文件名较长 |
| 风格+参数+序号 | ink_wash_steps20_cfg7.5_001.jpg | 包含生成参数 | 需要记录参数 |
| 项目+场景+版本 | project_fairyland_scene1_v2.jpg | 适合项目制管理 | 需要预先规划 |
批量重命名脚本示例:
import os
import re
from datetime import datetime
def batch_rename_images(image_dir, naming_pattern="theme_date_index"):
"""
批量重命名图片文件
参数:
image_dir: 图片目录路径
naming_pattern: 命名模式,可选值:
- "theme_date_index": 主题_日期_序号
- "date_theme_index": 日期_主题_序号
- "index_theme_date": 序号_主题_日期
"""
# 获取所有图片文件
image_extensions = ['.jpg', '.jpeg', '.png', '.webp', '.bmp']
image_files = []
for file in os.listdir(image_dir):
if any(file.lower().endswith(ext) for ext in image_extensions):
image_files.append(file)
if not image_files:
print("目录中没有找到图片文件")
return
print(f"找到 {len(image_files)} 个图片文件")
# 按修改时间排序(从旧到新)
image_files.sort(key=lambda x: os.path.getmtime(os.path.join(image_dir, x)))
# 用户输入主题(可以改为自动识别)
theme = input("请输入这批图片的主题(如:hanfu、ink_wash、portrait): ").strip()
if not theme:
theme = "liujuan"
# 获取当前日期
date_str = datetime.now().strftime("%Y%m%d")
# 批量重命名
renamed_count = 0
for index, old_name in enumerate(image_files, 1):
# 获取文件扩展名
name_part, ext = os.path.splitext(old_name)
# 根据命名模式构建新文件名
if naming_pattern == "theme_date_index":
new_name = f"{theme}_{date_str}_{index:03d}{ext}"
elif naming_pattern == "date_theme_index":
new_name = f"{date_str}_{theme}_{index:03d}{ext}"
elif naming_pattern == "index_theme_date":
new_name = f"{index:03d}_{theme}_{date_str}{ext}"
else:
new_name = f"{theme}_{date_str}_{index:03d}{ext}"
# 完整的旧路径和新路径
old_path = os.path.join(image_dir, old_name)
new_path = os.path.join(image_dir, new_name)
# 避免文件名冲突
counter = 1
while os.path.exists(new_path):
if naming_pattern == "theme_date_index":
new_name = f"{theme}_{date_str}_{index:03d}_{counter}{ext}"
elif naming_pattern == "date_theme_index":
new_name = f"{date_str}_{theme}_{index:03d}_{counter}{ext}"
else:
new_name = f"{index:03d}_{theme}_{date_str}_{counter}{ext}"
new_path = os.path.join(image_dir, new_name)
counter += 1
# 重命名文件
os.rename(old_path, new_path)
renamed_count += 1
print(f"重命名: {old_name} -> {new_name}")
print(f"\n批量重命名完成!共处理 {renamed_count} 个文件")
# 使用示例
if __name__ == "__main__":
# 设置图片目录
image_directory = "/home/user/liujuan_collection/20250315/"
# 执行批量重命名
batch_rename_images(image_dir=image_directory,
naming_pattern="theme_date_index") # 使用 主题_日期_序号 格式
更智能的命名方案:
如果你能获取到生成时的提示词,可以尝试从提示词中提取关键词作为文件名的一部分:
import os
import re
def extract_keywords_from_prompt(prompt, max_keywords=3):
"""从提示词中提取关键词"""
# 移除常见停用词
stop_words = {'a', 'an', 'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'}
# 简单分词(按空格和逗号分割)
words = re.split(r'[,\s]+', prompt.lower())
# 过滤停用词和短词
keywords = []
for word in words:
if (word not in stop_words and len(word) > 2 and word.isalpha() and
word not in keywords):
keywords.append(word)
if len(keywords) >= max_keywords:
break
return '_'.join(keywords) if keywords else "general"
# 使用示例
prompt = "oriental beauty, elegant Chinese woman, flowing hanfu dress, soft lighting, ink wash painting style"
keywords = extract_keywords_from_prompt(prompt, max_keywords=2)
print(f"提取的关键词: {keywords}")
# 输出: oriental_beauty
5. 专业级管理:为图片嵌入元数据
元数据是“关于数据的数据”。对于AI生成的图片来说,把生成信息嵌入到图片文件里,就像给每张图片配了一个永久的“身份证”。无论这个文件被复制到哪里、传到哪个平台,这些信息都会一直跟着它。
5.1 为什么元数据如此重要?
- 长期可追溯:半年后你看到一张图,还能知道当初是怎么生成它的,用了什么提示词和参数
- 便于搜索和管理:可以根据提示词内容、模型版本等条件搜索图片
- 版权保护:证明你是图片的创作者,记录生成时间和使用的模型
- 参数复用:看到好效果,可以直接读取参数来复现或微调
- 信息完整性:分享图片时,重要的生成信息不会丢失
5.2 理解图片元数据格式
图片文件通常支持多种元数据格式:
- EXIF:最常用的元数据格式,主要用于相机拍摄信息
- IPTC:新闻和出版行业标准
- XMP:Adobe推出的可扩展元数据平台
- ICC Profile:色彩配置文件
对于AI生成图片,我们通常使用EXIF的UserComment字段或XMP来存储自定义数据。
5.3 使用Python为图片添加元数据
需要用到PIL(Python Imaging Library)库和piexif库来处理EXIF数据。
第一步:安装必要的库
pip install Pillow piexif
第二步:基础元数据添加脚本
from PIL import Image
import piexif
import json
from datetime import datetime
def add_basic_metadata(image_path, prompt, model_info=None):
"""为图片添加基础元数据
参数:
image_path: 图片文件路径
prompt: 生成图片使用的提示词
model_info: 模型信息字典(可选)
"""
# 打开图片
try:
img = Image.open(image_path)
except Exception as e:
print(f"无法打开图片 {image_path}: {e}")
return False
# 准备元数据
if model_info is None:
model_info = {
"name": "LiuJuan20260223Zimage",
"version": "v1.0",
"base_model": "Tongyi-MAI/Z-Image",
"lora_version": "25"
}
metadata = {
"generator": "AI Generated",
"model": model_info["name"],
"model_version": model_info["version"],
"base_model": model_info["base_model"],
"lora_version": model_info["lora_version"],
"prompt": prompt,
"generation_time": datetime.now().isoformat(),
"software": "LiuJuan20260223Zimage WebUI",
"author": "AI Artist", # 可以修改为你的名字
"copyright": f"© {datetime.now().year} AI Generated Art"
}
# 将元数据转换为JSON字符串
metadata_json = json.dumps(metadata, ensure_ascii=False, indent=2)
# 获取现有的EXIF数据(如果有的话)
try:
exif_dict = piexif.load(img.info.get("exif", b""))
except:
# 创建新的EXIF字典
exif_dict = {"0th": {}, "Exif": {}, "GPS": {}, "1st": {}, "Interop": {}}
# 添加基本EXIF信息
exif_dict["0th"][piexif.ImageIFD.ImageDescription] = f"AI Generated Image - {model_info['name']}"
exif_dict["0th"][piexif.ImageIFD.Software] = metadata["software"]
exif_dict["0th"][piexif.ImageIFD.Artist] = metadata["author"]
exif_dict["0th"][piexif.ImageIFD.Copyright] = metadata["copyright"]
# 添加生成时间
exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal] = datetime.now().strftime("%Y:%m:%d %H:%M:%S")
exif_dict["Exif"][piexif.ExifIFD.DateTimeDigitized] = datetime.now().strftime("%Y:%m:%d %H:%M:%S")
# 添加自定义数据到UserComment字段
# 注意:EXIF的UserComment字段有长度限制(约65535字节)
if len(metadata_json.encode('utf-8')) < 60000: # 留一些余量
exif_dict["Exif"][piexif.ExifIFD.UserComment] = metadata_json.encode('utf-8')
else:
# 如果数据太大,只保存关键信息
short_metadata = {
"model": metadata["model"],
"prompt_preview": prompt[:100] + "..." if len(prompt) > 100 else prompt,
"time": metadata["generation_time"]
}
exif_dict["Exif"][piexif.ExifIFD.UserComment] = json.dumps(short_metadata).encode('utf-8')
print(f"提示词过长,已截断保存。完整提示词长度: {len(prompt)} 字符")
# 将EXIF数据转换为字节
try:
exif_bytes = piexif.dump(exif_dict)
except Exception as e:
print(f"创建EXIF数据时出错: {e}")
return False
# 保存图片(保留原始质量)
try:
# 如果是PNG格式,需要特殊处理
if image_path.lower().endswith('.png'):
# PNG不支持EXIF,但我们可以保存为JPEG或使用其他方法
print("PNG格式不支持EXIF,建议转换为JPEG格式")
return False
# 保存JPEG图片
img.sa ve(image_path, exif=exif_bytes, quality=95)
print(f"已为 {os.path.basename(image_path)} 添加元数据")
return True
except Exception as e:
print(f"保存图片时出错: {e}")
return False
# 使用示例
if __name__ == "__main__":
# 单张图片添加元数据
success = add_basic_metadata(
image_path="/path/to/your/image.jpg",
prompt="LiuJuan, beautiful anime girl, detailed eyes, flowing hair, traditional Chinese clothing, ink wash painting style",
model_info={
"name": "LiuJuan20260223Zimage",
"version": "v1.0",
"base_model": "Tongyi-MAI/Z-Image",
"lora_version": "25"
}
)
if success:
print("元数据添加成功!")
else:
print("元数据添加失败")
第三步:完整参数元数据添加
def add_ai_metadata(image_path, generation_params):
"""为AI生成图片添加完整的生成参数元数据
参数:
image_path: 图片文件路径
generation_params: 生成参数字典,包含:
- prompt: 正面提示词
- negative_prompt: 负面提示词(可选)
- steps: 采样步数
- cfg_scale: CFG Scale参数
- sampler: 采样器名称
- seed: 随机种子
- width: 图片宽度
- height: 图片高度
- model: 模型信息
"""
try:
img = Image.open(image_path)
except Exception as e:
print(f"无法打开图片: {e}")
return False
# 构建完整的元数据
metadata = {
"ai_generation": {
"tool": "LiuJuan20260223Zimage WebUI",
"version": "1.0",
"timestamp": datetime.now().isoformat()
},
"model": {
"name": generation_params.get("model", {}).get("name", "LiuJuan20260223Zimage"),
"version": generation_params.get("model", {}).get("version", "v1.0"),
"base_model": generation_params.get("model", {}).get("base_model", "Tongyi-MAI/Z-Image"),
"lora_version": generation_params.get("model", {}).get("lora_version", "25")
},
"prompts": {
"positive": generation_params.get("prompt", ""),
"negative": generation_params.get("negative_prompt", "")
},
"parameters": {
"steps": generation_params.get("steps", 20),
"cfg_scale": generation_params.get("cfg_scale", 7.5),
"sampler": generation_params.get("sampler", "DPM++ 2M Karras"),
"seed": generation_params.get("seed", -1),
"width": generation_params.get("width", img.width),
"height": generation_params.get("height", img.height),
"batch_size": generation_params.get("batch_size", 1),
"batch_count": generation_params.get("batch_count", 1)
},
"author": generation_params.get("author", "AI Artist"),
"copyright": f"© {datetime.now().year} AI Generated - {generation_params.get('author', 'AI Artist')}"
}
# 转换为JSON
metadata_json = json.dumps(metadata, ensure_ascii=False, indent=2)
# 处理EXIF数据
try:
exif_dict = piexif.load(img.info.get("exif", b""))
except:
exif_dict = {"0th": {}, "Exif": {}, "GPS": {}, "1st": {}, "Interop": {}}
# 添加标准EXIF字段
exif_dict["0th"][piexif.ImageIFD.ImageDescription] = "AI Generated Image - LiuJuan Model"
exif_dict["0th"][piexif.ImageIFD.Software] = "LiuJuan20260223Zimage AI Generator"
exif_dict["0th"][piexif.ImageIFD.Artist] = metadata["author"]
exif_dict["0th"][piexif.ImageIFD.Copyright] = metadata["copyright"]
# 添加时间信息
current_time = datetime.now().strftime("%Y:%m:%d %H:%M:%S")
exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal] = current_time
exif_dict["Exif"][piexif.ExifIFD.DateTimeDigitized] = current_time
# 添加AI生成信息到UserComment
# 注意:如果数据太大,可能需要分段存储
try:
exif_dict["Exif"][piexif.ExifIFD.UserComment] = metadata_json.encode('utf-8')
except:
# 如果数据太大,保存简化版本
simplified_metadata = {
"model": metadata["model"]["name"],
"prompt_preview": metadata["prompts"]["positive"][:200],
"parameters": {
"steps": metadata["parameters"]["steps"],
"cfg_scale": metadata["parameters"]["cfg_scale"],
"seed": metadata["parameters"]["seed"]
}
}
exif_dict["Exif"][piexif.ExifIFD.UserComment] = json.dumps(simplified_metadata).encode('utf-8')
# 保存图片
try:
exif_bytes = piexif.dump(exif_dict)
# 如果是JPEG格式
if image_path.lower().endswith(('.jpg', '.jpeg')):
img.sa ve(image_path, exif=exif_bytes, quality=95)
print(f"成功为 {os.path.basename(image_path)} 添加AI生成元数据")
return True
else:
# 对于PNG等其他格式,可能需要转换为JPEG或使用其他方法
print(f"警告: {image_path} 不是JPEG格式,EXIF支持可能有限")
# 可以尝试保存为JPEG副本
jpeg_path = os.path.splitext(image_path)[0] + "_with_metadata.jpg"
img.sa ve(jpeg_path, exif=exif_bytes, quality=95)
print(f"已保存为JPEG格式: {jpeg_path}")
return True
except Exception as e:
print(f"保存元数据时出错: {e}")
return False
# 使用示例
if __name__ == "__main__":
# 准备生成参数
params = {
"prompt": "LiuJuan, beautiful anime girl with traditional Chinese hairstyle, wearing elegant hanfu, standing in a classical garden with cherry blossoms, ink wash painting style, soft lighting, detailed eyes, flowing silk dress",
"negative_prompt": "western features, blonde hair, blue eyes, low quality, blurry, distorted, bad anatomy",
"steps": 25,
"cfg_scale": 7.5,
"sampler": "DPM++ 2M Karras",
"seed": 123456789,
"width": 768,
"height": 768,
"model": {
"name": "LiuJuan20260223Zimage",
"version": "v1.0",
"base_model": "Tongyi-MAI/Z-Image",
"lora_version": "25"
},
"author": "国风艺术创作者"
}
# 添加元数据
success = add_ai_metadata(
image_path="/path/to/liujuan_generated.jpg",
generation_params=params
)
5.4 批量添加元数据
如果你有一批图片,并且有对应的生成参数记录,可以批量处理:
import os
import csv
import json
def batch_add_metadata_from_log(image_dir, log_file):
"""从日志文件批量读取生成参数并添加元数据
支持多种日志格式:
1. CSV格式:每行包含图片文件名和生成参数
2. JSON格式:包含图片列表和对应参数
3. 文本格式:自定义格式
"""
# 检查日志文件是否存在
if not os.path.exists(log_file):
print(f"日志文件不存在: {log_file}")
return False
# 根据文件扩展名选择解析方式
file_ext = os.path.splitext(log_file)[1].lower()
image_params = {}
if file_ext == '.csv':
# 解析CSV文件
with open(log_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
filename = row.get('filename', '').strip()
if filename:
# 解析参数(假设CSV中有对应的列)
params = {
"prompt": row.get('prompt', ''),
"negative_prompt": row.get('negative_prompt', ''),
"steps": int(row.get('steps', 20)),
"cfg_scale": float(row.get('cfg_scale', 7.5)),
"seed": int(row.get('seed', -1)),
"model": {
"name": row.get('model_name', 'LiuJuan20260223Zimage'),
"lora_version": row.get('lora_version', '25')
}
}
image_params[filename] = params
elif file_ext == '.json':
# 解析JSON文件
with open(log_file, 'r', encoding='utf-8') as f:
data = json.load(f)
# 根据JSON结构解析
if isinstance(data, list):
for item in data:
filename = item.get('filename', '')
if filename:
image_params[filename] = item.get('params', {})
elif isinstance(data, dict):
image_params = data.get('images', {})
else:
# 尝试解析文本文件(自定义格式)
with open(log_file, 'r', encoding='utf-8') as f:
for line in f:
# 假设格式:文件名|提示词|参数
if '|' in line:
parts = line.strip().split('|')
if len(parts) >= 2:
filename = parts[0].strip()
prompt = parts[1].strip()
params = {"prompt": prompt}
image_params[filename] = params
print(f"从日志中读取了 {len(image_params)} 个图片的生成参数")
# 批量处理图片
processed = 0
failed = 0
for filename, params in image_params.items():
image_path = os.path.join(image_dir, filename)
if os.path.exists(image_path):
print(f"处理: {filename}")
success = add_ai_metadata(image_path, params)
if success:
processed += 1
else:
failed += 1
print(f"失败: {filename}")
else:
print(f"文件不存在: {filename}")
failed += 1
print(f"\n批量处理完成!")
print(f"成功: {processed} 个文件")
print(f"失败: {failed} 个文件")
return processed > 0
# 使用示例
if __name__ == "__main__":
# 批量处理
batch_add_metadata_from_log(
image_dir="/path/to/your/images",
log_file="/path/to/generation_log.csv" # 或 .json 文件
)
5.5 读取和查看元数据
添加了元数据之后,怎么查看呢?有几种方法:
方法一:使用Python读取
import piexif
import json
def read_image_metadata(image_path):
"""读取图片中的元数据"""
try:
exif_dict = piexif.load(image_path)
# 检查是否有UserComment字段
if piexif.ExifIFD.UserComment in exif_dict.get("Exif", {}):
user_comment = exif_dict["Exif"][piexif.ExifIFD.UserComment]
# 尝试解码为JSON
try:
# 移除可能的编码前缀
if user_comment.startswith(b'UNICODE\0'):
metadata_str = user_comment[8:].decode('utf-8')
else:
metadata_str = user_comment.decode('utf-8')
metadata = json.loads(metadata_str)
return metadata
except:
# 如果不是JSON,直接返回字符串
return user_comment.decode('utf-8', errors='ignore')
# 检查其他EXIF字段
basic_info = {}
if piexif.ImageIFD.ImageDescription in exif_dict.get("0th", {}):
basic_info["description"] = exif_dict["0th"][piexif.ImageIFD.ImageDescription].decode('utf-8', errors='ignore')
if piexif.ImageIFD.Software in exif_dict.get("0th", {}):
basic_info["software"] = exif_dict["0th"][piexif.ImageIFD.Software].decode('utf-8', errors='ignore')
if piexif.ExifIFD.DateTimeOriginal in exif_dict.get("Exif", {}):
basic_info["date_time"] = exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal].decode('utf-8', errors='ignore')
return basic_info if basic_info else None
except Exception as e:
print(f"读取元数据时出错: {e}")
return None
# 使用示例
metadata = read_image_metadata("/path/to/image.jpg")
if metadata:
print("找到元数据:")
if isinstance(metadata, dict):
print(json.dumps(metadata, indent=2, ensure_ascii=False))
else:
print(metadata)
else:
print("未找到元数据或读取失败")
方法二:使用命令行工具
# 安装exiftool(强大的EXIF工具)
# Ubuntu/Debian: sudo apt-get install libimage-exiftool-perl
# macOS: brew install exiftool
# 查看图片的所有EXIF信息
exiftool image.jpg
# 只查看特定字段
exiftool -UserComment image.jpg
# 查看JSON格式的UserComment
exiftool -UserComment -j image.jpg
# 批量查看目录下所有图片的元数据
exiftool -UserComment -j /path/to/images/*.jpg > metadata.json
方法三:使用图形化工具
- Windows:右键图片 → 属性 → 详细信息
- Mac:右键图片 → 显示简介 → 更多信息
- 在线工具:上传到
https://exif.tools/或https://metapicz.com/
6. 完整工作流示例:从生成到管理的自动化脚本
如果你经常使用LiuJuan模型生成图片,可以创建一个完整的自动化工作流。下面是一个示例脚本,它模拟了从生成到保存、命名、添加元数据的完整过程。
import os
import time
import json
import shutil
from datetime import datetime
from pathlib import Path
import piexif
from PIL import Image
class LiuJuanImageWorkflow:
"""LiuJuan图片生成与管理自动化工作流"""
def __init__(self, base_output_dir="./liujuan_output"):
"""初始化工作流
参数:
base_output_dir: 基础输出目录
"""
self.base_output_dir = Path(base_output_dir)
self.setup_directories()
# 初始化日志
self.log_file = self.base_output_dir / "generation_log.json"
self.generation_log = self.load_log()
def setup_directories(self):
"""创建必要的目录结构"""
directories = [
self.base_output_dir,
self.base_output_dir / "raw", # 原始生成文件
self.base_output_dir / "processed", # 处理后的文件
self.base_output_dir / "metadata", # 元数据备份
self.base_output_dir / "exports" # 导出文件
]
for directory in directories:
directory.mkdir(parents=True, exist_ok=True)
print(f"确保目录存在: {directory}")
def load_log(self):
"""加载或创建生成日志"""
if self.log_file.exists():
try:
with open(self.log_file, 'r', encoding='utf-8') as f:
return json.load(f)
except:
print("日志文件损坏,创建新日志")
return []
else:
return []
def sa ve_log(self):
"""保存生成日志"""
with open(self.log_file, 'w', encoding='utf-8') as f:
json.dump(self.generation_log, f, indent=2, ensure_ascii=False)
def generate_image(self, prompt, negative_prompt="", **kwargs):
"""生成图片(模拟函数,实际需要替换为真正的生成代码)
在实际使用中,这里应该调用LiuJuan模型的API或WebUI
"""
print(f"\n正在生成图片...")
print(f"提示词: {prompt[:50]}..." if len(prompt) > 50 else f"提示词: {prompt}")
# 模拟生成过程
time.sleep(2) # 模拟生成时间
# 生成唯一ID和文件名
generation_id = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
raw_filename = f"raw_{generation_id}.jpg"
raw_path = self.base_output_dir / "raw" / raw_filename
# 在实际应用中,这里应该保存真正的生成图片
# 现在我们创建一个示例图片
self.create_sample_image(raw_path, prompt)
# 记录生成信息
generation_info = {
"id": generation_id,
"timestamp": datetime.now().isoformat(),
"filename": raw_filename,
"prompt": prompt,
"negative_prompt": negative_prompt,
"parameters": {
"steps": kwargs.get("steps", 20),
"cfg_scale": kwargs.get("cfg_scale", 7.5),
"sampler": kwargs.get("sampler", "DPM++ 2M Karras"),
"seed": kwargs.get("seed", -1),
"width": kwargs.get("width", 768),
"height": kwargs.get("height", 768),
"model_version": kwargs.get("model_version", "LiuJuan20260223Zimage_25")
},
"raw_path": str(raw_path),
"processed_path": None,
"metadata_added": False,
"renamed": False,
"exported": False
}
# 添加到日志
self.generation_log.append(generation_info)
self.sa ve_log()
print(f"图片已生成: {raw_filename}")
return generation_info
def create_sample_image(self, path, prompt):
"""创建示例图片(实际使用中应替换为真正的生成)"""
# 创建一个简单的示例图片
img = Image.new('RGB', (768, 768), color=(240, 240, 255))
# 添加一些文本说明
from PIL import ImageDraw, ImageFont
draw = ImageDraw.Draw(img)
# 使用默认字体
try:
font = ImageFont.truetype("arial.ttf", 20)
except:
font = ImageFont.load_default()
# 在图片上添加提示词预览
draw.text((10, 10), f"AI Generated: LiuJuan Model", fill=(0, 0, 0), font=font)
draw.text((10, 40), f"Prompt: {prompt[:50]}...", fill=(100, 100, 100), font=font)
draw.text((10, 70), f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", fill=(100, 100, 100), font=font)
img.sa ve(path, quality=95)
def process_image(self, generation_info):
"""处理单张图片:添加元数据、重命名等"""
print(f"\n处理图片: {generation_info['filename']}")
raw_path = Path(generation_info["raw_path"])
if not raw_path.exists():
print(f"原始文件不存在: {raw_path}")
return False
# 1. 添加元数据
processed_filename = f"processed_{generation_info['id']}.jpg"
processed_path = self.base_output_dir / "processed" / processed_filename
# 复制文件到处理目录
shutil.copy2(raw_path, processed_path)
# 添加元数据
metadata_added = self.add_metadata(processed_path, generation_info)
if metadata_added:
generation_info["metadata_added"] = True
generation_info["processed_path"] = str(processed_path)
print(f"已添加元数据")
else:
print(f"添加元数据失败")
return False
# 2. 智能重命名
renamed_filename = self.generate_smart_name(generation_info)
renamed_path = self.base_output_dir / "processed" / renamed_filename
# 重命名文件
try:
os.rename(processed_path, renamed_path)
generation_info["renamed"] = True
generation_info["processed_path"] = str(renamed_path)
generation_info["final_filename"] = renamed_filename
print(f"已重命名为: {renamed_filename}")
except Exception as e:
print(f"重命名失败: {e}")
# 3. 备份元数据到单独文件
self.backup_metadata(generation_info)
# 更新日志
self.sa ve_log()
return True
def add_metadata(self, image_path, generation_info):
"""为图片添加元数据"""
try:
img = Image.open(image_path)
# 准备元数据
metadata = {
"ai_generation": {
"tool": "LiuJuan20260223Zimage Workflow",
"version": "1.0",
"timestamp": generation_info["timestamp"]
},
"model": {
"name": "LiuJuan20260223Zimage",
"version": generation_info["parameters"]["model_version"],
"base_model": "Tongyi-MAI/Z-Image"
},
"prompts": {
"positive": generation_info["prompt"],
"negative": generation_info["negative_prompt"]
},
"parameters": generation_info["parameters"],
"workflow_info": {
"processed_time": datetime.now().isoformat(),
"workflow_version": "1.0"
}
}
# 转换为JSON
metadata_json = json.dumps(metadata, ensure_ascii=False)
# 处理EXIF数据
try:
exif_dict = piexif.load(img.info.get("exif", b""))
except:
exif_dict = {"0th": {}, "Exif": {}, "GPS": {}, "1st": {}, "Interop": {}}
# 添加标准EXIF字段
exif_dict["0th"][piexif.ImageIFD.ImageDescription] = "AI Generated Image - LiuJuan Model"
exif_dict["0th"][piexif.ImageIFD.Software] = "LiuJuan20260223Zimage Workflow"
exif_dict["0th"][piexif.ImageIFD.Artist] = "AI Artist"
# 添加时间
current_time = datetime.now().strftime("%Y:%m:%d %H:%M:%S")
exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal] = current_time
# 添加AI生成信息
try:
exif_dict["Exif"][piexif.ExifIFD.UserComment] = metadata_json.encode('utf-8')
except:
# 如果数据太大,保存简化版本
simplified = {
"model": metadata["model"]["name"],
"prompt": metadata["prompts"]["positive"][:100],
"time": metadata["ai_generation"]["timestamp"]
}
exif_dict["Exif"][piexif.ExifIFD.UserComment] = json.dumps(simplified).encode('utf-8')
# 保存图片
exif_bytes = piexif.dump(exif_dict)
img.sa ve(image_path, exif=exif_bytes, quality=95)
return True
except Exception as e:
print(f"添加元数据时出错: {e}")
return False
def generate_smart_name(self, generation_info):
"""生成智能文件名"""
prompt = generation_info["prompt"].lower()
# 定义关键词映射
theme_keywords = {
"hanfu": "汉服",
"traditional": "传统",
"chinese": "中国风",
"ink": "水墨",
"painting": "绘画",
"beauty": "美人",
"elegant": "优雅",
"cute": "可爱",
"portrait": "肖像"
}
# 提取主题
theme = "国风"
for keyword, chinese in theme_keywords.items():
if keyword in prompt:
theme = chinese
break
# 提取风格关键词
style = ""
style_keywords = ["ink wash", "watercolor", "oil painting", "sketch"]
for keyword in style_keywords:
if keyword in prompt:
style = keyword.replace(" ", "_")
break
# 构建文件名
date_part = datetime.now().strftime("%Y%m%d")
time_part = generation_info["id"].split("_")[1] # 获取时间部分
if style:
filename = f"{theme}_{style}_{date_part}_{time_part}.jpg"
else:
filename = f"{theme}_{date_part}_{time_part}.jpg"
return filename
def backup_metadata(self, generation_info):
"""备份元数据到单独文件"""
metadata_dir = self.base_output_dir / "metadata"
metadata_file = metadata_dir / f"{generation_info['id']}.json"
with open(metadata_file, 'w', encoding='utf-8') as f:
json.dump(generation_info, f, indent=2, ensure_ascii=False)
print(f"元数据已备份: {metadata_file.name}")
def export_images(self, export_format="organized"):
"""导出处理后的图片"""
print(f"\n导出图片...")
export_dir = self.base_output_dir / "exports" / datetime.now().strftime("%Y%m%d_%H%M%S")
export_dir.mkdir(parents=True, exist_ok=True)
exported_count = 0
for item in self.generation_log:
if item.get("processed_path") and Path(item["processed_path"]).exists():
source_path = Path(item["processed_path"])
if export_format == "organized":
# 按主题组织
theme = self.extract_theme_from_prompt(item["prompt"])
theme_dir = export_dir / theme
theme_dir.mkdir(exist_ok=True)
dest_path = theme_dir / source_path.name
else:
# 平铺导出
dest_path = export_dir / source_path.name
shutil.copy2(source_path, dest_path)
exported_count += 1
# 标记为已导出
item["exported"] = True
self.sa ve_log()
print(f"已导出 {exported_count} 个文件到: {export_dir}")
return export_dir
def extract_theme_from_prompt(self, prompt):
"""从提示词中提取主题"""
prompt_lower = prompt.lower()
if any(word in prompt_lower for word in ["hanfu", "traditional dress", "chinese clothing"]):
return "汉服人像"
elif any(word in prompt_lower for word in ["ink", "watercolor", "painting"]):
return "水墨绘画"
elif any(word in prompt_lower for word in ["landscape", "scene", "garden"]):
return "场景"
elif any(word in prompt_lower for word in ["portrait", "face", "closeup"]):
return "肖像"
else:
return "其他"
def generate_report(self):
"""生成处理报告"""
report_file = self.base_output_dir / f"workflow_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"
with open(report_file, 'w', encoding='utf-8') as f:
f.write("# LiuJuan图片工作流处理报告\n\n")
f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"处理图片总数: {len(self.generation_log)}\n\n")
# 统计信息
processed_count = sum(1 for item in self.generation_log if item.get("metadata_added"))
renamed_count = sum(1 for item in self.generation_log if item.get("renamed"))
exported_count = sum(1 for item in self.generation_log if item.get("exported"))
f.write("## 处理统计\n\n")
f.write(f"- 已添加元数据: {processed_count}/{len(self.generation_log)}\n")
f.write(f"- 已重命名: {renamed_count}/{len(self.generation_log)}\n")
f.write(f"- 已导出: {exported_count}/{len(self.generation_log)}\n\n")
# 详细记录
f.write("## 图片详情\n\n")
for item in self.generation_log:
f.write(f"### {item.get('final_filename', item['filename'])}\n")
f.write(f"- **ID**: {item['id']}\n")
f.write(f"- **生成时间**: {item['timestamp']}\n")
f.write(f"- **提示词**: {item['prompt'][:100]}...\n")
f.write(f"- **参数**: {item['parameters']['steps']} steps, CFG {item['parameters']['cfg_scale']}\n")
f.write(f"- **状态**: ")
status_parts = []
if item.get("metadata_added"):
status_parts.append("✓ 元数据")
if item.get("renamed"):
status_parts.append("✓ 重命名")
if item.get("exported"):
status_parts.append("✓ 已导出")
f.write(", ".join(status_parts) if status_parts else "未处理")
f.write("\n\n")
print(f"报告已生成: {report_file}")
return report_file
def run_batch_processing(self):
"""批量处理所有未处理的图片"""
print("开始批量处理...")
unprocessed = [item for item in self.generation_log if not item.get("metadata_added")]
if not unprocessed:
print("没有需要处理的图片")
return
print(f"找到 {len(unprocessed)} 个未处理的图片")
success_count = 0
for item in unprocessed:
if self.process_image(item):
success_count += 1
print(f"\n批量处理完成!成功处理 {success_count}/{len(unprocessed)} 个图片")
# 生成报告
self.generate_report()
# 使用示例
if __name__ == "__main__":
# 初始化工作流
workflow = LiuJuanImageWorkflow(base_output_dir="./liujuan_workspace")
# 示例:生成一些测试图片
print("=" * 50)
print("LiuJuan图片工作流示例")
print("=" * 50)
test_prompts = [
"LiuJuan, beautiful Chinese woman in hanfu, ink wash painting style, elegant posture, traditional hairstyle",
"LiuJuan, cute anime girl with traditional clothing, cherry blossom background, soft lighting",
"LiuJuan, portrait of elegant woman, detailed eyes, flowing silk dress, classical Chinese garden"
]
# 生成图片
print("\n1. 生成测试图片...")
for i, prompt in enumerate(test_prompts, 1):
print(f"\n生成第 {i} 张图片...")
generation_info = workflow.generate_image(
prompt=prompt,
negative_prompt="western features, low quality, blurry",
steps=25,
cfg_scale=7.5,
seed=1000 + i,
model_version="LiuJuan20260223Zimage_25"
)
# 批量处理
print("\n2. 批量处理图片...")
workflow.run_batch_processing()
# 导出图片
print("\n3. 导出处理后的图片...")
export_dir = workflow.export_images(export_format="organized")
print(f"图片已导出到: {export_dir}")
# 生成最终报告
print("\n4. 生成处理报告...")
report_file = workflow.generate_report()
print("\n" + "=" * 50)
print("工作流执行完成!")
print(f"所有文件保存在: {workflow.base_output_dir}")
print(f"查看报告: {report_file}")
print("=" * 50)
这个脚本提供了一个完整的自动化工作流框架,你可以根据自己的实际需求进行修改和扩展。关键是要建立一个系统化的管理流程,而不是每次生成图片都手动处理。
7. 总结:建立高效的国风AI图片管理流程
通过今天的学习,你应该已经掌握了从LiuJuan模型生成图片到系统化管理的完整流程。回顾一下关键点,并给出一些实用建议。
7.1 核心要点回顾
- 单张图片导出是基础:浏览器右键保存适合偶尔使用的场景,但从服务器直接获取更可靠。
- 批量处理是效率关键:当图片数量多时,一定要用脚本批量处理,节省时间减少错误。
- 智能命名让管理更轻松:好的命名规则能让你的图片库井然有序,方便查找和整理。
- 元数据是长期价值的保障:把生成参数嵌入图片文件,确保信息不会丢失,方便未来查找和复用。
- 自动化工作流是专业选择:建立从生成到归档的完整流程,让创作更专注。
7.2 不同场景下的建议方案
根据你的使用频率和需求,可以选择不同的管理方案:
偶尔使用(每周<10张):
- 手动保存,用“主题_日期_序号”的方式命名
- 在文件名中简单记录关键参数,如“汉服少女_20250315_01_cfg7.5.jpg”
- 建立一个Excel或文本文件记录生成参数
经常使用(每天10-100张):
- 使用批量导出脚本,按日期自动整理
- 建立固定的命名规则,如“风格_主题_日期_时间.jpg”
- 用CSV或JSON文件记录所有生成参数
- 实现自动元数据嵌入
- 定期备份和整理
专业使用(每天>100张):
- 建立完整的自动化管道
- 使用数据库记录所有生成信息
- 实现自动分类和标签系统
- 建立版本控制和备份机制
- 开发简单的前端管理界面
7.3 实用工具推荐
- EXIF查看工具:
- ExifTool(命令行,功能强大)
- Exif Pilot(Windows图形界面)
- GraphicConverter(Mac)
- 在线工具:exif.tools、metapicz.com
- 批量重命名工具:
- Advanced Renamer(Windows)
- NameChanger(Mac)
- pyRenamer(Linux)
- 自己写Python脚本(最灵活)
- 图片管理软件:
- Adobe Bridge(专业)
- XnView MP(免费,功能全面)
- DigiKam(开源,支持元数据)
- 自动化脚本:
- 本文提供的Python脚本
- 结合cron或计划任务定时运行
- 集成到你的现有工作流中
7.4 下一步学习建议
如果你想要进一步优化你的图片管理工作流,可以考虑:
- 学习更多EXIF操作:除了基本的生成参数,还可以添加GPS信息、版权信息、关键词标签等。
- 建立图片数据库:使用SQLite或轻量级数据库管理大量图片和元数据。
- 实现智能分类:基于提示词内容自动给图片打标签、分类。
- 搭建Web管理界面:用Flask或Streamlit做一个简单的管理界面,可视化查看和搜索图片。
- 集成到创作流程:如果你有其他创作工具(如Photoshop、Clip Studio等),研究如何将AI生成图片无缝集成进去。
记住,好的工具和流程不会限制你的创造力,反而会释放它。当你不再担心“那张图放哪了”或“这个效果是怎么做出来的”时,你就能更专注于国风艺术的创作本身。
