首页 > 其他分享 >目标检测—Faster R-CNN详解

目标检测—Faster R-CNN详解

时间:2022-12-23 16:13:04浏览次数:65  
标签:box Faster image proposals boxes 详解 CNN anchors

简介

Faster R-CNN是继R-CNN,Fast R-CNN后基于Region-CNN的又一目标检测力作。Faster R-CNN发表于NIPS 2015。即便是2015年的算法,在现在也仍然有着广泛的应用以及不俗的精度。缺点是速度较慢,无法进行实时的目标检测。

Faster R-CNN是典型的two-stage目标检测框架,即先生成区域提议(Region Proposal),然后在产生的Region Proposal上做分类和回归。相较于前作R-CNN和Fast R-CNN,Faster R-CNN的改进主要在于区域提议方面,使用区域提议网络(Region Proposal Network, RPN)提供区域建议,取代了选择性搜索。RPN是全卷积神经网络,并与检测网络共享图像的卷积特征,减少了区域提议的计算开销。也就是说,可以将Faster R-CNN 看作是 RPN + Fast R-CNN。

Faster R-CNN的网络示意如下图。

 

学习Faster R-CNN目标检测框架,对于目标检测任务的熟悉和进一步研究有着非常大的帮助,接下来将主要通过Faster R-CNN的训练和推理过程,学习它的网络结构等内容。

Faster R-CNN 网络结构

Dataset

在提及Faster R-CNN框架前,首先还是要简单说明一下目标检测数据集。以Pascal VOC数据集为例,该数据集的一组数据包括一张图片,一个对应的xml标注文件。用于目标检测任务时,需要关注object标签内的标注信息,一般使用到的是:

bndbox,目标包围框的坐标;

difficult,目标是否难以识别;

name,目标的类别;

在构建自定义Dataset时,往往会将图片image转换成Tensor格式,标签target则处理成Tensor的字典。

 

Generator Transform

由于目标检测数据集的特殊性(每张图片大小不一,图片内目标的个数和大小也各不相同),在送入网络前,需要进行数据处理。

  1. 记录一个batch内图像(Tensor列表)以及目标边界框的原始尺寸,用于后处理(post process)。

  2. 对图像进行标准化处理(normalization),然后将图像及其目标框按照边长等比例缩放,记录缩放后尺寸。

将图像按照边长等比例缩放时,这一个batch内的图像的长宽只有一个对齐,而送入网络时需要尺寸均一致的张量,因此还需继续处理。

  1. 将上述batch内图像调整至统一的尺寸(冗余部分用0填充),得到完整的Tensor。

  2. 将打包好的图片Tensor以及缩放后的尺寸打包至ImageList类中。

Feature

将ImageList中的图像Tensor送入骨干网络进行特征提取。在论文中使用的模型是VGG,取单层特征图进行目标检测。现在比较经典的神经网络是ResNet结合特征金字塔网络(Feature Pyramid Network, FPN),得到图像的多尺度语义特征图,可以获得更高的目标检测精度。

经过特征提取后,得到一个有序字典features,字典的key为特征层的id,value为图像在该层的特征图。然后Faster R-CNN在每一个特征层上进行预测。

 

Region Proposals Network

将ImageList,features,以及标签targets(目标边界框)传入RPN网络。

Anchor Generator

在features的每个feature map中,每一个cell都生成k个锚框(anchor boxes)。这k个锚框由不同的尺寸和纵横比组成,一般将尺寸设置为 $ {128^2, 256^2, 512^2} $ ,纵横比为 $ {1:1, 1:2, 2:1} $,即k=9个anchors。

使用AnchorGenerator类生成锚框,forward代码如下,是anchors生成的过程。

