Faster R-CNN の最も完全な説明

1: より高速な R-CNN の改善

Faster R-CNN をよりよく理解したい場合は、まず従来の R-CNN と Fast R-CNN の原理を理解する必要があります。私が書いた 2 つのブログ投稿を参照してください。R の歴史の中で最も完全な説明です。 -CNN と Fast R - CNNの説明

話は戻りますが、R-CNNとFast RCNNの蓄積を経て、2016年にRoss B. Girshick氏が新たなFaster RCNNを提案しました。ネットワークの命名という観点から見ると、これは非常に簡単です。それでは、Faster R-CNN と比べて Faster はどこにあるのでしょうか?答えは、領域提案の抽出方法の変更です。

Fast R-CNN は ROI Pooling という特徴抽出方法を提案していますが、Region Proposal従来の R-CNN で領域を個別に CNN ネットワークに入力することの欠点を解決します。しかし!従来の選択的検索検索方法は、領域提案を決定するために常に使用され、トレーニングとテスト中の RP 検索に多くの時間が費やされます。Faster R-CNN のブレークスルーでは、RPN ネットワークを使用して RP を直接抽出し、ネットワーク全体に統合します。そのため、全体的なパフォーマンス、特に検出速度が大幅に向上します。

2: ネットワークアーキテクチャ

ここに画像の説明を挿入
上図は、faster_rcnn_test.ptPython バージョンの VGG16 モデルのネットワーク構造を示しており、ネットワーク構造が次のモジュールに分割されていることがわかります。

  • Conv レイヤー
    バックボーン レイヤーは主に、入力画像内の特徴を抽出し、後の 2 つのモジュールで使用する特徴マップを生成するために使用されます。
  • 領域提案ネットワーク (RPN)
    RPN モジュールは、元のイメージ内の領域提案領域をトレーニングおよび抽出するために使用されます。これは、ネットワーク モデル全体で最も重要なモジュールです。
  • Semi-Fast R-CNN
    Semi-Fast R-CNN は、Fast R-CNN のヘッド層とほぼ同じであり、どちらかというと RoiHead 層と呼ばれるため、私自身の名前です。RP が RPN モジュールによって決定された後、高速 R-CNN ネットワークをトレーニングして、RP エリアの分類と bbox フレームの微調整を完了できます。

要約すると、注意深い人であれば、Conv 層 + 準高速 R-CNN は高速 R-CNN ではないことがわかります。それで、より高速な R-CNN ネットワークは実際には RPN + 高速 R-CNN ですつまりtwo-stage、2 つのモジュールはトレーニング中に個別にトレーニングされ、RP はテスト中に最初に RPN によって生成され、次に RP を含む特徴マップが Fast R-CNN に入力されて、分類と予測フレーム回帰が完了します。タスク以下、3つのモジュールについて順番に詳しく説明していきます。

3: 変換レイヤーモジュール

ここに画像の説明を挿入

Conv レイヤーには、conv、pooling、relu の 3 つのレイヤーが含まれます。faster_rcnn_test.pt例として、Python バージョンの VGG16 モデルのネットワーク構造を取り上げます。Conv レイヤー部分には、13 の conv レイヤー、13 の relu レイヤー、および 4 つのプーリング レイヤーがあります。VGG16 に詳しくない友人は、次の 2 つの詳細に注意する必要があります。

  • すべての conv レイヤーは次のとおりです: kernel_size=3、pad=1、stride=1
  • すべてのプーリング層は次のとおりです: kernel_size=2、pad=0、stride=2

Conv レイヤー モジュールの後、(800×600)サイズ MxN の入力画像は (M/16)x(N/16) の特徴マップになります。このようにして、Conv レイヤーによって生成された特徴マップは元の画像に対応することができます。

4: 地域提案ネットワーク (RPN) モジュール

ここに画像の説明を挿入
いよいよ、最も重要な RPN モジュールが登場します。皆さんも気合を入れて、私と一緒に解析してください。最終的な分析では、RPN は 2 つの機能モジュールです。最初の機能モジュールは、バイナリ分類を使用して各アンカーの見通しをスコア化し、回帰を使用して各アンカーとそれに対応する GT の間の 4 つの微調整パラメーターを計算します。2 番目の機能モジュールは、最初の機能モジュールによって出力されたスコアと 4 つの微調整パラメータに基づいて、ROI を取得し、適切な RP を選択します。
実際、トレーニングする必要があるのは RPN の最初の機能モジュールだけであり、2 番目のモジュールは基本的な選択操作です。、パラメーターをトレーニングする必要はありません。RoiHead のトレーニングとテストに使用する領域の提案を選択するだけです。これら 2 つの機能モジュールから順に説明します。

