基于无监督backbone无需训练的类别无关目标统计CountingDINO算法详解

视频讲解1:Bilibili视频讲解

视频讲解2:https://www.douyin.com/video/7591321594094423332?count=10&cursor=0&enter_method=post&modeFrom=userPost&previous_page=personal_homepage&secUid=MS4wLjABAAAA0NVS_BfnZjuBUqHzrh-1oSxoNxExvuesrznu1Wu4-fc

论文下载:https://arxiv.org/abs/2504.16570

代码下载:https://lorebianchi98.github.io/CountingDINO/

https://github.com/KeepTryingTo

基于Zero-Shot的计数算法详解(T2ICount: Enhancing Cross-modal Understanding for Zero-Shot Counting)

统一的人群计数训练框架(PyTorch)------基于主流的密度图模型训练框架

算法VLCount详解(VLCounter: Text-aware Visual Representation for Zero-Shot Object Counting)

人群计数中常用数据集的总结以及使用方式(Python/PyTorch)

基于Zero-Shot的目标计数算法详解(Open-world Text-specified Object Counting)

基于zero-shot目标计数方法详解(Zero-Shot Object Counting)

基于Transformer的目标统计方法(CounTR: Transformer-based Generalised Visual Counting)

基于zero-shot目标统计算法详解(Zero-shot Object Counting with Good Exemplars)

开发词汇的目标计数COUNTGD:Multi-Modal Open-World Counting算法详解

Class-Agnostic Counting类别无关的统计算法讲解

LOCA类别无关的目标统计算法详解(A Low-Shot Object Counting Network With Iterative Prototype Adaptation)

论文提出了一种基于自监督学习的零样本目标计数方法,通过DINO特征提取器和创新相似度图机制实现无需人工标注的开放世界计数。该方法解决了现有类别无关计数(CAC)方法对标注数据的依赖问题,采用ROI-Align提取示例特征,通过卷积生成相似度图并归一化为密度图。通过图像分块处理增强空间分辨率,实验验证了方法的有效性。相关代码已开源,论文可访问arXiv获取。

目录

现有方法存在的局限性

提出的方法

具体方法

基于DINO的特征提取器

相似密度图生成

密度图归一化

阈值密度图过滤

增加空间分辨率

实验结果

综合比较

消融实验

可视化结果


现有方法存在的局限性

1. 对标注数据的严重依赖

当前最先进的类别无关计数(CAC)方法存在两个根本性限制: 监督式骨干网络:大多数方法使用在大型标注数据集(如ImageNet)上预训练的骨干网络进行特征提取;有监督训练:需要基于包含数千个点标注和边界框的标注数据集进行监督训练

2. 标注成本高昂且可扩展性差

点标注收集困难:在对象中心放置点标注是创建大规模数据集的重大障碍

数据集稀缺:导致可用的CAC数据集数量有限,且现有数据集存在明显局限性

3. 半监督方法的妥协

先前许多方法试图实现无监督计数,但最终仍屈服于: 使用不同数量的真实密度图进行监督微调;依赖需要昂贵标注训练的上游视觉骨干网络或分割模型

提出的方法

1. 自监督特征提取

使用DINO系列自监督视觉骨干网络提取对象感知特征;完全避免使用任何人工标注数据

2. 相似度图生成机制

推理阶段处理流程: 通过ROI-Align从用户提供的边界框中提取示例特征 ;将示例特征作为深度卷积核在图像特征图上进行卷积 ;生成突出显示与示例匹配区域的相似度图 ;跨示例平均相似度图产生全局响应。

3. 密度图归一化技术

将相似度图通过简单有效的归一化方案转换为密度图 ;确保每个对象实例的相似度响应积分为1.0 ;通过对密度图进行积分获得最终对象计数 。

4. 空间分辨率增强

将图像划分为不重叠的象限独立处理 ;解决DINO空间分辨率对小对象的限制 ;通过拼接象限特征图获得更高分辨率特征表示。

具体方法

我们这里先来看一下有关的类别无关的统计算法,部分算法是我们已经讲过的,并且链接已经在文章的最前面给出来了。

