小样本计数网络FamNet(Learning To Count Everything)

小样本计数网络FamNet(Learning To Count Everything)

  • 大多数计数方法都仅仅针对一类特定的物体,如人群计数、汽车计数、动物计数等。一些方法可以进行多类物体的计数,但是training set中的类别和test set中的类别必须是相同的。

  • 为了增加计数方法的可拓展性,Viresh Ranjan在2021年CVPR上提出了小样本计数网络(Few-Shot Counting, FSC) FamNet。

  • 论文地址:Learning To Count Everything

1 FamNet网络概述

  • 小样本计数如下图所示,给定一张或几张support描述计数的物体类别,并给定一张待计数的图像query,FSC希望计算出该类别的物体在query中出现的个数。
  • 除了在训练集中出现的类别 (称为base classes),在测试阶段,FSC还需要处理完全没有见过的类别 (称为novel classes)。

在这里插入图片描述

从今天的角度来看,小样本计数的baselines主要类别如下:

  • 第一类是feature-based的方法(下图a),将support和query的feature进行concat,之后训练一个回归头部,计算density map,典型网络有 Class-Agnostic Counting (Lu et al., ACCV 2018)。

  • 第二类是similarity-based的方法(下图b),将support和query的feature计算一个距离度量,得到一张similarity map,之后从similarity map回归density map,我们今天要介绍的Learning To Count Everything(Ranjan et al.,CVPR 2021)就是此类。

  • 这两种方法各有优劣。feature-based的方法对于语义信息的保持更好,而similarity-based的方法则对于support和query之间的关系感知更好,因此也有将两种方法结合起来的(下图c),如Few-shot Object Counting with Similarity-Aware Feature Enhancement (You et al., WACV 2022)。

在这里插入图片描述

1.1 FamNet网络架构

  • 绝大多数计数的方法都只能针对一个类别的情况,造成这种情况的原因有两个:

    • 计数需要很细致的标注
    • 甚至没有包含多个类别的数据集
  • 为了解决第一个问题,将计数看成是few-shot回归任务。

    • 提出了FamNet架构来解决few-shot counting的任务,在测试阶段引入了一个few-shot adaptation scheme提升了模型的性能
    • 值得注意的是,本文在测试过程中选择的是和训练过程中完全不同的类别
  • 为了解决第二个问题自己建立了一个数据集(如下图):FSC-147(a few-shot counting dataset,有147个类别,超过6000张图片)

    • 本文收集了超过6000张图片,共147个类别的图像,主要由厨房餐具、办公文具、信纸、交通工具、动物等组成。
    • 数量从7-3731,平均每幅图56个目标。每个目标实例用近似中心的点来标注。
    • 另外,三个目标实例随机挑选出来加上矩形框作为目标样例。
    • 注意一下数据集的划分:训练集:3659张,89类;验证集:1286张,29类;测试集:1190张,29类,总计147类,可以发现训练集和测试集的类别不一致

在这里插入图片描述

下图是FamNet的网络架构:

  • 输入:RGB图像以及想要计数的物体的几个sampler bounding boxes(下图中红框标注的鱼,只需要3个左右的bbox信息);

  • 输出:密度图(和输入图像尺寸是一样的,只有1个channel),根据密度图可以得到想要的object的数量。

  • FamNet主要由两个核心模块组成:一个是多尺度的特征提取模块(绿色矩形),一个是密度图预测的模块(绿色矩形)。

在这里插入图片描述

多尺度的特征提取模块

  • 为了处理大范围的物体类别,本文用在ImageNet上预训练了的网络进行特征提取,并且在训练时候冻结参数,不进行微调。

  • 具体来讲,FamNet用的是ImageNet上pretrained过的ResNet-50的前4个block进行多尺度特征提取,而且这些block的权重在FamNet训练时是不改变的。

  • 用ResNet-50的第3个和第4个block得到的特征图表征输入图像,对这些特征图用ROI pooing(紫色矩阵框)得到exempler的多尺度特征。

    • 在FamNet的源码中,ROI pooing实现其实非常简单,直接用的Pytorch中的插值函数实现。

    • ROI pooing是在Faster RCNN里提出的。Faster RCNN中将RPN生成的候选框投影到特征图上获得相应的特征矩阵后,由于每个特征矩阵的大小不一定是相同的,于是有一个ROI Pooling层,将这些特征矩阵缩放到相同尺度的特征图,Faster RCNN用的是7×7,然后再将特征图展平通过一系列全连接层得到预测结果。