【モジュール1】

最初のモジュールでは、元の画像内でアンカーを生成してマークする方法、およびマークされたアンカーを使用して RPN をトレーニングし、前景と背景のバイナリ分類と各アンカーのアンカー位置の回帰微調整を実行する方法について説明します。以下に段階的に説明します。

ステップ1: アンカーベースの生成

generate_anchor_baseまず、関数を使用してアンカーを生成する必要があります。コードの主な実装アイデア: まず、 3 つのスケールを持つ特徴マップfeature mapの左上隅に基づいて9个アンカーが生成され、各スケールは 3 つのスケールに対応します。次に、特徴マップの左上隅にある 9 つのアンカーに、4 つのプーリング層後の元の画像のスケーリングの Base_size を掛けます16倍(0.5,0.5)アンカー ポイントは、特徴マップ内のアンカー ポイントから元の画像上のアンカー ポイントに変更され(8,8)、元の画像上の 9 つのアンカーの w と h も 16 倍に増加しています。次に、元の画像の左上隅のアンカーに基づいて、base_size ピクセルごとに 9 つのアンカーを描画します。描画後、元の画像には約 20,000 個のアンカーが存在します。
具体的な実装方法については、次のコードを参照し、手書きの図を添付してください。

def generate_anchor_base(base_size=16, ratios=[0.5, 1, 2], #
                         anchor_scales=[8, 16, 32]):   #对特征图features以基准长度为16、选择合适的ratios和scales取基准锚点anchor_base。(选择长度为16的原因是图片大小为600*800左右,基准长度16对应的原图区域是256*256,考虑放缩后的大小有128*128,512*512比较合适)
#根据基准点生成9个基本的anchor的功能,ratios=[0.5,1,2],anchor_scales=[8,16,32]是长宽比和缩放比例,anchor_scales也就是在base_size的基础上再增加的量,本代码中对应着三种面积的大小(16*8)^2 ,(16*16)^2  (16*32)^2  也就是128,256,512的平方大小
    py = base_size / 2.
    px = base_size / 2.   

    anchor_base = np.zeros((len(ratios) * len(anchor_scales), 4),     
                           dtype=np.float32)  #(9,4),注意:这里只是以特征图的左上角点为基准产生的9个anchor,
    for i in six.moves.range(len(ratios)): #six.moves 是用来处理那些在python2 和 3里面函数的位置有变化的,直接用six.moves就可以屏蔽掉这些变化
        for j in six.moves.range(len(anchor_scales)):
            h = base_size * anchor_scales[j] * np.sqrt(ratios[i])
            w = base_size * anchor_scales[j] * np.sqrt(1. / ratios[i]) #生成9种不同比例的h和w
 '''
这9个anchor形状应为:
90.50967 *181.01933    = 128^2
181.01933 * 362.03867 = 256^2
362.03867 * 724.07733 = 512^2
128.0 * 128.0 = 128^2
256.0 * 256.0 = 256^2
512.0 * 512.0 = 512^2
181.01933 * 90.50967   = 128^2
362.03867 * 181.01933 = 256^2
724.07733 * 362.03867 = 512^2
该函数返回值为anchor_base,形状9*4,是9个anchor的左上右下坐标:
-37.2548 -82.5097 53.2548 98.5097
-82.5097	-173.019	98.5097	189.019
-173.019	-354.039	189.019	370.039
-56	-56	72	72
-120	-120	136	136
-248	-248	264	264
-82.5097	-37.2548	98.5097	53.2548
-173.019	-82.5097	189.019	98.5097
-354.039	-173.019	370.039	189.019
'''
            index = i * len(anchor_scales) + j
            anchor_base[index, 0] = py - h / 2.
            anchor_base[index, 1] = px - w / 2.
            anchor_base[index, 2] = py + h / 2.
            anchor_base[index, 3] = px + w / 2.  #计算出anchor_base画的9个框的左下角和右上角的4个anchor坐标值
    return anchor_base 

ここに画像の説明を挿入

以下の説明ではいくつかの変換関数を使用しているため、コメントを参照して理解できるように、最初にそれをここに掲載します。

def loc2bbox(src_bbox, loc): #已知源bbox 和位置偏差dx,dy,dh,dw,求目标框G
    if src_bbox.shape[0] == 0:
        return xp.zeros((0, 4), dtype=loc.dtype)        #src_bbox:(R,4),R为bbox个数,4为左下角和右上角四个坐标(这里有误,按照标准坐标系中y轴向下,应该为左上和右下角坐标)
    src_bbox = src_bbox.astype(src_bbox.dtype, copy=False) 
    src_height = src_bbox[:, 2] - src_bbox[:, 0]      #ymax-ymin
    src_width = src_bbox[:, 3] - src_bbox[:, 1]     #xmax-xmin
    src_ctr_y = src_bbox[:, 0] + 0.5 * src_height    y0+0.5h
    src_ctr_x = src_bbox[:, 1] + 0.5 * src_width   #x0+0.5w,计算出中心点坐标
