Detección de objetos

1. Detección de objetos

Las 5 principales aplicaciones de la visión por computadora

1.1 Breve descripción general y terminología de la detección de objetivos

inserte la descripción de la imagen aquí

  • El reconocimiento de objetos consiste en distinguir qué objetos hay en la imagen, la entrada es una imagen y la salida es una etiqueta de categoría y probabilidad. El algoritmo de detección de objetos no solo necesita detectar qué objetos hay en la imagen, sino que también genera el marco exterior (x, y, ancho, alto) del objeto para ubicar la posición del objeto.
  • La detección de objetos consiste en encontrar con precisión la ubicación del objeto en una imagen determinada y marcar la categoría del objeto.
  • El problema que debe resolver la detección de objetos es todo el proceso de dónde y qué es el objeto.
  • Sin embargo, este problema no es tan fácil de resolver: el tamaño del objeto varía mucho, el ángulo y la postura del objeto son inciertos y puede aparecer en cualquier parte de la imagen, sin mencionar que el objeto también puede ser de múltiples categorías. .

inserte la descripción de la imagen aquí

En la actualidad, los algoritmos de detección de objetivos que surgen en la academia y la industria se dividen en tres categorías:

  1. Algoritmo de detección de objetivos tradicional: Cascade + HOG/DPM + Haar/SVM y muchas mejoras y optimizaciones de los métodos anteriores;
  2. Área/marco candidato + clasificación de aprendizaje profundo: extrayendo áreas candidatas y clasificando las áreas correspondientes según métodos de aprendizaje profundo, como:
  • R-CNN (Búsqueda selectiva + CNN + SVM)
  • SPP-net (agrupación de ROI)
  • R-CNN rápido (búsqueda selectiva + CNN + ROI)
  • R-CNN más rápido (RPN + CNN + ROI)
  1. Método de regresión basado en aprendizaje profundo: YOLO/SSD y otros métodos

1.2 pagaré

Intersección sobre Unión es un estándar para medir la precisión de la detección de objetos correspondientes en un conjunto de datos específico.

IoU es un estándar de medición simple, siempre que la tarea de obtener un rango de predicción (cuadro límite) en la salida se pueda medir con IoU.

Para poder utilizar IoU para medir la detección de objetos de tamaño y forma arbitrarios, necesitamos:

  1. cuadros delimitadores de verdad sobre el terreno (marcan artificialmente el rango aproximado del objeto que se detectará en la imagen del conjunto de entrenamiento);
  2. El rango de resultados producidos por nuestro algoritmo.

Es decir, este criterio se utiliza para medir el grado de correlación entre lo real y lo predicho, cuanto mayor es el grado de correlación, mayor es el valor.
inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

1.3 TP TN FP FN

Hay 4 letras en TP TN FP FN, que son TFPN.
T es Verdadero;
F es Falso;
P es Positivo;
N es Negativo.

T o F representa si la muestra está clasificada correctamente.
P o N representa si la muestra era originalmente una muestra positiva o una muestra negativa.

TP (True Positives) significa que está clasificada como muestra positiva y es correcta.
TN (Verdadero Negativos) significa que está clasificada como muestra negativa, y es correcta, y
FP (Falsos Positivos) significa que está clasificada como muestra positiva, pero está mal (de hecho, esta muestra es una muestra negativa ).
FN (Falsos Negativos) significa que está dividido en muestras negativas, pero está mal (de hecho, esta muestra es una muestra positiva).
En el proceso de cálculo de mAP, se utilizan principalmente los tres conceptos de TP, FP y FN.

1.4 precisión (precisión) y recuperación (tasa de recuperación)

inserte la descripción de la imagen aquí
TP es un ejemplo que el clasificador considera una muestra positiva y de hecho es una muestra positiva. FP es un ejemplo que el clasificador considera una muestra positiva pero en realidad no es una muestra positiva. Precisión traducida al chino significa "la parte que "El clasificador considera una clase positiva y de hecho es una clase positiva y representa todas las clasificaciones. La proporción que el dispositivo considera como clase positiva".

inserte la descripción de la imagen aquí
TP es un ejemplo que el clasificador considera una muestra positiva y de hecho es una muestra positiva. FN es un ejemplo que el clasificador considera una muestra negativa pero en realidad no es una muestra negativa. Recordemos que traducido al chino significa "la parte que el clasificador considera una clase positiva y de hecho es una clase positiva que representa todo. De hecho, es la proporción de la clase positiva."

Precisión es encontrar lo correcto y recordar es encontrarlo todo.

inserte la descripción de la imagen aquí

  • El cuadro azul es el cuadro real. Los cuadros verde y rojo son cuadros de predicción, los cuadros verdes son muestras positivas y los cuadros rojos son muestras negativas.
  • En términos generales, cuando el cuadro de predicción y el cuadro real IOU> = 0,5, se considera una muestra positiva.

2. Regresión del cuadro delimitador

inserte la descripción de la imagen aquí

¿Qué es la regresión del cuadro delimitador?

  • La ventana generalmente está representada por un vector de cuatro dimensiones (x, y, w, h), que representan respectivamente las coordenadas del punto central y el ancho y alto de la ventana.
  • El cuadro rojo P representa la Propuesta original;
  • El cuadro verde G representa la verdad fundamental del objetivo;

Nuestro objetivo es encontrar una relación para que la ventana original de entrada P se asigne para obtener una ventana de regresión G^ que esté más cerca de la ventana real G.

Por lo tanto, el propósito de la regresión de marcos es:
dado (Px,Py,Pw,Ph) para encontrar un mapeo f, de modo que: f(Px,Py,Pw,Ph)=(Gx, Gy ,Gw ,Gh ) y ( Gx ,Gy ,Gw ,Gh )≈(Gx,Gy,Gw,Gh)

inserte la descripción de la imagen aquí

¿Cómo hacer una regresión fronteriza?
La idea más simple es: traducción + escala
inserte la descripción de la imagen aquí

Entrada:
P=(Px,Py,Pw,Ph)
(Nota: La entrada de la fase de entrenamiento también incluye Ground Truth)

Salida:
la transformación de traducción y el escalado requeridos dx, dy, dw, dh o Δx, Δy, Sw, Sh.

Con estas cuatro transformaciones, podemos obtener Ground Truth directamente.

3. R-CNN más rápido

RCNN más rápido se puede dividir en 4 contenidos principales:

  1. Capas conv : como método de detección de objetivos de la red CNN, Faster RCNN primero utiliza un conjunto de capas básicas conv + relu + pooling para extraer los mapas de características de la imagen. Los mapas de características se comparten para capas RPN posteriores y capas completamente conectadas.
  2. Redes de propuestas regionales (RPN) : la red RPN se utiliza para generar propuestas regionales. Juzgue si los anclajes son positivos o negativos a través de softmax y luego use la regresión del cuadro delimitador para corregir los anclajes y obtener propuestas precisas.
  3. Roi Pooling : esta capa recopila los mapas de características de entrada y las propuestas, extrae los mapas de características de la propuesta después de integrar esta información y los envía a la capa posterior completamente conectada para determinar la categoría de destino.
  4. Clasificación : utilice los mapas de características de la propuesta para calcular la categoría de la propuesta y, al mismo tiempo, vuelva a realizar la regresión del cuadro delimitador para obtener la posición final precisa del marco de detección.

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

3.1 RCNN más rápido: capa de conversión

1 Capas conv
Las capas conv incluyen conv, pooling y relu tres capas. Hay 13 capas de conversión, 13 capas de relu y 4 capas de agrupación.

En capas Conv:

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

En las capas Faster RCNN Conv, todas las convoluciones se procesan mediante pad (pad = 1, es decir, se rellena un círculo de 0), lo que hace que la imagen original adquiera un tamaño (M+2)x(N+2), y luego se hace un volumen de 3x3 Salida MxN después del producto. Es esta configuración la que hace que la capa conv en las capas Conv no cambie los tamaños de las matrices de entrada y salida.

inserte la descripción de la imagen aquí

De manera similar, la capa de agrupación kernel_size=2, stride=2 en las capas Conv.
De esta manera, cada matriz MxN que pase a través de la capa de agrupación tendrá un tamaño (M/2)x(N/2).

En resumen, en todas las capas Conv, las capas conv y relu no cambian los tamaños de entrada y salida, y solo la capa de agrupación hace que la longitud y el ancho de salida sean la mitad de la entrada.

Luego, una matriz de tamaño MxN se fija en (M/16)x(N/16) después de las capas Conv.
De esta manera, el mapa de características generado por las capas Conv puede corresponder a la imagen original.

3.2 RCNN más rápido: Redes de propuesta regional (RPN)

2. Redes de propuesta de región (RPN)
El método de detección clásico de Redes de propuesta de región (RPN) requiere mucho tiempo para generar marcos de detección. El uso directo de RPN para generar cuadros de detección es una gran ventaja de Faster R-CNN, que puede aumentar en gran medida la velocidad de generación de cuadros de detección.
inserte la descripción de la imagen aquí

  • Puedes ver que la red RPN en realidad está dividida en 2 líneas:
  1. El anterior usa softmax para clasificar anclajes para obtener clasificaciones positivas y negativas;
  2. El siguiente se utiliza para calcular el desplazamiento de regresión del cuadro delimitador para los anclajes para obtener una propuesta precisa.
  • La capa de Propuesta final es responsable de sintetizar los anclajes positivos y el desplazamiento de regresión del cuadro delimitador correspondiente para obtener propuestas y, al mismo tiempo, rechazar propuestas que son demasiado pequeñas y están más allá del límite.
  • De hecho, cuando toda la red alcanza la capa de propuesta, completa la función equivalente al posicionamiento del objetivo.

3.2.1 anclajes

Después de convolucionar la red RPN, se muestra cada píxel y se asigna a un área de la imagen original, se encuentra la posición central de esta área y se seleccionan 9 tipos de cuadros de anclaje de acuerdo con las reglas basadas en la posición central.

Hay 3 tipos de áreas para 9 rectángulos: 128.256.512; 3 formas: la relación de aspecto es aproximadamente 1:1, 1:2, 2:1. (no es una proporción fija, ajustable)

Los 4 valores de cada fila representan las coordenadas de las esquinas superior izquierda e inferior derecha del rectángulo.
inserte la descripción de la imagen aquí

Los mapas de características obtenidos al atravesar las capas Conv están equipados con estos 9 anclajes como marco de detección inicial para cada punto.
inserte la descripción de la imagen aquí

3.2.2 Softmax determina positivo y negativo

De hecho, RPN finalmente establece anclajes candidatos densos en la escala de la imagen original. Luego use cnn para juzgar qué anclas son anclas positivas con objetivos dentro y cuáles son anclas negativas sin objetivos. Entonces, es sólo una categoría binaria.

inserte la descripción de la imagen aquí
Se puede ver que num_output = 18 de su conv, es decir, la imagen de salida después de la convolución tiene un tamaño WxHx18.

Esto simplemente corresponde al hecho de que cada punto de los mapas de características tiene 9 anclajes, y cada anclaje puede ser positivo y negativo. Toda esta información se almacena en una matriz de tamaño WxHx(9*2).

¿Por qué haces esto? Seguido de la clasificación softmax para obtener anclajes positivos, es equivalente a extraer inicialmente el cuadro del área candidata al objetivo de detección (generalmente se cree que el objetivo está en los anclajes positivos).

Entonces, ¿por qué conectar una capa de remodelación antes y después de softmax? De hecho, es solo por conveniencia de la clasificación softmax.