密度图预测模块

  • 为了使模型不是针对特定类的,密度图预测模块的设计是需要类无关的。
  • 那么显然,为了类无关的就不能直接把提取到的图像特征送入密度图预测模块。作者使用了一个correlation map表征exempler特征和输入的图像特征的相似性(上图灰色矩形框),然后再做density prediction,这里作者使用卷积操作来计算exempler特征和输入图像特征的相似性
  • 同样,由于object可能会有尺度变化,因此作者把exempler的大小进行缩放(0.9-1.1),并生成多个correlation maps,这些特征图最后被concat后送入密度图预测模块。

1.2 FamNet中的损失函数

  • 在模型训练的时候,使用的损失函数是常规的MSE损失(只有密度图预测模块参与训练)。

  • 在模型预测或测试的时候,用的是Min-Count loss和Perturbation loss的加权和。

  • 可能你会好奇,预测时候还需要计算损失吗?

    • 这是因为训练阶段,我们利用的只是样本外观特征信息。
    • 在推理阶段,作者提出一种新方法(如下图推理加入Adaptation可以提高预测精度,源码中默认训练100个epochs)用于提升估计模块的精度,可以充分利用Bounding boxes样本的位置信息。

    在这里插入图片描述

最小计数损失(Min-Count loss)
L M i n C o u n t = ∑ b ∈ B m a x ( 0 , 1 − ∣ ∣ Z b ∣ ∣ 1 ) B 为给定所有的样本框, Z b 表示在密度图 Z 上位置 b 处的裁剪图 , ∣ ∣ Z b ∣ ∣ 1 为 Z b 所有值的求和 L_{MinCount}=\sum_{b\in B}max(0, 1-||Z_b||_1) \\ B为给定所有的样本框,Z_b表示在密度图Z上位置b处的裁剪图,||Z_b||_1为Z_b所有值的求和\\ LMinCount=bBmax(0,1∣∣Zb1)B为给定所有的样本框,Zb表示在密度图Z上位置b处的裁剪图,∣∣Zb1Zb所有值的求和
看了这个损失函数的数学表达式,你可能比较懵逼,那我们直接来看代码。

  • 通过下面代码,我们知道MinCountLoss其实就是为了约束每一个exempler的框内至少有一个物体
  • ∣ ∣ Z b ∣ ∣ 1 > 1 ||Z_b||_1>1 ∣∣Zb1>1时, L M i n C o u n t = 0 L_{MinCount}=0 LMinCount=0。因此,最小计数损失只会惩罚那些没有判断出exempler区域内目标大于1的情况。
# 主要是为了约束每一个exempler的框内至少有一个物体
def MincountLoss(output, boxes, use_gpu=True):ones = torch.ones(1)if use_gpu:ones = ones.cuda()Loss = 0.if boxes.shape[1] > 1:boxes = boxes.squeeze()# 遍历每一个提供的bboxfor tempBoxes in boxes.squeeze():y1 = int(tempBoxes[1])y2 = int(tempBoxes[3])x1 = int(tempBoxes[2])x2 = int(tempBoxes[4])# 将bbox区域内密度求和X = output[:, :, y1:y2, x1:x2].sum()# 小于1就计算loss,目的是确保每一个exempler的框内至少有一个物体if X.item() <= 1:Loss += F.mse_loss(X, ones)else:boxes = boxes.squeeze()y1 = int(boxes[1])y2 = int(boxes[3])x1 = int(boxes[2])x2 = int(boxes[4])X = output[:, :, y1:y2, x1:x2].sum()if X.item() <= 1:Loss += F.mse_loss(X,ones)  return Loss

扰动损失Perturbation loss

  • 密度图 Z Z Z本质上为exemplers和图像的关联响应(卷积)图。

  • 而exemplers周围的密度值理想情况下也会是一个高斯分布,所以不满足这个分布的情况就会有Loss。

  • G h × w G_{h×w} Gh×w为尺寸是 h × w h×w h×w的2D高斯分布图。

  • 预测最终所用的联合适应损失(adaptation loss)是Min-Count loss和Perturbation loss的加权和。

在这里插入图片描述

