
1. 项目概述当AI学会“看图说话”来发现异常最近在工业质检和安防监控的圈子里一个老生常谈但又始终在演进的话题就是“异常检测”。传统的路子无论是基于统计模型还是早期的深度学习都离不开一个核心前提你得有足够多的“坏样本”即异常数据来教模型什么是“不正常”。但在实际生产线上良品率高达99.9%那些罕见的缺陷或者突发故障根本收集不到足够的数据来训练一个监督模型。这就陷入了“无米之炊”的困境。所以当“无训练”或“零样本”异常检测的概念被提出来时总能引起一阵兴奋。而最近随着多模态大模型尤其是视觉-语言模型VLM的爆发这个领域出现了一个非常有意思的新思路LogSAD。这个标题听起来有点技术化拆开来看就是 “Logit-based Spatial Anomaly Detection”——基于逻辑值的空间异常检测。它的核心思想不再是让模型去记忆异常长什么样而是利用一个已经在大规模图文数据上预训练好的、具备强大“常识”的视觉-语言基础模型比如CLIP让它直接“看图说话”描述出正常区域应该是什么样然后通过对比找出“说不上来”或者“说得不对”的异常区域。简单来说这就像是请来一位见多识广的“老师傅”。这位老师傅没见过你家特定的生产线但他看过成千上万张各种机器、产品、场景的图片并且能用语言描述它们。现在你给他看一张你生产线上的正常产品图问他“师傅这块区域比如一个光滑的表面应该怎么描述”老师傅可能会说“这是一个金属表面光滑有反光无划痕。”然后你再给他看一张有缺陷的图指着同一个位置问同样的问题。如果那里有个裂纹老师傅的描述可能就会变得模糊、矛盾或者置信度很低。LogSAD要做的就是量化这种“描述置信度”在图像空间上的差异从而定位异常。这个方法之所以吸引人是因为它跳过了耗时的缺陷数据收集和模型训练阶段实现了“开箱即用”。它特别适合那些产品迭代快、缺陷模式未知或多样的场景比如新兴消费电子的外观检测、复杂装配结构的完整性检查或是户外安防中识别非常规入侵物体。2. 核心原理拆解视觉语言模型如何充当“异常感知器”要理解LogSAD我们必须先深入看看它依赖的“老师傅”——视觉-语言基础模型如CLIP——到底是如何工作的以及我们如何“借用”它的能力来做它原本不专门做的事情。2.1 视觉-语言基础模型的核心对齐的跨模态空间以CLIP为例它的训练目标非常简单却极其强大让模型学会将一张图片和一段描述它的文本在同一个语义空间里“对齐”。具体来说它通过海量的互联网图文对进行训练目标函数是让匹配的图片和文本对的相似度尽可能高而不匹配的尽可能低。训练完成后图片编码器和文本编码器会将任何输入的图片和文本分别映射到一个高维向量的同一个空间里。在这个空间里“狗”的图片向量和“一只狗”的文本向量距离会很近而和“一辆汽车”的文本向量距离会很远。关键点在于这种对齐是在非常抽象的语义级别上完成的。模型学会的不是像素级的模式而是关于物体、属性、场景、甚至部分关系的“常识”。这意味着对于一个训练好的CLIP模型即使你输入一张它从未在训练集中见过的特定工业零件图片只要你用合理的语言如“一个光滑的金属齿轮”去描述它模型也能计算出较高的相似度因为它理解“光滑”、“金属”、“齿轮”这些概念。2.2 从全局描述到局部定位Prompt Engineering与特征提取传统的CLIP应用是给整张图一个全局标签。但异常检测往往是像素级或区域级的任务。LogSAD的核心创新之一就在于它将这种全局的语义对齐能力“下放”到了图像的局部区域。具体操作分两步走构造文本提示词Prompt我们需要用语言来定义“正常”。这通常不是单一词汇而是一组描述性的短语。例如对于PCB板检测我们可能会构造这样一组提示词“a flawless soldering joint, shiny and smooth”(一个完美的焊点闪亮且光滑)“a clean circuit trace without breaks”(一条干净无断路的电路走线)“a correctly placed electronic component”(一个位置正确的电子元件)“an anomaly, defect, or error”(一个异常、缺陷或错误) – 这个作为反面对比。提取局部视觉特征我们需要获得图像每个局部区域的特征表示。CLIP的视觉编码器通常是Vision Transformer, ViT在处理图像时会将图像分割成一系列图像块patches并最终为每个块输出一个特征向量。这些特征向量天然就带有空间位置信息。LogSAD直接利用这些来自中间层或最后一层的块特征patch features作为图像局部区域的表征。2.3 Logit空间异常得分的计算“Logit”在这里指的是视觉特征与每个文本提示词之间的相似度分数在通过softmax之前。对于图像上的一个特定位置对应一个图像块特征我们计算它与所有文本提示词包括正常描述和异常描述的相似度。一个直观的理解是在一个完全正常的区域它的视觉特征应该与那些描述正常的文本提示词如“光滑表面”高度相似而与“异常”这个词的相似度很低。而在一个异常区域情况可能相反它与正常提示词的相似度会下降同时可能与“异常”提示词的相似度上升或者更常见的是它与所有提示词的相似度都变得很低且模糊因为模型无法用任何已知的清晰概念来描述它。LogSAD的异常得分Anomaly Score通常基于这种相似度分布的变化来计算。一种经典的做法是只使用描述“正常”的提示词计算某个区域特征与这些正常提示词的平均相似度或最高相似度。得分越低说明该区域越不像任何已知的“正常”模式因此异常的可能性越高。公式可以简化为异常得分 1 - max(相似度[区域特征, 正常提示词集合])或者使用与最优正常提示词相似度的负值。更高级的方法会考虑所有提示词正常和异常的相似度分布计算其熵或某种差异度量。核心思想始终是量化局部视觉内容与语言定义的“正常世界”的偏离程度。注意这里的选择——是只用正常提示词还是同时用正常和异常提示词——是一个重要的工程权衡。在真实无训练场景下我们无法预知异常的具体形态因此“异常”这个提示词可能过于笼统。实践中精心设计一组覆盖正常样本各方面属性的正面提示词往往比引入一个模糊的负面提示词更有效、更稳定。3. 方案设计与关键实现步骤理解了原理我们来看如何从零搭建一个LogSAD风格的异常检测系统。整个过程可以分解为几个清晰的模块我会结合我自己的实现经验分享每个环节的选型考量和实操细节。3.1 模型选型与初始化不止CLIP一种选择虽然CLIP是开创者但现在视觉-语言模型家族已经壮大了。选型需要考虑精度、速度和易用性。OpenAI CLIP (ViT-B/16或ViT-L/14):这是基准选择。ViT-B/16速度较快ViT-L/14精度更高但计算量更大。对于工业图像由于背景相对可控ViT-B/16通常已经能提供很好的效果。通过pip install ftfy regex tqdm torch torchvision和pip install githttps://github.com/openai/CLIP.git可以安装。import clip import torch device cuda if torch.cuda.is_available() else cpu model, preprocess clip.load(ViT-B/16, devicedevice) # model: 包含视觉编码器visual和文本编码器text # preprocess: 图像预处理函数调整大小、归一化等OpenCLIP:这是社区重现和扩展的CLIP提供了更多在不同数据集如LAION上训练的模型有时在特定领域表现更好。例如laion/CLIP-ViT-H-14-laion2B-s32B-b79K是一个巨大的模型能力很强。pip install open_clip_torchimport open_clip model, _, preprocess open_clip.create_model_and_transforms(ViT-B-16, pretrainedlaion2b_s34b_b88k) tokenizer open_clip.get_tokenizer(ViT-B-16)领域适配模型如果存在有些研究开始发布在医学、遥感等专业领域数据上微调过的VLM。如果你的应用场景非常垂直如钢材表面检测可以搜索是否有相关模型这可能会带来显著的性能提升。选型心得对于初次实验建议从标准的CLIP ViT-B/16开始。它平衡了效果和效率社区支持最好遇到问题也最容易找到解决方案。OpenCLIP更适合当你需要尝试更大规模模型或不同数据源预训练模型的时候。3.2 提示词工程定义“正常”的艺术这是LogSAD成功与否最关键的环节也是最能体现工程师经验的地方。提示词的质量直接决定了模型对“正常”的认知边界。基础构建法从物体类别、材质、颜色、状态、功能等多个维度描述正常样本。示例金属表面检测“homogeneous metallic surface”(均匀的金属表面)“smooth and reflective finish”(光滑的反光表面)“continuous material without cracks”(无裂纹的连续材料)“clean industrial part”(干净的工业零件)注意使用英文提示词效果通常更稳定因为大多数VLM是在英文语料上训练的。模板增强法利用CLIP训练时常见的图文对句式。例如使用“a photo of a [normal object]”,“an image of [normal state]”等模板。可以组合多个模板来增加鲁棒性。base_template “a photo of a {}” class_names [“flawless welding seam”, “intact circuit board”, “clean surface”] prompts [base_template.format(cn) for cn in class_names]负面提示词谨慎使用可以加入“blurry”,“damaged”,“defective”等作为对比。但在计算最终异常得分时通常更依赖正面提示词的“不匹配”程度因为负面提示可能过于宽泛。迭代优化法这是实战中的关键。准备一小批5-10张确认正常的图像计算它们在不同提示词下的平均相似度。保留那些能稳定产生高相似度的提示词剔除掉那些波动大或相似度低的提示词。这个过程有点像在给模型“校准”。实操技巧不要只用一个提示词使用一个提示词列表5-15个让模型从多个角度理解“正常”。这能有效避免因提示词偶然不匹配而造成的误检。同时提示词应尽可能具体到你的场景“a smooth surface”就比“a good thing”要好得多。3.3 特征提取与相似度计算流程这是算法的计算核心。我们需要获取图像局部特征并与文本提示词进行比对。步骤详解图像预处理与编码# 假设 image 是PIL Image对象 image_input preprocess(image).unsqueeze(0).to(device) # [1, 3, 224, 224] with torch.no_grad(): # 获取视觉特征。我们需要的是图像块patch的特征而非全局特征。 # CLIP的视觉编码器输出通常包含最后的分类token特征和图像块token特征。 image_features model.encode_image(image_input) # 这通常返回全局特征关键点标准的clip.encode_image返回的是全局池化后的特征。为了获取空间特征我们需要深入模型内部提取Transformer最后一层或倒数第二层的所有图像块token的特征。# 以OpenCLIP为例更直接地获取patch特征 import open_clip model, _, preprocess open_clip.create_model_and_transforms(ViT-B-16, pretrainedlaion2b_s34b_b88k) model.visual.output_tokens True # 设置视觉编码器输出所有tokens with torch.no_grad(): image_tokens model.visual(image_input) # 形状可能是 [1, num_patches1, feature_dim] # 第一个token通常是[CLS] token我们需要的是后面的patch tokens patch_features image_tokens[:, 1:, :] # [1, 196, 512] (对于ViT-B/16, 224x224输入有196个patch)文本编码text clip.tokenize(prompts).to(device) # prompts是你的提示词列表 with torch.no_grad(): text_features model.encode_text(text) # [num_prompts, feature_dim] text_features / text_features.norm(dim-1, keepdimTrue) # 归一化计算相似度图# 归一化视觉特征 patch_features_normalized patch_features / patch_features.norm(dim-1, keepdimTrue) # [1, 196, 512] # 计算每个patch与所有提示词的余弦相似度 similarity_map torch.einsum(npc,kc-npk, patch_features_normalized, text_features) # [1, 196, num_prompts] similarity_map similarity_map.squeeze(0) # [196, num_prompts]现在similarity_map[i, j]就代表了第i个图像块与第j个提示词的相似度。3.4 异常得分生成与后处理得到相似度图后我们需要将其转化为一张直观的异常热力图Heatmap和像素级得分。得分聚合策略最大相似度法对每个图像块取它与所有正常提示词相似度的最大值。异常得分 1 - 最大相似度。这是最直接的方法认为只要像任何一个正常描述就行。# 假设前K个提示词是描述正常的 normal_similarities similarity_map[:, :K] # [196, K] max_similarity_per_patch, _ torch.max(normal_similarities, dim1) # [196] anomaly_score_per_patch 1.0 - max_similarity_per_patch # [196]平均相似度法对每个图像块计算它与所有正常提示词的平均相似度。这种方法更平滑但对某些提示词不匹配可能过于敏感。基于熵的方法计算每个图像块在所有提示词正常异常上相似度分布的熵。熵值高说明模型“犹豫不决”无法用任何明确概念描述可能是异常。# 将相似度转化为概率分布使用softmax probs torch.softmax(similarity_map / temperature, dim-1) # [196, num_prompts] entropy_per_patch -torch.sum(probs * torch.log(probs 1e-10), dim-1) # [196] anomaly_score_per_patch entropy_per_patch从块得分到像素级热力图我们得到的是196个图像块的得分。需要将其上采样回原图尺寸。import torch.nn.functional as F # 假设原图是224x224patch_size16那么特征图是14x14 (19614*14) score_map_2d anomaly_score_per_patch.reshape(1, 1, 14, 14) # [1,1,14,14] # 使用双线性插值上采样到原图大小 anomaly_heatmap F.interpolate(score_map_2d, size(224, 224), modebilinear, align_cornersFalse) anomaly_heatmap anomaly_heatmap.squeeze().cpu().numpy() # [224, 224]现在anomaly_heatmap就是一个和输入图像大小一致的矩阵值越高表示该像素位置异常可能性越大。后处理与阈值化高斯平滑对热力图进行轻微的高斯滤波可以消除因块特征边界带来的网格状伪影。import cv2 anomaly_heatmap_smoothed cv2.GaussianBlur(anomaly_heatmap, (5,5), 1)自适应阈值由于不同图像的整体响应强度可能不同固定阈值效果不好。可以采用基于图像统计的阈值如取热力图均值加上若干倍标准差。mean_score np.mean(anomaly_heatmap_smoothed) std_score np.std(anomaly_heatmap_smoothed) threshold mean_score 3 * std_score # 一个常用的起点 binary_mask anomaly_heatmap_smoothed threshold连通域分析对二值化后的掩码进行形态学操作如开运算去除小噪点和连通域分析只保留面积大于一定阈值的区域作为最终的异常区域输出。4. 实战调优与性能提升技巧纸上得来终觉浅绝知此事要躬行。在真实场景中部署LogSAD会碰到许多论文里不会细说的挑战。下面分享几个关键的调优点和提升策略。4.1 处理高分辨率图像超越224x224的限制CLIP的标准输入是224x224但工业图像往往是几百万像素。直接下采样会丢失关键细节如微小的裂纹。滑动窗口Patch-wise策略将大图分割成重叠的小块如448x448对每个小块单独应用LogSAD最后将结果拼接起来。这是最直接的方法但计算量大且边缘区域可能因上下文缺失而误判。特征提取器适配使用支持更大输入尺寸的ViT变体或者采用“视觉金字塔”策略。例如先在不同尺度上提取特征然后融合多尺度信息。一些改进的VLM如InternImage原生支持可变分辨率。我的实战方案采用一种“粗定位细鉴别”的级联策略。首先将原图缩放到一个中等尺寸如512x512运行一次LogSAD得到一个低分辨率的异常热力图定位出可疑区域。然后只对这些可疑区域对应的原始高分辨率图像块进行第二次精细化的LogSAD分析。这样既保证了效率又保留了细节。4.2 提示词自动优化与少样本学习手动设计提示词费时费力且可能不全面。我们可以引入少量正常样本例如5-20张让系统自己学习如何描述“正常”。文本反转Textual Inversion这是一种微调技术它保持视觉编码器和文本编码器的权重不变只为你的特定正常样本学习一个新的“提示词向量”。这个向量不是自然语言词汇而是一个在文本嵌入空间中可学习的张量它能更好地捕捉你当前数据集的正常模式。学习完成后就用这个学到的向量作为你的“超级提示词”。提示词集成学习准备一个大的提示词候选池几十到上百个然后利用少量正常样本计算每个提示词与这些样本的平均相似度。选择相似度最高且最稳定方差小的一组提示词例如Top-10作为最终使用的提示词集。这相当于用数据驱动的方式做了一次提示词筛选。4.3 融合多层级视觉特征CLIP的ViT编码器不同层捕获的信息不同浅层特征包含更多边缘、纹理细节深层特征包含更多语义信息。对于异常检测纹理异常如划痕可能更依赖浅层特征而语义异常如错装零件更依赖深层特征。我们可以提取多个Transformer层的特征例如最后三层分别计算它们与文本提示词的相似度然后将得到的多个异常热力图进行融合如加权平均或取最大值。这样能综合利用不同抽象级别的信息提升检测的鲁棒性。实现时需要修改模型的前向传播钩住hook中间层的输出。4.4 处理复杂背景与多部件场景当图像中包含多个物体或复杂背景时LogSAD可能会将不属于主要检测对象的正常背景误判为异常因为背景不符合“光滑金属表面”的描述。解决方案引入分割先验如果条件允许使用一个现成的零样本分割模型如Grounding DINO SAM先粗略分割出感兴趣的产品区域。只在分割出的ROI感兴趣区域内应用LogSAD计算异常得分。背景抑制提示词在提示词列表中加入对背景的描述例如“a factory background”,“a conveyor belt”。在计算最终异常得分时可以尝试从区域特征与所有提示词的相似度中减去与背景提示词的相似度从而抑制背景响应。但这需要谨慎调整权重。5. 常见问题、局限性与应对策略即使经过精心调优LogSAD作为一种无训练方法其天花板和应用边界也是清晰的。了解这些局限才能更好地决定是否采用以及如何扬长避短。5.1 典型失败案例与原因分析问题现象可能原因分析与解决思路高误报将正常变异判为异常1. 提示词过于具体或狭隘。2. 光照、角度变化导致纹理外观改变。3. 产品存在合理的自然纹理或变化如木材纹理、织物纹理。1.拓宽提示词加入更泛化的描述如“natural texture variation”或使用多个描述同一物体不同状态的提示词。2.图像标准化在预处理阶段加入更鲁棒的颜色校正或光照归一化。3.注意力机制尝试让模型更关注于“是否存在不该有的东西”而非“纹理是否完全一致”。可引入基于边缘或显著性的注意力权重。漏报未能检测出明显异常1. 异常与某个正常提示词意外相似。2. 异常区域太小在224x224下特征被淹没。3. 异常类型超出了VLM的“常识”范围。1.优化提示词检查是否有提示词与异常特征产生高相似度修改或移除它。2.多尺度分析采用前面提到的“粗定位细鉴别”策略或直接使用更高分辨率的输入如果模型支持。3.引入对比提示明确加入“unusual pattern”,“unexpected object”等提示并设计得分函数使得异常区域与所有正常提示词相似度都低而与这类“异常提示”相似度相对升高。热力图模糊、定位不准1. ViT的patch划分导致定位粒度较粗。2. 特征上采样方法过于简单。1.使用重叠patch或更小patch的模型如ViT-S/8。2.改进上采样尝试使用引导滤波或基于图像边缘的优化上采样方法使热力图边缘更清晰。3.后处理优化对热力图进行锐化或使用条件随机场CRF进行细化计算成本较高。计算速度慢1. 模型太大如ViT-L。2. 滑动窗口处理高分辨率图。3. 提示词列表过长。1.模型轻量化换用更小的模型ViT-B/16甚至ViT-S精度损失可能可接受。2.推理优化使用ONNX/TensorRT转换模型或利用FP16半精度推理。3.选择性计算仅在全图置信度较低的区域进行精细计算或二次验证。5.2 方法的核心局限性依赖预训练模型的“常识”模型的能力上限受限于其预训练数据。如果异常是预训练数据中极其罕见或从未出现过的模式例如某种特殊的工业缺陷模型可能无法感知其“异常性”因为它没有对应的“正常”或“异常”概念。语言描述的模糊性用语言精确描述所有正常状态是困难的。例如“光滑”和“略微粗糙”的边界在哪里这会导致检测阈值难以确定且对描述性要求高的缺陷如“光泽度不足”不敏感。对上下文不敏感标准的LogSAD主要关注局部patch与文本的匹配对图像的整体布局、部件间关系等全局上下文利用不足。例如一个零件“出现在错误的位置”这种异常可能难以仅通过局部纹理检测出来。计算成本尽管无需训练但每次推理都需要进行前向传播尤其是处理高分辨率图像时相比一些轻量级的传统图像处理方法其计算开销仍然较大。5.3 与其他无训练方法的对比思考与基于重建的方法如Autoencoder对比重建方法假设模型只能完美重建正常模式。其优势是对纹理变化敏感但缺点是对结构性异常或新物体可能重建得“太好”而导致漏报。LogSAD则从语义层面判断对语义异常如多物、少物、错物可能更鲁棒但对细微纹理变化的敏感性可能不如前者。与基于预训练特征分布的方法如PaDiM对比PaDiM等方法需要用一个正常数据集来建立多元高斯分布模型。它们本质上是一种“有监督的无异常训练”需要正常数据。LogSAD是真正的“零样本”一张正常图都不需要尽管有会更好。在完全没有正常数据可用的极端情况下LogSAD是唯一可行的基于深度学习的方法。我的经验是不要将 LogSAD 视为银弹。它最适合的场景是缺陷的语义属性强例如出现了不该有的物体、材料明显错误、正常状态可以用语言相对清晰地描述、且对预先收集大量正常数据有困难的项目。在实际系统中我常常将它作为一个并行的“语义检验器”与一个轻量级的、针对纹理异常优化的传统算法如局部二值模式方差分析相结合利用投票或加权融合的方式做出最终决策这样能有效结合两者的优势覆盖更广的缺陷类型。