La explicación más completa de Faster R-CNN

Uno: Mejora de Faster R-CNN

Si desea comprender mejor Faster R-CNN, primero debe comprender los principios de R-CNN tradicional y Fast R-CNN. Puede consultar las dos publicaciones de blog escritas por mí. La explicación más completa en la historia de R -CNN y la explicación de Fast R - CNN .

Volviendo al tema, tras la acumulación de R-CNN y Fast RCNN, Ross B. Girshick propuso un nuevo Faster RCNN en 2016. Desde el punto de vista de la denominación de la red, es muy sencillo, entonces, ¿dónde se compara Faster con Faster R-CNN?La respuesta es: un cambio en el método de extracción de la propuesta de región

Aunque Fast R-CNN propone el método de extracción de características de ROI Pooling, resuelve las Region Proposaldesventajas de ingresar regiones en la red CNN por separado en R-CNN tradicional. ¡pero! ! ! El método de búsqueda de búsqueda selectiva tradicional siempre se usa para determinar la propuesta de región , y se dedica mucho tiempo a la búsqueda de RP durante el entrenamiento y las pruebas.El avance Faster R-CNN utiliza la red RPN para extraer directamente RP e integrarlo en la red general., por lo que el rendimiento general mejora considerablemente, especialmente en la velocidad de detección.

Dos: arquitectura de red

inserte la descripción de la imagen aquí
La figura anterior muestra faster_rcnn_test.ptla estructura de red del modelo VGG16 en la versión python, se puede ver claramente que la estructura de red se divide en los siguientes módulos:

  • Capas de conversión
    La capa Backbone se utiliza principalmente para extraer las características de la imagen de entrada y generar un mapa de características para que lo utilicen los dos últimos módulos.
  • Redes de propuesta de región (RPN)
    El módulo RPN se utiliza para entrenar y extraer el área de propuesta de región en la imagen original, que es el módulo más importante en todo el modelo de red.
  • Semi-Fast R-CNN
    Semi-Fast R-CNN es mi propio nombre, porque es casi lo mismo que la capa principal de Fast R-CNN, y se llama más capa RoiHead. Una vez que el módulo RPN determina el RP, la red Fast R-CNN se puede entrenar para completar la clasificación del área RP y el ajuste fino del marco bbox.

En resumen, se puede ver que las personas cuidadosas encontrarán que las capas Conv + Semi-Fast R-CNN no son Fast R-CNN . entonces,La red R-CNN más rápida es en realidad RPN + Fast R-CNN, es decir two-stage, los dos módulos también se entrenan por separado durante el entrenamiento, y el RPN primero genera el RP durante la prueba, y luego el mapa de características con RP se ingresa en Fast R-CNN para completar la regresión del marco de clasificación y predicción tareas _ A continuación, explicaré los tres módulos en detalle a su vez.

Tres: módulo de capas de conversión

inserte la descripción de la imagen aquí

Las capas de conv incluyen tres capas de conv, pooling y relu. faster_rcnn_test.ptTome como ejemplo la estructura de red en el modelo VGG16 en la versión de python . La parte de capas Conv tiene 13 capas conv, 13 capas relu y 4 capas de agrupación. Los amigos que no estén familiarizados con VGG16 deben prestar atención a dos detalles :

  • Todas las capas de conversión son: kernel_size=3, pad=1, stride=1
  • Todas las capas de agrupación son: kernel_size=2, pad=0, stride=2

¡Después del módulo de capas Conv, una (800×600)imagen de entrada de tamaño MxN se convierte en un mapa de características de (M/16)x(N/16)! De esta forma, el mapa de características generado por las capas Conv puede corresponder a la imagen original.

Cuatro: Módulo Redes de Propuestas Regionales (RPN)

inserte la descripción de la imagen aquí
Finalmente, se presenta el módulo RPN más importante, ¡todos levanten el ánimo y analícenlo conmigo! En el análisis final, RPN son dos módulos funcionales. El primer módulo funcional es usar la clasificación binaria para calificar la perspectiva de cada ancla, y usar la regresión para calcular los cuatro parámetros de ajuste fino entre cada ancla y su GT ​​correspondiente. El segundo módulo de funciones se basa en la puntuación generada por el primer módulo de funciones y cuatro parámetros de ajuste fino para obtener el ROI y seleccionar el RP apropiado.
De hecho, solo se necesita entrenar el primer módulo funcional de RPN, y el segundo módulo es una operación de selección básica., no es necesario entrenar ningún parámetro, simplemente seleccione la propuesta de región que se usará para el entrenamiento y las pruebas de RoiHead. Permítanme explicar a su vez a partir de estos dos módulos funcionales:

