ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Image-to-Prompt实战:用CLIP和BLIP把参考图变成文生图提示词

Image-to-Prompt实战:用CLIP和BLIP把参考图变成文生图提示词 简介基于CLIP与BLIP的多模态大模型实战项目源码包面向AI算法开发者、研究者和中高级学习者解决从图像自动生成提示词Image-to-Prompt的核心问题。资源共17个文件压缩包仅781KB包含6个Python脚本、6个TXT配置文件、1个Jupyter Notebook、1个YAML环境文件、1个TOML配置文件和README说明其中run_gradio.py提供可视化交互run_cli.py支持命令行调用ipynb便于逐步理解处理流程。目前已有629人学习下载。项目融合CLIP的图文对齐能力与BLIP的文本生成能力将视觉信息转化为可复用的文本提示适用于图像搜索、自动标注、内容分析等多模态应用场景。源码结构紧凑且扩展性强读者可借助示例快速迁移到自定义图像数据集或基于现有组件二次开发是兼顾理论理解与工程实践的优质入门与进阶资源。1. 多模态大模型里的 Image-to-Prompt 到底在解决什么问题把参考图变成文生图提示词做 AI 绘画的同志大概率都经历过这个场景收藏夹里存了上百张参考图真要用 Stable Diffusion 或 Midjourney 复刻的时候对着图憋不出提示词反过来做数据标注、给多模态大模型攒训练集的人也常需要稳定地把一张图的“描述真相”抽出来。Image-to-Prompt 要解决的就是这条路径输入一张图输出一段能直接喂给文生图模型的提示词。这里采用的方案是两个模型分工——CLIP 负责给画面打标签风格、媒介、光照、质量BLIP 负责写自然语言描述主体是谁、在做什么、场景什么样最后按规则拼成 prompt。这篇笔记从原理讲到可复现代码、调参边界和踩坑记录适合文生图玩家、做多模态应用开发的工程师以及被“提示词反推”需求反复找上门的项目负责人。2. CLIP 与 BLIP 分工拆解为什么“双模型反推”比单模型硬扛靠谱单模型能不能做 Image-to-Prompt能但各有各的瘸腿。只拿 BLIP 出 caption句子自然但你说不清风格是“赛博朋克”还是“新古典”文生图模型收到的风格信号很弱只拿 CLIP 加一个几百项的大词表硬分风格词能出来却拼不成一句人话主体信息全丢。我一般会把这类需求直接拆成“判别器 生成器”的组合CLIP 当判别器BLIP 当生成器。这个分工不是拍脑袋而是两个模型的结构决定了的。2.1 CLIP 擅长打分不擅长造句对比学习双塔的结构约束CLIP 的原始论文做的就是图文对比学习图像编码器ViT 或 ResNet和文本编码器Transformer各出一个归一化向量在约 4 亿图文对上做对比损失让配对的图与文靠近、没配对的推开。推理时它不“生成”任何文本而是把候选句子编码成向量与图像向量做点积再乘一个可学习的温度初始约为 0.07 的倒数输出相似度分数。所以 CLIP 骨子里是一个零样本分类器给一组候选标签它只能打分排序。这带来一个很实用的推论CLIP 的输出质量高度依赖候选词表和模板句。同一组标签写成 a photo of oil painting 和 oil painting 得分天差地别——因为训练数据里的图文对绝大多数是句子形态。这就是为什么在实际项目里CLIP 相关的模型应用几乎都要配一套“模板工程”而不是直接扔裸词进去。理解这层约束后面 3.2 的标签设计才有依据。顺带提醒CLIP 文本侧上下文只有 77 个 token模板句一长候选标签的容量就被压缩这也是 SD 沿用 77 token 限制的根因。2.2 BLIP 擅长描述不擅长分类image captioning 家族的取舍BLIPBootstrapping Language-Image Pre-training是 Salesforce 的视觉语言模型核心思路是用“生成 过滤”的自举策略清洗网络爬来的图文数据再用统一 backbone 同时做理解与生成任务。我们用的 image captioning 分支本质是 encoder-decoder图像进 ViT文本解码器逐 token 写出句子。它天然擅长把画面转述成自然语言而不是像分类器那样输出离散标签。这里有个常见的对比SD 社区常用的 WD14 tagger 输出的是标签集合ComfyUI 里的提示词反推节点、CLIP 询问机一类功能也偏标签流。标签流的好处是和 SD 的输入习惯吻合坏处是缺动词、缺场景关系BLIP 的长处恰好补这一块。所以要提醒一点别指望 BLIP 把风格说得准它写“雨后街道上的霓虹灯”很稳但“这是低调度摄影还是高调摄影”这种判断交给 CLIP 更靠谱。另外 BLIP 有 base 和 large 两个规格base 的 caption 更短更稳large 细节更多但解码更慢业务上我默认从 base 起步只有描述主体关系确实复杂才换 large。2.3 最小可运行代码HuggingFace 两行加载单张图先跑通先看最小闭环感受两个模型的实际输入输出。依赖transformers和 Pillow模型权重走 HuggingFace 自动下载# min_demo.py单张图同时跑 BLIP 描述 CLIP 风格打分 from PIL import Image import torch from transformers import CLIPProcessor, CLIPModel, BlipProcessor, BlipForConditionalGeneration image Image.open(ref.png).convert(RGB) # BLIP 生成自然语言描述 blip_processor BlipProcessor.from_pretrained(Salesforce/blip-image-captioning-base) blip_model BlipForConditionalGeneration.from_pretrained(Salesforce/blip-image-captioning-base) inputs blip_processor(imagesimage, return_tensorspt) out blip_model.generate(**inputs, max_length48, num_beams5) caption blip_processor.decode(out[0], skip_special_tokensTrue) # CLIP 对候选标签打分 clip_processor CLIPProcessor.from_pretrained(openai/clip-vit-base-patch32) clip_model CLIPModel.from_pretrained(openai/clip-vit-base-patch32) labels [oil painting, watercolor, photorealistic, cinematic lighting, low angle shot] texts [fa {label} image for label in labels] inputs clip_processor(texttexts, imagesimage, return_tensorspt, paddingTrue) with torch.no_grad(): scores clip_model(**inputs).logits_per_image.softmax(dim1)[0].tolist() for label, s in sorted(zip(labels, scores), keylambda x: -x[1]): print(f{label}: {s:.3f}) print(caption:, caption)逻辑说明BLIP 部分先把 PIL 图传进BlipProcessor它会自动把图缩放到 384×384 并做归一化generate走 beam search 出句子decode时skip_special_tokensTrue去掉boseos。CLIP 部分把候选文本和图像一起进 processorlogits_per_image是 (1, 候选数) 的相似度矩阵对 dim1 做 softmax 得到“这张图最可能是哪个标签”的分布。参数说明max_length48偏向长描述num_beams5是稳定性和耗时的折中paddingTrue让长度不等的模板句对齐。两个模型的处理器各自负责 resize不需要你手动统一尺寸这是新手最爱踩的坑。3. 搭建可复现的反推流程图像预处理、CLIP 标签打分与提示词组装最小 demo 能跑通但离“能用”还差三步预处理纪律、候选标签工程、拼装顺序。这三步决定反推结果是不是能直接进文生图也决定你后面调参时是不是在瞎试。3.1 图像预处理清单RGB、比例、分辨率这几步做不对后面全白搭读图这一步最容易翻车。手头图源五花八门网页截图是 JPEG表情包是 PNG 带透明通道手机照片带 EXIF 旋转老图是灰度模式。我统一的预处理就四行# preprocess.py所有入口共用的读图函数 from PIL import Image, ImageOps def load_rgb(path): img Image.open(path) img ImageOps.exif_transpose(img) # 修正手机竖拍横置 return img.convert(RGB) # 干掉 RGBA / 灰度 / P 模式参数说明exif_transpose会读取 JPEG 的 EXIF 方位信息并按它旋转否则竖拍照片会被横着送进模型BLIP 描述直接漂移convert(RGB)把 RGBA 的透明通道丢弃、灰度图复制成三通道避免 PIL 在隐式转换时把通道顺序搞乱。之后直接把这张 PIL 对象传给两个 processor它们内部会按各自尺寸CLIP ViT-B/32 是 224×224BLIP 是 384×384做 resize 加中心裁剪不需要你手动缩。这里有一个取舍中心裁剪意味着构图边角信息会被裁掉。如果参考图重点在画面中央比如人像、产品图默认裁剪没问题但全景风景、带文字排版的海报我会在进 CLIP 之前先手动裁掉明显的黑边或水印条再交给 processor。分辨率方面低于 100px 的缩略图两个模型都会出幻觉宁可先放大到 224 再送也不要直接传原尺寸。3.2 候选标签工程先建一份风格/媒介/光照词表再用模板句打分把几十上百个候选标签放进一个 list 里做一次 softmax是 CLIP 反推最大的坑。实体词person、animal对 CLIP 的响应天然强风格词会被压到小数点后三位而且不同组的候选数量不一样跨组 softmax 本身就有系统偏差。正确做法是分组词表加组内独立打分每组取前一到两个# tag_groups.py分组词表与配套模板 TAG_GROUPS { style: [oil painting, watercolor, anime, pixel art, cyberpunk, minimalist, art deco], medium: [photograph, 3d render, digital art, sketch], lighting: [cinematic, soft, neon, golden hour, studio, backlit], quality: [highly detailed, masterpiece, sharp focus, 8k], } TEMPLATES { style: lambda t: fin {t} style, medium: lambda t: fa {t} of the scene, lighting: lambda t: fwith {t} lighting, quality: lambda t: f{t} image, }逻辑说明每个组是一类属性模板句跟随属性类型。风格词用 in ... style媒介词用 a ... of the scene光照词用 with ... lighting质量词用 ... image。这样文本形态和训练语料的常见表达对齐CLIP 打分才有区分度。分组打分代码是把每个组的文本各自过一遍 CLIP# clip_score.py组内独立 softmax返回组内标签的降序分数 def group_scores(clip_model, clip_processor, image, group): labels TAG_GROUPS[group] texts [TEMPLATES[group](t) for t in labels] inputs clip_processor(texttexts, imagesimage, return_tensorspt, paddingTrue) with torch.no_grad(): probs clip_model(**inputs).logits_per_image.softmax(dim1)[0] return sorted(zip(labels, probs.tolist()), keylambda x: -x[1])参数说明paddingTrue让长度不等的模板句对齐softmax(dim1)在组内归一避免跨组候选数不等造成的偏差。调用方按组各取 top1~2。词表本身不要拍脑袋写去翻目标文生图模型用户常用的风格词清单再结合自己素材库的实际分布删减词表越贴近业务分布softmax 区分度越高这不是模型问题是数据问题。3.3 组装最终提示词BLIP 描述打底CLIP 标签按权重拼装最后一个环节是把 BLIP 的句子和 CLIP 的标签按顺序组装。组装规则看起来简单但我见过最多的“反推不好用”都栽在顺序上文生图模型对文本开头的 token 更敏感所以主句在最前风格、光照、质量按信息层级靠后。# image2prompt.py完整反推函数 import re import torch from PIL import Image, ImageOps from transformers import CLIPProcessor, CLIPModel, BlipProcessor, BlipForConditionalGeneration from tag_groups import TAG_GROUPS, TEMPLATES def generate_prompt(image, group_counts{style: 1, medium: 1, lighting: 1, quality: 2}): image ImageOps.exif_transpose(image).convert(RGB) # 1) BLIP 出主句 blip_processor BlipProcessor.from_pretrained(Salesforce/blip-image-captioning-base) blip_model BlipForConditionalGeneration.from_pretrained(Salesforce/blip-image-captioning-base) inputs blip_processor(imagesimage, return_tensorspt) out blip_model.generate(**inputs, max_length32, num_beams4) caption blip_processor.decode(out[0], skip_special_tokensTrue) caption re.sub(r^(a|an|the)\s, , caption).strip().rstrip(.) # 2) CLIP 按组打分 clip_processor CLIPProcessor.from_pretrained(openai/clip-vit-base-patch32) clip_model CLIPModel.from_pretrained(openai/clip-vit-base-patch32) tags [] for group, labels in TAG_GROUPS.items(): texts [TEMPLATES[group](t) for t in labels] inputs clip_processor(texttexts, imagesimage, return_tensorspt, paddingTrue) with torch.no_grad(): probs clip_model(**inputs).logits_per_image.softmax(dim1)[0] ranked sorted(zip(labels, probs.tolist()), keylambda x: -x[1]) tags [t for t, _ in ranked[: group_counts[group]]] # 3) 组装主句 标签逗号分隔保序去重 parts [caption] tags seen, prompt_parts set(), [] for p in parts: if p not in seen: seen.add(p) prompt_parts.append(p) return , .join(prompt_parts)参数说明max_length32比 demo 里的 48 保守原因是主句太长会挤占 77 token 预算详见第 4 章group_counts控制每个组取几个标签质量组取 2 个、其他组各 1 个是相对稳的起点。组装顺序上我习惯把主体句放首位媒介和风格居中光照、质量兜底这样无论目标模型是 SD 的标签流偏好还是更吃自然语言都不会在第一屏丢信息。这个函数就是整个反推工程的核心第 6 章的批量入口直接复用它。4. 让输出贴近原图的调参清单BLIP 生成参数、CLIP 温度与 top-k跑通之后进入“脏活”阶段调参。提示词工程到了这里不再是背模板而是对着输出做取舍。我把三个最影响结果的旋钮列出来每个都给经验取值范围和失败时的排查方向。4.1 BLIP 生成参数beam、max_length、重复惩罚的常见取值BLIP 的generate参数里真正值得调的是四个参数经验取值作用与代价max_length24~32控制句子长度。越短越稳、越省算力过长容易编造细节num_beams3~5beam search 宽度。5 比 3 稳但解码耗时接近翻倍no_repeat_ngram_size3禁止 3-gram 重复压制“a man a man”这类口吃输出num_return_sequences1~3返回多条候选配合 CLIP 重排提升稳定性如果只追求批量稳定max_length28, num_beams3是不错的起点如果对单张图质量有要求我会把候选条数拉起来用 CLIP 当阅读检查器重排# blip_rerank.pyBLIP 出 3 条候选交给 CLIP 挑最像原图的一条 outs blip_model.generate( **inputs, max_length32, num_beams4, num_return_sequences3, no_repeat_ngram_size3, ) cands [blip_processor.decode(o, skip_special_tokensTrue) for o in outs] ci clip_processor(textcands, imagesimage, return_tensorspt, paddingTrue) with torch.no_grad(): scores clip_model(**ci).logits_per_image[0] # 三条候选各一个分数 best cands[scores.argmax().item()]逻辑说明beam search 在num_return_sequences 1时返回前 N 条高分序列它们之间往往只有用词差异但其中一条可能更贴合画面。用 CLIP 的图文相似度挑最高分本质是用第二个模型做交叉验证比肉眼挑 caption 靠谱。注意这样生成耗时翻倍批量任务慎开。4.2 CLIP 侧的温度与 top-k先软化分布再决定取几个标签CLIP 内部已经带了学出来的温度logit_scale初始相当于 0.07所以logits_per_image的绝对值通常很大softmax 后分布会非常尖锐——一组 10 个标签top1 经常拿到 0.9 以上第二个标签几乎没机会。如果你希望 prompt 里有更多标签参与表达就在 softmax 前把 logits 除以一个人工温度# clip_temp.py软化 CLIP 分布再取 top-k T 2.5 # 温度越大分布越平top2/top3 才有机会出头 logits clip_model(**inputs).logits_per_image / T probs logits.softmax(dim1)[0]参数说明T1 等价于原始分布T2~3 是常用软化区间T 太大比如 5会让低分标签浑水摸鱼风格噪音明显变多。另一种更直接的做法是忽略概率、按排序取 top-k因为分数绝对值在不同图之间波动很大概率阈值比如 0.3没法通用k 反而是稳定参数。有一个必须记住的边界SD 的 text encoder 沿用了 CLIP 的 77 token 上下文反推 prompt 超过 77 个 token 会被直接截断截断点后面的光照、质量词全白写。我在实际项目里会先用clip_processor.tokenizer把组装好的 prompt 过一遍拿 token 数超过 65 就停手删词。4.3 提示词后处理去掉模型口水词按“主体风格光照质量”重排两个模型都有自己的口癖。BLIP 的句子习惯以 a、an 开头结尾带句号CLIP 的候选标签如果直接裸用偶尔会带出模板句残渣比如 photo of。进文生图模型之前我固定做三步清洗# clean.py清洗 重排 token 预算检查 import re def clean_caption(caption): caption re.sub(r^(a|an|the)\s, , caption) return caption.strip().rstrip(.).strip() def clean_tag(tag): return re.sub(r(photo of|image of|painting of)\s*.$, , tag).strip()清洗之后按“主体短句风格媒介光照质量”的顺序重排。这里有个多年养成的习惯质量词masterpiece、highly detailed永远放最后因为它们在 SD 里是通用放大器放前面会把风格词的注意力挤掉权重也不要全图(word:1.2)一个 prompt 里加权的词超过三四个效果反而像糊了一层油。MJ 用户则相反MJ 更吃连贯通顺的句子这时候把 CLIP 标签改写成短语插进句子中间而不是逗号列表。5. Image-to-Prompt 避坑记录5 个最容易翻车的现场与排查思路这套流程从原型到能稳定上线我反复踩了半年。以下是按“现象 → 原因 → 解决”整理的记录每条都是真实翻过车的不是理论推演。5.1 同一张图两次反推结果不一样是模型玄学还是有参数在抖动现象同一张图、同一台机器跑两次 BLIP 出来的句子不一样让人怀疑模型有随机性。 原因generate在默认参数下do_sampleFalse走的是 beam/greedy 解码理论上是确定性的。遇到“两次不一致”九成是某段代码悄悄开了采样或者读图路径不一致——比如一个分支做了exif_transpose另一个没有模型看到的输入根本不是同一张图。 解决把预处理统一收敛到唯一入口函数回归测试时跑两次并断言输出逐字相同先排除输入差异再谈调参。5.2 BLIP 生成的描述和原图对不上先查这三处现象给一张清晰猫图BLIP 输出 a man standing in a room跟画面毫无关系。 原因按出现频率排第一是输入图带透明通道或灰度读图时隐式转换把通道搞乱第二是 EXIF 旋转导致竖图横送画面主体被裁到视野外第三是缩略图分辨率太低模型开始自由发挥。 解决读图入口统一convert(RGB)加exif_transpose低于一定分辨率的图先放大预处理实在不行换blip-image-captioning-large试一次如果 large 能说对而 base 说不对说明是图太糊不是代码问题。5.3 CLIP 返回的全是 person、animal 这类通用词标签词表要背锅现象反推风格标签top1 永远落在 person、animal、car 这种实体词上。 原因实体词对 CLIP 的响应天然强于抽象风格词更糟的是把实体词和风格词放进同一个 list 做一次 softmax大类一骑绝尘风格词全被压死。模板写错也会放大这个问题比如风格词套了 a photo of ... 模板等于逼模型把它当实体理解。 解决实体词从标签词表里剔除主体交给 BLIP 写词表按属性分组、组内 softmax、每组取 top1~2模板句严格跟随属性类型。做完这三件事风格词才会浮出水面。5.4 CPU 上反推一张图要一分钟批量任务怎么救现象八核 CPU 跑 BLIP beam search单张四五十秒几千张图的反推任务排到天荒地老。 原因自回归解码每一步都要过一遍 decodermax_length和num_beams对耗时是乘法关系CLIP 一次 forward 只占极小比例瓶颈基本全在 BLIP。 解决批量任务把max_length压到 24、num_beams压到 3质量损失通常可以接受有 GPU 时模型.half()走 fp16 推理多条图不要试图拼 batch 一起送 BLIPbeam 长度不同导致收益很低逐张循环加多进程反而稳。真正大幅提速的路线是先用 CLIP-only 的标签流方案做初筛只对低置信度的少量图调 BLIP 细写。5.5 提示词喂回 SD/MJ 之后风格跑偏问题出在组装而不是模型现象反推 prompt 丢进 SD构图接近但风格、材质完全不对味。 原因BLIP 描述的是“画面里有什么”CLIP 标的是“风格/媒介”但组装时把长描述句放最前、风格词埋中间SD 的 text encoder 按 77 token 截断后风格信号被稀释甚至截掉。另一个常见错误是把 CLIP 的打分模板句in ... style 的完整文本原样拼进 prompt——模板是给打分用的不是给生成用的。 解决按“主句 风格 媒介 光照 质量”的固定模板组装风格词必要时加(style:1.2)权重SD 用逗号标签流MJ 用户则把标签改写成短句。同一套反推输出面向 SD 和 MJ 应该出两个组装版本这是提示词工程的一部分不是模型的锅。6. 把反推接进日常流程一个可复刻的 batch 工程与相似度验证6.1 最小工程结构与批量 CLI到这一步原理、代码、参数都有了剩下的是把它组装成一个能用的工程。最小结构就三个文件tag_groups.py放词表image2prompt.py放generate_prompt()再加一个批量入口。批量入口的代码很短但模型加载必须放循环外# batch.py遍历图片目录每张图输出同名 .txt import os, sys from PIL import Image from image2prompt import generate_prompt src, out sys.argv[1], sys.argv[2] os.makedirs(out, exist_okTrue) for fn in os.listdir(src): if not fn.lower().endswith((.jpg, .jpeg, .png, .webp)): continue img Image.open(os.path.join(src, fn)).convert(RGB) prompt generate_prompt(img) open(os.path.join(out, fn.rsplit(., 1)[0] .txt), w, encodingutf-8).write(prompt)最容易被忽略的是把from_pretrained写进了generate_prompt几百张图就重复加载几百次权重CPU 场景慢到没法用把 model 和 processor 提升成模块级单例只加载一次。参数说明sys.argv[1]和[2]分别是输入目录与输出目录输出文件与图片同名方便后续标注工具直接读取。6.2 用 CLIP 相似度做回归验证别靠肉眼猜反推质量不能用一两张图靠肉眼判断。我现在的做法是固定一组公开的典型 prompt 测试图比如“鹈鹕骑自行车”这类大家熟知的文生图考题——先用原始 prompt 让目标模型出图再跑反推最后用 CLIP 计算“反推 prompt 的文本向量”与“原图向量”的相似度并记基线。之后每次改词表、温度或组装顺序都在同一组图上重跑看相对变化而不是绝对值。这套流程把我从“凭感觉调参”里解放了出来。一个血泪教训一开始我只盯着 BLIP 的 caption 好不好看后来才发现纯标签流方案能覆盖八成文生图反推场景BLIP 只在主体关系复杂的图上才值得开如果词表稳定后风格识别还嫌不够准再考虑对 CLIP 做微调那是二期工程。判断反推需求先问目标模型是谁再决定要不要上双模型。希望帮到你。本文还有配套的精品资源点击获取
返回列表