def forward(self, image_list, feature_maps):
        # type: (ImageList, List[Tensor]) -> List[Tensor]
        
        grid_sizes = list([feature_map.shape[-2:] for feature_map in feature_maps])
        image_size = image_list.tensors.shape[-2:]
        dtype, device = feature_maps[0].dtype, feature_maps[0].device
        
        # one step in feature map equate n pixel stride in origin image
        strides = [[torch.tensor(image_size[0] // g[0], dtype=torch.int64, device=device),
                    torch.tensor(image_size[1] // g[1], dtype=torch.int64, device=device)] for g in grid_sizes]
​
        # 根据sizes和aspect_ratios生成anchors模板列表,len(list)=len(sizes)
        # [Tensor:(3, 4), ...] Tensor.shape = (len(aspect_ratios), 4)
        self.set_cell_anchors(dtype, device) 
        
        # 得到list列表,对应每张预测特征图映射回原图的anchors坐标信息
        # 通过特征图尺寸以及stride得到原图基准坐标,再与anchors模板坐标相加 
        anchors_over_all_feature_maps = self.cached_grid_anchors(grid_sizes, strides)
​
        anchors = torch.jit.annotate(List[List[torch.Tensor]], [])
        # 遍历一个batch中的每张图像
        for i, (image_height, image_width) in enumerate(image_list.image_sizes):
            anchors_in_image = []
            # 遍历每张预测特征图映射回原图的anchors坐标信息
            for anchors_per_feature_map in anchors_over_all_feature_maps:
                anchors_in_image.append(anchors_per_feature_map)
            anchors.append(anchors_in_image)
            
        # 将每一张图像的所有预测特征层的anchors坐标信息拼接在一起
        # anchors是个list,len(anchors)=batch,每个元素为一张图像的所有anchors
        anchors = [torch.cat(anchors_per_image) for anchors_per_image in anchors]
        self._cache.clear()
        return anchors

RPN Head

 

在RPN网络头部,先通过3x3的滑动窗口对每一个cell进一步提取特征,用于生成区域建议。

然后并行地连接两个全连接层(1x1大小的卷积层),cls layer 和 reg layer。分别预测每个cell生成anchor的类别分数(只关心是前景还是背景,输出特征维度为2k)和坐标(输出特征维度为4k)。

注:在论文中cls layer 预测2k个分数。在源码实现上,可以只预测k个分数,使用二至交叉熵计算损失,后续在损失计算时会再次提及。

 

经过PRN Head计算以后,得到两个结果:

  1. objectness (box_cls),表示预测的每个anchor的类别分数。是len(objectness) = len(features)的列表,列表的每个元素是(B, K, H, W)的Tensor。

  2. pred_bbox_deltas (box_regression),为预测的每个anchor的回归参数。是len(objectness) = len(features)的列表,列表的每个元素是(B, 4*K, H, W)的Tensor。

即经过RPN Head计算以后,我们得到了每个特征层下的预测分数以及预测回归参数。在进一步处理前,需要调整一下形状。

  1. objectiness,Tensor(num_anchors, 1)

  2. pred_bbox_deltas, Tensor(num_anchors, 4)

Box Coder Decode

然后将预测的回归参数(rel_codes)和锚框(boxes)进行编解码,得到最终的proposals。

$ x $,  $ x_a $,and  $ x^* $ are perdictied box, anchor box, and ground-truth box.

回归参数的计算公式如下。同样我们可以通过回归参数以及anchor box推导出计算ground truth box。

$$
t_x = (x − x_a)/w_a, \ \ t_y = (y − y_a)/h_a, \\ t_w = log(w/w_a), \ \ t_h = log(h/h_a),\\ t^∗_x = (x^∗ − x_a)/w_a, \ \ t^∗_y = (y^∗ − y_a)/h_a,\\ t^∗_w = log(w^∗/w_a), \ \ t^∗_h = log(h^∗/h_a),
$$

 

在具体实现中,

首先根据anchors的坐标(xmin,ymin,xmax,ymax),得到中心点坐标以及宽高。

widths  = boxes[:, 2] - boxes[:, 0]   # anchor/proposal宽度
heights = boxes[:, 3] - boxes[:, 1]  # anchor/proposal高度
ctr_x = boxes[:, 0] + 0.5 * widths   # anchor/proposal中心x坐标
ctr_y = boxes[:, 1] + 0.5 * heights  # anchor/proposal中心y坐标

然后根据rel_codes得到相应的回归参数。

wx, wy, ww, wh = self.weights  # RPN中为[1,1,1,1], fastrcnn中为[10,10,5,5]
dx = rel_codes[:, 0::4] / wx   # 预测anchors/proposals的中心坐标x回归参数
dy = rel_codes[:, 1::4] / wy   # 预测anchors/proposals的中心坐标y回归参数
dw = rel_codes[:, 2::4] / ww   # 预测anchors/proposals的宽度回归参数
dh = rel_codes[:, 3::4] / wh   # 预测anchors/proposals的高度回归参数
​
dw = torch.clamp(dw, max=self.bbox_xform_clip)
dh = torch.clamp(dh, max=self.bbox_xform_clip)

根据回归参数以及anchor box坐标由最上边的公式逆推,得到proposals的坐标。

 pred_ctr_x = dx * widths[:, None] + ctr_x[:, None]
 pred_ctr_y = dy * heights[:, None] + ctr_y[:, None]
 pred_w = torch.exp(dw) * widths[:, None]
 pred_h = torch.exp(dh) * heights[:, None]
​
# xmin
pred_boxes1 = pred_ctr_x - torch.tensor(0.5,) * pred_w
# ymin
pred_boxes2 = pred_ctr_y - torch.tensor(0.5,) * pred_h
# xmax
pred_boxes3 = pred_ctr_x + torch.tensor(0.5,) * pred_w
# ymax
pred_boxes4 = pred_ctr_y + torch.tensor(0.5,) * pred_h

这样我们得到了proposals,并调整至shape=(batch, num_anchors_per_img, 4)。

 

Filter Proposals

在之前的步骤中,我们生成了大量的proposals(10000个以上)。显然这么多proposals是不必要的,因此我们在这里要滤除大量的proposals,只留下2000个proposals。

根据objectness得分,进行top_n运算,获取每个特征图上预测概率排名靠前(pre_nms_top_n)的索引。

根据得到的索引,将objectness更新,仅保留索引处的概率信息。并通过sigmoid函数,将objectness预测值处理成概率形式。

最后我们遍历每个batch:

  1. 调整预测的bounding box坐标,将越界坐标调整至图片边界。

  2. 滤除面积过小的bounding box(将高宽不满足min_size的box剔除)。

  3. 移除小概率的bounding box(将概率小于score_threshold的box剔除)。

  4. 非极大值抑制。

  5. 返回处理剩余的前top_n个proposals。

这样,得到我们最终的proposals和对应的scores。

 

ROI

在经过RPN后,我们得到了2000个proposals。接下来将通过ROI (Region of Interest)进一步提取骨干网络的特征,得到proposals的回归参数以及分类类别。

在训练模式下,需要对proposals划分正负样本。这个放到后续损失计算部分再讲。

ROI Pooling (Align)

将features,proposals,image_shapes传入至ROI Poooing层。在最初版本的Faster R-CNN中,是使用的ROI Pooling。在后续改进中(Mask R-CNN)将这一步换成了 MultiScaleRoIAlign。

官方示例如下

 

Faster R-CNN的ROI Pooling将backbone得到的多尺度features池化为若干7x7大小的特征图。在Faster R-CNN中,得到的池化后的特征图尺寸为(1024, 256, 7, 7)。1024为一个batch内proposals的个数,256为backbone的intermediate channels。

Two MLP Head

得到池化特征后,送入由两个全连接层构成的多层感知机,层间使用ReLU函数激活,用于进一步提取特征。第一个全连接层将池化特征的维度从intermediate size投射至representation size,第二个全连接层投射维度不变。

最终得到了(num_proposals, representation_size)的特征图。

 

Predictor

在 Fast R-CNN Predictor阶段,就负责对Proposals进行预测。

一个全连接层负责预测proposals的类别,$ (channels \to num\_classes) $。

一个全连接层负责预测proposals的坐标回归参数,$ (channels \to num\_classes \times 4) $。

即得到class_logits, box_regression。

如果是训练模式,将预测的结果与此前的样本(labels,regression_targets)进行损失计算更新网络即可。

 

Post Process Detections

若是推理模式,则不需上述的损失计算这一过程。但需进行后处理,将回归参数转换为坐标,即通过class_logits, box_regression, proposals, images_shapes,得到最终的预测边界框坐标和类别分数。

首先,通过Box Coder Decode,将box_regression和proposals进行计算,得到预测的1000 * num_classes个(在推理模式下,RPN生成1000个Proposals,训练模式下生成2000个Proposals,每个Proposal对每个类别都预测边界框回归参数和得分)边界框的预测坐标。

然后将预测的类别分数进行softmax处理,处理成概率分数。

最后遍历batch内的每个图像:

  1. 裁剪预测的bounding boxes坐标,将越界包围框调整到图像边界。

  2. 移除索引为背景(即类别0)的所有信息。

  3. 移除低概率目标(预测概率小于score threshold,一般为目标个数分之一)。

  4. 移除小目标。

  5. NMS非极大值抑制处理,滤除过于相近的目标包围框(返回结果由scores从大到小排序)。

  6. 选取前topk个预测目标(一般是100个)。

得到最终的boxes,scores,labels,打包至result变量中。

Post Process

因为我们输入网络的图像已经根据指定的边长进行了等比例缩放,预测后的目标包围框也是基于缩放后的尺寸。因此需要借助缩放后的尺寸image_sizes(保存在了ImageList中),以及最初统计的图像原尺寸original_image_sizes进行缩放处理,得到最终的边界框坐标、类别名称、预测分数detections。

最后将detections绘制到原图即可。

损失计算

Faster R-CNN的损失包括两个部分,训练过程中,在第一阶段RPN产生区域提议和第二阶段进行目标预测都会有损失计算,而网络推理时,是不需要进行损失计算的。

Region Proposal Network

在RPN网络中,我们得到了proposals和对应的score。要想让RPN网络生成更加准确的proposals和score,需要对其生成的anchor回归参数以及类别进行训练。

Assgin Targets to Anchors

通过assign_targets_to_anchors函数,计算每个anchor最匹配的ground truth box,将anchor划分为正负样本,用于后续的RPN网络训练。

def assign_targets_to_anchors(self, anchors, targets):
    # type: (List[Tensor], List[Dict[str, Tensor]]) -> Tuple[List[Tensor], List[Tensor]]
   
    labels = []
    matched_gt_boxes = []
    # 遍历每张图像的anchors和targets
    for anchors_per_image, targets_per_image in zip(anchors, targets):
        gt_boxes = targets_per_image["boxes"]
        if gt_boxes.numel() == 0:
            device = anchors_per_image.device
            matched_gt_boxes_per_image = torch.zeros(anchors_per_image.shape, )
            labels_per_image = torch.zeros((anchors_per_image.shape[0],), )
        else:
            # 计算anchors与gtbox的iou, shape=(num_gt, num_anchors)
            match_quality_matrix = box_ops.box_iou(gt_boxes, anchors_per_image)
            # 计算每个anchors与gt匹配iou最大的索引(iou<0.3索引置为-1,0.3<iou<0.7索引为-2)
            matched_idxs = self.proposal_matcher(match_quality_matrix)
         
            # 这里使用clamp设置下限0是为了方便取每个anchors对应的gt_boxes信息
            # 负样本和舍弃的样本都是负值,所以为了防止越界直接置为0
            # 计算目标边界框回归损失时只会用到正样本。
            matched_gt_boxes_per_image = gt_boxes[matched_idxs.clamp(min=0)]
​
            # 记录所有anchors匹配后的标签(正样本处标记为1,负样本处标记为0,丢弃样本处标记为-2)
            labels_per_image = matched_idxs >= 0
            labels_per_image = labels_per_image.to(dtype=torch.float32)
​
            # background (negative examples)
            bg_indices = matched_idxs == self.proposal_matcher.BELOW_LOW_THRESHOLD  # -1
            labels_per_image[bg_indices] = 0.0
​
            # discard indices that are between thresholds
            inds_to_discard = matched_idxs == self.proposal_matcher.BETWEEN_THRESHOLDS
            
            labels_per_image[inds_to_discard] = -1.0
​
            labels.append(labels_per_image)
            matched_gt_boxes.append(matched_gt_boxes_per_image)
            
    return labels, matched_gt_boxes

proposal_matcher,将计算得到的iou矩阵在dim=0处取最大值,计算每个proposals的最佳匹配ground truth。得到matched_vals, matches (value, index)。

然后将iou<low_threshold的索引设置为-1,为负样本;将iou在low_threshold和high_threshold之间的anchors索引设置为-2,为丢弃样本。(此时若正样本数量过少,可以通过set_low_quality_matches_,将iou低于给定阈值的anchors依旧设置为正样本)

通过得到的索引,就可以得到每个图像匹配到的ground truth box。

最终返回labels(所有anchors的标签)matched_gt_boxes (每个anchor匹配到的gt box,非正样本为首个gt box)。

Box Coder Encode

Anchor Generator生成的所有anchor与上一步得到的anchors对应的ground truth boxes,计算边界框的回归参数。计算公式即为RPN部分Box Coder Decode中所给的公式。

Sampler

在计算RPN部分的损失时,首先要区分正负样本。

通过fg_bg_sampler函数将labels分为正负样本,即得到sampled_pos_inds, sampled_neg_inds。

在fg_bg_sampler内,统计正样本和负样本。(借助labels的索引,大于0为正,等于0为负)

在训练策略中,每个图像默认使用256个样本进行训练,正负1比1。若正样本不够,就将所有的正样本都加入训练,剩下的负样本补齐,反之同理。

使用torch.randperm生成随机序列,用于打乱正负样本的索引。

最后使用anchors长度的mask,将正负样本对应的索引处标为1,非样本处标为0,得到sampled_pos_inds, sampled_neg_inds。

得到正负样本的index后,将所有正负样本索引拼接在一起,得到sampled_pos_inds,用于回归损失。然后将objectness展平,用于分类损失。

Loss Compute

$$
L(\{p_i\},\{t_i\}) = \frac{1}{N_{cls}}\sum_{i}L_{cls}(p_i,p_i^*) \\ \qquad\qquad\qquad\quad +\lambda \frac{1}{N_{reg}}\sum_{i}p_i^*L_{reg}(t_i,t_i^*)
$$

 

 

损失包括两个内容,一个是目标分类损失,一个是预测边界框的回归损失。

分类损失使用的是二值交叉熵损失。

$$
L_{cls}=-p_i^*log(p_i) + (1-p_i^*)log(1-p_i^*)
$$

 

其中,p_i表示第i个anchor预测有目标的概率;p_i^*表示真值,正样本为1,负样本为0。

 

回归损失使用的是Smooth L1 损失。

$$
L_{reg}(t_i, t_i^*)=\sum_{i}smooth_{L_1}(t_i - t_i^*)
$$

 

其中,t_i表示第i个anchor的预测回归参数;t_i^*表示第i个样本的真值,由GT box 和该anchor计算得出。

$$
smooth_{L_1}= \begin{cases} 0.5x^2 \qquad \lvert x \rvert \leq 1 \\[2ex] \lvert x \rvert-0.5 \quad otherwise \end{cases}
$$

 

Fast R-CNN Detector

Select Training Samples

在RPN网络中,生成了2000个proposals。为了训练和计算损失,依旧需要划分正负样本。

首先将ground turth boxes拼接到proposals后,充当一部分正样本(满足正样本条件的proposals可能很少)。

然后调用assign_targets_to_proposal函数,为每个proposals匹配对应的gt box,得到matched_idxs, labels(与RPN部分的方法基本相同)。

def assign_targets_to_proposals(self, proposals, gt_boxes, gt_labels):
# type: (List[Tensor], List[Tensor], List[Tensor]) -> Tuple[List[Tensor], List[Tensor]]
   
    matched_idxs = []
    labels = []
    # 遍历每张图像的proposals, gt_boxes, gt_labels信息
    for proposals_in_image, gt_boxes_in_image, gt_labels_in_image in zip(proposals,                                                                     gt_boxes, gt_labels):
        if gt_boxes_in_image.numel() == 0:  # 该张图像中没有gt框,为背景
            # background image
            device = proposals_in_image.device
            clamped_matched_idxs_in_image = torch.zeros((proposals_in_image.shape[0],), )
            labels_in_image = torch.zeros((proposals_in_image.shape[0],),)
        else:
            # 计算proposal与每个gt_box的iou, shape=(num_gt, num_proposals)
            match_quality_matrix = box_ops.box_iou(gt_boxes_in_image, proposals_in_image)
​
            # 计算proposal与每个gt_box匹配的iou最大值,并记录索引,
            # iou<low_threshold索引值为 -1,low_threshold<=iou<high_threshold索引值为 -2
            matched_idxs_in_image = self.proposal_matcher(match_quality_matrix)
            # 限制最小值,防止匹配标签时出现越界的情况
            # 注意-1, -2对应的gt索引会调整到0,获取的标签类别为第0个gt的类别(实际上并不是),后续处理
            clamped_matched_idxs_in_image = matched_idxs_in_image.clamp(min=0)
            # 获取proposal匹配到的gt对应标签
            labels_in_image = gt_labels_in_image[clamped_matched_idxs_in_image]
            labels_in_image = labels_in_image.to(dtype=torch.int64)
​
            # label background (below the low threshold)
            # 将gt索引为-1的类别设置为0,即背景,负样本
            bg_inds = matched_idxs_in_image == self.proposal_matcher.BELOW_LOW_THRESHOLD 
            labels_in_image[bg_inds] = 0
​
            # label ignore proposals (between low and high threshold)
            # 将gt索引为-2的类别设置为-1, 即废弃样本
            ignore_inds = matched_idxs_in_image==self.proposal_matcher.BETWEEN_THRESHOLDS 
            labels_in_image[ignore_inds] = -1  # -1 is ignored by sampler
​
            matched_idxs.append(clamped_matched_idxs_in_image)
            labels.append(labels_in_image)
            
    return matched_idxs, labels

然后通过subsmaple划分正负样本。subsmaple内的核心函数即为RPN使用过的fg_bg_sampler,得到sampled_pos_inds, sampled_neg_inds。然后记录所有样本的索引,总合成sampled_inds(每个图像共512个样本,正负比1:3,某一类不足用另一类补全)。

然后遍历batch内的每张图像,获取该图像的正负样本索引,以及proposals、labels、对应的ground truth box 索引、以及对应正负样本的ground truth box 坐标。

使用Box Coder Encode,根据ground truth boxes坐标和生成的proposals坐标计算ground truth boxes的回归参数。

最终得到proposals,labels,regression_targets,都是长度为batch的列表。列表的每一个元素都是对应图像的相关tensor信息(例如proposals[0]: Tensor (512, 4) )。

 

Loss Compute

在预测阶段,Faster R-CNN使用的其实还是Fast R-CNN的检测头,需要预测proposals的类别和边界框回归参数,因此也是一个多任务损失,沿用了前代Fast R-CNN的损失函数。

$$
L(p,u,t^u,v)=L_{cls}(p,u)+\lambda[u \ge 1]L_{loc}(t^u, v) \tag{1}
$$  

 

 

其中,p是Predictor预测的softmax概率分布,u是目标真实类别标签。

t^u是Predictor预测的对应类别为u(预测器为每个类别都预测一组回归参数)的边界框回归参数,v是对应真实目标的边界框回归参数。

分类损失,多类别交叉熵损失。

$$
L_{cls}(p,u)=-\log{p_u}\tag{2}
$$

 

标准的交叉熵损失函数为L=-\sum_{i}u_i log({p_i})。

当类别i不为u时,显然u_ilog(p_i)为0。仅保留非零项,即预测到类别u时,可以得到损失函数(2)。

 

回归损失,Smooth L1损失。

$$
L_{loc}(t^u,v)=\sum_{i\in\{x,y,w,h\}}smooth_{L_1}(t_i^u-v_i) \tag{3}
$$

 

其中回归损失的系数是艾弗森括号,如果方括号内的条件满足则为1,不满足则为0,即正样本才计算定位损失。

那么因此,class_logits与labels,box_regression与regression_targes分别进行损失计算即可。

 

 

总结

至此,Faster R-CNN的整个网络流程就结束了,相关细节也基本都涉及到。

最后补充一张Faster R-CNN的流程图,来自于WZMIAOMIAO

 

 

 

Faster R-CNN是目标检测的经典力作,技术相对成熟,而且至今仍有较为不错的精度,应用广泛。个人在VOC2012训练集上进行训练,在验证集上进行验证,可以达到80.65%的mAP(Iou=0.5)。

 

以下是对于一幅图像的检测,可以看到非常精准。

 

 

 

但是目标检测任务比较复杂,实现细节也较多,需要不断学习。本文内容若有错误,欢迎大佬批评指正。

标签:box,Faster,image,proposals,boxes,详解,CNN,anchors
From: https://www.cnblogs.com/Brisling/p/17000893.html

相关文章

  • JavaScript大文件上传详解及实例代码
    ​以ASP.NETCoreWebAPI 作后端 API ,用 Vue 构建前端页面,用 Axios 从前端访问后端 API,包括文件的上传和下载。 准备文件上传的API #region 文件上传 ......
  • C++中map用法详解
    Map是c++的一个标准容器,她提供了很好一对一的关系,在一些程序中建立一个map可以起到事半功倍的效果,总结了一些map基本简单实用的操作!1.map最基本的构造函数;map<string,......
  • Day07_05_分布式教程之分布式事务详解
    分布式事务详解一.分布式事务的概念随着分布式计算的发展,事务在分布式计算领域也得到了广泛的应用.在单机数据库中,我们很容易能够实现一套满足 ​​ACID​​ 特性的事......
  • Day10_08_消息队列之RabbitMQ消息可靠性传输 消息确认机制 死信队列详解及代码实现
    RabbitMQ消息可靠性传输消息确认机制死信队列详解及代码实现一.消息可靠传递的重要性我们在项目中使用RabbitMQ时,可能会遇到这样的问题:假如在一个订单系统中,用户付款......
  • Day10_09_消息队列之RabbitMQ消息可靠性详解
    RabbitMQ消息可靠性详解一.消息发送确认与消息接收确认(ACK)默认情况下如果一个Message被消费者正确接收则会被从Queue中移除.如果一个Queue没被任何消费者订阅消费,......
  • Day11_03_Redis教程之Redis服务器客户端安装配置及配置文件详解
    Redis服务器客户端安装配置及配置文件详解一.Redis的安装在ubuntu18.04下,可以直接通过命令安装.1.更新系统环境$sudoapt-getupdate#更新软件列表$sudoapt-getupgra......
  • js大文件上传详解及实例代码
    ​4GB以上超大文件上传和断点续传服务器的实现随着视频网站和大数据应用的普及,特别是高清视频和4K视频应用的到来,超大文件上传已经成为了日常的基础应用需求。但是在很......
  • DataTable小详解
    @目录DataTable小详解基础操作筛选DataTable数据DataTable.Select()小详解排序分组分页去重穿插一下LinqDynamic利用反射建立模糊查询通用工具类穿插一下List分组DataTab......
  • SpringBoot2.x系列教程14--SpringBoot特性之SpringApplication详解
    SpringBoot系列教程14--SpringBoot特性之SpringApplication详解作者:一一哥从本章节开始,我们将深入详细的介绍SpringBoot,通过阅读本节你可以了解到需要使用和定制的核心特性......
  • CAD动态块缩放的特性详解
    CAD动态块相信大家都不陌生,那么,你知道CAD动态块缩放特性吗?不知道也没关系,本文我们将以浩辰CAD软件为例来给大家分享一下利用XY参数与缩放动作配对的实例来说明CAD动态块......