def PerturbationLoss(output, boxes, sigma=8, use_gpu=True):Loss = 0.if boxes.shape[1] > 1:boxes = boxes.squeeze()# 遍历每一个提供的bboxfor tempBoxes in boxes.squeeze():y1 = int(tempBoxes[1])y2 = int(tempBoxes[3])x1 = int(tempBoxes[2])x2 = int(tempBoxes[4])out = output[:, :, y1:y2, x1:x2]# 作者认为预测密度图会围绕bbox呈Gaussian分布,所以不满足这个分布的情况就会有LossGaussKernel = matlab_style_gauss2D(shape=(out.shape[2], out.shape[3]), sigma=sigma)GaussKernel = torch.from_numpy(GaussKernel).float()if use_gpu:GaussKernel = GaussKernel.cuda()Loss += F.mse_loss(out.squeeze(), GaussKernel)else:boxes = boxes.squeeze()y1 = int(boxes[1])y2 = int(boxes[3])x1 = int(boxes[2])x2 = int(boxes[4])out = output[:, :, y1:y2, x1:x2]Gauss = matlab_style_gauss2D(shape=(out.shape[2], out.shape[3]), sigma=sigma)GaussKernel = torch.from_numpy(Gauss).float()if use_gpu:GaussKernel = GaussKernel.cuda()Loss += F.mse_loss(out.squeeze(), GaussKernel)return Loss

实验部分不再介绍,可以参考原文,只贴出一个与其他少样本方法的比较。

在这里插入图片描述

2 FamNet源码分析

源码来自官方作者实现:GitHub - cvlab-stonybrook/LearningToCountEverything

下面我们通过源码,来对FamNet有更深的理解。

下面代码在源码基础上增加了中文注释并进行了微调,仓库地址:小样本计数网络FamNet

2.1 模型创建

  • 利用在ImageNet数据集上预训练的ResNet-50模型的layer3、layer4作特征提取
  • 密度预测模块CountRegressor由5个卷积和3个上采样层(上采样2倍)组成
# LearningToCountEverything/model.py
class Resnet50FPN(nn.Module):def __init__(self):super(Resnet50FPN, self).__init__()self.resnet = torchvision.models.resnet50(pretrained=True)children = list(self.resnet.children())self.conv1 = nn.Sequential(*children[:4])self.conv2 = children[4]self.conv3 = children[5]self.conv4 = children[6]def forward(self, im_data):feat = OrderedDict()feat_map = self.conv1(im_data)feat_map = self.conv2(feat_map)feat_map3 = self.conv3(feat_map)feat_map4 = self.conv4(feat_map3)feat['map3'] = feat_map3feat['map4'] = feat_map4return featclass CountRegressor(nn.Module):def __init__(self, input_channels, pool='mean'):super(CountRegressor, self).__init__()self.pool = pool# 经过三次上采样,将feature map采样到原始图像大小self.regressor = nn.Sequential(nn.Conv2d(in_channels=input_channels, out_channels=196, kernel_size=7, padding=3),nn.ReLU(),nn.UpsamplingBilinear2d(scale_factor=2),nn.Conv2d(in_channels=196, out_channels=128, kernel_size=5, padding=2),nn.ReLU(),nn.UpsamplingBilinear2d(scale_factor=2),nn.Conv2d(in_channels=128, out_channels=64, kernel_size=3, padding=1),nn.ReLU(),nn.UpsamplingBilinear2d(scale_factor=2),nn.Conv2d(in_channels=64, out_channels=32, kernel_size=1),nn.ReLU(),nn.Conv2d(in_channels=32, out_channels=1, kernel_size=1),  # 输出通道为1nn.ReLU(),)def forward(self, im):num_sample = im.shape[0]if num_sample == 1:# im.squeeze(0)先压缩通道0output = self.regressor(im.squeeze(0))if self.pool == 'mean':# 取多个bbox所产生的correlation map的平均值output = torch.mean(output, dim=(0), keepdim=True)return outputelif self.pool == 'max':output, _ = torch.max(output, 0, keepdim=True)return outputelse:for i in range(0, num_sample):output = self.regressor(im[i])if self.pool == 'mean':output = torch.mean(output, dim=(0), keepdim=True)elif self.pool == 'max':output, _ = torch.max(output, 0, keepdim=True)if i == 0:Output = outputelse:Output = torch.cat((Output, output), dim=0)return Output

