益阳营销型网站建设,做外贸网哪些网站免费,黑马程序员就业情况,织梦网站首页栏目修改小样本计数网络FamNet(Learning To Count Everything) 大多数计数方法都仅仅针对一类特定的物体#xff0c;如人群计数、汽车计数、动物计数等。一些方法可以进行多类物体的计数#xff0c;但是training set中的类别和test set中的类别必须是相同的。 为了增加计数方法的可拓…小样本计数网络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描述计数的物体类别并给定一张待计数的图像queryFSC希望计算出该类别的物体在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-147a 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所有值的求和\\ LMinCountb∈B∑max(0,1−∣∣Zb∣∣1)B为给定所有的样本框Zb表示在密度图Z上位置b处的裁剪图,∣∣Zb∣∣1为Zb所有值的求和 看了这个损失函数的数学表达式你可能比较懵逼那我们直接来看代码。
通过下面代码我们知道MinCountLoss其实就是为了约束每一个exempler的框内至少有一个物体当 ∣ ∣ Z b ∣ ∣ 1 1 ||Z_b||_11 ∣∣Zb∣∣11时 L M i n C o u n t 0 L_{MinCount}0 LMinCount0。因此最小计数损失只会惩罚那些没有判断出exempler区域内目标大于1的情况。
# 主要是为了约束每一个exempler的框内至少有一个物体
def MincountLoss(output, boxes, use_gpuTrue):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, sigma8, use_gpuTrue):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]), sigmasigma)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]), sigmasigma)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(pretrainedTrue)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, poolmean):super(CountRegressor, self).__init__()self.pool pool# 经过三次上采样将feature map采样到原始图像大小self.regressor nn.Sequential(nn.Conv2d(in_channelsinput_channels, out_channels196, kernel_size7, padding3),nn.ReLU(),nn.UpsamplingBilinear2d(scale_factor2),nn.Conv2d(in_channels196, out_channels128, kernel_size5, padding2),nn.ReLU(),nn.UpsamplingBilinear2d(scale_factor2),nn.Conv2d(in_channels128, out_channels64, kernel_size3, padding1),nn.ReLU(),nn.UpsamplingBilinear2d(scale_factor2),nn.Conv2d(in_channels64, out_channels32, kernel_size1),nn.ReLU(),nn.Conv2d(in_channels32, out_channels1, kernel_size1), # 输出通道为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), keepdimTrue)return outputelif self.pool max:output, _ torch.max(output, 0, keepdimTrue)return outputelse:for i in range(0, num_sample):output self.regressor(im[i])if self.pool mean:output torch.mean(output, dim(0), keepdimTrue)elif self.pool max:output, _ torch.max(output, 0, keepdimTrue)if i 0:Output outputelse:Output torch.cat((Output, output), dim0)return Output2.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_channels6这是因为map3和map4分别产生三个尺度0.9、1.0、1.1的correlation mapregressor CountRegressor(input_channels6, poolmean).to(device)weights_normal_init(regressor, dev0.001)regressor.train()# 3、这里冻结Resnet50FPN只更新CountRegressor的参数optimizer optim.Adam(regressor.parameters(), lrargs.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 / fFamNet_{best_mae}.pthtorch.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并concat在一起然后将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] .npydensity 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 isnt 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]), modebilinear)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可能会有尺度变化因此作者把exempler的大小进行缩放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.13、生成多个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 dont 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), modebilinear)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), modebilinear)# concatexamples_features torch.cat((examples_features, feat), dim0)Convolving example features over image features【使用卷积计算相似】这里把examples_features当作卷积核 在 image_features上进行卷积得到correlation maph, 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(inputF.pad(inputimage_features, pad((int(w/2)), int((w-1)/2), int(h/2), int((h-1)/2))), # 进行填充使卷积后correlation map高宽不变weightexamples_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), modebilinear)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), dim1)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]), modebilinear)Combined torch.cat((Combined, combined), dim1)cnter 1if ix 0:All_feat 1.0 * Combined.unsqueeze(0)else:All_feat torch.cat((All_feat, Combined.unsqueeze(0)), dim0)return All_feat
2.3 模型预测
预测需要图片标注少量的bbox如果没有提供bbox文件则提示用户输入一组边界框预测过程中的预处理和训练基本一致不过没有密度图相关的处理。多尺度特征提取模块提取特征后可以进行自适应训练(默认100轮)再输出到密度图预测模块预测所用的loss为MincountLoss和PerturbationLoss的加权和。这两种损失函数已经在1.2中介绍过不再介绍。在某些情况下损失可能会变为零其中损失是一个值为零的标量而不是张量。因此仅针对非零情况执行梯度下降。这里使用官方提供的预训练模型进行检测可视化的检测结果如下图。
# 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_IDos.environ[CUDA_VISIBLE_DEVICES] str(args.gpu_id)# 1、多尺度特征提取模块resnet50_conv Resnet50FPN()# 2、密度图预测模块# input_channels6这是因为Resnet50FPN的map3和map4分别产生三个尺度0.9、1.0、1.1的correlation mapregressor CountRegressor(input_channels6, poolmean)# 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_locationtorch.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(), lrargs.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_gpuuse_gpu)lPerturbation args.weight_perturbation * PerturbationLoss(output, boxes, sigma8, use_gpuuse_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))