ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

开集动物重识别实战:校准相似度与图聚类

2026/8/30 9:28:07 拓冰建站 浏览量
开集动物重识别实战:校准相似度与图聚类 如果你手头有一个“闭集”动物识别模型也就是训练时把所有动物个体 ID 都固定好推理时只能从这些 ID 里选一个输出那么一旦遇到一只从没见过的动物模型大概率会硬把它分到某个已知 ID 上。这在真实场景里很尴尬野生保护区里新来的个体、养殖场新出生的奶牛、社群关系研究里刚迁移的猴群成员都意味着“未知个体”而不是“某个已知个体的变形”。如果模型不能准确地说出“我不知道它是谁”下游的种群统计、个体追踪、行为分析都会被污染。“Calibrated Similarity and Graph Clustering for Open-Set Animal Re-Identification”这个标题所指向的方向正是要把动物重识别从“闭集分类”推向“开集识别”。它的核心思路可以拆成两层第一层是把每一张动物图像映射成特征向量计算相似度时不仅要看“像不像”还要看“这个相似度有没有经过校准”第二层是不再对单张图做硬性 ID 判断而是把一批图建成一张图结构通过聚类把属于同一只动物的所有图像聚合在一起再在簇的级别上决定“这个簇对应哪个已知个体还是一只新动物”。本文会从开集动物重识别的问题出发逐步拆解 Calibrated Similarity校准相似度和 Graph Clustering图聚类这两个关键设计然后给出一个可工程化的落地框架包括特征提取、相似度校准、聚类决策、指标评估和常见问题排查。读完你至少能知道为什么开集识别比闭集识别难为什么相似度分数不能直接拿来判阈值以及如何把“特征 相似度 图聚类”组装成一套实用的动物个体识别 Pipeline。1. 这篇文章真正要解决的问题动物 ReID 和人脸识别有相似之处但难点完全不同。人脸识别有大量公开数据集一个 ID 可以有几万张图年龄、角度、光照变化也都有人专门建模。动物个体识别面对的对象往往是某个物种中的一个种群个体数量少则几十多则几千每只个体能收集到的图片数量并不均衡。比如监控相机拍到一只花豹可能只有左侧身、远处、背光这几张而另一只花豹可能因为经常出现在固定路径上图像数量特别多。这种不平衡会直接影响分类器的训练也会让相似度分数产生明显的类别偏好。更关键的是动物识别场景通常不是封闭的。你不可能在模型训练前就知道保护区里所有个体的名单。今天拍到的个体明天可能被标记为新个体上个月标记过的成年个体下个月可能会换个角度、换个季节的毛色再次出现。如果按传统分类模型来做模型只能保证在训练集内把 ID 分开遇到新个体时没有正确答案可选。也就是说闭集模型的“高准确率”在开集场景里会变成一种误导它的输出仍然是一个已知 ID但实际语义已经完全错误了。开集动物重识别真正要解决的问题有两个验证问题给定两张动物图像判断它们是不是同一只个体。这是底层能力也是后面所有识别行为的基础。识别与入库问题给定一张查询图在已知个体库中检索最相似的结果如果检索结果的可信度不足则将它标记为新个体并触发入库流程。Calibrated Similarity 解决的是“相似度分数怎么用才可靠”的问题Graph Clustering 解决的是“如何利用一批图像之间的多元关系而不是只看单张查询图的单次匹配”的问题。两者组合起来才是完整的开集识别闭环。下面我先解释这几个概念再把它们串成一个完整的架构。2. 核心概念与适用场景2.1 开集Open-Set与闭集Closed-Set重识别闭集识别是最常见的分类任务训练集和测试集的类别集合完全一致模型只需要把输入映射到有限的类别空间里。动物 ReID 的早期方法大多走这条路把每只个体当成一个类别用分类损失训练一个深度模型。开集识别则不同。测试阶段会出现训练阶段从未见过的类别模型不仅要能识别已知类别还要能“拒绝”未知类别。在动物 ReID 里这个“拒绝”机制天然很重要因为野外数据采集是一个持续过程新个体永远可能出现。用一张表可以看得很清楚维度闭集 ReID开集 ReID类别集合训练和测试共享测试可能出现新 ID模型输出直接输出类别概率特征向量 相似度 开集判定新个体入库不支持支持且需要置信度判断评价指标Top-1 / mAPVerification AUC、Open-set F1、入库准确率核心难点类间区分已知类内紧凑 未知类拒识在真实项目里闭集模型往往可以作为特征提取器使用但不能直接拿它的分类分数去做开集判断。因为分类分数经过 Softmax 后会被压缩到已知类别上对新类别的响应被强行摊派到旧类别里没有任何“未知”空间。2.2 校准相似度Calibrated Similarity相似度校准本质上是对“原始特征距离”做一次变换让它更接近人类预期的置信度。我们用两个特征向量计算余弦相似度会得到一个 0 到 1 之间的分数。但问题是这个分数在不同个体、不同图像质量、不同背景下的分布并不一致。有的个体因为纹理独特随便拍都很像有的个体纹路相近即使同一个体相似度也只有 0.7。如果直接拿 0.8 作为阈值很可能把难区分的已知个体误判成未知同时把不同个体误判成同一个体。校准的目标就是让相似度分数具备“跨类别可比性”。常见做法包括温度缩放对相似度除以一个温度系数再通过逻辑回归学习最优温度特征归一化对特征向量做 L2 归一化让相似度更稳定距离比校准用目标相似度和该图像与其他所有图像的平均相似度的比值来减弱“这个个体本身容易被匹配”的先验偏置。后面我会用代码演示一个最小实现。2.3 图聚类Graph Clustering图聚类在动物 ReID 中的作用是把“一对一的相似度判断”升级为“一对多的关系分析”。假设一个相机陷阱在一个下午拍到了 50 张动物的照片其中多张属于同一只个体。如果只做两两比对你很难确定碎片化的匹配结果。但如果你把这些图像都看成图的节点相似度看成节点之间的边然后用聚类算法去划分节点就能利用“传递性”把同一只动物的不同姿态、不同角度聚合在一起。图聚类的好处不只是更准还很适合人工介入。聚类完成后每一簇可以对应一个临时身份。你可以对簇内图像做一致性检查也可以对簇中心做一次验证再决定簇是否入库、是否需要人工标注。这种“簇级别决策”比“单张图级别决策”更适合生产环境因为单张图可能模糊、遮挡、背景干扰但一个簇的统计信息更能反映真实身份。3. 方法整体架构从工程角度看这套方法可以分成四个模块特征提取模块输入图像输出高维特征向量通常是一个基于 CNN 或 ViT 的骨干网络。相似度校准模块对特征向量两两计算相似度并做校准转换。图聚类模块把一组图像构建成图利用校准后的相似度做边权执行聚类。开集判定模块对聚类结果进行后处理决定每个簇对应已知 ID、新 ID 还是需要人工复核。整体流程用伪代码表示如下# pipeline.py - 开集动物重识别流程伪代码 def open_set_animal_reid(query_images, gallery_images, feature_extractor, calibrator, clusterer): # 1. 特征提取 query_feats [feature_extractor(img) for img in query_images] gallery_feats [feature_extractor(img) for img in gallery_images] # 2. 成对相似度计算 sim_matrix compute_pairwise_similarity(query_feats gallery_feats) # 3. 相似度校准 calibrated_sim calibrator.calibrate(sim_matrix) # 4. 构造图并聚类 clusters clusterer.cluster(calibrated_sim) # 5. 开集判定 final_labels [] for cluster in clusters: label decide_known_or_unknown(cluster, calibrated_sim, known_centers, threshold) final_labels.append(label) return final_labels从结构上看这个架构并不复杂。真正的难点在细节特征提取器是否对开集友好相似度校准是否只拟合了训练集分布聚类算法是否能处理几十万张图的大规模数据开集阈值怎样选择才不至于误杀已知个体下面几节我会一一拆解。4. 相似度校准模块解析4.1 为什么原始相似度不够用假设两个不同个体之间的相似度分布在 0.70 附近同一个体之间的相似度分布在 0.85 附近表面上差距很大。但如果某个个体的纹理特别特殊同一只个体不同照片之间的相似度可以达到 0.96而另一只花纹平淡的个体同一只个体不同角度之间的相似度只有 0.75。这个时候如果你拿 0.80 做全局阈值后者会被误判成“不同个体”前者则可能把所有相似照片都聚成同一个体。也就是说原始相似度没有考虑“个体可区分性”带来的偏置。校准相似度要做的事情就是把这种偏置消除掉让相似度分数更像一个统一的置信度。4.2 温度缩放校准最经典的做法是在相似度矩阵上使用温度缩放可以写成# calibrate.py - 温度缩放示例 import numpy as np def temperature_scale(sim_matrix, temperature1.0): 对相似度矩阵做温度缩放。 temperature 1.0 表示不缩放temperature 1.0 让分布更平滑 temperature 1.0 让分布更尖锐。 scaled sim_matrix / temperature # 将值投影回 [0, 1]保证后续阈值语义一致 return (scaled - scaled.min()) / (scaled.max() - scaled.min())温度参数可以通过验证集学习目标是让同一个体的配对分数尽可能接近 1不同个体的配对分数尽可能接近 0。更严谨的方式是使用逻辑回归把相似度作为特征把“是否是同一个体”作为标签学习一个单调变换。这个方法在许多度量学习项目中都有效不建议跳过。4.3 距离比校准温度缩放是全局的对每个个体都应用同一个变换。更精细的做法是引入局部对比计算“查询图与候选图的相似度”除以“查询图与所有其他图的平均相似度”。如果一个查询图本身就很“容易匹配”那么它与所有候选图的相似度都会偏高除以平均值后可以压低这种虚高。# distance_ratio.py - 距离比校准示例 def distance_ratio_calibrate(sim_matrix): sim_matrix[i][j] 表示图像 i 与 j 的原始相似度。 返回距离比校准后的相似度。 n sim_matrix.shape[0] calibrated np.zeros_like(sim_matrix) for i in range(n): row sim_matrix[i].copy() # 防止自己与自己比较 row[i] np.nan mean_sim np.nanmean(row) if mean_sim 1e-8: calibrated[i] sim_matrix[i] / mean_sim else: calibrated[i] sim_matrix[i] return calibrated这个方法的关键是“和谁比”。如果查询图是某一个体的清晰正面照它和其他图的平均相似度不会太高因此有效相似度会被保留如果查询图是模糊背景图它和所有图都相似距离比就会显著下降从而降低误匹配概率。4.4 相似度校准的工程注意事项校准参数必须在验证集上学习不要在测试集上学习。如果后期不断加入新个体需要定期重新校准确认分数分布是否漂移。对相似的物种比如两只花纹接近的猫校准后的分数仍可能偏高不能只依赖一个标量。校准不是“提精度”的万能药它的主要价值是让阈值更可靠、聚类更稳定。5. 图聚类与开集判定模块解析5.1 构建相似度图图聚类第一步是构建图。节点是图像边是图像之间的相似度。为了控制计算复杂度通常只保留相似度较高的边或者使用 Top-K 邻近边。边权直接使用校准后的相似度也可以进一步归一化。# build_graph.py - 构建Top-K相似度图 import networkx as nx import numpy as np def build_topk_graph(calibrated_sim, topk10, min_sim0.5): 基于校准相似度矩阵构建图。 - topk: 每个节点保留的最强边数量 - min_sim: 低于该值的边直接丢弃 g nx.Graph() n calibrated_sim.shape[0] for node in range(n): g.add_node(node) for i in range(n): scores calibrated_sim[i].copy() scores[i] -1 # 忽略自环 # 取TopK索引 top_indices np.argsort(scores)[::-1][:topk] for j in top_indices: if scores[j] min_sim and not g.has_edge(i, j): g.add_edge(i, j, weightfloat(scores[j])) return g在实际项目中如果图像数量很大建议用 FAISS 或 hnswlib 先做近邻检索只保留 Top-K避免构建全图 O(n²) 的相似度矩阵。5.2 选择聚类算法图的聚类算法有很多常见的有连通分量适合相似度阈值很可靠、每条边都可信的情况。标签传播Label Propagation速度快适合大规模图。谱聚类效果好但计算开销大。贪心合并基于模块度可以通过社区发现工具实现比如 python-louvain。DBSCAN虽然不直接处理图但可以把相似度转成距离后使用也常见于开集识别。从动物 ReID 场景看我更推荐先做“强连通分量”再做“模块度合并”。因为同一只动物的图像可能因为视角差异被切分成多个子簇先合并强关联子簇再根据簇间相似度做二次合并可以兼顾准确率和召回率。# cluster.py - 两阶段图聚类示例 import networkx as nx def two_stage_cluster(g, merge_threshold0.7): # 第一阶段强连通分量 first_cc list(nx.connected_components(g)) # 第二阶段按簇间相似度合并 clusters [set(c) for c in first_cc] changed True while changed: changed False new_clusters [] used [False] * len(clusters) for i in range(len(clusters)): if used[i]: continue merged clusters[i] for j in range(i 1, len(clusters)): if used[j]: continue if cluster_similarity(clusters[i], clusters[j], g) merge_threshold: merged merged | clusters[j] used[j] True changed True new_clusters.append(merged) used[i] True clusters new_clusters return clusters这里的cluster_similarity可以定义为两个簇之间所有边的平均相似度也可以定义为两个簇最近邻图像之间的相似度。后者更适合个体姿态变化大的场景。5.3 开集判定簇级别而不是图像级别聚类完成后每个簇都代表一组可能属于同一只动物的图像。接下来要做的是决定这个簇的身份计算簇中心特征。用簇中心与已知个体库中的每个个体特征计算校准相似度。取最大相似度和对应 ID。如果最大相似度高于阈值标记为已知 ID否则标记为 unknown进入人工复核或自动入库队列。# decision.py - 簇级别开集判定示例 def decide_cluster_label(cluster_center_feat, known_centers, known_ids, threshold): known_centers: 形状 [num_known, feat_dim] known_ids: 已知个体ID列表 threshold: 开集判定阈值 sims np.dot(known_centers, cluster_center_feat) max_idx int(np.argmax(sims)) if sims[max_idx] threshold: return known_ids[max_idx], float(sims[max_idx]) else: return unknown, float(sims[max_idx])这个阈值的选择非常关键。阈值过高会把已知个体误判成新个体导致 ID 重复插入阈值过低会把新个体误判成已知个体污染数据库。建议把阈值选择建模成一个二分类问题在验证集上同时计算误识率FPR和漏识率FNR取两者平衡点作为阈值比如 HTERHalf Total Error Rate最小化。5.4 为什么图聚类能帮助开集判定单张查询图可能与多个已知个体都有一定相似度尤其是当个体间差异不大时。但如果把一个簇内所有图像放在一起看簇中心特征会平滑掉单张图的质量波动也会强化该个体的一致性特征。这相当于“多视图融合”是开集判定更稳的关键。此外图聚类还能自动发现“新个体的重复出现”。如果同一个未知动物出现了 20 次聚类后它们会形成一个大簇系统再去判断时可信度会远高于只见一次的模糊图。这在野生动物监测里非常重要因为单次出现可能是误拍多次出现通常是真实个体。6. 工程化落地流程6.1 数据与特征提取实践开集动物 ReID 的工程链路并不复杂重点是把每一步做扎实。我把落地流程分成七个阶段。构建个体数据库至少为每只已知个体保留 10-20 张高质量图片覆盖不同姿态和光照。数据清洗去掉模糊、遮挡、非目标物种的图片。训练/微调特征提取器使用度量学习损失比如 ArcFace、CosFace、Triplet Loss。特征向量入库将已知个体特征保存为向量索引并记录每只个体的多个特征代表。推理时特征提取查询图先过同一个特征提取器。相似度校准使用预先训练好的校准参数。聚类与开集判定对一批查询图执行图聚类再逐个簇决定最终标签。特征提取器的选择上如果物种数据少建议使用在大型视觉数据集上预训练的模型然后在目标物种的数据集上微调不要从头训练。如果条件允许也可以使用自监督预训练模型它的泛化能力通常更好对开集识别也有帮助。6.2 配置示例下面是一个简化的开集动物 ReID 系统配置文件方便你理解各模块参数之间的关系。# config.yaml 示例 data: known_db_path: ./datasets/known_animals query_dir: ./datasets/query_images image_size: 224 model: backbone: resnet50 embedding_dim: 512 pretrained: true checkpoint: ./weights/reid_model.pth calibrate: method: temperature temperature: 1.2 min_similarity: 0.5 cluster: method: topk_graph topk: 10 min_sim: 0.5 merge_threshold: 0.7 open_set: decision_threshold: 0.65 auto_register: true human_threshold: 0.55在这个配置里decision_threshold0.65表示簇中心与已知个体库的最大校准相似度达到 0.65 时判定为已知个体如果低于 0.55直接判为 unknown 并自动入库如果落在 0.55 到 0.65 之间进入人工复核队列。这种多阈值策略比单一阈值更适合生产环境。6.3 一条可运行的训练命令示例为了不依赖具体框架版本我这里给出一个通用训练流程。假设你已经用 PyTorch 写好了一个度量学习模型命令行可以这样设计python train_reid.py \ --config config.yaml \ --loss arcface \ --epochs 60 \ --batch-size 32 \ --lr 1e-4 \ --save-dir ./weights训练完成后验证集上不仅要看 Top-1 准确率还要看验证集上正负样本对的相似度分布。建议保存验证集上所有正对和负对的分数用来绘制校准曲线和选择阈值。6.4 推理与入库流程推理时通常不是一次只处理一张图而是一次处理一个批次。在监控相机场景中可以设置定时任务把一段时间内拍摄到的动物图像全部放到待处理队列然后执行批量聚类再进入开集判定。这样做比实时单张识别更稳因为聚类能够利用时间窗口内的多次观测。入库环节要做的事情是如果某个簇判为 unknown先为该簇生成一个临时 ID再让技术人员或生态研究者查看簇内的典型图片确认后把它转换成正式 ID。自动入库有一定的风险尤其是当聚类错误时可能把两只不同个体的图像误聚成一个簇。因此建议对“自动入库”增加一个条件只有簇内图像数量大于等于某阈值比如 3 张且簇内一致性高才允许自动入库否则进入人工复核。7. 评估指标与验证方案开集动物 ReID 不能只看 Top-1。因为新增个体的判定同样重要评价指标需要覆盖三个层面。7.1 闭集识别指标即使模型最终用于开集闭集指标仍然能反映特征提取器的区分能力。常用的包括Recall1 / Top-1 AccuracymAPMean Average PrecisionCMC 曲线Cumulative Matching Characteristic。这些指标用于评估“已知个体检索”的稳定度。如果一个模型连已知个体的 Top-1 都做不好开集判定通常也不会有太大改善。7.2 验证指标验证任务关心的是“一张查询图和一张库图是不是同一只个体”。可以把所有查询图和库图组合成正负样本对然后用 AUCROC 曲线下面积和准确率评估。正样本对来自同一只动物随机取若干张。 负样本对来自不同动物随机取若干张。 评估指标AUC、FPRFNR、HTER。HTER 是验证任务常用的单值指标表示假正率和假负率的平均值。HTER 越低说明相似度分数对正负样本的区分度越好。7.3 开集识别指标开集识别通常还关心以下问题已知个体是否被正确识别未知个体是否被正确拒识未知个体被误判为已知个体的比例是多少被拒识的 unknown 个体后续是否正确入库常用的指标包括指标含义Open-Set F1已知类和未知类综合 F1需要先定义样本级别标签Correct Classification Rate (CCR)已知类中被正确分类的比例False Positive Rate (FPR)未知类被误判为已知类的比例Registration Precision被自动入库的簇中真正是新个体的比例Registration Recall真正的新个体中被正确自动入库的比例在生产环境里我更推荐把“入库精度”放在首位。因为错误入库会导致个体编号混乱后期清洗成本极高。宁可让系统多生成一些“待复核”任务也不要冒险自动入库一个错误簇。7.4 离线验证设计开集验证不能只在测试集上做一次。一个合理的设计是把已知个体库切成两个子集一个作为“已知”一个模拟“新个体”。用模拟新个体评估拒识率和误识率。通过调整阈值观察 CCR 和 FPR 的 tradeoff。最终选择的阈值应该让 FPR 低于你的业务容忍度而不是单纯追求最高准确率。这种模拟方式能避免你直接拿真实新个体在系统上线后才踩坑。8. 常见问题与排查思路问题现象可能原因排查方式解决方案所有相似度都偏高没有明显区分特征提取器没有针对目标物种微调或者图过于相似查看验证集上的相似度直方图用度量学习损失重新微调或引入难样本挖掘聚类结果碎片化同一只动物被分成多个簇边阈值过高或 Top-K 过小导致连接关系中断检查图中每个簇的平均节点数降低边阈值增大 Top-K或在第二层合并时调高簇间相似度不同个体被聚到同一个簇相似度校准不足或者个体外观本来就接近抽查簇内的图像查看是否有遮挡或远景误检增加特征维度、使用更高分辨率输入或强制人工复核大簇新个体入库后旧模型无法识别同一个个体的后续图片阈值过严或特征提取器对新外观不够鲁棒查看新增个体的特征在库中的分布降低阈值并重新校准或定期用累积数据微调特征提取器推理速度太慢全图相似度矩阵计算复杂度过高统计图像数量、特征维度、聚类耗时改用 Top-K 近邻检索使用 FAISS 或 hnswlib 构建索引未知个体被反复报告为“新个体”即使它已经入库入库后没有同步更新已知个体索引检查入库流程是否写入了向量索引入库操作必须同时更新数据库、特征文件和向量索引排查时第一步不要直接调参数而是先看“相似度分数分布”。把一批已知正对和负对的分数画成直方图如果两个分布几乎重合说明问题在特征层面如果两个分布分得开但阈值选错说明问题在校准或决策层面。这两个层面的处理方式完全不同。9. 最佳实践与工程建议9.1 不要把相似度阈值当成静态参数动物个体在不同季节、不同年龄段会有外观变化。如果系统运行半年后新入库个体的外观分布和训练集差异变大原有的阈值可能不再适用。建议定期用最近一段时间的人工复核结果重新计算校准参数和阈值。可以做成每周或每月一次的自动任务。9.2 主动学习优先于无脑标注开集动物 ReID 最贵的是标注成本。系统自动判为 unknown 的簇里并不都是真正的新个体有一部分可能是已知个体在极端角度下的误判。因此要优先让标注人员处理“高不确定”的簇也就是评分落在阈值附近的簇而不是让标注人员从头到尾标记所有图片。主动学习能显著降低标注量同时提高系统对边界样本的鲁棒性。9.3 多模态信息可以显著改善聚类如果数据来源是视频或包含地理位置、时间戳的相机陷阱强烈建议把这些元信息引入聚类。比如两只动物虽然外观相似但如果出现时间间隔了几百公里基本可以排除是同一个体。结合时间和位置信息一方面可以减少图聚类中的误连边另一方面也能帮助拆分外观相近但活动范围不重叠的个体。9.4 把“人工复核”设计成系统的一部分完全自动化的开集识别在现实中很难做到零失误。更稳妥的方案是把系统设计成人机协同自动聚类和自动判定负责处理高置信度样本低置信度样本进入人工复核队列人工复核结果回传后再更新特征库和校准参数。这样既利用了 AI 的批量处理能力又避免了完全自动化带来的错误积累。9.5 从少量个体起步逐步扩展刚开始做动物 ReID 项目时不要一口气训练一个覆盖 1000 个个体的模型。建议先用 30-50 只个体跑通整个开集闭环验证相似度校准和聚类策略是否稳定。再逐步增加个体数量。因为个体越多库中的相似个体也就越多对特征判别力和聚类算法的要求都会上升。先在小规模下把阈值、入库规则、复核流程理顺再扩展规模是最稳妥的路径。开集动物重识别是一个“特征 校准 聚类”的组合问题。Calibrated Similarity 负责让相似度分数在个体之间可比较Graph Clustering 负责把图像级别的匹配提升到簇级别的决策。如果你正在做一个类似的动物识别系统建议从验证集上的相似度分布图开始先看清正负样本是否可分再决定要不要使用更复杂的聚类算法。阈值不是拍脑袋定的而是根据业务对“误识新个体”和“漏识新个体”的容忍度来设计的。把这一点想清楚比堆很多模型涨点更重要。