2.2 模型训练

  • 训练过程中采用MSE损失函数
  • 多尺度特征提取模块使用Resnet50FPN、密度图预测模块使用CountRegressor
  • 训练过程,ResNet-50中block的权重是不改变的,只更新CountRegressor的参数
# LearningToCountEverything/train.py
def train(args):device = torch.device(args.gpu if torch.cuda.is_available() else "cpu")# MSE损失函数criterion = nn.MSELoss().to(device)# 1、多尺度特征提取模块resnet50_conv = Resnet50FPN().to(device)resnet50_conv.eval()# 2、密度图预测模块# input_channels=6,这是因为map3和map4分别产生三个尺度(0.9、1.0、1.1)的correlation mapregressor = CountRegressor(input_channels=6, pool='mean').to(device)weights_normal_init(regressor, dev=0.001)regressor.train()# 3、这里冻结Resnet50FPN,只更新CountRegressor的参数optimizer = optim.Adam(regressor.parameters(), lr=args.learning_rate)best_mae, best_rmse = 1e7, 1e7stats = list()for epoch in range(0, args.epochs):regressor.train()# 训练1个epochtrain_loss, train_mae, train_rmse = train_one_epoch(args, resnet50_conv, optimizer, regressor, criterion, device)regressor.eval()# 模型评估val_mae, val_rmse = eval(args, resnet50_conv, regressor, device)# 将训练过程中的loss等信息保存到文件中stats.append((train_loss, train_mae, train_rmse, val_mae, val_rmse))stats_file = join(args.output_dir, "stats" + ".txt")with open(stats_file, 'w') as f:for s in stats:f.write("%s\n" % ','.join([str(x) for x in s]))if best_mae < val_mae:best_mae = val_maebest_rmse = val_rmsemodel_name = args.output_dir + '/' + f"FamNet_{best_mae}.pth"torch.save(regressor.state_dict(), model_name)print("Epoch {}, Avg. Epoch Loss: {} Train MAE: {} Train RMSE: {} Val MAE: {} Val RMSE: {} Best Val MAE: {} Best Val RMSE: {} ".format(epoch + 1, stats[-1][0], stats[-1][1], stats[-1][2], stats[-1][3], stats[-1][4], best_mae, best_rmse))
  • train_one_epoch如下所示
  • 训练过程中,需要对图像大小进行预处理
    • 训练过程中同时需对密度图进行处理,预测或测试不需对密度图进行处理。
    • 如果高度和宽度均小于指定值,则不进行调整大小。
    • 如果图像的宽度或高度超过指定值,则调整图像大小,使得:
      • 新高度和新宽度的最大值不超过指定值
      • 新高度和新宽度可被8整除
      • 保持纵横比
  • 核心函数为extract_features,用来获取不同尺度的correlation map,然后将6个correlation map进行concat,输入到密度图预测模块regressor