类无关计数(CAC)最近将物体计数范式转向开放世界环境,使模型能够处理训练期间未见的任意类别。在这种设置下,用户不再受限于预定义类别,而是在推理时可以通过提供视觉示例来指定新的物体类别,通常以三个边界框的形式将同一输入图像中的视觉原型包围。开创性的 FamNet将多尺度特征提取与通过内积操作进行示例图像相关结合起来,用于密度图预测。RCAC遵循类似的架构,但引入了一个特征增强模块,通过合成具有不同颜色、形状和尺度的示例来提高多样性。BMNet强调了基于内积相关的局限性,并提出了一种可学习的二次相似性损失,借鉴度量学习,以实现更好的匹配。CounTR 提出了一种在任意语义类别图像中计数元素的方法,通过采用两阶段训练流程:基于 MAE 的自监督阶段,用于学习强大的视觉特征,以及全监督的最终阶段。LOCA引入了一个物体原型提取模块,通过交叉注意力迭代优化示例特征,而 PseCo 则依赖流行的 SAM 用于实例分割。CACViT利用单个预训练的视觉变换器,其注意力机制同时用于特征提取和匹配。DAVE提出了一种两阶段检测与验证框架:先通过密度图识别候选框,然后利用基于示例的聚类进行验证。与这些监督方法相比,UnCounTR通过使用从无监督数据生成的自我拼贴和 DINO 特征进行训练,而无需人工标注。相反,TFPOC和 OmniCount提出了使用 SAM 的无监督训练流水线,尽管 SAM 是经过标签训练的,因此方法并非完全无标签。

基于DINO的特征提取器

椭圆加权掩码生成:将坐标归一化到 单位圆空间
椭圆方程:( (x−w/2) / (w/2))^2 +( (y−h/2) / (h/2))^2 ≤1
即:中心在 (w/2, h/2),x 半轴长 w/2,y 半轴长 h/2
归一化后:
norm_x ∈ [-1, 1]
norm_y ∈ [-1, 1]
若 norm_x² + norm_y² ≤ 1,则点在椭圆内

python 复制代码
#计算一个 h×w 图像中每个像素被内接椭圆(实际上是内接圆,但按宽高比例拉伸为椭圆)覆盖的比例(coverage),通过在每个像素内进行亚像素采样实现
def ellipse_coverage(h, w, samples_per_pixel=10):
    # Create a grid of pixel centers
    y = torch.linspace(0.5, h - 0.5, h).view(h, 1).expand(h, w)
    x = torch.linspace(0.5, w - 0.5, w).view(1, w).expand(h, w)

    # Generate subpixel offsets for sampling
    sp = samples_per_pixel
    sub = torch.linspace(-0.5 + 1/(2*sp), 0.5 - 1/(2*sp), sp)
    dy, dx = torch.meshgrid(sub, sub, indexing='ij')
    offsets = torch.stack([dy.reshape(-1), dx.reshape(-1)], dim=1)  # (sp*sp, 2)

    # Expand pixel grid to sample subpixel points
    total_samples = sp * sp
    x_samples = x.unsqueeze(-1) + offsets[:, 1].view(1, 1, -1)
    y_samples = y.unsqueeze(-1) + offsets[:, 0].view(1, 1, -1)

    # Normalize coordinates to ellipse space
    norm_x = (x_samples - w / 2) / (w / 2)
    norm_y = (y_samples - h / 2) / (h / 2)

    inside = (norm_x ** 2 + norm_y ** 2) <= 1.0
    coverage = inside.float().mean(dim=2)  # average over samples

    return coverage

比如这里的w = 10, h = 10,生成的椭圆加权掩码如下:

相似密度图生成