【Módulo 1】

En el primer módulo, explicará cómo generar y marcar anclas en la imagen original, y cómo usar las anclas marcadas para entrenar RPN para realizar la clasificación binaria de primer plano y fondo y el ajuste fino de regresión de las posiciones de ancla para cada ancla. La siguiente es una explicación paso a paso:

paso 1: generar_base_ancla

Primero, necesitamos generate_anchor_basegenerar anclas usando una función. La idea principal de implementación del código: primero, el ancla feature mapse genera en función de la esquina superior izquierda del mapa de características, con tres escalas, y cada escala corresponde a tres escalas. 9个A continuación, multiplique las 9 anclas en la esquina superior izquierda del mapa de características por el tamaño_base de escala de la imagen original, que es después de 4 capas de agrupación 16倍. Los puntos de anclaje se cambiaron de los del mapa de características (0.5,0.5)a los de la imagen original (8,8), y la w y la h de los nueve anclajes en la imagen original también se incrementaron 16 veces. Luego, según los anclajes en la esquina superior izquierda de la imagen original, dibuje 9 anclajes cada base_size píxeles. Después de dibujar, hay unas 20.000 anclas en la imagen original.
Para conocer el método de implementación específico, consulte el siguiente código y adjunte el diagrama escrito a mano:

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 

inserte la descripción de la imagen aquí

Dado que se utilizan algunas funciones de conversión en las siguientes explicaciones, las publicaré aquí primero, para que pueda entenderlas primero de acuerdo con los comentarios:

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

paso 2: AnchorTargetCreator

Después de generar anclajes cercanos 20000个en la imagen original, AnchorTargetCreatorse usa una función para anotarlos para el entrenamiento. La idea principal de implementación del código: para el etiquetado , primero elimine los anclajes que superan el límite de la imagen original, dejando casi 15,000. Luego, calcule el Iou máximo de cada ancla y qué bbox y el valor de IOU, IOU>0.7el ancla es pos_anchor IOU<0.3y el ancla es neg_anchor. Al mismo tiempo, también es necesario calcular el pagaré de cada bbox y qué ancla es la más grande (de hecho, es la diferencia entre la fila más grande y la columna más grande de la matriz), y las anclas del pagaré más grande correspondientes a cada bbox también se establecen directamente en pos_anchor. Pero al final, debe seleccionar aleatoriamente cada pos y neg 128个, es decir 128个正样本和128个负样本, establecer la etiqueta de 128 muestras positivas en 1, establecer la etiqueta de 128 muestras negativas en 0 y establecer las etiquetas de las restantes (20000-256). ) anclas Ambos se establecen en 0. Para el etiquetado de parámetros de los 4 cuadros de regresión , primero establezca todos los súper cuadros en (0, 0, 0, 0), y los 4 parámetros de las casi 15 000 anclas en el cuadro son las compensaciones reales de sus bboxes correspondientes al máximo cantidad de pagarés. El código específico es el siguiente:

# 下面是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

paso 3: entrenamiento RPN

Después de generar y marcar las muestras de entrenamiento, finalmente llegamos al enlace de entrenamiento del primer módulo funcional. Primero, se opera el mapa de características 3×3卷积, y luego se divide en dos ramas. Cada rama se opera primero 1×1卷积, y el propósito es comprimir el canal. El número de canales de la primera rama está comprimido 9×2, 9 representa los 9 anclajes de cada punto de anclaje y 2 representa la probabilidad de que cada anclaje sea el primer plano o el fondo. El número de canales de la segunda rama está comprimido 9×4, 9 representa los 9 anclajes de cada punto de anclaje y 4 representa el valor predicho de los 4 parámetros de posición de cada anclaje. Para cada lote mínimo, la pérdida de clasificación y la pérdida de regresión solo se calculan para 128 muestras negativas y 128 muestras positivas (en realidad, solo se calcula la pérdida de regresión para muestras positivas). La función de pérdida es la siguiente:
inserte la descripción de la imagen aquí
la función de pérdida de clasificación selecciona la función de pérdida de entropía cruzada tradicional, y la función de pérdida de clasificación selecciona la función de pérdida de regresión Smooth L1 Loss, de la siguiente manera:
inserte la descripción de la imagen aquí
Dado que en el proceso real, N c N_cnortedo= min_lote ,N r N_rnorter= El tamaño del mapa de características, la brecha entre los dos es demasiado grande y el parámetro λ se usa para equilibrar los dos, de modo que el proceso de cálculo de pérdida total de la red pueda considerar uniformemente dos tipos de pérdida.