# LearningToCountEverything/train.py
# 模型训练
def train_one_epoch(args, resnet50_conv, optimizer, regressor, criterion, device):print("Training on FSC147 train set data")# 加载数据集及相关信息annotations, data_split, im_dir, gt_dir = load_data(args)# 训练数据集中图片名称, 如['7.jpg',...]im_ids = data_split['train']random.shuffle(im_ids)train_mae = 0train_rmse = 0train_loss = 0cnt = 0pbar = tqdm(im_ids)for im_id in pbar:cnt += 1anno = annotations[im_id]bboxes = anno['box_examples_coordinates']# 1、获取每幅图片上少量的exemplar object的bbox信息rects = list()for bbox in bboxes:x1 = bbox[0][0]y1 = bbox[0][1]x2 = bbox[2][0]y2 = bbox[2][1]rects.append([y1, x1, y2, x2])# 2、加载图片image = Image.open('{}/{}'.format(im_dir, im_id))image.load()# 3、加载密度图density_path = gt_dir + '/' + im_id.split(".jpg")[0] + ".npy"density = np.load(density_path).astype('float32')# 4、装入到字典中sample = {'image': image,'lines_boxes': rects,'gt_density': density}# 训练过程中,对图像进行预处理sample = TransformTrain(sample)image, boxes, gt_density = sample['image'].to(device), sample['boxes'].to(device), sample['gt_density'].to(device)with torch.no_grad():# 获取不同尺度的correlation map,并concat在一起features = extract_features(resnet50_conv, image.unsqueeze(0), boxes.unsqueeze(0), MAPS, Scales)features.requires_grad = Trueoptimizer.zero_grad()# 将6个correlation map进行concat,然后输入 密度图预测模块output = regressor(features)# if image size isn't divisible by 8, gt size is slightly different from output sizeif output.shape[2] != gt_density.shape[2] or output.shape[3] != gt_density.shape[3]:orig_count = gt_density.sum().detach().item()gt_density = F.interpolate(gt_density, size=(output.shape[2], output.shape[3]), mode='bilinear')new_count = gt_density.sum().detach().item()if new_count > 0:# 保证gt_density缩放后的count和orig_count相同gt_density = gt_density * (orig_count / new_count)# 计算mse lossloss = criterion(output, gt_density)loss.backward()optimizer.step()train_loss += loss.item()pred_cnt = torch.sum(output).item()gt_cnt = torch.sum(gt_density).item()cnt_err = abs(pred_cnt - gt_cnt)# 计算训练过程中mae和rmsetrain_mae += cnt_errtrain_rmse += cnt_err ** 2pbar.set_description('真实cnt: {:6.1f}, 预测cnt: {:6.1f}, 错误个数: {:6.1f}. Current MAE: {:5.2f}, RMSE: {:5.2f}'.format(gt_cnt, pred_cnt,abs(pred_cnt - gt_cnt), train_mae/cnt, (train_rmse/cnt)**0.5))# 计算训练1个epoch的train_loss、train_mae、train_rmsetrain_loss = train_loss / len(im_ids)train_mae = (train_mae / len(im_ids))train_rmse = (train_rmse / len(im_ids))**0.5return train_loss, train_mae, train_rmse

核心函数为extract_features

  • 使用了一个correlation map表征exempler特征和输入的图像特征的相似性(利用卷积实现)
  • 使用双线性插值实现ROI Pooling操作,使得每一张图片中的exemplars的高和宽统一
  • 由于object可能会有尺度变化,因此作者把exemplers的大小进行缩放(0.9、1.0、1.1)
  • 生成多个correlation maps(map3和map4分别产生三个尺度的correlation map),这些特征图最后被concat,最后送入密度图预测模块