#src_height为Ph,src_width为Pw,src_ctr_y为Py,src_ctr_x为Px
    dy = loc[:, 0::4]      #python [start:stop:step] 
    dx = loc[:, 1::4]
    dh = loc[:, 2::4]
    dw = loc[:, 3::4]
RCNN中提出的边框回归:寻找原始proposal与近似目标框G之间的映射关系,公式在上面
    ctr_y = dy * src_height[:, xp.newaxis] + src_ctr_y[:, xp.newaxis]  #ctr_y为Gy
    ctr_x = dx * src_width[:, xp.newaxis] + src_ctr_x[:, xp.newaxis] # ctr_x为Gx
    h = xp.exp(dh) * src_height[:, xp.newaxis] #h为Gh
    w = xp.exp(dw) * src_width[:, xp.newaxis] #w为Gw
#上面四行得到了回归后的目标框(Gx,Gy,Gh,Gw)
    dst_bbox = xp.zeros(loc.shape, dtype=loc.dtype)  #loc.shape:(R,4),同src_bbox
    dst_bbox[:, 0::4] = ctr_y - 0.5 * h
    dst_bbox[:, 1::4] = ctr_x - 0.5 * w
    dst_bbox[:, 2::4] = ctr_y + 0.5 * h
    dst_bbox[:, 3::4] = ctr_x + 0.5 * w   #由中心点转换为左上角和右下角坐标
    return dst_bbox
    
def bbox2loc(src_bbox, dst_bbox): #已知源框和目标框求出其位置偏差
    height = src_bbox[:, 2] - src_bbox[:, 0]
    width = src_bbox[:, 3] - src_bbox[:, 1]
    ctr_y = src_bbox[:, 0] + 0.5 * height
    ctr_x = src_bbox[:, 1] + 0.5 * width #计算出源框中心点坐标

    base_height = dst_bbox[:, 2] - dst_bbox[:, 0]
    base_width = dst_bbox[:, 3] - dst_bbox[:, 1]
    base_ctr_y = dst_bbox[:, 0] + 0.5 * base_height
    base_ctr_x = dst_bbox[:, 1] + 0.5 * base_width ##计算出目标框中心点坐标

    eps = xp.finfo(height.dtype).eps  #求出最小的正数
    height = xp.maximum(height, eps) 
    width = xp.maximum(width, eps)  #将height,width与其比较保证全部是非负

    dy = (base_ctr_y - ctr_y) / height
    dx = (base_ctr_x - ctr_x) / width
    dh = xp.log(base_height / height)
    dw = xp.log(base_width / width)  #根据上面的公式二计算dx,dy,dh,dw

    loc = xp.vstack((dy, dx, dh, dw)).transpose()    #np.vstack按照行的顺序把数组给堆叠起来
    return loc

def bbox_iou(bbox_a, bbox_b):  #求两个bbox的相交的交并比
    if bbox_a.shape[1] != 4 or bbox_b.shape[1] != 4:
        raise IndexError  #确保bbox第二维为bbox的四个坐标(ymin,xmin,ymax,xmax)
    tl = xp.maximum(bbox_a[:, None, :2], bbox_b[:, :2])  #tl为交叉部分框左上角坐标最大值,为了利用numpy的广播性质,bbox_a[:, None, :2]的shape是(N,1,2),bbox_b[:, :2]shape是(K,2),由numpy的广播性质,两个数组shape都变成(N,K,2),也就是对a里每个bbox都分别和b里的每个bbox求左上角点坐标最大值
    br = xp.minimum(bbox_a[:, None, 2:], bbox_b[:, 2:]) #br为交叉部分框右下角坐标最小值
    area_i = xp.prod(br - tl, axis=2) * (tl < br).all(axis=2) #所有坐标轴上tl<br时,返回数组元素的乘积(y1max-yimin)X(x1max-x1min),bboxa与bboxb相交区域的面积
    area_a = xp.prod(bbox_a[:, 2:] - bbox_a[:, :2], axis=1)  #计算bboxa的面积
    area_b = xp.prod(bbox_b[:, 2:] - bbox_b[:, :2], axis=1) #计算bboxb的面积
    return area_i / (area_a[:, None] + area_b - area_i) #计算IOU

ステップ2: AnchorTargetCreator