【Módulo 2】

El segundo módulo se basa en la salida de puntuación del primer módulo funcional y los cuatro parámetros de posición para obtener el ROI y seleccionar el RP adecuado. Este módulo ProposalCreatorse completa en la función.La idea central del código es afinar todos los anclajes en la imagen original a través de la salida del primer módulo entrenado sobre los 20000个4 parámetros de posición del anclaje y generar 20000个ROI. A continuación, se recorta el ROI y se eliminan los ROI cuya longitud y anchura después del recorte son menores que el umbral establecido. Luego, clasifique los ROI restantes de mayor a menor de acuerdo con el puntaje de primer plano. Si se usa para el entrenamiento de RoiHead , tome 前12000个el ROI. Después de la evaluación secundaria de NMS, solo tome 前2000个el ROI como las propuestas finales de la región. Si se usa para la prueba RoiHead , se toma el ROI 前2000个Después de la selección secundaria de NMS, solo 前300个se toma el ROI como propuesta de región final. El código específico se implementa de la siguiente manera:

# 下面是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

Ejemplo: R-CNN semirápido (RoiHead)

Después de introducir el módulo RPN, se ha completado la tarea más importante de extracción de RP. A continuación, RoiHead solo necesita utilizar la salida de resultados de RP de RPN como entrada para entrenamiento y pruebas. Explicaré la fase de entrenamiento y el módulo de prueba por separado:

【Etapa de entrenamiento】

paso 1: Etiquete las muestras de capacitación en RP

Si está en la fase de formación, RPN dará salida a cerca de 2000 propuestas de la región. Entonces, ¿cómo seleccionar muestras y etiquetarlas? ProposalTargetCreatorLa función realiza esta tarea. La idea central del código es: primero, empalmar 2000个RP y M个Ground Truth juntos, es decir, todos los GT también se usan como RP. ¿ Por qué ?

energía nuclear por delante: De hecho, la respuesta es muy simple, ahora es la fase de entrenamiento de RoiHead, entrenando las capacidades de clasificación y regresión secundaria de RoiHead. En otras palabras, es necesario ingresar datos de entrenamiento con etiquetas de categoría y parámetros de ubicación reales en la red. El ROI seleccionado por RPN en un amplio rango que incluye objetos reales puede usarse como datos de entrenamiento. Para ser honesto, todos están torcidos. Usarlos para entrenar RoiHead en realidad se basa principalmente en la situación real de la fase de prueba. Después de todo, debe basarse en la situación real a la que la tarea debe adaptarse. Durante la prueba real, RPN descubre el RP, y RoiHead los clasifica y los corrige con bbox.Estos RPs estan torcidos. Es decir, es impreciso clasificar estas muestras, para no cubrir completamente las muestras calificadas de los objetos físicos. Después de todo, ahora es la etapa de entrenamiento. No es una mala idea alimentar a la red en secreto con un poco de "carbohidratos de alta calidad", y entrenarla directamente con GT de la más alta calidad. Las muestras clasificadas genuinas son cómodas e incómodas, y es inútil para nada.

Volviendo al tema, después de empalmar RP y GT, calcule la etiqueta del GT correspondiente a su IOU máximo y use (label+1) como la etiqueta de categoría (1~20) de cada RP. Luego seleccione 64uno de los RP con IOU>0.5 como muestra positiva, IOU<0.5seleccione uno de los RP 64como muestra negativa y establezca la etiqueta de la muestra negativa en 0, y finalmente empaque un total de 128 muestras positivas y negativas como entrada de entrenamiento. de RoiHead . El código de implementación específico es el siguiente:

# 下面是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

paso 2: entrenamiento formal