# LearningToCountEverything/utils.py
def extract_features(feature_model, image, boxes, feat_map_keys=['map3','map4'], exemplar_scales=[0.9, 1.1]):"""1、使用了一个correlation map表征exempler特征和输入的图像特征的相似性(利用卷积实现)2、由于object可能会有尺度变化,因此作者把exempler的大小进行缩放(0.9、1.1)3、生成多个correlation maps(map3和map4分别产生三个尺度的correlation map),这些特征图最后被concat送入密度图预测模块"""N, M = image.shape[0], boxes.shape[2] # 图片个数及exampler的个数# 使用ResNet-50的第三个和第四个block得到的特征图表征输入图像# Getting features for the image N * C * H * WImage_features = feature_model(image)# Getting features for the examples (N*M) * C * h * wfor ix in range(0, N):# boxes = boxes.squeeze(0)boxes = boxes[ix][0]  # 一张图像中exampler的bbox信息cnter = 0for keys in feat_map_keys:image_features = Image_features[keys][ix].unsqueeze(0)if keys == 'map1' or keys == 'map2':Scaling = 4.0elif keys == 'map3':Scaling = 8.0elif keys == 'map4':Scaling = 16.0else:Scaling = 32.0# exempler的bbox信息缩放到特征图尺度boxes_scaled = boxes / Scalingboxes_scaled[:, 1:3] = torch.floor(boxes_scaled[:, 1:3])boxes_scaled[:, 3:5] = torch.ceil(boxes_scaled[:, 3:5])# make the end indices exclusiveboxes_scaled[:, 3:5] = boxes_scaled[:, 3:5] + 1feat_h, feat_w = image_features.shape[-2], image_features.shape[-1]# make sure exemplars don't go out of boundboxes_scaled[:, 1:3] = torch.clamp_min(boxes_scaled[:, 1:3], 0)boxes_scaled[:, 3] = torch.clamp_max(boxes_scaled[:, 3], feat_h)boxes_scaled[:, 4] = torch.clamp_max(boxes_scaled[:, 4], feat_w)            box_hs = boxes_scaled[:, 3] - boxes_scaled[:, 1]box_ws = boxes_scaled[:, 4] - boxes_scaled[:, 2]            # 获取一张图片中exemplars在特征图上最大的高和宽max_h = math.ceil(max(box_hs))max_w = math.ceil(max(box_ws))            for j in range(0, M):y1, x1 = int(boxes_scaled[j, 1]), int(boxes_scaled[j, 2])y2, x2 = int(boxes_scaled[j, 3]), int(boxes_scaled[j, 4])# print(y1,y2,x1,x2,max_h,max_w)if j == 0:# 获取exemplars的相应特征图examples_features = image_features[:, :, y1:y2, x1:x2]if examples_features.shape[2] != max_h or examples_features.shape[3] != max_w:# 双线性插值填充(目的:每一张图片中的exemplars的高和宽统一)examples_features = F.interpolate(examples_features, size=(max_h, max_w), mode='bilinear')else:feat = image_features[:, :, y1:y2, x1:x2]if feat.shape[2] != max_h or feat.shape[3] != max_w:# 双线性插值填充(目的:每一张图片中的exemplars的高和宽统一)feat = F.interpolate(feat, size=(max_h, max_w), mode='bilinear')# concatexamples_features = torch.cat((examples_features, feat), dim=0)"""Convolving example features over image features【使用卷积计算相似】这里把examples_features当作卷积核 在 image_features上进行卷积,得到correlation map"""h, w = examples_features.shape[2], examples_features.shape[3]# input shape(minibatch, in_channels, iH, iW)# weight shape(out_channels, in_channels/groups, kH, kW)# out_channels就是一张图片中bbox的个数features = F.conv2d(input=F.pad(input=image_features, pad=((int(w/2)), int((w-1)/2), int(h/2), int((h-1)/2))), # 进行填充,使卷积后correlation map高宽不变weight=examples_features)combined = features.permute([1, 0, 2, 3])# computing features for scales 0.9 and 1.1# 考虑到不同 scale 的 object 我们会对其进行缩放# 缩放之后会得到新的 correlation mapfor scale in exemplar_scales:h1 = math.ceil(h * scale)w1 = math.ceil(w * scale)if h1 < 1: # use original size if scaled size is too smallh1 = hif w1 < 1:w1 = wexamples_features_scaled = F.interpolate(examples_features, size=(h1,w1), mode='bilinear')features_scaled = F.conv2d(F.pad(image_features, ((int(w1/2)), int((w1-1)/2), int(h1/2), int((h1-1)/2))),examples_features_scaled)features_scaled = features_scaled.permute([1, 0, 2, 3])# 把所有的correlation map concatenate 在一起combined = torch.cat((combined, features_scaled), dim=1)if cnter == 0:Combined = 1.0 * combinedelse:# 对map4进行上采样,和map3统一宽和高if Combined.shape[2] != combined.shape[2] or Combined.shape[3] != combined.shape[3]:combined = F.interpolate(combined, size=(Combined.shape[2], Combined.shape[3]), mode='bilinear')Combined = torch.cat((Combined, combined), dim=1)cnter += 1if ix == 0:All_feat = 1.0 * Combined.unsqueeze(0)else:All_feat = torch.cat((All_feat, Combined.unsqueeze(0)), dim=0)return All_feat

2.3 模型预测

  • 预测需要图片标注少量的bbox,如果没有提供bbox文件,则提示用户标注一组边界框
  • 预测过程中的预处理和训练基本一致,不过没有密度图相关的处理。
  • 多尺度特征提取模块提取特征后,可以开启自适应训练(默认100轮),然后再输出到密度图预测模块
  • 预测所用的loss为MincountLoss和PerturbationLoss的加权和。这两种损失函数已经在1上文中介绍过,不再介绍。
  • 在某些情况下,损失可能会变为零,其中损失是一个值为零的标量而不是张量。因此,仅针对非零情况执行梯度下降。
  • 这里使用官方提供的预训练模型进行检测,可视化的检测结果如下图。
    在这里插入图片描述