20000个元の画像内にニアアンカーを生成した後、AnchorTargetCreator関数を使用してトレーニング用にアンカーに注釈を付けます。コードの主な実装アイデア:ラベル付けの場合、最初に元の画像の境界を超えるアンカーを削除し、約 15,000 個を残します。次に、各アンカーの最大 iou とどの bbox および iou 値を計算します。IOU>0.7アンカーは pos_anchor、IOU<0.3アンカーは neg_anchor です。同時に、各 bbox の IOU とどのアンカーが最大であるかを計算することも必要です (実際、これはマトリックスの最大の行と最大の列の差です)。また、最大の IOU のアンカーも計算します。各bboxに対応するものもpos_anchorに直接設定されます。しかし、最終的には、 pos と neg のそれぞれをランダムに選択する必要があります。128个つまり128个正样本和128个负样本、128 個の陽性サンプルのラベルを 1 に設定し、128 個の陰性サンプルのラベルを 0 に設定し、残りのラベル (20000-256) を設定する必要があります。 ) アンカー どちらも 0 に設定されます。4 つの回帰ボックスのパラメータ ラベル付けでは、まずすべてのスーパー ボックスを (0, 0, 0, 0) に設定します。ボックス内の約 15,000 個のアンカーの 4 つのパラメータは、最大値に対応する bbox の実際のオフセットです。 IOUの数量。具体的なコードは次のとおりです。

# 下面是AnchorTargetCreator()代码,作用是生成训练要用的anchor(与对应框iou值最大或者最小的各128个框的坐标和256个label(0或者1))
class AnchorTargetCreator(object):  # 利用每张图中bbox的真实标签来为所有任务分配ground truth!
    # 为Faster-RCNN专有的RPN网络提供自我训练的样本,RPN网络正是利用AnchorTargetCreator产生的样本作为数据进行网络的训练和学习的,这样产生的预测anchor的类别和位置才更加精确,anchor变成真正的ROIS需要进行位置修正,而AnchorTargetCreator产生的带标签的样本就是给RPN网络进行训练学习用哒
    def __call__(self, bbox, anchor, img_size):  # anchor:(S,4),S为anchor数
        img_H, img_W = img_size
        n_anchor = len(anchor)  # 一般对应20000个左右anchor
        inside_index = _get_inside_index(anchor, img_H, img_W)  # 将那些超出图片范围的anchor全部去掉,只保留位于图片内部的序号
        anchor = anchor[inside_index]  # 保留位于图片内部的anchor
        argmax_ious, label = self._create_label(inside_index, anchor, bbox)  # 筛选出符合条件的正例128个负例128并给它们附上相应的label
        loc = bbox2loc(anchor, bbox[argmax_ious])  # 计算每一个anchor与对应bbox求得iou最大的bbox计算偏移量(注意这里是位于图片内部的每一个)
        label = _unmap(label, n_anchor, inside_index, fill=-1)  # 将位于图片内部的框的label对应到所有生成的20000个框中(label原本为所有在图片中的框的)
        loc = _unmap(loc, n_anchor, inside_index, fill=0)  # 将回归的框对应到所有生成的20000个框中(label原本为所有在图片中的框的)
        return loc, label

        # 下面为调用的_creat_label() 函数

    def _create_label(self, inside_index, anchor, bbox):
        label = np.empty((len(inside_index),), dtype=np.int32)  # inside_index为所有在图片范围内的anchor序号
        label.fill(-1)  # 全部填充-1
        argmax_ious, max_ious, gt_argmax_ious = self._calc_ious(anchor, bbox, inside_index)
        调用_calc_ious()函数得到每个anchor与哪个bbox的iou最大以及这个iou值、每个bbox与哪个anchor的iou最大(需要体会从行和列取最大值的区别)
        label[
            max_ious < self.neg_iou_thresh] = 0  # 把每个anchor与对应的框求得的iou值与负样本阈值比较,若小于负样本阈值,则label设为0,pos_iou_thresh=0.7, neg_iou_thresh=0.3
        label[gt_argmax_ious] = 1  # 把与每个bbox求得iou值最大的anchor的label设为1
        label[max_ious >= self.pos_iou_thresh] = 1  ##把每个anchor与对应的框求得的iou值与正样本阈值比较,若大于正样本阈值,则label设为1
        n_pos = int(self.pos_ratio * self.n_sample)  # 按照比例计算出正样本数量,pos_ratio=0.5,n_sample=256
        pos_index = np.where(label == 1)[0]  # 得到所有正样本的索引
        if len(pos_index) > n_pos:  # 如果选取出来的正样本数多于预设定的正样本数,则随机抛弃,将那些抛弃的样本的label设为-1
            disable_index = np.random.choice(
                pos_index, size=(len(pos_index) - n_pos), replace=False)
            label[disable_index] = -1
        n_neg = self.n_sample - np.sum(label == 1)  # 设定的负样本的数量
        neg_index = np.where(label == 0)[0]  # 负样本的索引
        if len(neg_index) > n_neg:
            disable_index = np.random.choice(
                neg_index, size=(len(neg_index) - n_neg),
                replace=False)  # 随机选择不要的负样本,个数为len(neg_index)-neg_index,label值设为-1
            label[disable_index] = -1
        return argmax_ious, label

    # 下面为调用的_calc_ious()函数
    def _calc_ious(self, anchor, bbox, inside_index):
        ious = bbox_iou(anchor, bbox)  # 调用bbox_iou函数计算anchor与bbox的IOU, ious:(N,K),N为anchor中第N个,K为bbox中第K个,N大概有15000个
        argmax_ious = ious.argmax(axis=1)  # 1代表行,0代表列
        max_ious = ious[np.arange(len(inside_index)), argmax_ious]  # 求出每个anchor与哪个bbox的iou最大,以及最大值,max_ious:[1,N]
        gt_argmax_ious = ious.argmax(axis=0)
        gt_max_ious = ious[gt_argmax_ious, np.arange(ious.shape[1])]  # 求出每个bbox与哪个anchor的iou最大,以及最大值,gt_max_ious:[1,K]
        gt_argmax_ious = np.where(ious == gt_max_ious)[0]  # 然后返回最大iou的索引(每个bbox与哪个anchor的iou最大),有K个
        return argmax_ious, max_ious, gt_argmax_ious