La matriz de los anclajes positivos/negativos anteriores se almacena en caffe en forma de [1, 18, H, W]. En la clasificación softmax, se requiere una clasificación binaria positiva/negativa, por lo que la capa de remodelación la cambiará al tamaño [1, 2, 9xH, W], es decir, "desalojará" una dimensión para la clasificación softmax y luego la remodelará nuevamente a el estado original.

En resumen, la red RPN utiliza anclajes y softmax para extraer inicialmente anclajes positivos como regiones candidatas.

3.2.3 regresión del cuadro delimitador en propuestas

inserte la descripción de la imagen aquí
Puede ver num_output = 36 de conv, es decir, la imagen de salida después de esta convolución es WxHx36. Esto equivale a mapas de características con 9 anclas para cada punto, y cada ancla tiene 4 transformaciones para la regresión:
inserte la descripción de la imagen aquí

3.2.4 Capa de propuesta

La capa de propuesta es responsable de sintetizar todas las transformaciones y anclajes positivos, calcular una propuesta precisa y enviarla a la capa de agrupación de RoI posterior.

La capa de propuesta tiene 4 entradas:

  1. Resultados del clasificador de anclajes positivos vs negativos rpn_cls_prob_reshape,
  2. La cantidad de transformación del registro bbox correspondiente rpn_bbox_pred,
  3. soy_info
  4. parámetro feature_stride=16

im_info: para una imagen PxQ de cualquier tamaño, primero cambie su forma a un MxN fijo antes de pasarla a Faster RCNN, e im_info=[M, N, scale_factor] guarda toda la información de esta escala.
La imagen de entrada pasa a través de Conv Layers y adquiere un tamaño WxH=(M/16)x(N/16) después de 4 veces de agrupación, donde feature_stride=16 guarda esta información y se usa para calcular el desplazamiento del ancla.

Las capas de propuesta se procesan en el siguiente orden:

  1. Utilice la cantidad de transformación para realizar la regresión de regresión bbox en todos los anclajes positivos
  2. De acuerdo con las puntuaciones softmax positivas de entrada, los anclajes se clasifican de mayor a menor y se extraen los primeros anclajes pre_nms_topN (por ejemplo, 6000), es decir, se extraen los anclajes positivos después de la posición corregida.
  3. Realice NMS (supresión no máxima) en los anclajes positivos restantes.
  4. Luego envíe la propuesta.

La detección en sentido estricto debería terminar aquí, y la parte siguiente debería pertenecer a la identificación.

Estructura de red RPN, resumida: Generar anclajes -> clasificador softmax para extraer anclajes positivos -> anclajes positivos de regresión bbox reg -> Capa de propuesta para generar propuestas

3.3 RCNN más rápido: agrupación de Roi

La capa RoI Pooling es responsable de recopilar propuestas y calcular los mapas de características de las propuestas, que se envían a la red posterior.

La capa de agrupación de Rol tiene 2 entradas:

  1. Mapas de características originales
  2. Cuadros de propuestas generados por RPN (varios tamaños)

inserte la descripción de la imagen aquí

¿Por qué se necesita la agrupación de RoI?
Para las CNN tradicionales (como AlexNet y VGG), cuando se entrena la red, el tamaño de la imagen de entrada debe ser un valor fijo y la salida de la red también es un vector o matriz de tamaño fijo. Este problema se vuelve más problemático si el tamaño de la imagen de entrada es variable.

Hay 2 soluciones:

  1. Parte del recorte de la imagen se pasa a la red para transferir la imagen (destruyendo la estructura completa de la imagen)
  2. Deformar al tamaño requerido y pasarlo a la red (destruyendo la información de forma original de la imagen)
    inserte la descripción de la imagen aquí

Principio de agrupación de RoI
Nuevos parámetros pooled_w, pooled_h y espacial_scale (1/16)

Proceso de avance de la capa de agrupación de RoI:

  1. Dado que la propuesta corresponde a la escala M N, primero use el parámetro espacial_escala para mapearla nuevamente a la escala del mapa de características de tamaño (M/16) (N/16);
  2. Luego divida horizontalmente el área del mapa de características correspondiente a cada propuesta en una cuadrícula de pooled_w * pooled_h;
  3. Realice un procesamiento de agrupación máximo en cada parte de la red.

Después de procesar de esta manera, los resultados de salida de propuestas con diferentes tamaños tienen un tamaño fijo pooled_w * pooled_h y se realiza una salida de longitud fija.

Luego divida el área del mapa de características correspondiente a cada propuesta horizontalmente en cuadrículas pooled_w * pooled_h;
realice el procesamiento de agrupación máximo en cada cuadrícula.

Después de procesar de esta manera, los resultados de salida de propuestas con diferentes tamaños tienen un tamaño fijo pooled_w * pooled_h y se realiza una salida de longitud fija.
inserte la descripción de la imagen aquí

3.4 RCNN más rápido: clasificación

La parte de Clasificación utiliza los mapas de características de la propuesta obtenida, calcula a qué categoría pertenece cada propuesta (como personas, automóviles, televisores, etc.) a través de la capa de conexión completa y softmax, y genera el vector de probabilidad cls_prob;

Al mismo tiempo, la regresión del cuadro delimitador se usa nuevamente para obtener el desplazamiento de posición bbox_pred de cada propuesta, que se usa para devolver un marco de detección de objetivos más preciso.
inserte la descripción de la imagen aquí

Después de obtener los mapas de características de propuesta de tamaño pooled_w * pooled_h de RoI Pooling, se envían a la red de seguimiento y se realizan las dos cosas siguientes:

  1. Clasifique propuestas a través de conexión completa y softmax, que en realidad es la categoría de reconocimiento
  2. Realice nuevamente la regresión del cuadro delimitador en las propuestas para obtener cuadros de predicción de mayor precisión

Capa completamente conectada Capas InnerProduct:
inserte la descripción de la imagen aquí

3.5 Comparación de redes

inserte la descripción de la imagen aquí

3.6 Ejemplo de código

3.6.1 Construcción de la red