# LearningToCountEverything/demo.py
def detect(args):if not torch.cuda.is_available() or args.gpu_id < 0:use_gpu = Falseprint("===> Using CPU mode.")else:use_gpu = Trueos.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"os.environ["CUDA_VISIBLE_DEVICES"] = str(args.gpu_id)# 1、多尺度特征提取模块resnet50_conv = Resnet50FPN()# 2、密度图预测模块# input_channels=6,这是因为Resnet50FPN的map3和map4分别产生三个尺度(0.9、1.0、1.1)的correlation mapregressor = CountRegressor(input_channels=6, pool='mean')# 3、加载训练模型if use_gpu:resnet50_conv.cuda()regressor.cuda()regressor.load_state_dict(torch.load(args.model_path))else:regressor.load_state_dict(torch.load(args.model_path, map_location=torch.device('cpu')))resnet50_conv.eval()regressor.eval()image_name = os.path.basename(args.input_image)image_name = os.path.splitext(image_name)[0]# 如果没有提供bbox文件,则提示用户输入一组边界框# if no bounding box file is given, prompt the user for a set of bounding boxesif args.bbox_file is None:out_bbox_file = "{}/{}_box.txt".format(args.output_dir, image_name)fout = open(out_bbox_file, "w")im = cv2.imread(args.input_image)cv2.imshow('image', im)rects = select_exemplar_rois(im)rects1 = list()for rect in rects:y1, x1, y2, x2 = rectrects1.append([y1, x1, y2, x2])print(rects1)fout.write("{} {} {} {}\n".format(y1, x1, y2, x2))fout.close()cv2.destroyAllWindows()print("selected bounding boxes are saved to {}".format(out_bbox_file))else:with open(args.bbox_file, "r") as fin:lines = fin.readlines()rects1 = list()for line in lines:data = line.split()y1 = int(data[0])x1 = int(data[1])y2 = int(data[2])x2 = int(data[3])rects1.append([y1, x1, y2, x2])print("Bounding boxes: ", end="")print(rects1)# 3、加载图像、并进行预处理image = Image.open(args.input_image)image.load()sample = {'image': image, 'lines_boxes': rects1}sample = Transform(sample)image, boxes = sample['image'], sample['boxes']if use_gpu:image = image.cuda()boxes = boxes.cuda()# 4、多尺度特征提取模块提取特征with torch.no_grad():features = extract_features(resnet50_conv, image.unsqueeze(0), boxes.unsqueeze(0), MAPS, Scales)# 5、输出预测结果if not args.adapt:# 不采用自适应,直接输出预测结果with torch.no_grad():output = regressor(features)else:# 采用自适应,先进行训练(默认100轮),再输出features.required_grad = Trueadapted_regressor = regressoradapted_regressor.train()# 仍然只对regressor参数进行微调optimizer = optim.Adam(adapted_regressor.parameters(), lr=args.learning_rate)pbar = tqdm(range(args.gradient_steps))for step in pbar:optimizer.zero_grad()output = adapted_regressor(features)# 此时所用loss为MincountLoss和PerturbationLoss的加权和lCount = args.weight_mincount * MincountLoss(output, boxes, use_gpu=use_gpu)lPerturbation = args.weight_perturbation * PerturbationLoss(output, boxes, sigma=8, use_gpu=use_gpu)Loss = lCount + lPerturbation# 在某些情况下,损失可能会变为零,其中损失是一个值为零的标量而不是张量。# 因此,仅针对非零情况执行梯度下降。if torch.is_tensor(Loss):Loss.backward()optimizer.step()pbar.set_description('Adaptation step: {:<3}, loss: {}, predicted-count: {:6.1f}'.format(step, Loss.item(),output.sum().item()))features.required_grad = Falseoutput = adapted_regressor(features)print(output.shape)print('===> The predicted count is: {:6.2f}'.format(output.sum().item()))# 6、可视化预测结果rslt_file = "{}/{}_out.png".format(args.output_dir, image_name)visualize_output_and_save(image.detach().cpu(), output.detach().cpu(), boxes.cpu(), rslt_file)print("===> Visualized output is saved to {}".format(rslt_file))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/620165.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

speccpu2017安装与使用

国产化桌面下Speccpu2017安装与使用 1、 安装依赖库 安装speccpu2017前需要安装依赖包&#xff0c;通过终端命令对依赖包进行安装 sudo apt-get install gcc g gfortran &#xff08;以上是已经安装好的&#xff09; 注&#xff1a;若安装不上&#xff0c;需替换/etc/apt下的s…

JMeter入门教程 —— 事务!