ステップ3: RPNのトレーニング

トレーニング サンプルを生成してマーク付けした後、最終的に最初の機能モジュールのトレーニング リンクに到達します。まず、 Feature Map が3×3卷积操作され、次に 2 つのブランチに分割され、各ブランチが最初に操作され1×1卷积、その目的はチャネルを圧縮することです。最初のブランチのチャネル数は圧縮されており9×2、9 は各アンカー ポイントの 9 つのアンカーを表し、2 は各アンカーが前景または背景である確率を表します。2 番目のブランチのチャネル数は圧縮されており9×4、9 は各アンカー ポイントの 9 つのアンカーを表し、4 は各アンカーの 4 つの位置パラメータの予測値を表します。各最小バッチについて、分類損失と回帰損失は 128 個の陰性サンプルと 128 個の陽性サンプルに対してのみ計算されます(実際には、陽性サンプルに対しては回帰損失のみが計算されます)。損失関数は次のとおりです。次のように、
ここに画像の説明を挿入
分類損失関数は従来のクロスエントロピー損失関数を選択し、分類損失関数はスムーズ L1 損失回帰損失関数を選択します。実際のプロセスでは、
ここに画像の説明を挿入
N c N_cNc= min_batch ,N r N_rNr= 特徴マップのサイズ、2 つの間のギャップが大きすぎるため、パラメータ λ を使用して 2 つのバランスを取るため、合計ネットワーク損失の計算プロセスで 2 種類の損失を均等に考慮できるようになります。

【モジュール2】

2 番目のモジュールは、最初の機能モジュールによって出力されたスコアと 4 つの位置パラメータに基づいて、ROI を取得し、適切な RP を選択します。このモジュールは関数内で完成しており、コードの中心となるアイデアは、アンカーの 4 つの位置パラメータProposalCreatorに関する最初のトレーニング済みモジュールの出力を通じて、元の画像内のすべてのアンカーを微調整し、ROI を生成することです次に、ROI をトリミングし、トリミング後の長さと幅が設定された閾値より小さい ROI を削除します。次に、残りの ROI を前景スコアに応じて大きいものから小さいものに並べ替えます。RoiHeadトレーニングに使用される場合は、 ROI を取得します。NMS の 2 次スクリーニングの後、 ROI のみを最終領域提案として取得します。RoiHead テストに使用される場合はROI が取得され、NMS の 2 次スクリーニングの後、ROI のみが最終的な領域提案として取得されます。具体的なコードは次のように実装されます。20000个20000个前12000个前2000个前2000个前300个