python 复制代码
# 从一个特征图(feats)中根据给定的边界框(bbox)提取一个固定空间尺寸的特征区域
        pooled = ops.roi_align(
            feats, [bbox_tensor.unsqueeze(0).float().to(device)],
            output_size=output_size, spatial_scale=1.0
        )
        # 默认为false
        if config.ellipse_kernel_cleaning:
            ellipse = ellipse_coverage(pooled.shape[-2], pooled.shape[-1]).unsqueeze(0).unsqueeze(0).to(device)
            pooled *= ellipse

        pooled_features_list.append(pooled)

        if config.exemplar_avg:
            continue

        conv_weights = pooled.view(feats.shape[1], 1, *output_size)
        conv_layer = nn.Conv2d(
            in_channels=feats.shape[1],
            out_channels=1 if config.cosine_similarity else feats.shape[1],
            kernel_size=output_size,
            padding=0,
            groups=1 if config.cosine_similarity else feats.shape[1],
            bias=False
        )
        # 使用提取的特征来初始化当前卷积的权重
        conv_layer.weight = nn.Parameter(pooled if config.cosine_similarity else conv_weights)

        with torch.no_grad():
            # 对当前的特征进行卷积(也就是使用样例初始化的权重卷积来计算)
            output = conv_layer(feats[0])

密度图归一化

python 复制代码
    
if config.use_roi_norm and config.roi_norm_after_mean:
        # 将所有的特征图缩放至统一大小以及计算每一个特征图的高宽比
        output, resize_ratios = resize_conv_maps(conv_maps)
        # 在同一个维度计算所有密度图的均值
        output = output.mean(dim=0)
        # 默认为true
        if config.use_minmax_norm:
            output = rescale_tensor(output)

        pooled_vals = []
        for bbox, ratio in zip(bboxes, resize_ratios):
            scaled_bbox = torch.tensor([
                bbox[0] * ratio[1], bbox[1] * ratio[0],
                bbox[2] * ratio[1], bbox[3] * ratio[0]
            ]).int()
            # scaled_bbox = torch.tensor(bboxes_tointeger(scaled_bbox.unsqueeze(0), config.remove_bbox_intersection)[0])
            output_size = (
                int(scaled_bbox[3] - scaled_bbox[1]),
                int(scaled_bbox[2] - scaled_bbox[0])
            )
            pooled = ops.roi_align(
                output.unsqueeze(0).unsqueeze(0),
                [scaled_bbox.unsqueeze(0).float().to(device)],
                output_size=output_size, spatial_scale=1.0
            )
            pooled_vals.append(pooled)

        # 默认为true
        if config.ellipse_normalization:
            norm_coeff = sum(
                [(p[0, 0] * ellipse_coverage(p.shape[-2], p.shape[-1]).to(device)).sum() for p in pooled_vals]) / (
                                     len(pooled_vals) * config.scaling_coeff)
        else:
            norm_coeff = sum([p.sum() for p in pooled_vals]) / (len(pooled_vals) * config.scaling_coeff)

        if config.fixed_norm_coeff is not None:
            norm_coeff = config.fixed_norm_coeff

        output = output / norm_coeff

阈值密度图过滤

python 复制代码
        if config.filter_background is True:
            thresh = max([f.shape[-2] * f.shape[-1] for f in pooled_feats])
            thresh = (1 / thresh) * 1.0
            output[output < thresh] = 0

增加空间分辨率

实验结果

综合比较

消融实验

可视化结果

相关推荐
有时间要学习15 小时前
面试150——第三周
算法·面试
爱思德学术15 小时前
中国计算机学会(CCF)推荐学术会议-C(软件工程/系统软件/程序设计语言):IEEE COMPSAC 2026
人工智能·区块链·软件工程
一车小面包15 小时前
Neo4j中的APOC
算法·neo4j
拖拖76515 小时前
打破固定输出的边界:深入解读 Pointer Networks (Ptr-Nets)
人工智能
weixin_4331793315 小时前
python - for循环,字符串,元组基础
开发语言·python
H_BB15 小时前
前缀和算法详解
数据结构·算法
^哪来的&永远~15 小时前
Python 轻量级 UI:EEG 与 fNIRS 预处理图形界面
python·可视化·功能连接·eeg·mne·fnirs·eeglab
AI大佬的小弟15 小时前
Python基础(11):Python中函数参数的进阶模式详解
python·lambda函数·函数的参数解释·函数的参数进阶·位置参数·关键词参数·匿名函数与普通函数
聆风吟º15 小时前
【数据结构手札】时间复杂度详解:概念 | 大O渐进表示法 | 习题
数据结构·算法·时间复杂度·大o渐进表示法