import cv2
import keras
import numpy as np
import colorsys
import pickle
import os
import nets.frcnn as frcnn
from nets.frcnn_training import get_new_img_size
from keras import backend as K
from keras.layers import Input
from keras.applications.imagenet_utils import preprocess_input
from PIL import Image,ImageFont, ImageDraw
from utils.utils import BBoxUtility
from utils.anchors import get_anchors
from utils.config import Config
import copy
import math
class FRCNN(object):
    _defaults = {
    
    
        "model_path": 'model_data/voc_weights.h5',
        "classes_path": 'model_data/voc_classes.txt',
        "confidence": 0.7,
    }

    @classmethod
    def get_defaults(cls, n):
        if n in cls._defaults:
            return cls._defaults[n]
        else:
            return "Unrecognized attribute name '" + n + "'"

    #---------------------------------------------------#
    #   初始化faster RCNN
    #---------------------------------------------------#
    def __init__(self, **kwargs):
        self.__dict__.update(self._defaults)
        self.class_names = self._get_class()
        self.sess = K.get_session()
        self.config = Config()
        self.generate()
        self.bbox_util = BBoxUtility()
    #---------------------------------------------------#
    #   获得所有的分类
    #---------------------------------------------------#
    def _get_class(self):
        classes_path = os.path.expanduser(self.classes_path)
        with open(classes_path) as f:
            class_names = f.readlines()
        class_names = [c.strip() for c in class_names]
        return class_names

    #---------------------------------------------------#
    #   获得所有的分类
    #---------------------------------------------------#
    def generate(self):
        model_path = os.path.expanduser(self.model_path)
        assert model_path.endswith('.h5'), 'Keras model or weights must be a .h5 file.'
        
        # 计算总的种类
        self.num_classes = len(self.class_names)+1

        # 载入模型,如果原来的模型里已经包括了模型结构则直接载入。
        # 否则先构建模型再载入
        self.model_rpn,self.model_classifier = frcnn.get_predict_model(self.config,self.num_classes)
        self.model_rpn.load_weights(self.model_path,by_name=True)
        self.model_classifier.load_weights(self.model_path,by_name=True,skip_mismatch=True)
                
        print('{} model, anchors, and classes loaded.'.format(model_path))

        # 画框设置不同的颜色
        hsv_tuples = [(x / len(self.class_names), 1., 1.)
                      for x in range(len(self.class_names))]
        self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
        self.colors = list(
            map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
                self.colors))
    
    def get_img_output_length(self, width, height):
        def get_output_length(input_length):
            # input_length += 6
            filter_sizes = [7, 3, 1, 1]
            padding = [3,1,0,0]
            stride = 2
            for i in range(4):
                # input_length = (input_length - filter_size + stride) // stride
                input_length = (input_length+2*padding[i]-filter_sizes[i]) // stride + 1
            return input_length
        return get_output_length(width), get_output_length(height) 
    
    #---------------------------------------------------#
    #   检测图片
    #---------------------------------------------------#
    def detect_image(self, image):
        image_shape = np.array(np.shape(image)[0:2])
        old_width = image_shape[1]
        old_height = image_shape[0]
        old_image = copy.deepcopy(image)
        width,height = get_new_img_size(old_width,old_height)


        image = image.resize([width,height])
        photo = np.array(image,dtype = np.float64)

        # 图片预处理,归一化
        photo = preprocess_input(np.expand_dims(photo,0))
        preds = self.model_rpn.predict(photo)
        # 将预测结果进行解码
        anchors = get_anchors(self.get_img_output_length(width,height),width,height)

        rpn_results = self.bbox_util.detection_out(preds,anchors,1,confidence_threshold=0)
        R = rpn_results[0][:, 2:]
        
        R[:,0] = np.array(np.round(R[:, 0]*width/self.config.rpn_stride),dtype=np.int32)
        R[:,1] = np.array(np.round(R[:, 1]*height/self.config.rpn_stride),dtype=np.int32)
        R[:,2] = np.array(np.round(R[:, 2]*width/self.config.rpn_stride),dtype=np.int32)
        R[:,3] = np.array(np.round(R[:, 3]*height/self.config.rpn_stride),dtype=np.int32)
        
        R[:, 2] -= R[:, 0]
        R[:, 3] -= R[:, 1]
        base_layer = preds[2]
        
        delete_line = []
        for i,r in enumerate(R):
            if r[2] < 1 or r[3] < 1:
                delete_line.append(i)
        R = np.delete(R,delete_line,axis=0)
        
        bboxes = []
        probs = []
        labels = []
        for jk in range(R.shape[0]//self.config.num_rois + 1):
            ROIs = np.expand_dims(R[self.config.num_rois*jk:self.config.num_rois*(jk+1), :], axis=0)
            
            if ROIs.shape[1] == 0:
                break

            if jk == R.shape[0]//self.config.num_rois:
                #pad R
                curr_shape = ROIs.shape
                target_shape = (curr_shape[0],self.config.num_rois,curr_shape[2])
                ROIs_padded = np.zeros(target_shape).astype(ROIs.dtype)
                ROIs_padded[:, :curr_shape[1], :] = ROIs
                ROIs_padded[0, curr_shape[1]:, :] = ROIs[0, 0, :]
                ROIs = ROIs_padded
            
            [P_cls, P_regr] = self.model_classifier.predict([base_layer,ROIs])

            for ii in range(P_cls.shape[1]):
                if np.max(P_cls[0, ii, :]) < self.confidence or np.argmax(P_cls[0, ii, :]) == (P_cls.shape[2] - 1):
                    continue

                label = np.argmax(P_cls[0, ii, :])

                (x, y, w, h) = ROIs[0, ii, :]

                cls_num = np.argmax(P_cls[0, ii, :])

                (tx, ty, tw, th) = P_regr[0, ii, 4*cls_num:4*(cls_num+1)]
                tx /= self.config.classifier_regr_std[0]
                ty /= self.config.classifier_regr_std[1]
                tw /= self.config.classifier_regr_std[2]
                th /= self.config.classifier_regr_std[3]

                cx = x + w/2.
                cy = y + h/2.
                cx1 = tx * w + cx
                cy1 = ty * h + cy
                w1 = math.exp(tw) * w
                h1 = math.exp(th) * h

                x1 = cx1 - w1/2.
                y1 = cy1 - h1/2.

                x2 = cx1 + w1/2
                y2 = cy1 + h1/2

                x1 = int(round(x1))
                y1 = int(round(y1))
                x2 = int(round(x2))
                y2 = int(round(y2))

                bboxes.append([x1,y1,x2,y2])
                probs.append(np.max(P_cls[0, ii, :]))
                labels.append(label)

        if len(bboxes)==0:
            return old_image
        
        # 筛选出其中得分高于confidence的框
        labels = np.array(labels)
        probs = np.array(probs)
        boxes = np.array(bboxes,dtype=np.float32)
        boxes[:,0] = boxes[:,0]*self.config.rpn_stride/width
        boxes[:,1] = boxes[:,1]*self.config.rpn_stride/height
        boxes[:,2] = boxes[:,2]*self.config.rpn_stride/width
        boxes[:,3] = boxes[:,3]*self.config.rpn_stride/height
        results = np.array(self.bbox_util.nms_for_out(np.array(labels),np.array(probs),np.array(boxes),self.num_classes-1,0.4))
        
        top_label_indices = results[:,0]
        top_conf = results[:,1]
        boxes = results[:,2:]
        boxes[:,0] = boxes[:,0]*old_width
        boxes[:,1] = boxes[:,1]*old_height
        boxes[:,2] = boxes[:,2]*old_width
        boxes[:,3] = boxes[:,3]*old_height

        font = ImageFont.truetype(font='model_data/simhei.ttf',size=np.floor(3e-2 * np.shape(image)[1] + 0.5).astype('int32'))
        
        thickness = (np.shape(old_image)[0] + np.shape(old_image)[1]) // width
        image = old_image
        for i, c in enumerate(top_label_indices):
            predicted_class = self.class_names[int(c)]
            score = top_conf[i]

            left, top, right, bottom = boxes[i]
            top = top - 5
            left = left - 5
            bottom = bottom + 5
            right = right + 5

            top = max(0, np.floor(top + 0.5).astype('int32'))
            left = max(0, np.floor(left + 0.5).astype('int32'))
            bottom = min(np.shape(image)[0], np.floor(bottom + 0.5).astype('int32'))
            right = min(np.shape(image)[1], np.floor(right + 0.5).astype('int32'))

            # 画框框
            label = '{} {:.2f}'.format(predicted_class, score)
            draw = ImageDraw.Draw(image)
            label_size = draw.textsize(label, font)
            label = label.encode('utf-8')
            print(label)
            
            if top - label_size[1] >= 0:
                text_origin = np.array([left, top - label_size[1]])
            else:
                text_origin = np.array([left, top + 1])

            for i in range(thickness):
                draw.rectangle(
                    [left + i, top + i, right - i, bottom - i],
                    outline=self.colors[int(c)])
            draw.rectangle(
                [tuple(text_origin), tuple(text_origin + label_size)],
                fill=self.colors[int(c)])
            draw.text(text_origin, str(label,'UTF-8'), fill=(0, 0, 0), font=font)
            del draw
        return image

    def close_session(self):
        self.sess.close()

3.6.2 Guión de entrenamiento

from __future__ import division
from nets.frcnn import get_model
from nets.frcnn_training import cls_loss,smooth_l1,Generator,get_img_output_length,class_loss_cls,class_loss_regr

from utils.config import Config
from utils.utils import BBoxUtility
from utils.roi_helpers import calc_iou

from keras.utils import generic_utils
from keras.callbacks import TensorBoard, ModelCheckpoint, ReduceLROnPlateau, EarlyStopping
import keras
import numpy as np
import time 
import tensorflow as tf
from utils.anchors import get_anchors

def write_log(callback, names, logs, batch_no):
    for name, value in zip(names, logs):
        summary = tf.Summary()
        summary_value = summary.value.add()
        summary_value.simple_value = value
        summary_value.tag = name
        callback.writer.add_summary(summary, batch_no)
        callback.writer.flush()

if __name__ == "__main__":
    config = Config()
    NUM_CLASSES = 21
    EPOCH = 100
    EPOCH_LENGTH = 2000
    bbox_util = BBoxUtility(overlap_threshold=config.rpn_max_overlap,ignore_threshold=config.rpn_min_overlap)
    annotation_path = '2007_train.txt'

    model_rpn, model_classifier,model_all = get_model(config,NUM_CLASSES)
    base_net_weights = "model_data/voc_weights.h5"
    
    model_all.summary()
    model_rpn.load_weights(base_net_weights,by_name=True)
    model_classifier.load_weights(base_net_weights,by_name=True)

    with open(annotation_path) as f: 
        lines = f.readlines()
    np.random.seed(10101)
    np.random.shuffle(lines)
    np.random.seed(None)

    gen = Generator(bbox_util, lines, NUM_CLASSES, solid=True)
    rpn_train = gen.generate()
    log_dir = "logs"
    # 训练参数设置
    logging = TensorBoard(log_dir=log_dir)
    callback = logging
    callback.set_model(model_all)
    
    model_rpn.compile(loss={
    
    
                'regression'    : smooth_l1(),
                'classification': cls_loss()
            },optimizer=keras.optimizers.Adam(lr=1e-5)
    )
    model_classifier.compile(loss=[
        class_loss_cls, 
        class_loss_regr(NUM_CLASSES-1)
        ], 
        metrics={
    
    'dense_class_{}'.format(NUM_CLASSES): 'accuracy'},optimizer=keras.optimizers.Adam(lr=1e-5)
    )
    model_all.compile(optimizer='sgd', loss='mae')

    # 初始化参数
    iter_num = 0
    train_step = 0
    losses = np.zeros((EPOCH_LENGTH, 5))
    rpn_accuracy_rpn_monitor = []
    rpn_accuracy_for_epoch = [] 
    start_time = time.time()
    # 最佳loss
    best_loss = np.Inf
    # 数字到类的映射
    print('Starting training')

    for i in range(EPOCH):

        if i == 20:
            model_rpn.compile(loss={
    
    
                        'regression'    : smooth_l1(),
                        'classification': cls_loss()
                    },optimizer=keras.optimizers.Adam(lr=1e-6)
            )
            model_classifier.compile(loss=[
                class_loss_cls, 
                class_loss_regr(NUM_CLASSES-1)
                ], 
                metrics={
    
    'dense_class_{}'.format(NUM_CLASSES): 'accuracy'},optimizer=keras.optimizers.Adam(lr=1e-6)
            )
            print("Learning rate decrease")
        
        progbar = generic_utils.Progbar(EPOCH_LENGTH) 
        print('Epoch {}/{}'.format(i + 1, EPOCH))
        while True:
            if len(rpn_accuracy_rpn_monitor) == EPOCH_LENGTH and config.verbose:
                mean_overlapping_bboxes = float(sum(rpn_accuracy_rpn_monitor))/len(rpn_accuracy_rpn_monitor)
                rpn_accuracy_rpn_monitor = []
                print('Average number of overlapping bounding boxes from RPN = {} for {} previous iterations'.format(mean_overlapping_bboxes, EPOCH_LENGTH))
                if mean_overlapping_bboxes == 0:
                    print('RPN is not producing bounding boxes that overlap the ground truth boxes. Check RPN settings or keep training.')
            
            X, Y, boxes = next(rpn_train)
            
            loss_rpn = model_rpn.train_on_batch(X,Y)
            write_log(callback, ['rpn_cls_loss', 'rpn_reg_loss'], loss_rpn, train_step)
            P_rpn = model_rpn.predict_on_batch(X)
            height,width,_ = np.shape(X[0])
            anchors = get_anchors(get_img_output_length(width,height),width,height)
            
            # 将预测结果进行解码
            results = bbox_util.detection_out(P_rpn,anchors,1, confidence_threshold=0)
            
            R = results[0][:, 2:]

            X2, Y1, Y2, IouS = calc_iou(R, config, boxes[0], width, height, NUM_CLASSES)

            if X2 is None:
                rpn_accuracy_rpn_monitor.append(0)
                rpn_accuracy_for_epoch.append(0)
                continue
            
            neg_samples = np.where(Y1[0, :, -1] == 1)
            pos_samples = np.where(Y1[0, :, -1] == 0)

            if len(neg_samples) > 0:
                neg_samples = neg_samples[0]
            else:
                neg_samples = []

            if len(pos_samples) > 0:
                pos_samples = pos_samples[0]
            else:
                pos_samples = []

            rpn_accuracy_rpn_monitor.append(len(pos_samples))
            rpn_accuracy_for_epoch.append((len(pos_samples)))

            if len(neg_samples)==0:
                continue

            if len(pos_samples) < config.num_rois//2:
                selected_pos_samples = pos_samples.tolist()
            else:
                selected_pos_samples = np.random.choice(pos_samples, config.num_rois//2, replace=False).tolist()
            try:
                selected_neg_samples = np.random.choice(neg_samples, config.num_rois - len(selected_pos_samples), replace=False).tolist()
            except:
                selected_neg_samples = np.random.choice(neg_samples, config.num_rois - len(selected_pos_samples), replace=True).tolist()
            
            sel_samples = selected_pos_samples + selected_neg_samples
            loss_class = model_classifier.train_on_batch([X, X2[:, sel_samples, :]], [Y1[:, sel_samples, :], Y2[:, sel_samples, :]])

            write_log(callback, ['detection_cls_loss', 'detection_reg_loss', 'detection_acc'], loss_class, train_step)


            losses[iter_num, 0]  = loss_rpn[1]
            losses[iter_num, 1] = loss_rpn[2]
            losses[iter_num, 2] = loss_class[1]
            losses[iter_num, 3] = loss_class[2]
            losses[iter_num, 4] = loss_class[3]


            train_step += 1
            iter_num += 1
            progbar.update(iter_num, [('rpn_cls', np.mean(losses[:iter_num, 0])), ('rpn_regr', np.mean(losses[:iter_num, 1])),
                                  ('detector_cls', np.mean(losses[:iter_num, 2])), ('detector_regr', np.mean(losses[:iter_num, 3]))])

            
            if iter_num == EPOCH_LENGTH:
                loss_rpn_cls = np.mean(losses[:, 0])
                loss_rpn_regr = np.mean(losses[:, 1])
                loss_class_cls = np.mean(losses[:, 2])
                loss_class_regr = np.mean(losses[:, 3])
                class_acc = np.mean(losses[:, 4])

                mean_overlapping_bboxes = float(sum(rpn_accuracy_for_epoch)) / len(rpn_accuracy_for_epoch)
                rpn_accuracy_for_epoch = []

                if config.verbose:
                    print('Mean number of bounding boxes from RPN overlapping ground truth boxes: {}'.format(mean_overlapping_bboxes))
                    print('Classifier accuracy for bounding boxes from RPN: {}'.format(class_acc))
                    print('Loss RPN classifier: {}'.format(loss_rpn_cls))
                    print('Loss RPN regression: {}'.format(loss_rpn_regr))
                    print('Loss Detector classifier: {}'.format(loss_class_cls))
                    print('Loss Detector regression: {}'.format(loss_class_regr))
                    print('Elapsed time: {}'.format(time.time() - start_time))

                
                curr_loss = loss_rpn_cls + loss_rpn_regr + loss_class_cls + loss_class_regr
                iter_num = 0
                start_time = time.time()

                write_log(callback,
                        ['Elapsed_time', 'mean_overlapping_bboxes', 'mean_rpn_cls_loss', 'mean_rpn_reg_loss',
                        'mean_detection_cls_loss', 'mean_detection_reg_loss', 'mean_detection_acc', 'total_loss'],
                        [time.time() - start_time, mean_overlapping_bboxes, loss_rpn_cls, loss_rpn_regr,
                        loss_class_cls, loss_class_regr, class_acc, curr_loss],i)
                    
                
                if config.verbose:
                    print('The best loss is {}. The current loss is {}. Saving weights'.format(best_loss,curr_loss))
                if curr_loss < best_loss:
                    best_loss = curr_loss
                model_all.save_weights(log_dir+"/epoch{:03d}-loss{:.3f}-rpn{:.3f}-roi{:.3f}".format(i,curr_loss,loss_rpn_cls+loss_rpn_regr,loss_class_cls+loss_class_regr)+".h5")
                
                break

3.6.3 Guión de predicción

from keras.layers import Input
from frcnn import FRCNN 
from PIL import Image

frcnn = FRCNN()

while True:
    img = input('img/street.jpg')
    try:
        image = Image.open('img/street.jpg')
    except:
        print('Open Error! Try again!')
        continue
    else:
        r_image = frcnn.detect_image(image)
        r_image.show()
frcnn.close_session()
    

4. Una etapa o dos etapas

dos etapas : El algoritmo de dos etapas primero utilizará una red para generar una propuesta, como la búsqueda selectiva y la red RPN. Después de la aparición de RPN, el método ss básicamente se abandona. Después de que la red RPN esté conectada a la columna vertebral de la red de extracción de características de imagen, la pérdida de RPN (pérdida de regresión de bbox + pérdida de clasificación) se configurará para entrenar la red RPN y la propuesta generada por RPN se enviará a la red posterior. para una regresión y clasificación de bbox más refinadas.

one-stage : Una etapa persigue la velocidad y abandona la arquitectura de dos etapas, es decir, ya no configura una red separada para generar una propuesta, sino que realiza directamente un muestreo denso en el mapa de características para generar una gran cantidad de cuadros anteriores. , Como el método grid de YOLO. Estas cajas anteriores no se procesan en dos pasos y el tamaño de la caja a menudo se especifica artificialmente.

El algoritmo de dos etapas es principalmente la serie RCNN, que incluye RCNN, Fast-RCNN y Faster-RCNN. El siguiente Mask-RCNN combina la arquitectura Faster-RCNN, la columna vertebral de ResNet y FPN (Feature Pyramid Networks) y el método de segmentación en FCN, lo que mejora la precisión de la detección al completar la segmentación.

El algoritmo de una etapa más típico es YOLO, que es extremadamente rápido.

5. Yolo

Detección de peatones - Yolo3
inserte la descripción de la imagen aquí

5.1 Yolo-Solo miras una vez

inserte la descripción de la imagen aquí

El algoritmo YOLO utiliza un modelo CNN independiente para lograr la detección de objetivos de un extremo a otro:

  1. Cambie el tamaño a 448 448 y la imagen se dividirá en 7 7 cuadrículas (celdas)
  2. CNN extrae características y predice: la parte convolucional es responsable de extraer características y la parte completamente conectada es responsable de la predicción.
  3. filtrar bbox (a través de nms)

inserte la descripción de la imagen aquí

  • El algoritmo YOLO en su conjunto consiste en dividir la imagen de entrada en cuadrículas S S, aquí hay 3 3 cuadrículas.
  • Cuando el punto central del objetivo detectado cae dentro de esta cuadrícula, esta cuadrícula es responsable de detectar el objetivo, como la persona en la figura.
  • Ingresamos esta imagen en la red, y el tamaño de salida final también es S S n (n es el número de canales), y la salida S S corresponde a la imagen de entrada original S S (ambas son 3 * 3).
  • Si nuestra red puede detectar un total de 20 categorías de objetivos, entonces el número de canales de salida n=2*(4+1)+20=30. El 2 aquí significa que cada cuadrícula tiene dos cuadros de calibración (señalados en el documento), 4 representa la información de coordenadas del marco de calibración, 1 representa la confianza del marco de calibración y 20 es el número de categorías del objetivo de detección.
  • Entonces, el tamaño de salida final de la red es S S n=3 3 30.

Acerca del marco de calibración

  • La salida de la red es un tensor de S x S x (5*B+C) (tamaño S, número B de cuadros de calibración, número C de categorías de detección, información 5 de cuadros de calibración).
  • 5 se divide en 4+1:
  • 4 representa la información de ubicación del marco de calibración. El punto central (x, y) del cuadro, la altura y el ancho h, w del cuadro.
  • 1 representa la confianza de cada marco de calibración y la información de precisión del marco de calibración.

inserte la descripción de la imagen aquí

En general, YOLO no predice las coordenadas exactas del centro del cuadro delimitador. Predice:

  • desplazamiento relativo a la esquina superior izquierda de la celda de la cuadrícula del objetivo previsto;
  • Desplazamiento normalizado utilizando la dimensionalidad de las celdas del mapa de características.

Por ejemplo: tome
la imagen de arriba como ejemplo, si la predicción del centro es (0.4, 0.7), las coordenadas del centro en el mapa de características de 13 x 13 son (6.4, 6.7) (las coordenadas de la esquina superior izquierda del glóbulo rojo son (6,6)).

Sin embargo, si las coordenadas x,y predichas son mayores que 1, como (1.2, 0.7). Entonces las coordenadas centrales previstas son (7.2, 6.7). Tenga en cuenta que el centro está en la celda a la derecha de la celda roja. Esto rompe la teoría detrás de YOLO, porque si asumimos que el cuadro rojo es responsable de predecir el perro objetivo, entonces el centro del perro debe estar en la celda roja y no en la celda de la cuadrícula al lado.

Entonces, para solucionar este problema, realizamos una función sigmoidea en la salida, comprimiendo la salida en el intervalo de 0 a 1, asegurando efectivamente que el centro esté en la celda de la cuadrícula donde se realiza la predicción.

La confianza de cada cuadro de calibración y la información de precisión del cuadro de calibración:

El lado izquierdo representa si hay un objetivo en la cuadrícula que contiene el cuadro de calibración. Sí = 1 No = 0.

El lado derecho representa la precisión del marco de calibración. La parte de la derecha es realizar una operación IOU en dos marcos de calibración (uno es la verdad del terreno y el otro es el marco de calibración predicho), es decir, la relación de intersección y la unión de los dos marcos de calibración. Cuanto mayor sea el valor, es decir, cuanto más se superpongan los marcos de calibración, más preciso será.
inserte la descripción de la imagen aquí

Podemos calcular los puntajes de confianza específicos de la clase/puntuaciones de clase de cada marco de calibración: expresa la posibilidad de que el objetivo en el marco de calibración pertenezca a cada categoría y qué tan bien el marco de calibración coincide con el objetivo.

La información de clase predicha por cada cuadrícula se multiplica por la información de confianza predicha por el cuadro delimitador para obtener la puntuación de confianza específica de la clase de cada cuadro delimitador.
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

  • Ha realizado más de 20 convoluciones y cuatro agrupaciones máximas. Entre ellos, la convolución 3x3 se usa para extraer características, la convolución 1x1 se usa para comprimir características y finalmente comprimir la imagen al tamaño de 7x7xfilter, lo que equivale a dividir la imagen completa en cuadrículas de 7x7, y cada cuadrícula es responsable del objetivo. Detección de su propia área. .
  • Toda la red finalmente usa la capa completamente conectada para hacer el tamaño del resultado (7x7x30), donde 7x7 representa una cuadrícula de 7x7, los primeros 20 de 30 representan el tipo de predicción y los últimos 10 representan dos cuadros de predicción y su confianza (5x2). .

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

Realice la misma operación para cada bbox de cada cuadrícula: 7x7x2 = 98 bbox (cada bbox tiene información de clase correspondiente e información de coordenadas)
inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

Después de obtener el puntaje de confianza específico de la clase de cada bbox, establezca el umbral, filtre los cuadros con puntajes bajos y realice el procesamiento NMS en los cuadros reservados para obtener el resultado final de la detección.

inserte la descripción de la imagen aquí

Después de ordenar, las probabilidades son diferentes en los cuadros de diferentes posiciones:
inserte la descripción de la imagen aquí

Utilice el valor máximo como bbox_max y compárelo con un valor distinto de cero (bbox_cur) más pequeño que él: IOU
inserte la descripción de la imagen aquí

De forma recursiva, el siguiente bbox_cur (0.2) distinto de cero se utiliza como bbox_max para continuar comparando pagarés:
inserte la descripción de la imagen aquí

Finalmente, quedan n casillas.
inserte la descripción de la imagen aquí

Después de obtener el puntaje de confianza específico de la clase de cada bbox, establezca el umbral, filtre los cuadros con puntajes bajos y realice el procesamiento NMS en los cuadros reservados para obtener el resultado final de la detección.
inserte la descripción de la imagen aquí

Para las puntuaciones de la categoría bb3(20×1), encuentre el índice correspondiente a la categoría más grande.---->la puntuación más grande en la clase bb3(20×1)---->puntuación
inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

Desventajas de Yolo:

  • YOLO no es bueno para objetos que están muy cerca uno del otro (la situación en la que están muy juntos y los puntos medios caen en la misma cuadrícula), y hay detecciones de grupos pequeños que no son efectivas porque solo se predicen dos en una cuadrícula. caja, y sólo pertenecen a una clase.
  • En la imagen de prueba, la capacidad de generalización es débil cuando el mismo tipo de objetos tiene relaciones de aspecto inusuales y otras situaciones.

5.2 Yolo2

  1. Yolo2 utiliza una nueva red de clasificación como parte de extracción de características.
  2. La red utiliza más núcleos de convolución de 3 x 3, duplicando el número de canales después de cada operación de agrupación.
  3. Coloque el núcleo de convolución de 1 x 1 entre los núcleos de convolución de 3 x 3 para comprimir las características.
  4. Utilice la normalización por lotes para estabilizar el entrenamiento de modelos y acelerar la convergencia.
  5. Se mantiene un acceso directo para almacenar las funciones anteriores.
  6. En comparación con yolo1, yolo2 ha agregado la parte del marco anterior y la forma de la salida final conv_dec es (13,13,425):
  • 13x13 es dividir la imagen completa en cuadrículas de 13x13 para realizar predicciones.
  • 425 se puede descomponer en (85x5). En 85, porque yolo2 se usa comúnmente en el conjunto de datos coco, que tiene 80 clases; las 5 restantes se refieren a x, y, w, h y su confianza. x5 significa que el resultado de la predicción contiene 5 cuadros, correspondientes a los 5 cuadros anteriores.

inserte la descripción de la imagen aquí

5.2.1 Yolo2 - usando cajas de anclaje

inserte la descripción de la imagen aquí

5.2.2 Yolo2 – Clústeres de dimensiones (agrupación de dimensiones)

Utilice la agrupación de kmeans para obtener la información del cuadro anterior:
el cuadro anterior se configuró manualmente y YOLO2 intenta contar el cuadro anterior que está más en línea con el tamaño del objeto en la muestra, de modo que la red se ajuste El cuadro anterior a la posición real puede reducirse. El enfoque de YOLO2 es realizar un análisis de conglomerados en los límites marcados en el conjunto de entrenamiento para encontrar el tamaño de borde que coincida lo más posible con la muestra.

Lo más importante para un algoritmo de agrupación es elegir cómo calcular la "distancia" entre dos fronteras. Para la distancia euclidiana comúnmente utilizada, una frontera grande producirá un error mayor, pero lo que nos importa es el pagaré de la frontera. Por lo tanto, YOLO2 utiliza la siguiente fórmula para calcular la "distancia" entre dos bordes al agrupar.
inserte la descripción de la imagen aquí

En el caso de seleccionar diferentes valores k de agrupamiento, se calculan los k bordes de centroides obtenidos y se calcula el pagaré promedio de los bordes marcados en la muestra y cada centroide.

Obviamente, cuanto mayor sea el número de borde k, mayor será el pagaré promedio.

YOLO2 elige k = 5 como un compromiso entre el número de fronteras y el pagaré. En comparación con el marco anterior seleccionado manualmente, se pueden lograr 61 IOU promedio utilizando 5 marcos de agrupación, lo que equivale a 60,9 IOU promedio de 9 marcos anteriores configurados manualmente.
inserte la descripción de la imagen aquí

El autor finalmente selecciona cinco centros de conglomerados como marco previo. Para los dos conjuntos de datos, el ancho y alto de los cinco cuadros anteriores son los siguientes:
COCO: (0.57273, 0.677385), (1.87446, 2.06253), (3.33843, 5.47434), (7.88282, 3.52778), (9.77052, 9.16828)

COV: (1,3221, 1,73145), (3,19275, 4,00944), (5,05587, 8,09892), (9,47112, 4,84053), (11,2364, 10,0071)

5.3 Yolo3

En comparación con los yolo1 y yolo2 anteriores, YOLOv3 ha mejorado mucho. Las principales direcciones de mejora son:

  1. Usando la red residual Residual
  2. Extraiga múltiples capas de características para la detección de objetivos. Se extraen un total de tres capas de características y sus formas son (13,13,75), (26,26,75), (52,52,75). La última dimensión es 75 porque el gráfico se basa en el conjunto de datos voc, que tiene 20 clases. Yolo3 tiene 3 fotogramas anteriores para cada capa de entidades, por lo que la dimensión final es 3x25.
  3. Adopta el diseño UpSampling2d.
    inserte la descripción de la imagen aquí

5.4 Ejemplo de código (yolo v3)

5.4.1 Construcción de modelos

# -*- coding:utf-8 -*-

import numpy as np
import tensorflow as tf
import os

class yolo:
    def __init__(self, norm_epsilon, norm_decay, anchors_path, classes_path, pre_train):
        """
        Introduction
        ------------
            初始化函数
        Parameters
        ----------
            norm_decay: 在预测时计算moving average时的衰减率
            norm_epsilon: 方差加上极小的数,防止除以0的情况
            anchors_path: yolo anchor 文件路径
            classes_path: 数据集类别对应文件
            pre_train: 是否使用预训练darknet53模型
        """
        self.norm_epsilon = norm_epsilon
        self.norm_decay = norm_decay
        self.anchors_path = anchors_path
        self.classes_path = classes_path
        self.pre_train = pre_train
        self.anchors = self._get_anchors()
        self.classes = self._get_class()

    #---------------------------------------#
    #   获取种类和先验框
    #---------------------------------------#
    def _get_class(self):
        """
        Introduction
        ------------
            获取类别名字
        Returns
        -------
            class_names: coco数据集类别对应的名字
        """
        classes_path = os.path.expanduser(self.classes_path)
        with open(classes_path) as f:
            class_names = f.readlines()
        class_names = [c.strip() for c in class_names]
        return class_names

    def _get_anchors(self):
        """
        Introduction
        ------------
            获取anchors
        """
        anchors_path = os.path.expanduser(self.anchors_path)
        with open(anchors_path) as f:
            anchors = f.readline()
        anchors = [float(x) for x in anchors.split(',')]
        return np.array(anchors).reshape(-1, 2)

    #---------------------------------------#
    #   用于生成层
    #---------------------------------------#
    # l2 正则化
    def _batch_normalization_layer(self, input_layer, name = None, training = True, norm_decay = 0.99, norm_epsilon = 1e-3):
        '''
        Introduction
        ------------
            对卷积层提取的feature map使用batch normalization
        Parameters
        ----------
            input_layer: 输入的四维tensor
            name: batchnorm层的名字
            trainging: 是否为训练过程
            norm_decay: 在预测时计算moving average时的衰减率
            norm_epsilon: 方差加上极小的数,防止除以0的情况
        Returns
        -------
            bn_layer: batch normalization处理之后的feature map
        '''
        bn_layer = tf.layers.batch_normalization(inputs = input_layer,
            momentum = norm_decay, epsilon = norm_epsilon, center = True,
            scale = True, training = training, name = name)
        return tf.nn.leaky_relu(bn_layer, alpha = 0.1)

    # 这个就是用来进行卷积的
    def _conv2d_layer(self, inputs, filters_num, kernel_size, name, use_bias = False, strides = 1):
        """
        Introduction
        ------------
            使用tf.layers.conv2d减少权重和偏置矩阵初始化过程,以及卷积后加上偏置项的操作
            经过卷积之后需要进行batch norm,最后使用leaky ReLU激活函数
            根据卷积时的步长,如果卷积的步长为2,则对图像进行降采样
            比如,输入图片的大小为416*416,卷积核大小为3,若stride为2时,(416 - 3 + 2)/ 2 + 1, 计算结果为208,相当于做了池化层处理
            因此需要对stride大于1的时候,先进行一个padding操作, 采用四周都padding一维代替'same'方式
        Parameters
        ----------
            inputs: 输入变量
            filters_num: 卷积核数量
            strides: 卷积步长
            name: 卷积层名字
            trainging: 是否为训练过程
            use_bias: 是否使用偏置项
            kernel_size: 卷积核大小
        Returns
        -------
            conv: 卷积之后的feature map
        """
        conv = tf.layers.conv2d(
            inputs = inputs, filters = filters_num,
            kernel_size = kernel_size, strides = [strides, strides], kernel_initializer = tf.glorot_uniform_initializer(),
            padding = ('SAME' if strides == 1 else 'VALID'), kernel_regularizer = tf.contrib.layers.l2_regularizer(scale = 5e-4), use_bias = use_bias, name = name)
        return conv

    # 这个用来进行残差卷积的
    # 残差卷积就是进行一次3X3的卷积,然后保存该卷积layer
    # 再进行一次1X1的卷积和一次3X3的卷积,并把这个结果加上layer作为最后的结果
    def _Residual_block(self, inputs, filters_num, blocks_num, conv_index, training = True, norm_decay = 0.99, norm_epsilon = 1e-3):
        """
        Introduction
        ------------
            Darknet的残差block,类似resnet的两层卷积结构,分别采用1x1和3x3的卷积核,使用1x1是为了减少channel的维度
        Parameters
        ----------
            inputs: 输入变量
            filters_num: 卷积核数量
            trainging: 是否为训练过程
            blocks_num: block的数量
            conv_index: 为了方便加载预训练权重,统一命名序号
            weights_dict: 加载预训练模型的权重
            norm_decay: 在预测时计算moving average时的衰减率
            norm_epsilon: 方差加上极小的数,防止除以0的情况
        Returns
        -------
            inputs: 经过残差网络处理后的结果
        """
        # 在输入feature map的长宽维度进行padding
        inputs = tf.pad(inputs, paddings=[[0, 0], [1, 0], [1, 0], [0, 0]], mode='CONSTANT')
        layer = self._conv2d_layer(inputs, filters_num, kernel_size = 3, strides = 2, name = "conv2d_" + str(conv_index))
        layer = self._batch_normalization_layer(layer, name = "batch_normalization_" + str(conv_index), training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
        conv_index += 1
        for _ in range(blocks_num):
            shortcut = layer
            layer = self._conv2d_layer(layer, filters_num // 2, kernel_size = 1, strides = 1, name = "conv2d_" + str(conv_index))
            layer = self._batch_normalization_layer(layer, name = "batch_normalization_" + str(conv_index), training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
            conv_index += 1
            layer = self._conv2d_layer(layer, filters_num, kernel_size = 3, strides = 1, name = "conv2d_" + str(conv_index))
            layer = self._batch_normalization_layer(layer, name = "batch_normalization_" + str(conv_index), training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
            conv_index += 1
            layer += shortcut
        return layer, conv_index

    #---------------------------------------#
    #   生成_darknet53
    #---------------------------------------#
    def _darknet53(self, inputs, conv_index, training = True, norm_decay = 0.99, norm_epsilon = 1e-3):
        """
        Introduction
        ------------
            构建yolo3使用的darknet53网络结构
        Parameters
        ----------
            inputs: 模型输入变量
            conv_index: 卷积层数序号,方便根据名字加载预训练权重
            weights_dict: 预训练权重
            training: 是否为训练
            norm_decay: 在预测时计算moving average时的衰减率
            norm_epsilon: 方差加上极小的数,防止除以0的情况
        Returns
        -------
            conv: 经过52层卷积计算之后的结果, 输入图片为416x416x3,则此时输出的结果shape为13x13x1024
            route1: 返回第26层卷积计算结果52x52x256, 供后续使用
            route2: 返回第43层卷积计算结果26x26x512, 供后续使用
            conv_index: 卷积层计数,方便在加载预训练模型时使用
        """
        with tf.variable_scope('darknet53'):
            # 416,416,3 -> 416,416,32
            conv = self._conv2d_layer(inputs, filters_num = 32, kernel_size = 3, strides = 1, name = "conv2d_" + str(conv_index))
            conv = self._batch_normalization_layer(conv, name = "batch_normalization_" + str(conv_index), training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
            conv_index += 1
            # 416,416,32 -> 208,208,64
            conv, conv_index = self._Residual_block(conv, conv_index = conv_index, filters_num = 64, blocks_num = 1, training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
            # 208,208,64 -> 104,104,128
            conv, conv_index = self._Residual_block(conv, conv_index = conv_index, filters_num = 128, blocks_num = 2, training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
            # 104,104,128 -> 52,52,256
            conv, conv_index = self._Residual_block(conv, conv_index = conv_index, filters_num = 256, blocks_num = 8, training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
            # route1 = 52,52,256
            route1 = conv
            # 52,52,256 -> 26,26,512
            conv, conv_index = self._Residual_block(conv, conv_index = conv_index, filters_num = 512, blocks_num = 8, training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
            # route2 = 26,26,512
            route2 = conv
            # 26,26,512 -> 13,13,1024
            conv, conv_index = self._Residual_block(conv, conv_index = conv_index,  filters_num = 1024, blocks_num = 4, training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
            # route3 = 13,13,1024
        return  route1, route2, conv, conv_index

    # 输出两个网络结果
    # 第一个是进行5次卷积后,用于下一次逆卷积的,卷积过程是1X1,3X3,1X1,3X3,1X1
    # 第二个是进行5+2次卷积,作为一个特征层的,卷积过程是1X1,3X3,1X1,3X3,1X1,3X3,1X1
    def _yolo_block(self, inputs, filters_num, out_filters, conv_index, training = True, norm_decay = 0.99, norm_epsilon = 1e-3):
        """
        Introduction
        ------------
            yolo3在Darknet53提取的特征层基础上,又加了针对3种不同比例的feature map的block,这样来提高对小物体的检测率
        Parameters
        ----------
            inputs: 输入特征
            filters_num: 卷积核数量
            out_filters: 最后输出层的卷积核数量
            conv_index: 卷积层数序号,方便根据名字加载预训练权重
            training: 是否为训练
            norm_decay: 在预测时计算moving average时的衰减率
            norm_epsilon: 方差加上极小的数,防止除以0的情况
        Returns
        -------
            route: 返回最后一层卷积的前一层结果
            conv: 返回最后一层卷积的结果
            conv_index: conv层计数
        """
        conv = self._conv2d_layer(inputs, filters_num = filters_num, kernel_size = 1, strides = 1, name = "conv2d_" + str(conv_index))
        conv = self._batch_normalization_layer(conv, name = "batch_normalization_" + str(conv_index), training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
        conv_index += 1
        conv = self._conv2d_layer(conv, filters_num = filters_num * 2, kernel_size = 3, strides = 1, name = "conv2d_" + str(conv_index))
        conv = self._batch_normalization_layer(conv, name = "batch_normalization_" + str(conv_index), training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
        conv_index += 1
        conv = self._conv2d_layer(conv, filters_num = filters_num, kernel_size = 1, strides = 1, name = "conv2d_" + str(conv_index))
        conv = self._batch_normalization_layer(conv, name = "batch_normalization_" + str(conv_index), training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
        conv_index += 1
        conv = self._conv2d_layer(conv, filters_num = filters_num * 2, kernel_size = 3, strides = 1, name = "conv2d_" + str(conv_index))
        conv = self._batch_normalization_layer(conv, name = "batch_normalization_" + str(conv_index), training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
        conv_index += 1
        conv = self._conv2d_layer(conv, filters_num = filters_num, kernel_size = 1, strides = 1, name = "conv2d_" + str(conv_index))
        conv = self._batch_normalization_layer(conv, name = "batch_normalization_" + str(conv_index), training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
        conv_index += 1
        route = conv
        conv = self._conv2d_layer(conv, filters_num = filters_num * 2, kernel_size = 3, strides = 1, name = "conv2d_" + str(conv_index))
        conv = self._batch_normalization_layer(conv, name = "batch_normalization_" + str(conv_index), training = training, norm_decay = norm_decay, norm_epsilon = norm_epsilon)
        conv_index += 1
        conv = self._conv2d_layer(conv, filters_num = out_filters, kernel_size = 1, strides = 1, name = "conv2d_" + str(conv_index), use_bias = True)
        conv_index += 1
        return route, conv, conv_index

    # 返回三个特征层的内容
    def yolo_inference(self, inputs, num_anchors, num_classes, training = True):
        """
        Introduction
        ------------
            构建yolo模型结构
        Parameters
        ----------
            inputs: 模型的输入变量
            num_anchors: 每个grid cell负责检测的anchor数量
            num_classes: 类别数量
            training: 是否为训练模式
        """
        conv_index = 1
        # route1 = 52,52,256、route2 = 26,26,512、route3 = 13,13,1024
        conv2d_26, conv2d_43, conv, conv_index = self._darknet53(inputs, conv_index, training = training, norm_decay = self.norm_decay, norm_epsilon = self.norm_epsilon)
        with tf.variable_scope('yolo'):
            #--------------------------------------#
            #   获得第一个特征层
            #--------------------------------------#
            # conv2d_57 = 13,13,512,conv2d_59 = 13,13,255(3x(80+5))
            conv2d_57, conv2d_59, conv_index = self._yolo_block(conv, 512, num_anchors * (num_classes + 5), conv_index = conv_index, training = training, norm_decay = self.norm_decay, norm_epsilon = self.norm_epsilon)

            #--------------------------------------#
            #   获得第二个特征层
            #--------------------------------------#
            conv2d_60 = self._conv2d_layer(conv2d_57, filters_num = 256, kernel_size = 1, strides = 1, name = "conv2d_" + str(conv_index))
            conv2d_60 = self._batch_normalization_layer(conv2d_60, name = "batch_normalization_" + str(conv_index),training = training, norm_decay = self.norm_decay, norm_epsilon = self.norm_epsilon)
            conv_index += 1
            # unSample_0 = 26,26,256
            unSample_0 = tf.image.resize_nearest_neighbor(conv2d_60, [2 * tf.shape(conv2d_60)[1], 2 * tf.shape(conv2d_60)[1]], name='upSample_0')
            # route0 = 26,26,768
            route0 = tf.concat([unSample_0, conv2d_43], axis = -1, name = 'route_0')
            # conv2d_65 = 52,52,256,conv2d_67 = 26,26,255
            conv2d_65, conv2d_67, conv_index = self._yolo_block(route0, 256, num_anchors * (num_classes + 5), conv_index = conv_index, training = training, norm_decay = self.norm_decay, norm_epsilon = self.norm_epsilon)

            #--------------------------------------#
            #   获得第三个特征层
            #--------------------------------------# 
            conv2d_68 = self._conv2d_layer(conv2d_65, filters_num = 128, kernel_size = 1, strides = 1, name = "conv2d_" + str(conv_index))
            conv2d_68 = self._batch_normalization_layer(conv2d_68, name = "batch_normalization_" + str(conv_index), training=training, norm_decay=self.norm_decay, norm_epsilon = self.norm_epsilon)
            conv_index += 1
            # unSample_1 = 52,52,128
            unSample_1 = tf.image.resize_nearest_neighbor(conv2d_68, [2 * tf.shape(conv2d_68)[1], 2 * tf.shape(conv2d_68)[1]], name='upSample_1')
            # route1= 52,52,384
            route1 = tf.concat([unSample_1, conv2d_26], axis = -1, name = 'route_1')
            # conv2d_75 = 52,52,255
            _, conv2d_75, _ = self._yolo_block(route1, 128, num_anchors * (num_classes + 5), conv_index = conv_index, training = training, norm_decay = self.norm_decay, norm_epsilon = self.norm_epsilon)

        return [conv2d_59, conv2d_67, conv2d_75]


5.4.2 Archivo de configuración

num_parallel_calls = 4
input_shape = 416
max_boxes = 20
jitter = 0.3
hue = 0.1
sat = 1.0
cont = 0.8
bri = 0.1
norm_decay = 0.99
norm_epsilon = 1e-3
pre_train = True
num_anchors = 9
num_classes = 80
training = True
ignore_thresh = .5
learning_rate = 0.001
train_batch_size = 10
val_batch_size = 10
train_num = 2800
val_num = 5000
Epoch = 50
obj_threshold = 0.5
nms_threshold = 0.5
gpu_index = "0"
log_dir = './logs'
data_dir = './model_data'
model_dir = './test_model/model.ckpt-192192'
pre_train_yolo3 = True
yolo3_weights_path = './model_data/yolov3.weights'
darknet53_weights_path = './model_data/darknet53.weights'
anchors_path = './model_data/yolo_anchors.txt'
classes_path = './model_data/coco_classes.txt'

image_file = "./img/img.jpg"

5.4.3 detectar archivo

import os
import config
import argparse
import numpy as np
import tensorflow as tf
from yolo_predict import yolo_predictor
from PIL import Image, ImageFont, ImageDraw
from utils import letterbox_image, load_weights

# 指定使用GPU的Index
os.environ["CUDA_VISIBLE_DEVICES"] = config.gpu_index

def detect(image_path, model_path, yolo_weights = None):
    """
    Introduction
    ------------
        加载模型,进行预测
    Parameters
    ----------
        model_path: 模型路径,当使用yolo_weights无用
        image_path: 图片路径
    """
    #---------------------------------------#
    #   图片预处理
    #---------------------------------------#
    image = Image.open(image_path)
    # 对预测输入图像进行缩放,按照长宽比进行缩放,不足的地方进行填充
    resize_image = letterbox_image(image, (416, 416))
    image_data = np.array(resize_image, dtype = np.float32)
    # 归一化
    image_data /= 255.
    # 转格式,第一维度填充
    image_data = np.expand_dims(image_data, axis = 0)
    #---------------------------------------#
    #   图片输入
    #---------------------------------------#
    # input_image_shape原图的size
    input_image_shape = tf.placeholder(dtype = tf.int32, shape = (2,))
    # 图像
    input_image = tf.placeholder(shape = [None, 416, 416, 3], dtype = tf.float32)

    # 进入yolo_predictor进行预测,yolo_predictor是用于预测的一个对象
    predictor = yolo_predictor(config.obj_threshold, config.nms_threshold, config.classes_path, config.anchors_path)
    with tf.Session() as sess:
        #---------------------------------------#
        #   图片预测
        #---------------------------------------#
        if yolo_weights is not None:
            with tf.variable_scope('predict'):
                boxes, scores, classes = predictor.predict(input_image, input_image_shape)
            # 载入模型
            load_op = load_weights(tf.global_variables(scope = 'predict'), weights_file = yolo_weights)
            sess.run(load_op)
            
            # 进行预测
            out_boxes, out_scores, out_classes = sess.run(
            [boxes, scores, classes],
            feed_dict={
    
    
                # image_data这个resize过
                input_image: image_data,
                # 以y、x的方式传入
                input_image_shape: [image.size[1], image.size[0]]
            })
        else:
            boxes, scores, classes = predictor.predict(input_image, input_image_shape)
            saver = tf.train.Saver()
            saver.restore(sess, model_path)
            out_boxes, out_scores, out_classes = sess.run(
            [boxes, scores, classes],
            feed_dict={
    
    
                input_image: image_data,
                input_image_shape: [image.size[1], image.size[0]]
            })

        #---------------------------------------#
        #   画框
        #---------------------------------------#
        # 找到几个box,打印
        print('Found {} boxes for {}'.format(len(out_boxes), 'img'))
        font = ImageFont.truetype(font = 'font/FiraMono-Medium.otf', size = np.floor(3e-2 * image.size[1] + 0.5).astype('int32'))
        
        # 厚度
        thickness = (image.size[0] + image.size[1]) // 300

        for i, c in reversed(list(enumerate(out_classes))):
            # 获得预测名字,box和分数
            predicted_class = predictor.class_names[c]
            box = out_boxes[i]
            score = out_scores[i]

            # 打印
            label = '{} {:.2f}'.format(predicted_class, score)

            # 用于画框框和文字
            draw = ImageDraw.Draw(image)
            # textsize用于获得写字的时候,按照这个字体,要多大的框
            label_size = draw.textsize(label, font)

            # 获得四个边
            top, left, bottom, right = box
            top = max(0, np.floor(top + 0.5).astype('int32'))
            left = max(0, np.floor(left + 0.5).astype('int32'))
            bottom = min(image.size[1]-1, np.floor(bottom + 0.5).astype('int32'))
            right = min(image.size[0]-1, np.floor(right + 0.5).astype('int32'))
            print(label, (left, top), (right, bottom))
            print(label_size)
            
            if top - label_size[1] >= 0:
                text_origin = np.array([left, top - label_size[1]])
            else:
                text_origin = np.array([left, top + 1])

            # My kingdom for a good redistributable image drawing library.
            for i in range(thickness):
                draw.rectangle(
                    [left + i, top + i, right - i, bottom - i],
                    outline = predictor.colors[c])
            draw.rectangle(
                [tuple(text_origin), tuple(text_origin + label_size)],
                fill = predictor.colors[c])
            draw.text(text_origin, label, fill=(0, 0, 0), font=font)
            del draw
        image.show()
        image.save('./img/result1.jpg')

if __name__ == '__main__':

    # 当使用yolo3自带的weights的时候
    if config.pre_train_yolo3 == True:
        detect(config.image_file, config.model_dir, config.yolo3_weights_path)

    # 当使用模型的时候
    else:
        detect(config.image_file, config.model_dir)

5.4.4 gen_anchors.py

import numpy as np
import matplotlib.pyplot as plt

def convert_coco_bbox(size, box):
    """
    Introduction
    ------------
        计算box的长宽和原始图像的长宽比值
    Parameters
    ----------
        size: 原始图像大小
        box: 标注box的信息
    Returns
        x, y, w, h 标注box和原始图像的比值
    """
    dw = 1. / size[0]
    dh = 1. / size[1]
    x = (box[0] + box[2]) / 2.0 - 1
    y = (box[1] + box[3]) / 2.0 - 1
    w = box[2]
    h = box[3]
    x = x * dw
    w = w * dw
    y = y * dh
    h = h * dh
    return x, y, w, h


def box_iou(boxes, clusters):
    """
    Introduction
    ------------
        计算每个box和聚类中心的距离值
    Parameters
    ----------
        boxes: 所有的box数据
        clusters: 聚类中心
    """
    box_num = boxes.shape[0]
    cluster_num = clusters.shape[0]
    box_area = boxes[:, 0] * boxes[:, 1]
    #每个box的面积重复9次,对应9个聚类中心
    box_area = box_area.repeat(cluster_num)
    box_area = np.reshape(box_area, [box_num, cluster_num])

    cluster_area = clusters[:, 0] * clusters[:, 1]
    cluster_area = np.tile(cluster_area, [1, box_num])
    cluster_area = np.reshape(cluster_area, [box_num, cluster_num])

    #这里计算两个矩形的iou,默认所有矩形的左上角坐标都是在原点,然后计算iou,因此只需取长宽最小值相乘就是重叠区域的面积
    boxes_width = np.reshape(boxes[:, 0].repeat(cluster_num), [box_num, cluster_num])
    clusters_width = np.reshape(np.tile(clusters[:, 0], [1, box_num]), [box_num, cluster_num])
    min_width = np.minimum(clusters_width, boxes_width)

    boxes_high = np.reshape(boxes[:, 1].repeat(cluster_num), [box_num, cluster_num])
    clusters_high = np.reshape(np.tile(clusters[:, 1], [1, box_num]), [box_num, cluster_num])
    min_high = np.minimum(clusters_high, boxes_high)

    iou = np.multiply(min_high, min_width) / (box_area + cluster_area - np.multiply(min_high, min_width))
    return iou


def avg_iou(boxes, clusters):
    """
    Introduction
    ------------
        计算所有box和聚类中心的最大iou均值作为准确率
    Parameters
    ----------
        boxes: 所有的box
        clusters: 聚类中心
    Returns
    -------
        accuracy: 准确率
    """
    return np.mean(np.max(box_iou(boxes, clusters), axis =1))


def Kmeans(boxes, cluster_num, iteration_cutoff = 25, function = np.median):
    """
    Introduction
    ------------
        根据所有box的长宽进行Kmeans聚类
    Parameters
    ----------
        boxes: 所有的box的长宽
        cluster_num: 聚类的数量
        iteration_cutoff: 当准确率不再降低多少轮停止迭代
        function: 聚类中心更新的方式
    Returns
    -------
        clusters: 聚类中心box的大小
    """
    boxes_num = boxes.shape[0]
    best_average_iou = 0
    best_avg_iou_iteration = 0
    best_clusters = []
    anchors = []
    np.random.seed()
    # 随机选择所有boxes中的box作为聚类中心
    clusters = boxes[np.random.choice(boxes_num, cluster_num, replace = False)]
    count = 0
    while True:
        distances = 1. - box_iou(boxes, clusters)
        boxes_iou = np.min(distances, axis=1)
        # 获取每个box距离哪个聚类中心最近
        current_box_cluster = np.argmin(distances, axis=1)
        average_iou = np.mean(1. - boxes_iou)
        if average_iou > best_average_iou:
            best_average_iou = average_iou
            best_clusters = clusters
            best_avg_iou_iteration = count
        # 通过function的方式更新聚类中心
        for cluster in range(cluster_num):
            clusters[cluster] = function(boxes[current_box_cluster == cluster], axis=0)
        if count >= best_avg_iou_iteration + iteration_cutoff:
            break
        print("Sum of all distances (cost) = {}".format(np.sum(boxes_iou)))
        print("iter: {} Accuracy: {:.2f}%".format(count, avg_iou(boxes, clusters) * 100))
        count += 1
    for cluster in best_clusters:
        anchors.append([round(cluster[0] * 416), round(cluster[1] * 416)])
    return anchors, best_average_iou



def load_cocoDataset(annfile):
    """
    Introduction
    ------------
        读取coco数据集的标注信息
    Parameters
    ----------
        datasets: 数据集名字列表
    """
    data = []
    coco = COCO(annfile)
    cats = coco.loadCats(coco.getCatIds())
    coco.loadImgs()
    base_classes = {
    
    cat['id'] : cat['name'] for cat in cats}
    imgId_catIds = [coco.getImgIds(catIds = cat_ids) for cat_ids in base_classes.keys()]
    image_ids = [img_id for img_cat_id in imgId_catIds for img_id in img_cat_id ]
    for image_id in image_ids:
        annIds = coco.getAnnIds(imgIds = image_id)
        anns = coco.loadAnns(annIds)
        img = coco.loadImgs(image_id)[0]
        image_width = img['width']
        image_height = img['height']

        for ann in anns:
            box = ann['bbox']
            bb = convert_coco_bbox((image_width, image_height), box)
            data.append(bb[2:])
    return np.array(data)


def process(dataFile, cluster_num, iteration_cutoff = 25, function = np.median):
    """
    Introduction
    ------------
        主处理函数
    Parameters
    ----------
        dataFile: 数据集的标注文件
        cluster_num: 聚类中心数目
        iteration_cutoff: 当准确率不再降低多少轮停止迭代
        function: 聚类中心更新的方式
    """
    last_best_iou = 0
    last_anchors = []
    boxes = load_cocoDataset(dataFile)
    box_w = boxes[:1000, 0]
    box_h = boxes[:1000, 1]
    plt.scatter(box_h, box_w, c = 'r')
    anchors = Kmeans(boxes, cluster_num, iteration_cutoff, function)
    plt.scatter(anchors[:,0], anchors[:, 1], c = 'b')
    plt.show()
    for _ in range(100):
        anchors, best_iou = Kmeans(boxes, cluster_num, iteration_cutoff, function)
        if best_iou > last_best_iou:
            last_anchors = anchors
            last_best_iou = best_iou
            print("anchors: {}, avg iou: {}".format(last_anchors, last_best_iou))
    print("final anchors: {}, avg iou: {}".format(last_anchors, last_best_iou))



if __name__ == '__main__':
    process('./annotations/instances_train2014.json', 9)

5.4.5 utilidades.py

import json
import numpy as np
import tensorflow as tf
from PIL import Image
from collections import defaultdict

def load_weights(var_list, weights_file):
    """
    Introduction
    ------------
        加载预训练好的darknet53权重文件
    Parameters
    ----------
        var_list: 赋值变量名
        weights_file: 权重文件
    Returns
    -------
        assign_ops: 赋值更新操作
    """
    with open(weights_file, "rb") as fp:
        _ = np.fromfile(fp, dtype=np.int32, count=5)

        weights = np.fromfile(fp, dtype=np.float32)

    ptr = 0
    i = 0
    assign_ops = []
    while i < len(var_list) - 1:
        var1 = var_list[i]
        var2 = var_list[i + 1]
        # do something only if we process conv layer
        if 'conv2d' in var1.name.split('/')[-2]:
            # check type of next layer
            if 'batch_normalization' in var2.name.split('/')[-2]:
                # load batch norm params
                gamma, beta, mean, var = var_list[i + 1:i + 5]
                batch_norm_vars = [beta, gamma, mean, var]
                for var in batch_norm_vars:
                    shape = var.shape.as_list()
                    num_params = np.prod(shape)
                    var_weights = weights[ptr:ptr + num_params].reshape(shape)
                    ptr += num_params
                    assign_ops.append(tf.assign(var, var_weights, validate_shape=True))

                # we move the pointer by 4, because we loaded 4 variables
                i += 4
            elif 'conv2d' in var2.name.split('/')[-2]:
                # load biases
                bias = var2
                bias_shape = bias.shape.as_list()
                bias_params = np.prod(bias_shape)
                bias_weights = weights[ptr:ptr + bias_params].reshape(bias_shape)
                ptr += bias_params
                assign_ops.append(tf.assign(bias, bias_weights, validate_shape=True))

                # we loaded 1 variable
                i += 1
            # we can load weights of conv layer
            shape = var1.shape.as_list()
            num_params = np.prod(shape)

            var_weights = weights[ptr:ptr + num_params].reshape((shape[3], shape[2], shape[0], shape[1]))
            # remember to transpose to column-major
            var_weights = np.transpose(var_weights, (2, 3, 1, 0))
            ptr += num_params
            assign_ops.append(tf.assign(var1, var_weights, validate_shape=True))
            i += 1

    return assign_ops


def letterbox_image(image, size):
    """
    Introduction
    ------------
        对预测输入图像进行缩放,按照长宽比进行缩放,不足的地方进行填充
    Parameters
    ----------
        image: 输入图像
        size: 图像大小
    Returns
    -------
        boxed_image: 缩放后的图像
    """
    image_w, image_h = image.size
    w, h = size
    new_w = int(image_w * min(w*1.0/image_w, h*1.0/image_h))
    new_h = int(image_h * min(w*1.0/image_w, h*1.0/image_h))
    resized_image = image.resize((new_w,new_h), Image.BICUBIC)

    boxed_image = Image.new('RGB', size, (128, 128, 128))
    boxed_image.paste(resized_image, ((w-new_w)//2,(h-new_h)//2))
    return boxed_image


def draw_box(image, bbox):
    """
    Introduction
    ------------
        通过tensorboard把训练数据可视化
    Parameters
    ----------
        image: 训练数据图片
        bbox: 训练数据图片中标记box坐标
    """
    xmin, ymin, xmax, ymax, label = tf.split(value = bbox, num_or_size_splits = 5, axis=2)
    height = tf.cast(tf.shape(image)[1], tf.float32)
    weight = tf.cast(tf.shape(image)[2], tf.float32)
    new_bbox = tf.concat([tf.cast(ymin, tf.float32) / height, tf.cast(xmin, tf.float32) / weight, tf.cast(ymax, tf.float32) / height, tf.cast(xmax, tf.float32) / weight], 2)
    new_image = tf.image.draw_bounding_boxes(image, new_bbox)
    tf.summary.image('input', new_image)


def voc_ap(rec, prec):
    """
    --- Official matlab code VOC2012---
    mrec=[0 ; rec ; 1];
    mpre=[0 ; prec ; 0];
    for i=numel(mpre)-1:-1:1
        mpre(i)=max(mpre(i),mpre(i+1));
    end
    i=find(mrec(2:end)~=mrec(1:end-1))+1;
    ap=sum((mrec(i)-mrec(i-1)).*mpre(i));
    """
    rec.insert(0, 0.0)  # insert 0.0 at begining of list
    rec.append(1.0)  # insert 1.0 at end of list
    mrec = rec[:]
    prec.insert(0, 0.0)  # insert 0.0 at begining of list
    prec.append(0.0)  # insert 0.0 at end of list
    mpre = prec[:]
    for i in range(len(mpre) - 2, -1, -1):
        mpre[i] = max(mpre[i], mpre[i + 1])

    i_list = []
    for i in range(1, len(mrec)):
        if mrec[i] != mrec[i - 1]:
            i_list.append(i)
    ap = 0.0
    for i in i_list:
        ap += ((mrec[i] - mrec[i - 1]) * mpre[i])
    return ap, mrec, mpre

5.4.6 Guión de predicción

import os
import config
import random
import colorsys
import numpy as np
import tensorflow as tf
from model.yolo3_model import yolo


class yolo_predictor:
    def __init__(self, obj_threshold, nms_threshold, classes_file, anchors_file):
        """
        Introduction
        ------------
            初始化函数
        Parameters
        ----------
            obj_threshold: 目标检测为物体的阈值
            nms_threshold: nms阈值
        """
        self.obj_threshold = obj_threshold
        self.nms_threshold = nms_threshold
        # 预读取
        self.classes_path = classes_file
        self.anchors_path = anchors_file
        # 读取种类名称
        self.class_names = self._get_class()
        # 读取先验框
        self.anchors = self._get_anchors()

        # 画框框用
        hsv_tuples = [(x / len(self.class_names), 1., 1.)for x in range(len(self.class_names))]

        self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
        self.colors = list(map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), self.colors))
        random.seed(10101)
        random.shuffle(self.colors)
        random.seed(None)

    def _get_class(self):
        """
        Introduction
        ------------
            读取类别名称
        """
        classes_path = os.path.expanduser(self.classes_path)
        with open(classes_path) as f:
            class_names = f.readlines()
        class_names = [c.strip() for c in class_names]
        return class_names

    def _get_anchors(self):
        """
        Introduction
        ------------
            读取anchors数据
        """
        anchors_path = os.path.expanduser(self.anchors_path)
        with open(anchors_path) as f:
            anchors = f.readline()
            anchors = [float(x) for x in anchors.split(',')]
            anchors = np.array(anchors).reshape(-1, 2)
        return anchors
    
    #---------------------------------------#
    #   对三个特征层解码
    #   进行排序并进行非极大抑制
    #---------------------------------------#
    def boxes_and_scores(self, feats, anchors, classes_num, input_shape, image_shape):
        """
        Introduction
        ------------
            将预测出的box坐标转换为对应原图的坐标,然后计算每个box的分数
        Parameters
        ----------
            feats: yolo输出的feature map
            anchors: anchor的位置
            class_num: 类别数目
            input_shape: 输入大小
            image_shape: 图片大小
        Returns
        -------
            boxes: 物体框的位置
            boxes_scores: 物体框的分数,为置信度和类别概率的乘积
        """
        # 获得特征
        box_xy, box_wh, box_confidence, box_class_probs = self._get_feats(feats, anchors, classes_num, input_shape)
        # 寻找在原图上的位置
        boxes = self.correct_boxes(box_xy, box_wh, input_shape, image_shape)
        boxes = tf.reshape(boxes, [-1, 4])
        # 获得置信度box_confidence * box_class_probs
        box_scores = box_confidence * box_class_probs
        box_scores = tf.reshape(box_scores, [-1, classes_num])
        return boxes, box_scores

    # 获得在原图上框的位置
    def correct_boxes(self, box_xy, box_wh, input_shape, image_shape):
        """
        Introduction
        ------------
            计算物体框预测坐标在原图中的位置坐标
        Parameters
        ----------
            box_xy: 物体框左上角坐标
            box_wh: 物体框的宽高
            input_shape: 输入的大小
            image_shape: 图片的大小
        Returns
        -------
            boxes: 物体框的位置
        """
        box_yx = box_xy[..., ::-1]
        box_hw = box_wh[..., ::-1]
        # 416,416
        input_shape = tf.cast(input_shape, dtype = tf.float32)
        # 实际图片的大小
        image_shape = tf.cast(image_shape, dtype = tf.float32)

        new_shape = tf.round(image_shape * tf.reduce_min(input_shape / image_shape))

        offset = (input_shape - new_shape) / 2. / input_shape
        scale = input_shape / new_shape
        box_yx = (box_yx - offset) * scale
        box_hw *= scale

        box_mins = box_yx - (box_hw / 2.)
        box_maxes = box_yx + (box_hw / 2.)
        boxes = tf.concat([
            box_mins[..., 0:1],
            box_mins[..., 1:2],
            box_maxes[..., 0:1],
            box_maxes[..., 1:2]
        ], axis = -1)
        boxes *= tf.concat([image_shape, image_shape], axis = -1)
        return boxes

    # 其实是解码的过程
    def _get_feats(self, feats, anchors, num_classes, input_shape):
        """
        Introduction
        ------------
            根据yolo最后一层的输出确定bounding box
        Parameters
        ----------
            feats: yolo模型最后一层输出
            anchors: anchors的位置
            num_classes: 类别数量
            input_shape: 输入大小
        Returns
        -------
            box_xy, box_wh, box_confidence, box_class_probs
        """
        num_anchors = len(anchors)
        anchors_tensor = tf.reshape(tf.constant(anchors, dtype=tf.float32), [1, 1, 1, num_anchors, 2])
        grid_size = tf.shape(feats)[1:3]
        predictions = tf.reshape(feats, [-1, grid_size[0], grid_size[1], num_anchors, num_classes + 5])

        # 这里构建13*13*1*2的矩阵,对应每个格子加上对应的坐标
        grid_y = tf.tile(tf.reshape(tf.range(grid_size[0]), [-1, 1, 1, 1]), [1, grid_size[1], 1, 1])
        grid_x = tf.tile(tf.reshape(tf.range(grid_size[1]), [1, -1, 1, 1]), [grid_size[0], 1, 1, 1])
        grid = tf.concat([grid_x, grid_y], axis = -1)
        grid = tf.cast(grid, tf.float32)

        # 将x,y坐标归一化,相对网格的位置
        box_xy = (tf.sigmoid(predictions[..., :2]) + grid) / tf.cast(grid_size[::-1], tf.float32)
        # 将w,h也归一化
        box_wh = tf.exp(predictions[..., 2:4]) * anchors_tensor / tf.cast(input_shape[::-1], tf.float32)
        box_confidence = tf.sigmoid(predictions[..., 4:5])
        box_class_probs = tf.sigmoid(predictions[..., 5:])
        return box_xy, box_wh, box_confidence, box_class_probs
        
    def eval(self, yolo_outputs, image_shape, max_boxes = 20):
        """
        Introduction
        ------------
            根据Yolo模型的输出进行非极大值抑制,获取最后的物体检测框和物体检测类别
        Parameters
        ----------
            yolo_outputs: yolo模型输出
            image_shape: 图片的大小
            max_boxes:  最大box数量
        Returns
        -------
            boxes_: 物体框的位置
            scores_: 物体类别的概率
            classes_: 物体类别
        """
        # 每一个特征层对应三个先验框
        anchor_mask = [[6, 7, 8], [3, 4, 5], [0, 1, 2]]
        boxes = []
        box_scores = []
        # inputshape是416x416
        # image_shape是实际图片的大小
        input_shape = tf.shape(yolo_outputs[0])[1 : 3] * 32
        # 对三个特征层的输出获取每个预测box坐标和box的分数,score = 置信度x类别概率
        #---------------------------------------#
        #   对三个特征层解码
        #   获得分数和框的位置
        #---------------------------------------#
        for i in range(len(yolo_outputs)):
            _boxes, _box_scores = self.boxes_and_scores(yolo_outputs[i], self.anchors[anchor_mask[i]], len(self.class_names), input_shape, image_shape)
            boxes.append(_boxes)
            box_scores.append(_box_scores)
        # 放在一行里面便于操作
        boxes = tf.concat(boxes, axis = 0)
        box_scores = tf.concat(box_scores, axis = 0)

        mask = box_scores >= self.obj_threshold
        max_boxes_tensor = tf.constant(max_boxes, dtype = tf.int32)
        boxes_ = []
        scores_ = []
        classes_ = []

        #---------------------------------------#
        #   1、取出每一类得分大于self.obj_threshold
        #   的框和得分
        #   2、对得分进行非极大抑制
        #---------------------------------------#
        # 对每一个类进行判断
        for c in range(len(self.class_names)):
            # 取出所有类为c的box
            class_boxes = tf.boolean_mask(boxes, mask[:, c])
            # 取出所有类为c的分数
            class_box_scores = tf.boolean_mask(box_scores[:, c], mask[:, c])
            # 非极大抑制
            nms_index = tf.image.non_max_suppression(class_boxes, class_box_scores, max_boxes_tensor, iou_threshold = self.nms_threshold)
            
            # 获取非极大抑制的结果
            class_boxes = tf.gather(class_boxes, nms_index)
            class_box_scores = tf.gather(class_box_scores, nms_index)
            classes = tf.ones_like(class_box_scores, 'int32') * c

            boxes_.append(class_boxes)
            scores_.append(class_box_scores)
            classes_.append(classes)
        boxes_ = tf.concat(boxes_, axis = 0)
        scores_ = tf.concat(scores_, axis = 0)
        classes_ = tf.concat(classes_, axis = 0)
        return boxes_, scores_, classes_


 
    #---------------------------------------#
    #   predict用于预测,分三步
    #   1、建立yolo对象
    #   2、获得预测结果
    #   3、对预测结果进行处理
    #---------------------------------------#
    def predict(self, inputs, image_shape):
        """
        Introduction
        ------------
            构建预测模型
        Parameters
        ----------
            inputs: 处理之后的输入图片
            image_shape: 图像原始大小
        Returns
        -------
            boxes: 物体框坐标
            scores: 物体概率值
            classes: 物体类别
        """
        model = yolo(config.norm_epsilon, config.norm_decay, self.anchors_path, self.classes_path, pre_train = False)
        # yolo_inference用于获得网络的预测结果
        output = model.yolo_inference(inputs, config.num_anchors // 3, config.num_classes, training = False)
        boxes, scores, classes = self.eval(output, image_shape, max_boxes = 20)
        return boxes, scores, classes

6. Expansión-SSD

inserte la descripción de la imagen aquí

SSD también es una red de capas de múltiples funciones, que tiene un total de 11 capas y la primera mitad de la estructura es VGG16:

  1. Primero, a través de múltiples capas convolucionales 3X3, 5 agrupaciones máximas con un tamaño de paso de 2 para extraer características, se forman 5 bloques y el cuarto bloque se usa para extraer objetivos pequeños (la característica de los objetivos grandes se conserva después de múltiples convoluciones) es mejor, pequeña Las características de destino desaparecerán y las características de destino pequeñas deberán extraerse en una capa relativamente temprana).
  2. Realice una dilatación de expansión del núcleo de convolución.
  3. Lea las características del séptimo Bloque7.
  4. Utilice la convolución 1x1 y 3x3 para extraer características respectivamente, use el tamaño de paso 2 cuando la convolución 3x3, reduzca la cantidad de características y obtenga las características del octavo Bloque8.
  5. Repita el paso 4 para obtener las características de las capas convolucionales 9, 10 y 11.

Supongo que te gusta

Origin blog.csdn.net/m0_63260018/article/details/132268168
Recomendado
Clasificación