# 下面是ProposalCreator的代码: 这部分的操作不需要进行反向传播,因此可以利用numpy/tensor实现
class ProposalCreator:  # 对于每张图片,利用它的feature map,计算(H/16)x(W/16)x9(大概20000)个anchor属于前景的概率,然后从中选取概率较大的12000张,利用位置回归参数,修正这12000个anchor的位置, 利用非极大值抑制,选出2000个ROIS以及对应的位置参数。
    def __call__(self, loc, score, anchor, img_size,
                 scale=1.):  # 这里的loc和score是经过region_proposal_network中经过1x1卷积分类和回归得到的
        if self.parent_model.training:
            n_pre_nms = self.n_train_pre_nms  # 12000
            n_post_nms = self.n_train_post_nms  # 经过NMS后有2000个

        else:
            n_pre_nms = self.n_test_pre_nms  # 6000
            n_post_nms = self.n_test_post_nms  # 经过NMS后有300个


        roi = loc2bbox(anchor, loc)  # 将bbox转换为近似groudtruth的anchor(即rois)
        roi[:, slice(0, 4, 2)] = np.clip(roi[:, slice(0, 4, 2)], 0, img_size[0])  # 裁剪将rois的ymin,ymax限定在[0,H]
        roi[:, slice(1, 4, 2)] = np.clip(roi[:, slice(1, 4, 2)], 0, img_size[1])  # 裁剪将rois的xmin,xmax限定在[0,W]

        min_size = self.min_size * scale  # 16
        hs = roi[:, 2] - roi[:, 0]  # rois的宽
        ws = roi[:, 3] - roi[:, 1]  # rois的长
        keep = np.where((hs >= min_size) & (ws >= min_size))[0]  # 确保rois的长宽大于最小阈值
        roi = roi[keep, :]

        score = score[keep]  # 对剩下的ROIs进行打分(根据region_proposal_network中rois的预测前景概率)
        order = score.ravel().argsort()[::-1]  # 将score拉伸并逆序(从高到低)排序
        if n_pre_nms > 0:
            order = order[:n_pre_nms]  # train时从20000中取前12000个rois,test取前6000个
        roi = roi[order, :]

        keep = non_maximum_suppression(
        cp.ascontiguousarray(cp.asarray(roi)),
            thresh=self.nms_thresh)  # (具体需要看NMS的原理以及输入参数的作用)调用非极大值抑制函数,将重复的抑制掉,就可以将筛选后ROIS进行返回。经过NMS处理后Train数据集得到2000个框,Test数据集得到300个框
        if n_post_nms > 0:
            keep = keep[:n_post_nms]
        roi = roi[keep]
        return roi

五:準高速R-CNN(RoiHead)

RPN モジュールの導入後、RP 抽出の最も重要なタスクが完了しました。次に、RoiHead は、RPN によって出力された RP 結果をトレーニングとテストの入力として使用するだけで済みます。トレーニングフェーズとテストモジュールについては個別に説明します。

【トレーニングステージ】

step1: RP でトレーニング サンプルにラベルを付ける

トレーニング段階にある場合、RPN は約 2000 の地域提案を出力します。では、サンプルを選択してラベルを付けるにはどうすればよいでしょうか? ProposalTargetCreatorこの機能はこのタスクを実現するもので、コードの中心的な考え方は、まず2000个RP とM个Ground Truth を結合する、つまりすべての GT が RP としても使用されるということです。なぜ

原子力エネルギーの先行き: 実際、答えは非常に簡単です。今は RoiHead のトレーニング段階であり、RoiHead の分類機能と二次回帰機能をトレーニングしています。つまり、カテゴリラベルと実際の位置パラメータを備えた教師データをネットワークに入力する必要があり、RPNで選択した現実の物体を含む広い範囲のROIを教師データとして利用することができますが、正直言ってどれも歪んでいます。これらを使用して RoiHead をトレーニングするのは、実際には主にテスト フェーズの実際の状況に基づいています。結局のところ、タスクが適応する必要がある実際の状況に基づいている必要があります。実際のテスト中に、RPN は RP を見つけます。 RoiHead はそれらを分類し、bbox で修正します。つまり、これらのサンプルを分類することは不正確であり、物理的オブジェクトの適格なサンプルを完全にカバーするわけではありません。結局のところ、今は訓練の段階です。ネットワークに少量の「高品質の炭水化物」をこっそり与えて、最高品質の GT で直接訓練するのも悪くありません。本物の機密サンプルは快適で不快であり、それは何の役にも立たない。

話を戻します。RP と GT を接続した後、最大 IOU に対応する GT のラベルを計算し、(ラベル + 1) を各 RP のカテゴリ ラベル (1 ~ 20) として使用します。次に、64IOU>0.5 の RP の 1 つをポジティブ サンプルとして選択し、IOU<0.5RP の 1 つを64ネガティブ サンプルとして選択し、ネガティブ サンプルのラベルを 0 に設定して、最後に合計 128 個のポジティブおよびネガティブ サンプルをトレーニング入力としてパックします。ロイヘッドの具体的な実装コードは以下のとおりです。

