Interpretación del código fuente de softnms (python)

prefacio

La razón por la que quiero escribir este artículo es que recientemente me encontré con algo bastante difícil. Si desea mover un modelo de detección de objetivos y su procesamiento posterior relacionado a un dispositivo integrado, no puede usar la biblioteca opencv de c ++, y no puede usar cv2. La función dnn.nms se usa para el procesamiento posterior de nms. Debe implementarse en c, por lo que debe comprender el proceso de nms y escribir un c nms a mano, así que fui a Internet para encontrar el código fuente de python de softnms para tratar de interpretar.De hecho, la diferencia entre softnms y nms no es más que el peso de la multiplicación de la puntuación de cada cuadro en nms, por lo que este artículo también puede considerarse como la interpretación del código fuente de nms .

Pensamiento

En vista del hecho de que algunos principiantes pueden no entender nms, permítanme hablar sobre el proceso general y las ideas aquí.
Por ejemplo, el modelo onnx derivado del peso coco antes del entrenamiento de yolov5s, encontrará que su salida es un vector float32[1,20160,85].
1 significa lote, y este concepto básico no se discutirá.
85 significa coco80 categorías + 4 coordenadas (x1, x2, y1, y2) + 1 confianza (confianza).
20160 significa que hay 20160 filas en la salida, es decir, hay tantos cuadros, por lo que, por supuesto, no podemos contar todos los cuadros como nuestro objetivo, y encontrará que la imagen está llena de cuadros desordenados, por lo que Necesitamos un método que nos ayude a filtrar los marcos redundantes, y la comprensión simple de la idea de nms es calcular el IOU para cada marco y el resto de los marcos, y eliminar los marcos que superan el umbral de IOU establecido.
Con respecto a iou, vea la figura a continuación, transferida de wiki iou :
inserte la descripción de la imagen aquí
es decir, el área superpuesta de dos cajas dividida por el área que cubren en total, y la intersección dividida por la unión.

código y cuestiones clave

El código se selecciona de softnms , agregando algunas funciones de impresión fáciles de entender y eliminando la dependencia de tensorflow.

import numpy as np
import time


def py_cpu_softnms(dets, sc, Nt=0.3, sigma=0.5, thresh=0.001, method=2):
    """
    py_cpu_softnms
    :param dets:   boexs 坐标矩阵 format [y1, x1, y2, x2]
    :param sc:     每个 boxes 对应的分数
    :param Nt:     iou 交叠门限
    :param sigma:  使用 gaussian 函数的方差
    :param thresh: 最后的分数门限
    :param method: 使用的方法
    :return:       留下的 boxes 的 index
    """

    # 就是在框矩阵的最后一列加上从0开始的序号
    N = dets.shape[0]
    indexes = np.array([np.arange(N)])
    dets = np.concatenate((dets, indexes.T), axis=1)
    print(f'dets is {
      
      dets}\n')
    # 顺序是y1,x1,y2,x2
    y1 = dets[:, 0]
    x1 = dets[:, 1]
    y2 = dets[:, 2]
    x2 = dets[:, 3]
    print(y1)
    print(x1)
    print(y2)
    print(x2)
    scores = sc
    print(f'scores is {
      
      scores}\n')
    areas = (x2 - x1 + 1) * (y2 - y1 + 1)
    print(f'areas is {
      
      areas}\n')
    for i in range(N):
        # 临时存储方便后面参数交换
        tBD = dets[i, :].copy()
        print(f'tBD before is{
      
      tBD}')
        tscore = scores[i].copy()
        print(f'tscore is {
      
      tscore}')
        tarea = areas[i].copy()
        pos = i + 1

        # 选取最大分数
        if i != N-1:
            maxscore = np.max(scores[pos:], axis=0)
            print(f'maxscore is : {
      
      maxscore}')
            maxpos = np.argmax(scores[pos:], axis=0)
            print(f'maxpos is : {
      
      maxpos}')
        # 这里如果是最后一位了就直接选取它自己,节省时间
        else:
            maxscore = scores[-1]
            maxpos = 0
        if tscore < maxscore:
            print(1)
            dets[i, :] = dets[maxpos + i + 1, :]
            dets[maxpos + i + 1, :] = tBD
            tBD = dets[i, :]
            print(f'dets After is{
      
      dets}')
            print(f'tBD After is{
      
      tBD}')
            scores[i] = scores[maxpos + i + 1]
            scores[maxpos + i + 1] = tscore
            tscore = scores[i]

            areas[i] = areas[maxpos + i + 1]
            areas[maxpos + i + 1] = tarea
            tarea = areas[i]

        # IoU 计算
        xx1 = np.maximum(dets[i, 1], dets[pos:, 1])
        yy1 = np.maximum(dets[i, 0], dets[pos:, 0])
        xx2 = np.minimum(dets[i, 3], dets[pos:, 3])
        yy2 = np.minimum(dets[i, 2], dets[pos:, 2])

        w = np.maximum(0.0, xx2 - xx1 + 1)
        h = np.maximum(0.0, yy2 - yy1 + 1)
        inter = w * h
        ovr = inter / (areas[i] + areas[pos:] - inter)
        print(ovr)
        # 三种方法: 1.linear 2.gaussian 3.原始的NMS
        if method == 1:  # linear
            weight = np.ones(ovr.shape)
            weight[ovr > Nt] = weight[ovr > Nt] - ovr[ovr > Nt]
        elif method == 2:  # gaussian
            weight = np.exp(-(ovr * ovr) / sigma)
        else:  # 原始的NMS
            weight = np.ones(ovr.shape)
            weight[ovr > Nt] = 0

        scores[pos:] = weight * scores[pos:]
        print(f'scores [pos:] is {
      
      scores[pos:]}')
    # 选择正确的box序号
    inds = dets[:, 4][scores > thresh]
    keep = inds.astype(int)

    return keep