简介&#xff1a; JMeter中事务的基本介绍 1.任务背景 JMeter中的事务是通过事务控制器实现的。&#xff0c;为了衡量服务器对某一个或一系列操作处理的响应时间&#xff0c;需要定义事务。下面我们详细介绍在JMeter中如何使用事务 2.任务目标 掌握基于JMeter性能测试脚本开…

win11黑屏无法唤醒的解决教程,升级win11后黑屏无法唤醒

通常,电脑开启了睡眠模式后,我们只要不使用电脑一段时间,系统就会自动进入睡眠状态,这样可以有效节约电脑消耗。所以,当你升级win11后,系统出现黑屏了,那么可能是处于睡眠状态中,多点击几次鼠标就能唤醒系统了。但是,也有用户遇到无法唤醒的情况,是睡眠模式的问题还是…

简单认识Git(dirsearch、githack下载),git泄露(ctfhub)

目录 dirsearch下载地址: githack下载&#xff08;一次不成功可多试几次&#xff09; 一、什么是Git 1.git结构 2.git常用命令及示例 3.Git泄露原理 二、Git泄露 1.Log 2.Stash 3.Index 工具准备&#xff1a;dirsearch、githack dirsearch下载地址: GitHub - mauroso…

Angular 使用DomSanitizer防范跨站脚本攻击

跨站脚本Cross-site scripting 简称XSS&#xff0c;是代码注入的一种&#xff0c;是一种网站应用程序的安全漏洞攻击。它允许恶意用户将代码注入到网页上&#xff0c;其他用户在使用网页时就会收到影响&#xff0c;这类攻击通常包含了HTML和用户端脚本语言&#xff08;JS&…

XWX-SX三箱社交箱

简单介绍&#xff1a; 动物行为学是一门研究动物行为的科学&#xff0c;它包括观察动物在自然环境中的行为&#xff0c;以及在控制环境中的实验行为。三箱社交实验是其中一种常见的实验方法&#xff0c;用于评估动物的社交行为和决策制定能力。这种实验在许多领域都有应用&…

如何查看个人大数据信用报告?查询报告哪家好呢?

大数据信用报告是现代社会中非常重要的信用评估工具&#xff0c;对于个人来说也具有非常重要的意义。那么&#xff0c;如何查看个人大数据信用报告?查询报告哪家好呢?本文将为您介绍。 首先&#xff0c;查看个人大数据信用报告需要了解报告的内容和格式 一般来说&#xff0c;…

【攻防世界】bug

垂直越权IP绕过文件上传 文件上传绕过&#xff1a; 1. mime检测 2. 大小写绕过 3. 等价替换&#xff08;php5&#xff0c;php3&#xff09; 4. 利用JavaScript执行php代码&#xff08;正常的php代码会被检测到&#xff0c;所以就用JavaScript来执行&#xff09; <script lan…

7 个 Python 问题,来扫扫盲

这 7 个问题&#xff0c;我是有收获的&#xff0c;整理如下&#xff1a; 1、反射算术运算符 你可能知道 Python 里面的魔法函数&#xff0c;比如 __add__ 和 __sub__ 代表 - 运算符&#xff0c;表示 obj /- something&#xff0c;但你可能不知道还有一个 __radd__&#xff0…

李廉洋:4.15黄金,原油最新资讯,美盘走势分析及策略。

由于欧洲央行很可能先于美联储降息&#xff0c;美元走强。法国兴业银行分析师基特•朱克斯表示&#xff0c;市场“假设我们看到欧洲央行将在6月降息&#xff0c;但美联储不会”&#xff0c;这对美元有利。朱克斯表示&#xff0c;尽管在货币政策决定之前会公布一些相关数据&…

【Node.js】Express学习笔记(黑马)

目录 初识 ExpressExpress 简介Express 的基本使用托管静态资源nodemon Express 路由路由的概念路由的使用 Express 中间件中间件的概念Express 中间件的初体验中间件的分类 初识 Express Express 简介 什么是 Express&#xff1f; 官方给出的概念&#xff1a;Express 是基于…

软件杯 深度学习卷积神经网络的花卉识别

文章目录 0 前言1 项目背景2 花卉识别的基本原理3 算法实现3.1 预处理3.2 特征提取和选择3.3 分类器设计和决策3.4 卷积神经网络基本原理 4 算法实现4.1 花卉图像数据4.2 模块组成 5 项目执行结果6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基…