# 下面是ProposalTargetCreator代码:ProposalCreator产生2000个ROIS,但是这些ROIS并不都用于训练,经过本ProposalTargetCreator的筛选产生128个用于自身的训练
    class ProposalTargetCreator(object):  # 为2000个rois赋予ground truth!(严格讲挑出128个赋予ground truth!)
        # 输入:2000个rois、一个batch(一张图)中所有的bbox ground truth(R,4)、对应bbox所包含的label(R,1)(VOC2007来说20类0-19)
        # 输出:128个sample roi(128,4)、128个gt_roi_loc(128,4)、128个gt_roi_label(128,1)
        def __call__(self, roi, bbox, label, loc_normalize_mean=(0., 0., 0., 0.),
                     loc_normalize_std=(0.1, 0.1, 0.2, 0.2)):  # 因为这些数据是要放入到整个大网络里进行训练的,比如说位置数据,所以要对其位置坐标进行数据增强处理(归一化处理)
            n_bbox, _ = bbox.shape
            roi = np.concatenate((roi, bbox), axis=0)  # 首先将2000个roi和m个bbox给concatenate了一下成为新的roi(2000+m,4)。
            pos_roi_per_image = np.round(
                self.n_sample * self.pos_ratio)  # n_sample = 128,pos_ratio=0.5,round 对传入的数据进行四舍五入
            iou = bbox_iou(roi, bbox)  # 计算每一个roi与每一个bbox的iou  (2000+m,m)
            gt_assignment = iou.argmax(axis=1)  # 按行找到最大值,返回最大值对应的序号以及其真正的IOU。返回的是每个roi与**哪个**bbox的最大,以及最大的iou值
            max_iou = iou.max(axis=1)  # 每个roi与对应bbox最大的iou
            gt_roi_label = label[gt_assignment] + 1  # 从1开始的类别序号,给每个类得到真正的label(将0-19变为1-20)
            pos_index = np.where(max_iou >= self.pos_iou_thresh)[0]  # 同样的根据iou的最大值将正负样本找出来,pos_iou_thresh=0.5
            pos_roi_per_this_image = int(
                min(pos_roi_per_image, pos_index.size))  # 需要保留的roi个数(满足大于pos_iou_thresh条件的roi与64之间较小的一个)
            if pos_index.size > 0:
                pos_index = np.random.choice(
                    pos_index, size=pos_roi_per_this_image, replace=False)  # 找出的样本数目过多就随机丢掉一些

            neg_index = np.where((max_iou < self.neg_iou_thresh_hi) &
                                 (max_iou >= self.neg_iou_thresh_lo))[0]  # neg_iou_thresh_hi=0.5,neg_iou_thresh_lo=0.0
            neg_roi_per_this_image = self.n_sample - pos_roi_per_this_image  # #需要保留的roi个数(满足大于0小于neg_iou_thresh_hi条件的roi与64之间较小的一个)
            neg_roi_per_this_image = int(min(neg_roi_per_this_image,
                                             neg_index.size))
            if neg_index.size > 0:
                neg_index = np.random.choice(
                    neg_index, size=neg_roi_per_this_image, replace=False)  # 找出的样本数目过多就随机丢掉一些

            keep_index = np.append(pos_index, neg_index)
            gt_roi_label = gt_roi_label[keep_index]
            gt_roi_label[pos_roi_per_this_image:] = 0  # 负样本label 设为0
            sample_roi = roi[keep_index]
            # 那么此时输出的128*4的sample_roi就可以去扔到 RoIHead网络里去进行分类与回归了。同样, RoIHead网络利用这sample_roi+featue为输入,输出是分类(21类)和回归(进一步微调bbox)的预测值,那么分类回归的groud truth就是ProposalTargetCreator输出的gt_roi_label和gt_roi_loc。
            gt_roi_loc = bbox2loc(sample_roi, bbox[gt_assignment[keep_index]])  # 求这128个样本的groundtruth
            gt_roi_loc = ((gt_roi_loc - np.array(loc_normalize_mean, np.float32)
                           ) / np.array(loc_normalize_std,
                                        np.float32))  # ProposalTargetCreator首次用到了真实的21个类的label,且该类最后对loc进行了归一化处理,所以预测时要进行均值方差处理
            return sample_roi, gt_roi_loc, gt_roi_label

step2: 正式なトレーニング

128 個のマークされたトレーニング サンプルを元の画像から特徴マップ内の対応する ROI 領域に投影し、次に RoiPooling 層に入り、これらの異なるサイズの ROI 領域を同じ長さのベクトルに変換し、FC 層の 2 つの層を通過します4096。 、それぞれ、softmax21 分類スコアと bbox84パラメーターの予測結果を取得(21 * 4)し、それらをバックプロパゲーションの損失関数に入れてネットワークの重みを更新し、陽性サンプルの回帰ボックス損失のみを計算します。損失関数は RPN の関数と似ているため、ここでは詳細には触れず、損失関数のコア コードを貼り付けます。

