PSD 文件进知识库,它是怎么被「读懂」的
PSD 不是图片,也不是文档,它是一棵图层树。有谷大脑用两路并行——合成图走 OCR,图层文字单独抽取——让设计稿里的文字和内容都能被搜到。
设计师把一份 PSD 上传到知识库,然后问:「这个设计稿里有没有提到产品名 A」。
大多数工具的处理方式是:把 PSD 转成一张图,然后跑 OCR。如果 OCR 识别率够高,能搜到;如果设计里有描边、投影,或者那段文字用了非常规字体,OCR 就认不出来,搜索返回空。
有谷大脑的处理方式不一样,因为 PSD 文件本身的结构让我们有第二条路。
PSD 不是图片,是一棵图层树
一个 PSD 文件由很多图层叠加构成:文字图层(type)、形状图层、像素图层、组图层……
其中文字图层很特殊:它存的不只是「渲染出来的样子」,还有原始的 text 字符串。这段字符串不管字体是什么、有没有描边,只要是用 Photoshop 文字工具打进去的,都能直接读出来。
这意味着 PSD 有两种读法:
合成图(composite):把所有图层按混合模式压成一张 PNG,视觉上就是「这个设计稿看起来的样子」。跑 OCR 或视觉语言模型,识别出页面上的文字。
图层文字(layer text):遍历所有图层,找到 kind == 'type' 的,直接拿 .text 属性,不管图层是否可见、是否隐藏。
有谷把这两路都做了,然后合并。
第一路:合成图的处理细节
用 psd-tools 的 PSDImage.composite() 生成合成图,转成 RGBA PNG 送给 OCR/VL pipeline。听起来简单,实际上有几个坑。
画布尺寸问题。 设计师的画布经常很大:一张活动长图可能 1920×6000px,一份品牌规范可能有 A4×20 页叠在一张画布上。OCR 在处理超大尺寸图时速度会急剧下降,有时还会直接崩掉。有谷在合成前做了长边缩放:超过 8000px 的长边按比例缩到阈值以内,OCR 完成后再把识别出的文字坐标换算回原始比例。
全透明画布。 部分设计文件是「空画布」——可能是模板文件,所有可见内容在图层上,但背景 layer 的 visible 是 false。合成出来的图像 alpha 通道全是 0(完全透明),OCR 送进去会返回空结果,但却浪费了 OCR 服务的调用配额。有谷在送 OCR 前检测 alpha 通道的 min/max 值,全透明的直接跳 OCR,只走图层文字路径。
合成失败的降级。 某些特殊混合模式(Linear Dodge、Vivid Light)在 psd-tools 里并非完全支持,合成时可能抛出异常,或者合成出来颜色跑偏。有谷在合成阶段套了 try/except,失败时退化到:先把各图层单独渲染再粗暴拼合的降级合成,实在不行就放弃合成图,只保留图层文字路径的输出。
第二路:图层文字抽取
遍历 PSD 的图层树,找出所有 kind == 'type' 的图层,读取其 .text 属性:
def extract_psd_text(psd_path: str) -> str:
psd = PSDImage.open(psd_path)
parts = []
for layer in psd.descendants():
if getattr(layer, 'kind', None) != 'type':
continue
text = getattr(layer, 'text', None)
if not text or not text.strip():
continue
parts.append(text.strip())
return '\n'.join(parts)
几个值得注意的设计决策:
psd.descendants() 而不是 psd。PSD 的图层结构是嵌套的,组图层(group)里可能还有组图层,文字图层藏在第三四层深处。descendants() 会做深度优先遍历,把所有子孙图层都拿到,不会漏。
包含隐藏图层。descendants() 不过滤 visible=False 的图层,设计师藏在隐藏图层里的内容也会被抽出来。这是有意为之:知识库的搜索目标是「文件里存了什么」,而不是「当前这个设计版本展示了什么」。设计稿里一个隐藏图层写着「备用方案:改名 B 产品」,用户问「有没有提过 B 产品」,就应该搜到。
不做去重。同一段文字可能在多个 Smart Object(智能对象)里重复出现——比如页眉模板被复制了 20 次。有谷目前不对图层文字做去重,因为去重需要引入相似度判断,实现复杂度高,且对搜索的实际影响有限(重复的内容向量化后在 embedding 空间本就很接近,检索时不会被多次返回)。
抽取完成后,图层文字以独立的文本块追加,并带 【PSD 文字图层】 前缀标识来源,方便后续 RAG pipeline 做内容归因。
两路合并之后
合成图 OCR 的结果和图层文字抽取的结果,在 RAG pipeline 里被当作同一个文档的两段内容,各自向量化,都参与检索。这样:
- 位图图层(比如截图贴图)里的文字,靠 OCR 路径覆盖
- 有描边/投影/特殊字体导致 OCR 识别率低的文字图层,靠图层文字路径覆盖
- 隐藏图层的内容,只有图层文字路径能看到
用户提问时,两路内容都在候选池里,检索结果取最相关的。
为什么不只做 OCR
单跑 OCR 的问题不只是识别率。一个 100 层的设计稿,合成图是一张大图,OCR 识别一遍要几秒到几十秒;而图层文字遍历所有图层拿 .text,整个过程在毫秒级就能完成。
如果 PSD 里 80% 的文字都在文字图层(设计师用 Photoshop 正常打字的情况),图层文字路径几乎能零成本拿到大部分内容,OCR 只是补充——而不是全量依赖。这对处理速度和 OCR 服务成本都有显著影响。
另一个维度:设计稿里的文字图层通常是「应该被搜索到的文字」——标题、正文、标注、说明。而合成图里 OCR 可能还会识别到背景渐变产生的噪点字符、图标里的装饰线等误识别结果。图层文字路径的信噪比要高得多。
PSD 和其他文件格式的对比
相同点:PSD 和 PDF/PPTX 都需要「提取可搜索文本 + 提取视觉资产」这两件事。
不同点在于结构:
PDF 和 PPTX 有分页结构,有谷对它们按页处理——每一页渲染成图、做 layout 检测、提取 native 图片资产、和检测框做 IoU 融合。这个流程是串行的,一页完成才能开始下一页。
PSD 通常是单画布,不分页,所以有谷对 PSD 走「一次性抽取路径」,不需要等多页 layout 结果。整个 PSD 一次合成、一次 OCR、一次图层遍历,三步走完。
这个区别影响了上传后的等待时间:一份 30 页的 PDF,每页的 OCR 和 layout 检测都要排队等待,完整入库可能需要几分钟;同等内容量的 PSD(单画布),通常在 30 秒内就能完成。
实际中遇到的问题
图层文字里的换行和空格。设计师用 Photoshop 打字时,一个文字图层里可能有多行,.text 属性会把换行符(\r 或 \n)一起保留下来。有谷在处理时做了简单的 strip,但保留内部换行,作为文本块里的段落分隔。
Smart Object(智能对象)。Smart Object 是嵌入在 PSD 里的另一个文件(可以是另一个 PSD,也可以是 PNG/SVG)。Smart Object 图层本身没有 .text 属性,但它里面嵌套的 PSD 可能有文字图层。有谷目前不递归展开 Smart Object 内部,这是一个已知的覆盖盲区,在未来的版本里会处理。
文字图层的 warp 变形。有些文字图层用了路径变形或 warp 效果,视觉上文字是弯曲的或沿路径排列的,但 .text 属性拿到的仍是原始字符串,没有位置信息。这对搜索无影响,但在需要展示文字位置时会有偏差。
什么情况下 PSD 在知识库里最有用
最典型的场景:设计和产品在同一个知识库空间里协作。产品的需求文档、设计稿、设计说明都在同一个库,问「这个功能的设计稿里有没有提到 XXX 状态」,不用去 Figma 或 Google Drive 翻,直接在对话里问。
另一个场景:历史资产归档。公司几年的品牌物料、活动设计稿、UI 规范都传进去,问「我们之前在哪个活动里用过这个配色」,图层里的颜色批注和文字标注能被搜到。
PSD 的内容密度很高(一个文件可能是几十到几百个设计元素),而且过去几乎没有工具能对设计稿做语义检索。这是有谷在文件类型支持上比较有差异化的地方之一。