Proyecte las 128 muestras de entrenamiento marcadas desde la imagen original al área de ROI correspondiente en el mapa de características y luego ingrese a la capa RoiPooling para convertir estas áreas de ROI de diferentes tamaños en vectores de la misma longitud y luego pase a través de dos capas de capa 4096FC , obtenga respectivamente el puntaje de clasificación softmax21 y los resultados de predicción de los 84parámetros bbox (21 * 4), colóquelos en la función de pérdida para retropropagación para actualizar el peso de la red, y solo calcule la pérdida del cuadro de regresión de la muestra positiva. La función de pérdida es similar a la de RPN, por lo que no entraré en detalles aquí y pegaré el código central de la función de pérdida:

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())#求交叉熵损失

【Etapa de prueba】

La fase de prueba de RoiHead es ingresar la salida de 300 RP del RPN a la red y, finalmente, generar la clase de cada RP y 4 parámetros de ajuste fino de la caja de regresión. Elimine los RP que superen el umbral de fondo (0) y la puntuación máxima de la categoría (1~20) sea inferior al umbral. Finalmente, de acuerdo con los parámetros de regresión, ajuste los cuadros de RP restantes después de la selección para obtener el límite final. ¡caja! Hasta ahora, has terminado.

Seis: método de entrenamiento R-CNN más rápido

Faster-RCNN tiene dos métodos de entrenamiento: entrenamiento iterativo alterno de cuatro pasos y entrenamiento conjunto. Este artículo explica principalmente el método de entrenamiento de iteraciones alternas de cuatro pasos, de la siguiente manera:

1. Entrene RPN, use un modelo de preentrenamiento de conjunto de datos grande para inicializar la convolución compartida y los pesos de RPN, y entrene RPN de extremo a extremo para generar propuestas regionales 2. Entrene Fast
R-CNN y use el mismo modelo preentrenado para inicialice la convolución compartida [Tenga en cuenta que esto es para inicializar una nueva red convolucional compartida con la misma estructura que el primer paso, no la entrenada en el primer paso], bloquee los pesos RPN entrenados en el primer paso y entrene el RCNN con el Propuestas obtenidas por la Red RPN
3. Tuning RPN, usando la convolución compartida y RCNN entrenado en el paso 2, arreglando la capa convolucional compartida, y continuando entrenando RPN, creo que este paso es equivalente a afinar el RPN entrenado en el paso 1, 4.
Sintonice Fast R-CNN, use la convolución compartida y el RPN entrenados en el paso 3 (corrija la capa de convolución compartida) y continúe entrenando y ajustando RCNN 5. Repita los pasos
3 y 4 anteriores para la iteración. (Generalmente, es suficiente para llegar al paso 4, y el efecto después del entrenamiento iterativo casi no mejora)

Aquí hay un diagrama de flujo del proceso de capacitación, que debería ser más claro:
inserte la descripción de la imagen aquí

Siete: método de prueba R-CNN más rápido

A continuación, explicaremos el proceso de prueba de toda la red.¡Ya casi terminas!

Paso 1: la imagen de entrada pasa a través de la capa convolucional para obtener el mapa de características

paso 2: el mapa de características obtiene 300 RP a través de RPN

paso 3: Ingrese RP en la red RoiHead

paso 4: Obtener la puntuación de categoría y los parámetros de posición de bbox de cada RP

paso 5: Seleccione el ROI final por el umbral de puntuación

paso 6: ajuste el cuadro bbox de ROI en combinación con los parámetros de posición

paso 7: Dibuje el marco de detección final después de NMS

Ocho: Resumen

Aunque Fast R-CNN ha mejorado mucho en velocidad y precisión, todavía no logra la detección de objetivos de extremo a extremo . Por ejemplo, la adquisición de regiones candidatas no se puede realizar de forma sincrónica, y la velocidad aún mejora.

Adjuntar una últimaFin de diagrama de flujo esquemático súper grande,Para referencia:

inserte la descripción de la imagen aquí


  Hasta ahora, he dado una explicación detallada de todo el proceso y los detalles de Faster R-CNN. Espero que te sea útil. Si tienes alguna pregunta o sugerencia que no entiendas, deja un comentario. abajo. (La palabra clave no es fácil, todosDale un pulgar hacia arriba, deja una fragancia persistente en tu mano.~¡Gracias! )

Soy un pescado salado de Jiangnan que lucha en el atolladero de CV, ¡trabajemos duro juntos y no nos arrepintamos!

Supongo que te gusta

Origin blog.csdn.net/weixin_43702653/article/details/124045469
Recomendado
Clasificación