def test():
    # 模拟数据
    boxes = np.array([[200, 200, 400, 400], [220, 220, 420, 420], [200, 240, 400, 440], [240, 200, 440, 400], [1, 1, 2, 2]], dtype=np.float32)
    print(boxes.shape)
    boxscores = np.array([0.9, 0.8, 0.7, 0.6, 0.5], dtype=np.float32)
    index = py_cpu_softnms(boxes, boxscores, method=3)
    print(index)


if __name__ == '__main__':
    test()

Se estima que muchas personas se preguntan por qué este ciclo for es tan complicado ¿No es suficiente para calcular el pagaré para todas y cada una de las demás cajas?
De hecho, esta es la sutileza de este código: si simulo cinco cajas como el código anterior, si calculo iou para todas y cada una de las demás cajas, tengo que calcular 5x4 = 20 veces .
Pero si selecciono la puntuación más grande en la parte de atrás cada vez, y si la puntuación actual es menor, intercambiaré sus posiciones, de modo que el número de cálculos de iou sea 4+3+2+1=5x4/2= 10 veces , ahorra la mitad del tiempo. Si no puede entenderlo, le sugiero que lo vuelva a ejecutar. Todavía es difícil transmitir esto con palabras, por lo que vale la pena los detalles.
¿Dónde se refleja en el código la diferencia entre el nms original y el nms suave?
Preste atención a la matriz de pesos en el código. Hay tres formas de elegir los pesos aquí. Después de cada cálculo de los pesos, se multiplicará por la matriz de puntuación actual. Si es el nms original, todos los valores que se superponen con él y son mayores que el umbral se establecerán en 0. , lo que significa que esas casillas se ignorarán en el siguiente proceso de cálculo, pero si es softnms, ya sea lineal o gaussiano, reservará una determinada puntuación para la caja, que también puede desempeñar algún papel. De hecho, este método se usa en muchos algoritmos. Se refleja en el modelo, pero si puede mejorar el rendimiento del modelo en realidad es incierto, y debe probarlo para saberlo. .

Supongo que te gusta

Origin blog.csdn.net/weixin_43945848/article/details/128464292
Recomendado
Clasificación