def _fast_rcnn_loc_loss(pred_loc, gt_loc, gt_label, sigma): #输入分别为rpn回归框的偏移量与anchor与bbox的偏移量以及label
    in_weight = t.zeros(gt_loc.shape).cuda()
    # Localization loss is calculated only for positive rois.
    # NOTE:  unlike origin implementation, 
    # we don't need inside_weight and outside_weight, they can calculate by gt_label
    in_weight[(gt_label > 0).view(-1, 1).expand_as(in_weight).cuda()] = 1
    loc_loss = _smooth_l1_loss(pred_loc, gt_loc, in_weight.detach(), sigma) #sigma设置为1
    # Normalize by total number of negtive and positive rois.
    loc_loss /= ((gt_label >= 0).sum().float()) # ignore gt_label==-1 for rpn_loss #除去背景类
    return loc_loss
roi_cls_loss = nn.CrossEntropyLoss()(roi_score, gt_roi_label.cuda())#求交叉熵损失

【テスト段階】

RoiHead のテスト フェーズでは、RPN から出力された 300 の RP をネットワークに入力し、最終的に各 RP のクラスと 4 つの回帰ボックス微調整パラメーターを出力します。バックグラウンド (0) のしきい値よりも高く、最大カテゴリ (1 ~ 20) のスコアがしきい値より低い RP を除去します。最後に、回帰パラメーターに従って、スクリーニング後に残りの RP ボックスを微調整して、最終的な境界を取得します。箱!ここまでで完了です。

6: より高速な R-CNN トレーニング方法

Faster-RCNN には、4 ステップの交互反復トレーニングと共同トレーニングという 2 つのトレーニング方法があります。この記事では主に、次の 4 ステップ交互反復のトレーニング方法を説明します。

1. RPN をトレーニングし、大規模なデータセットの事前トレーニング モデルを使用して共有畳み込みと RPN 重みを初期化し、RPN をエンドツーエンドでトレーニングして領域提案を生成します; 2. Fast R-CNN をトレーニングし、同じ事前トレーニング済みモデルを使用して
、共有畳み込みを初期化します [これは、最初のステップでトレーニングされたものではなく、最初のステップと同じ構造を持つ新しい共有畳み込みネットワークを初期化することであることに注意してください]、最初のステップでトレーニングされた RPN 重みをロックし、次のパラメータを使用して RCNN をトレーニングします。 RPN ネットワークによって取得された提案;
3. ステップ 2 でトレーニングされた共有畳み込みと RCNN を使用して RPN を調整し、共有畳み込み層を修正し、RPN のトレーニングを継続します。このステップは、ステップ 2 でトレーニングされた RPN を微調整するのと同等だと思います。 1; 4
. Fast R-CNN を調整し、ステップ 3 でトレーニングした共有畳み込みと RPN を使用し (共有畳み込み層を修正)、RCNN のトレーニングと微調整を続けます 5. 上記のステップ 3 と 4 を繰り返します
(通常はステップ4までで十分であり、反復トレーニング後の効果はほとんど改善されません)

以下にトレーニング プロセスのフローチャートを示します。これはより明確です。
ここに画像の説明を挿入

7: より高速な R-CNN テスト方法

次に、ネットワーク全体のテストプロセスについて説明します。ほぼ終わりです!

ステップ 1: 入力画像は畳み込み層を通過して特徴マップを取得します

ステップ 2: フィーチャー マップは RPN を通じて 300 RP を取得します

step3: RoiHeadネットワークにRPを入力する

step4: 各RPのカテゴリスコアとbbox位置パラメータを取得する

step5: スコア閾値により最終的な ROI を選択する

step6: 位置パラメータと組み合わせて ROI の bbox ボックスを微調整する

step7: NMS後の最終検出枠を描画

8: まとめ

Fast R-CNN は速度と精度が大幅に向上しましたが、候補領域の取得は同期的に実行できず、速度も依然として向上しています。

最後にもう一つ付けて超大型概略フローチャート終了、参考のために:

ここに画像の説明を挿入


  ここまで、Faster R-CNN の全体の流れと詳細について詳しく説明してきましたが、ご参考になれば幸いです。わからないことやご質問がございましたら、コメントを残してください。下。(暗号は簡単じゃないよ、みんな親指を立てて、手に残る香りを残してください~ありがとう!

私はCVの泥沼で奮闘する江南塩辛です、悔いを残さないように一緒に頑張りましょう!

おすすめ

転載: blog.csdn.net/weixin_43702653/article/details/124045469
おすすめ