Principe de l'algorithme YOLOv3 et implémentation de la palette

Principe de l'algorithme YOLOv3 et implémentation de la palette

Notes d'étude YOLOv3 organisées selon le cours d'introduction à base zéro de la pagaie.

1. Vue d'ensemble

L'algorithme classique de la série R-CNN est également appelé algorithme de détection de cible en deux étapes. Étant donné que cette méthode doit d'abord générer des zones candidates, puis classer et prédire les coordonnées de position des zones candidates, la vitesse de l'algorithme est très lente. À cela correspond l'algorithme de détection en une seule étape représenté par l'algorithme YOLO, qui ne nécessite qu'un seul réseau pour générer simultanément des zones candidates et prédire la catégorie et les coordonnées de position des objets.

Différent de la série d'algorithmes R-CNN, YOLOv3 utilise une structure de réseau unique, qui peut prédire la catégorie et l'emplacement de l'objet tout en générant la zone candidate, et n'a pas besoin d'être divisé en deux étapes pour terminer la tâche de détection. De plus, le nombre de trames de prédiction générées par l'algorithme YOLOv3 est bien inférieur à celui de Faster R-CNN. Chaque trame réelle dans Faster R-CNN peut correspondre à plusieurs régions candidates positives, tandis que chaque trame réelle dans YOLOv3 correspond à une seule région candidate positive. Ces caractéristiques rendent l'algorithme YOLOv3 plus rapide et peuvent atteindre le niveau de réponse en temps réel.

Joseph Redmon et d'autres ont proposé l'algorithme YOLO (You Only Look Once, YOLO) en 2015, communément appelé YOLOv1 ; en 2016, ils ont amélioré l'algorithme et proposé la version YOLOv2 ; en 2018, la version YOLOv3 a été développée.

2. Idée de conception de modèle YOLOv3

  • Pendant la phase de formation :
  1. Générez une série de régions candidates sur l'image selon certaines règles, puis marquez les régions candidates en fonction de la relation de position entre ces régions candidates et le cadre réel de l'objet sur l'image. Les régions candidates qui sont suffisamment proches de la boîte de vérité au sol seront marquées comme des échantillons positifs, et la position de la boîte de vérité au sol sera utilisée comme cible de position de l'échantillon positif. Les régions candidates qui s'écartent du cadre réel sont marquées comme des échantillons négatifs, et les échantillons négatifs n'ont pas besoin de prédire la position ou la catégorie.
  2. Utilisez un réseau neuronal convolutionnel pour extraire les caractéristiques de l'image et prédire l'emplacement et la catégorie des régions candidates. De cette manière, chaque trame de prédiction peut être considérée comme un échantillon, et la valeur de l'étiquette est obtenue en marquant la trame réelle par rapport à sa position et sa catégorie, en prédisant sa position et sa catégorie à travers le modèle de réseau et en comparant la valeur de prédiction du réseau avec la valeur de l'étiquette, puis Une fonction de perte peut être construite.
  • Dans l'étape de prédiction, la trame de prédiction est calculée en fonction de la trame d'ancrage prédéfinie et des caractéristiques d'image extraites, puis la suppression de la valeur non maximale multiclasse est utilisée pour éliminer les trames qui se chevauchent afin d'obtenir le résultat final.

Le flux de l'algorithme de YOLOv3 est illustré à la figure 1.


Figure 1 : Schéma de conception de détection de cible

Ensuite, l'algorithme YOLOv3 est profondément compris à partir des deux dimensions de la formation et de la prédiction.

3. Formation au modèle YOLOv3

Le processus de formation de l'algorithme YOLOv3 peut être divisé en deux parties, comme le montre la figure 2.

  • Générez une série de régions candidates sur l'image selon certaines règles, puis marquez les régions candidates en fonction de la relation de position entre ces régions candidates et le cadre réel de l'objet sur l'image. Les régions candidates qui sont suffisamment proches de la boîte de vérité au sol seront marquées comme des échantillons positifs, et la position de la boîte de vérité au sol sera utilisée comme cible de position de l'échantillon positif. Les régions candidates qui s'écartent du cadre réel sont marquées comme des échantillons négatifs, et les échantillons négatifs n'ont pas besoin de prédire la position ou la catégorie.
  • Utilisez un réseau neuronal convolutionnel pour extraire les caractéristiques de l'image et prédire l'emplacement et la catégorie des régions candidates. De cette manière, chaque trame de prédiction peut être considérée comme un échantillon, et la valeur de l'étiquette est obtenue en marquant la trame réelle par rapport à sa position et sa catégorie, en prédisant sa position et sa catégorie à travers le modèle de réseau et en comparant la valeur de prédiction du réseau avec la valeur de l'étiquette, puis Une fonction de perte peut être construite.



Figure 2 : Organigramme de formation de l'algorithme YOLOv3

  • Le côté gauche de la figure 2 est l'image d'entrée. Le processus illustré dans la partie supérieure consiste à utiliser le réseau neuronal convolutif pour extraire les caractéristiques de l'image. Au fur et à mesure que le réseau continue de se propager, la taille de la carte des caractéristiques devient de plus en plus petite. , et chaque pixel représente une image plus abstraite. Le modèle d'entités jusqu'à la carte d'entités en sortie, sa taille est réduite à 1 de l'image d'origine 32 \frac{1}{32}3 21
  • La partie inférieure de la figure 2 décrit le processus de génération des régions candidates. Tout d'abord, l'image d'origine est divisée en plusieurs petits carrés, et la taille de chaque petit carré est de 32 × 32 32 \times 323 2×3 2 , puis générez une série de boîtes d'ancrage avec chaque petit carré comme centre, et l'image entière sera couverte par les boîtes d'ancrage. Une trame de prédiction correspondante est générée sur la base de chaque trame d'ancrage, et ces trames de prédiction sont marquées selon la relation de position entre la trame d'ancrage et la trame de prédiction et la trame réelle de l'objet sur l'image.
  • La sortie de la carte des caractéristiques dans la branche supérieure est associée à l'étiquette de boîte de prédiction générée dans la branche inférieure, une fonction de perte est créée et le processus d'apprentissage de bout en bout est lancé.

Ensuite, le principe de l'algorithme de chaque nœud du processus sera présenté en détail.

3.1 Générer des régions candidates

Comment générer des régions candidates est la conception de base du modèle de détection. À l'heure actuelle, la plupart des modèles basés sur des réseaux de neurones convolutifs adoptent les méthodes suivantes :

  1. Une série de boîtes d'ancrage avec des positions fixes sont générées sur l'image selon certaines règles, et ces boîtes d'ancrage sont considérées comme des régions candidates possibles.
  2. Prédire si le cadre d'ancrage contient l'objet cible. S'il contient l'objet cible, il est également nécessaire de prédire la catégorie de l'objet contenu et l'ampleur de l'ajustement du cadre de prédiction par rapport à la position du cadre d'ancrage.

1. Générer la boîte d'ancrage

Dans l'algorithme YOLOv3, l'image originale est divisée en m × nm\times nm×n zones, commele montre la figure 3, la hauteur de l'image d'origineH = 640 H = 640H=6 4 0 , largeurL = 480 L=480O=4 8 0 , si nous choisissons la taille de la petite zone comme32 × 32 32 \times 323 2×3 2 , puismmm etnnn sont respectivement :

m = 640 32 = 20 m = \frac{640}{32} = 20m=3 26 4 0=2 0

n = 480 32 = 15 n = \frac{480}{32} = 15n=3 24 8 0=1 5

C'est-à-dire que nous avons divisé l'image originale en petites zones carrées avec 20 lignes et 15 colonnes.



Figure 3 : Diviser l'image en plusieurs petits carrés de 32 x 32

L'algorithme YOLOv3 génère une série de boîtes d'ancrage au centre de chaque région. Pour la commodité de l'affichage, nous dessinons uniquement la boîte d'ancrage générée près de la position du petit carré dans la dixième rangée et la quatrième colonne de la figure, comme illustré à la figure 4.



Figure 4 : Générez 3 boîtes d'ancrage dans la petite zone carrée à la ligne 10, colonne 4


illustrer:

Ici, afin de correspondre aux numéros du programme, le numéro de la ligne du haut est la ligne 0 et le numéro de la colonne la plus à gauche est la colonne 0.


2. Générer la boîte de prédiction

Il a été souligné précédemment que la position de la boîte d'ancrage est fixe et qu'il est impossible de coïncider avec la boîte englobante de l'objet. Il est nécessaire d'affiner la position en fonction de la boîte d'ancrage pour générer une prédiction. boîte. Le cadre de prédiction aura une position centrale et une taille différentes par rapport au cadre d'ancrage. Comment obtenir le cadre de prédiction ? Considérons d'abord comment générer ses coordonnées de position centrale.

Par exemple, une boîte d'ancrage est générée au centre de la petite zone carrée de la 10e ligne et de la 4e colonne de la figure ci-dessus, comme indiqué dans la boîte verte en pointillés. Prendre la largeur du petit carré comme unité de longueur,

Les coordonnées de position du coin supérieur gauche de cette petite zone carrée sont :
cx = 4 c_x = 4cx=4
cy = 10 c_y = 10cy=1 0

Les coordonnées du centre de la zone de cette boîte d'ancrage sont :
center_x = cx + 0,5 = 4,5 center\_x = c_x + 0,5 = 4,5centre _ x _ _ _ _ _=cx+0 . 5=4 . 5
centre _ y = cy + 0,5 = 10,5 centre\_y = c_y + 0,5 = 10,5c e n t e r _ y=cy+0 . 5=1 0 . 5

Les coordonnées du centre de la boîte de prédiction peuvent être générées de la manière suivante :
bx = cx + σ ( tx ) b_x = c_x + \sigma(t_x)bx=cx+s ( tx)
par = cy + σ ( ty ) b_y = c_y + \sigma(t_y)by=cy+s ( ty)

tx t_xtxty t_ytyest un nombre réel, σ ( x ) \sigma(x)σ ( x ) est la fonction sigmoïde que nous avons apprise précédemment, qui est définie comme suit :

σ ( X ) = 1 1 + exp ( − X ) \sigma(x) = \frac{1}{1 + exp(-x)}σ ( x )=1+e x p ( - x )1

Puisque la valeur de la fonction de Sigmoid est comprise entre 0 ∼ 1 0 \thicksim 101 , de sorte que le point central de la boîte de prédiction calculée par la formule ci-dessus se situe toujours dans la petite zone de la dixième ligne et de la quatrième colonne.

tx = ty = 0 t_x=t_y=0tx=ty=0 ,bx = cx + 0,5 b_x = c_x + 0,5bx=cx+0 . 5par = cy + 0,5 b_y = c_y + 0,5by=cy+0,5 , le centre du cadre de prédiction coïncide avec le centre du cadre d'ancrage, qui sont tous deux le centre de la petite zone .

La taille de la boîte d'ancrage est prédéfinie et peut être considérée comme un hyperparamètre dans le modèle. La taille de la boîte d'ancrage dessinée dans la figure ci-dessous est

ph = 350 p_h = 350ph=3 5 0
pw = 250 p_w = 250pw=2 5 0

À ce stade, la taille du cadre prédit peut être générée par la formule suivante :

bh = pheth b_h = p_h e^{t_h}bh=pheth
pc = pwetw b_w = p_w e^{t_w}bw=pwetw

如果tx = ty = 0 , th = tw = 0 t_x=t_y=0, t_h=t_w=0tx=ty=0 ,th=tw=0 , la boîte de prédiction coïncide avec la boîte d'ancrage.

Si donné tx , ty , th , tw t_x, t_y, t_h, t_wtx,ty,th,twL'assignation aléatoire est la suivante :

tx = 0,2 , ty = 0,3 , tw = 0,1 , th = − 0,12 t_x = 0,2, t_y = 0,3, t_w = 0,1, t_h = -0,12tx=0 . 2 ,ty=0 . 3 ,tw=0 . 1 ,th=0 . 1 2

Ensuite, les coordonnées du cadre de prédiction peuvent être obtenues sous la forme (154,98, 357,44, 276,29, 310,42), comme indiqué dans le cadre bleu de la figure 5.



Figure 5 : Générer des boîtes de prédiction


Description :
Les coordonnées ici sont xywh xywhformat xywh . _ _


Ici nous demandons : quand tx , ty , tw , th t_x, t_y, t_w, t_htx,ty,tw,thLorsque la valeur est définie, l'image prédite peut-elle coïncider avec l'image réelle ? Pour répondre à la question, il suffit de mettre bx , by , bh , bw b_x, b_y, b_h, b_w dans les coordonnées de la case de prédiction ci-dessusbx,by,bh,bwDéfinissez-le comme la position de la vraie boîte, puis vous pourrez résoudre le ttla valeur de t .

令:
σ ( tx ∗ ) + cx = gtx \sigma(t^*_x) + c_x = gt_xs ( tX)+cx=g tx
σ ( ty ∗ ) + cy = gty \sigma(t^*_y) + c_y = gt_ys ( ty)+cy=g ty
pwetw ∗ = gth p_w e^{t^*_w} = gt_hpwetw=g th
pheth ∗ = gtw p_h e^{t^*_h} = gt_wpheth=g tw

可以求解出:( tx ∗ , ty ∗ , tw ∗ , th ∗ ) (t^*_x, t^*_y, t^*_w, t^*_h)( tX,ty,tw,th)

si ttt est la valeur de sortie prédite par le réseau,t ∗ t^*t comme valeur cible, et l'écart entre elles comme fonction de perte, un problème de régression peut être établi, en apprenant les paramètres du réseau, de sorte quettt est suffisamment proche det ∗ t^*t , de sorte que les coordonnées de position et la taille du cadre de prédiction puissent être résolues.

Le cadre de prédiction peut être considéré comme un réglage fin basé sur le cadre d'ancrage. Chaque cadre d'ancrage aura un cadre de prédiction correspondant. Nous devons déterminer le tx , ty , tw , th t_x, t_y, t_w, t_htx,ty,tw,th, de manière à calculer la position et la forme de la boîte prédite correspondant à la boîte d'ancrage.

3. Étiquetez la zone candidate

Dans YOLOv3, chaque région générera 3 boîtes d'ancrage de formes différentes, et chaque boîte d'ancrage est une région candidate possible. Pour ces régions candidates, nous devons comprendre les éléments suivants :

  • La question de savoir si la boîte d'ancrage contient des objets peut être considérée comme un problème à deux catégories, qui est représenté par l'étiquette d'objectivité. Lorsque le cadre d'ancrage contient un objet, objectness=1, indiquant que le cadre prédit appartient à la classe positive ; lorsque le cadre d'ancrage ne contient pas d'objet, définissez objectness=0, indiquant que le cadre d'ancrage appartient à la classe négative ; il est un autre cas, certaines trames prédites et trames réelles L'IoU entre est très grand, mais pas le plus grand, il peut donc ne pas être approprié de définir directement son étiquette d'objectivité sur 0 en tant qu'échantillon négatif. Afin d'éviter cette situation, le L'algorithme YOLOv3 fixe un seuil IoU iou_threshold, lors de la prédiction L'objectivité de la boîte n'est pas 1, mais lorsque son IoU avec une boîte réelle est supérieure à iou_threshold, son étiquette d'objectivité est fixée à -1 et ne participe pas au calcul de la perte fonction.

  • Si le cadre d'ancrage contient des objets, il est alors nécessaire de calculer la position centrale et la taille du cadre prédit correspondant, ou les tx, ty, tw, th t_x, t_y, t_w, t_h ci-dessustx,ty,tw,thCombien devrait-il être.

  • Si la boîte d'ancrage contient des objets, alors il est nécessaire de calculer quelle est la catégorie spécifique. Ici, la variable label est utilisée pour représenter le label de la catégorie à laquelle elle appartient.

Si la zone d'ancrage de l'étiquette contient des objets

Comme le montre la figure 6 , il y a un total de cibles 3. En prenant le portrait le plus à gauche comme exemple, son cadre réel est (133.96, 328.42, 186.06, 374.63) (133.96, 328.42, 186.06, 374.63)( 1 3 3 . 9 6 ,3 2 8 . 4 2 ,1 8 6 . 0 6 ,3 7 4 . 6 3 )


Figure 6 : Sélectionnez la boîte d'ancrage qui se trouve dans la même zone que le centre de la boîte de vérité au sol

Les coordonnées du point central de la boîte de vérité terrain sont :

centre _ x = 133,96 centre\_x = 133,96centre _ x _ _ _ _ _=1 3 3 . 9 6

centre _ y = 328,42 centre\_y = 328,42c e n t e r _ y=3 2 8 . 4 2

je = 133,96 / 32 = 4,18625 je = 133,96 / 32 = 4,18625je=1 3 3 . 9 6 / 3 2=4 . 1 8 6 2 5

j = 328.42 / 32 = 10.263125 j = 328.42 / 32 = 10.263125 j=3 2 8 . 4 2 / 3 2=1 0 . 2 6 3 1 2 5

Il tombe dans le petit carré de la ligne 10, colonne 4, comme illustré à la figure 13. Cette petite zone carrée peut générer 3 boîtes d'ancrage de formes différentes, et leurs nombres et tailles sur la figure sont A 1 ( 116 , 90 ) , A 2 ( 156 , 198 ) , A 3 ( 373 , 326 ) A_1(116 , 90 ), A_2(156, 198), A_3(373, 326)UN1( 1 1 6 ,9 0 ) ,UN2( 1 5 6 ,1 9 8 ) ,UN3( 3 7 3 ,3 2 6 )

Utilisez ces 3 boîtes d'ancrage de formes différentes et la boîte réelle pour calculer IoU, et sélectionnez la boîte d'ancrage avec la plus grande IoU. Afin de simplifier le calcul ici, seule la forme de la boîte d'ancrage est prise en compte, et le décalage entre celle-ci et le centre de la boîte réelle n'est pas pris en compte. Les résultats de calcul spécifiques sont figure 7.



Figure 7 : IoU des boîtes de vérité de terrain et des boîtes d'ancrage sélectionnées

Parmi eux, celui avec le plus grand IoU avec le cadre réel est le cadre d'ancrage A 3 A_3UN3, la forme est ( 373 , 326 ) (373, 326)( 3 7 3 ,3 2 6 ) , définissez l'étiquette d'objet de son cadre de prédiction correspondant sur 1, et la catégorie d'objet qu'il inclut est la catégorie de l'objet dans le cadre réel.

À son tour, vous pouvez trouver les boîtes d'ancrage avec la plus grande IoU correspondant aux autres boîtes réelles, puis définir les étiquettes d'objectivité de leurs boîtes prédites sur 1. Il y a au total 20 × 15 × 3 = 900 20 \times 15 \times 3 = 9002 0×1 5×3=9 0 0 cases d'ancrage, seules 3 cases prédites seront marquées comme positives.

Étant donné que chaque trame réelle correspond à une seule trame prédite avec une étiquette d'objectivité positive, si l'IoU entre certaines trames prédites et la trame réelle est grande, mais pas la plus grande, alors définissez directement son étiquette d'objectivité sur 0 en tant qu'échantillon négatif, peut ne pas être approprié. Afin d'éviter cette situation, l'algorithme YOLOv3 définit un seuil IoU iou_threshold. Lorsque l'objectivité de la trame prédite n'est pas 1, mais que son IoU avec une trame réelle est supérieure à iou_threshold, son étiquette d'objectivité est définie sur -1 et ne participer à la perte Calcul des fonctions.

Toutes les autres boîtes prédites ont leurs étiquettes d'objectivité définies sur 0, indiquant une classe négative.

Pour le cadre de prédiction avec objectness=1, il est nécessaire de déterminer davantage sa position et l'étiquette de classification spécifique de l'objet contenu, mais pour le cadre de prédiction avec objectness=0 ou -1, il ne se soucie pas de leur position et de leur catégorie.

Taille de la position de la zone de prédiction d'étiquette

Lorsque l'objectivité de la boîte d'ancrage = 1, il est nécessaire de déterminer l'amplitude de la position de la boîte de prédiction par rapport à son réglage fin, c'est-à-dire l'étiquette de position de la boîte d'ancrage.

Nous avons déjà posé une telle question : quand tx , ty , tw , th t_x, t_y, t_w, t_htx,ty,tw,thLorsque la valeur est définie, l'image prédite peut-elle coïncider avec l'image réelle ? La méthode consiste à utiliser bx , by , bh , bw b_x, b_y, b_h, b_w dans les coordonnées du cadre de prédictionbx,by,bh,bwDéfinissez comme coordonnées du cadre réel, vous pouvez résoudre le ttla valeur de t .

令:
σ ( tx ∗ ) + cx = gtx \sigma(t^*_x) + c_x = gt_xs ( tX)+cx=g tx
σ ( ty ∗ ) + cy = gty \sigma(t^*_y) + c_y = gt_ys ( ty)+cy=g ty
pwetw ∗ = gtw p_w e^{t^*_w} = gt_wpwetw=g tw
pheth ∗ = gth p_h e^{t^*_h} = gt_hpheth=g th

Pour tx ∗ t_x^*tXty ∗ t_y^*ty, puisque la fonction inverse de Sigmoïde n'est pas facile à calculer, on utilise directement σ ( tx ∗ ) \sigma(t^*_x)s ( tX)σ ( ty ∗ ) \sigma(t^*_y)s ( ty) comme cible de régression.

dx ∗ = σ ( tx ∗ ) = gtx − cx d_x^* = \sigma(t^*_x) = gt_x - c_xdX=s ( tX)=g txcx

dy ∗ = σ ( ty ∗ ) = gty − cy d_y^* = \sigma(t^*_y) = gt_y - c_ydy=s ( ty)=g tycy

tw ∗ = log ( gtwpw ) t^*_w = log(\frac{gt_w}{p_w})tw=l o g (pwg tw)

th ∗ = log ( gthph ) t^*_h = log(\frac{gt_h}{p_h})th=l o g (phg th)

如果( tx , ty , th , tw ) (t_x, t_y, t_h, t_w)( tx,ty,th,tw) est la valeur de sortie prédite par le réseau, will( dx ∗ , dy ∗ , tw ∗ , th ∗ ) (d_x^*, d_y^*, t_w^*, t_h^*)( dX,dy,tw,th)作为( σ ( tx ) , σ ( ty ) , th , tw ) (\sigma(t_x), \sigma(t_y), t_h, t_w)( s ( tx) ,s ( ty) ,th,tw) valeur cible, en utilisant l'écart entre eux comme fonction de perte, un problème de régression peut être établi, et en apprenant les paramètres du réseau,ttt est suffisamment proche det ∗ t^*t , de sorte que la position de la boîte de prédiction puisse être résolue.

Annoter la catégorie d'objet dans la boîte d'ancrage

Pour la boîte d'ancrage avec objectness=1, sa catégorie spécifique doit être déterminée. Comme mentionné ci-dessus, le cadre d'ancrage avec l'objectivité marquée 1 aura un cadre réel qui lui correspond, et la catégorie d'objet à laquelle appartient le cadre d'ancrage est la catégorie d'objet contenue dans le cadre réel correspondant. Ici, un vecteur unique est utilisé pour représenter l'étiquette d'étiquette de catégorie. Par exemple, il y a 10 catégories au total, et la catégorie d'objet contenue dans le cadre réel est la deuxième catégorie, alors l'étiquette est ( 0 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ( 0,1,0 ,0,0,0,0,0,0,0)( 0 ,1 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 )

Pour résumer, comme le montre la figure 8.



Figure 8 : Diagramme schématique du processus d'étiquetage

Grâce à l'introduction ci-dessus, nous avons une compréhension préliminaire de la méthode d'étiquetage de la zone candidate dans YOLOv3. De cette façon, nous pouvons obtenir l'étiquette de cadre de prédiction réelle. Dans Paddle, ces opérations ont été encapsulées dans l' API paddle.vision.ops.yolo_loss Lors du calcul de la perte, il vous suffit d'appeler cette API pour implémenter simplement le processus ci-dessus. Examinons ensuite la structure du réseau de YOLOv3 et voyons comment le réseau calcule la valeur prédite correspondante.

3.2 Structure du réseau YOLOv3

1. colonne vertébrale

Le réseau dorsal utilisé par l'algorithme YOLOv3 est Darknet53. La structure spécifique du réseau est illustrée à la figure 9, et il a obtenu de bons résultats sur la tâche de classification d'images ImageNet. Dans la tâche de détection, la mise en commun moyenne, la couche entièrement connectée et Softmax derrière C0 dans la figure sont supprimées, et la structure de réseau de l'entrée à C0 est conservée comme structure de réseau de base du modèle de détection, également connue sous le nom de réseau fédérateur. Le modèle YOLOv3 ajoutera des modules réseau liés à la détection basés sur le réseau fédérateur.



Figure 9 : Structure du réseau Darknet53

Le programme suivant est le code d'implémentation du réseau fédérateur Darknet53. Ici, extrayez les données de sortie représentées par C0, C1 et C2 dans la figure ci-dessus, et vérifiez leurs formes respectivement, C 0 [ 1 , 1024 , 20 , 20 ] C0 [ 1 , 1024, 20, 20]C 0 [ 1 ,1 0 2 4 ,2 0 ,2 0 ]C 1 [ 1 , 512 , 40 , 40 ] C1 [1, 512, 40, 40]C 1 [ 1 ,5 1 2 ,4 0 ,4 0 ]C 2 [ 1 , 256 , 80 , 80 ] C2 [1, 256, 80, 80]C 2 [ 1 ,2 5 6 ,8 0 ,8 0 ]

# coding=utf-8
# 导入环境
import os
import random
import xml.etree.ElementTree as ET
import numpy as np
import matplotlib.pyplot as plt
# 在notebook中使用matplotlib.pyplot绘图时,需要添加该命令进行显示
%matplotlib inline
from matplotlib.image import imread
import matplotlib.patches as patches
import cv2
from PIL import Image, ImageEnhance
import paddle
import paddle.nn.functional as F


# 将卷积和批归一化封装为ConvBNLayer,方便后续复用
class ConvBNLayer(paddle.nn.Layer):
    def __init__(self, ch_in, ch_out,  kernel_size=3, stride=1, groups=1, padding=0, act="leaky"):
        # 初始化函数
        super(ConvBNLayer, self).__init__()
        # 创建卷积层
        self.conv = paddle.nn.Conv2D(in_channels=ch_in, out_channels=ch_out, kernel_size=kernel_size, stride=stride, padding=padding, 
            groups=groups, weight_attr=paddle.ParamAttr(initializer=paddle.nn.initializer.Normal(0., 0.02)), bias_attr=False)
        # 创建批归一化层
        self.batch_norm = paddle.nn.BatchNorm2D(num_features=ch_out,
            weight_attr=paddle.ParamAttr(initializer=paddle.nn.initializer.Normal(0., 0.02), regularizer=paddle.regularizer.L2Decay(0.)),
            bias_attr=paddle.ParamAttr(initializer=paddle.nn.initializer.Constant(0.0), regularizer=paddle.regularizer.L2Decay(0.)))
        self.act = act

    def forward(self, inputs):
        # 前向计算
        out = self.conv(inputs)
        out = self.batch_norm(out)
        if self.act == 'leaky':
            out = F.leaky_relu(x=out, negative_slope=0.1)
        return out
# 定义下采样模块,使图片尺寸减半
class DownSample(paddle.nn.Layer):
    def __init__(self, ch_in, ch_out, kernel_size=3, stride=2, padding=1):
        # 初始化函数
        super(DownSample, self).__init__()
        # 使用 stride=2 的卷积,可以使图片尺寸减半
        self.conv_bn_layer = ConvBNLayer(ch_in=ch_in, ch_out=ch_out, kernel_size=kernel_size, stride=stride, padding=padding)
        self.ch_out = ch_out
        
    def forward(self, inputs):
        # 前向计算
        out = self.conv_bn_layer(inputs)
        return out
# 定义残差块
class BasicBlock(paddle.nn.Layer):
    def __init__(self, ch_in, ch_out):
        # 初始化函数
        super(BasicBlock, self).__init__()
        # 定义两个卷积层
        self.conv1 = ConvBNLayer(ch_in=ch_in, ch_out=ch_out, kernel_size=1, stride=1, padding=0)
        self.conv2 = ConvBNLayer(ch_in=ch_out, ch_out=ch_out*2, kernel_size=3, stride=1, padding=1)
        
    def forward(self, inputs):
        # 前向计算
        conv1 = self.conv1(inputs)
        conv2 = self.conv2(conv1)
        # 将第二个卷积层的输出和最初的输入值相加
        out = paddle.add(x=inputs, y=conv2)
        return out
# 将多个残差块封装为一个层级,方便后续复用
class LayerWarp(paddle.nn.Layer):
    def __init__(self, ch_in, ch_out, count, is_test=True):
        # 初始化函数
        super(LayerWarp,self).__init__()
        self.basicblock0 = BasicBlock(ch_in, ch_out)
        self.res_out_list = []
        for i in range(1, count):
            # 使用add_sublayer添加子层
            res_out = self.add_sublayer("basic_block_%d" % (i), BasicBlock(ch_out*2, ch_out))
            self.res_out_list.append(res_out)

    def forward(self,inputs):
        # 前向计算
        y = self.basicblock0(inputs)
        for basic_block_i in self.res_out_list:
            y = basic_block_i(y)
        return y
# DarkNet 每组残差块的个数,来自DarkNet的网络结构图
DarkNet_cfg = {
    
    53: ([1, 2, 8, 8, 4])}
# 创建DarkNet53骨干网络
class DarkNet53_conv_body(paddle.nn.Layer):
    def __init__(self):
        # 初始化函数
        super(DarkNet53_conv_body, self).__init__()
        self.stages = DarkNet_cfg[53]
        self.stages = self.stages[0:5]

        # 第一层卷积
        self.conv0 = ConvBNLayer(ch_in=3, ch_out=32, kernel_size=3, stride=1, padding=1)

        # 下采样,使用stride=2的卷积来实现
        self.downsample0 = DownSample(ch_in=32, ch_out=32 * 2)

        # 添加各个层级的实现
        self.darknet53_conv_block_list = []
        self.downsample_list = []
        for i, stage in enumerate(self.stages):
            conv_block = self.add_sublayer("stage_%d" % (i), LayerWarp(32*(2**(i+1)), 32*(2**i), stage))
            self.darknet53_conv_block_list.append(conv_block)
        # 两个层级之间使用DownSample将尺寸减半
        for i in range(len(self.stages) - 1):
            downsample = self.add_sublayer("stage_%d_downsample" % i, DownSample(ch_in=32*(2**(i+1)), ch_out=32*(2**(i+2))))
            self.downsample_list.append(downsample)

    def forward(self,inputs):
        # 前向计算
        out = self.conv0(inputs)
        out = self.downsample0(out)
        blocks = []
        # 依次将各个层级作用在输入上面
        for i, conv_block_i in enumerate(self.darknet53_conv_block_list): 
            out = conv_block_i(out)
            blocks.append(out)
            if i < len(self.stages) - 1:
                out = self.downsample_list[i](out)
        # 将C0, C1, C2作为返回值
        return blocks[-1:-4:-1] 

2. Module de prédiction

Ci-dessus, nous avons appris que dans l'algorithme YOLOv3, le réseau doit produire 3 ensembles de résultats, à savoir :

  • Indique si la boîte de prédiction contient l'objet. Cela peut également être compris comme la probabilité d'objet = 1, ici vous pouvez laisser le réseau sortir un nombre réel xxx , puis utilisezSigmoïde ( x ) Sigmoïde(x)S i g m o i d ( x ) indique la probabilité que l'objectivité soit positiveP obj P_{obj}Po b j

  • Prédire l'emplacement et la forme de l'objet. Le réseau peut être utilisé pour produire 4 nombres réels pour représenter la position et la forme de l'objet tx , ty , tw , th t_x, t_y, t_w, t_htx,ty,tw,th

  • Prédire les classes d'objets. Prédisez la catégorie spécifique de l'objet dans l'image, ou quelle est la probabilité qu'il appartienne à chaque catégorie. Le nombre total de catégories est C, et la probabilité des objets appartenant à chaque catégorie doit être prédite ( P 1 , P 2 , . . . , PC ) (P_1, P_2, ..., P_C)( P1,P2,. . . ,PC) , vous pouvez utiliser le réseau pour sortir C des nombres réels( x 1 , x 2 , . . . , x C ) (x_1, x_2, ..., x_C)( x1,X2,. . . ,XC) , trouvez la fonction sigmoïde pour chaque nombre réel, soitP i = S igmoïde ( xi ) P_i = Sigmoïde(x_i)Pje=S i g m o i d ( xje) , alors la probabilité que l'objet appartienne à chaque catégorie peut être exprimée.

Par conséquent, pour une boîte prédite, le réseau doit sortir ( 5 + C ) (5 + C)( 5+C ) des nombres réels pour caractériser s'il contient des objets, des positions et des dimensions de forme, et la probabilité d'appartenir à chaque catégorie.

Puisque nous avons généré 3 boîtes de prédiction dans chaque petite zone carrée, le nombre total de valeurs de prédiction qui doivent être sorties par le réseau pour toutes les boîtes de prédiction est :

[ 3 × ( 5 + C ) ] × m × n [3 \fois (5 + C)] \fois m \fois n[ 3×( 5+C ) ]×m×n

Un autre point plus important est que la sortie du réseau doit être capable de distinguer la position de la petite zone carrée, et la carte des caractéristiques ne peut pas être directement connectée à une taille de sortie de [ 3 × ( 5 + C ) ] × m × n [3 \times (5 + C)] \times m \times n[ 3×( 5+C ) ]×m×La couche entièrement connectée de n .

Continuez à utiliser l'image ci-dessus ici, observez maintenant la carte des caractéristiques, après la mise en commun du noyau à convolution multiple, sa foulée = 32, 640 × 480 640 \times 4806 4 0×L'image d'entrée de taille 4 8 0 devient20 × 15 20\times152 0×1 5 cartes de caractéristiques ; et le nombre de petites zones carrées est exactement20 × 15 20\times152 0×1 5 , ce qui signifie que chaque pixel sur la carte des caractéristiques peut correspondre à une petite zone carrée sur l'image originale. C'est pourquoi nous avons d'abord défini la taille de la petite zone carrée sur 32, ce qui peut astucieusement faire correspondre la petite zone carrée avec les pixels sur la carte des caractéristiques, et résoudre la correspondance entre les positions spatiales.



Figure 10 : Comparaison de la carte d'entités C0 et de la forme de petite zone carrée

Ci-dessous également besoin de point de pixel ( i , j ) (i, j)( je ,j ) est associée à la valeur prédite requise par la petite zone carrée de la ligne i, colonne j, chaque petite zone carrée génère 3 trames de prédiction, et chaque trame de prédiction nécessite ( 5 + C ) (5 + C)( 5+C ) valeurs prédites réelles, alors chaque pixel correspond à3 × ( 5 + C ) 3 \times (5 + C)3×( 5+C ) nombres réels. Pour résoudre ce problème, plusieurs convolutions sont effectuées sur la carte des caractéristiques et le nombre final de canaux de sortie est défini sur3 × ( 5 + C ) 3 \times (5 + C)3×( 5+C ) , la carte de caractéristiques générée peut être subtilement mise en correspondance avec la valeur prédite requise par chaque trame de prédiction. Après convolution, la taille garantie de la carte d'entités en sortie devient[ 1 , 75 , 20 , 15 ] [1, 75, 20, 15][ 1 ,7 5 ,2 0 ,1 5 ] . Le nombre de boîtes d'ancrage ou de boîtes de prédiction générées par chaque petite zone carrée est de 3, le nombre de catégories d'objets est de 20 et le nombre de valeurs prédites requises pour chaque zone est de 3 × ( 5 + 20 ) = 75 3 \times (5 + 20) = 753×( 5+2 0 )=7 5 , exactement égal au nombre de canaux de sortie.

À ce stade, la manière dont la carte d'entités en sortie est associée à la région candidate est illustrée à la figure 11.

P 0 [ t , 0 : 25 , je , j ] P0[t, 0:25, je, j]P 0 [ t ,0:2 5 ,je ,j ] et la petite zone carrée ( i , j )sur la t-ième image d'entrée( je ,j ) Correspondant aux 25 valeurs prédites requises par la première trame de prédiction,P 0 [ t , 25 : 50 , i , j ] P0[t, 25:50, i, j]P 0 [ t ,2 5:5 0 ,je ,j ] et la petite zone carrée ( i , j )sur la t-ième image d'entrée( je ,j ) Correspondant aux 25 valeurs prédites requises par la deuxième trame de prédiction,P 0 [ t , 50 : 75 , i , j ] P0[t, 50:75, i, j]P 0 [ t ,5 0:7 5 ,je ,j ] et la petite zone carrée ( i , j )sur la t-ième image d'entrée( je ,j ) Correspondant aux 25 valeurs prédites requises par la troisième trame de prédiction.

P 0 [ t , 0 : 4 , je , j ] P0[t, 0:4, je, j]P 0 [ t ,0:4 ,je ,j ] et la petite zone carrée ( i , j )sur la t-ième image d'entrée( je ,j ) correspondant à la position de la première trame de prédiction,P 0 [ t , 4 , i , j ] P0[t, 4, i, j]P 0 [ t ,4 ,je ,j ] et la petite zone carrée ( i , j )sur la t-ième image d'entrée( je ,j ) L'objectivité correspondant au premier cadre de prédiction,P 0 [ t , 5 : 25 , i , j ] P0[t, 5:25, i, j]P 0 [ t ,5:2 5 ,je ,j ] et la petite zone carrée ( i , j )sur la t-ième image d'entrée( je ,j ) La correspondance de catégorie du premier cadre de prédiction.

De cette manière, la carte des caractéristiques de sortie du réseau peut être subtilement mise en correspondance avec le cadre de prédiction généré par chaque petite zone carrée.



Figure 11 : Association de la feature map P0 avec les régions candidates

La carte de caractéristiques de sortie du réseau fédérateur est C0, et le programme suivant consiste à effectuer plusieurs convolutions sur C0 pour obtenir la carte de caractéristiques P0 liée à la trame de prédiction.

class YoloDetectionBlock(paddle.nn.Layer):
    # define YOLOv3 detection head
    # 使用多层卷积和BN提取特征
    def __init__(self,ch_in,ch_out,is_test=True):
        super(YoloDetectionBlock, self).__init__()

        assert ch_out % 2 == 0, \
            "channel {} cannot be divided by 2".format(ch_out)

        self.conv0 = ConvBNLayer(ch_in=ch_in, ch_out=ch_out, kernel_size=1, stride=1, padding=0)
        self.conv1 = ConvBNLayer(ch_in=ch_out, ch_out=ch_out*2, kernel_size=3, stride=1, padding=1)
        self.conv2 = ConvBNLayer(ch_in=ch_out*2, ch_out=ch_out, kernel_size=1, stride=1, padding=0)
        self.conv3 = ConvBNLayer(ch_in=ch_out, ch_out=ch_out*2, kernel_size=3, stride=1, padding=1)
        self.route = ConvBNLayer(ch_in=ch_out*2, ch_out=ch_out, kernel_size=1, stride=1, padding=0)
        self.tip = ConvBNLayer(ch_in=ch_out, ch_out=ch_out*2, kernel_size=3, stride=1, padding=1)
        
    def forward(self, inputs):
        out = self.conv0(inputs)
        out = self.conv1(out)
        out = self.conv2(out)
        out = self.conv3(out)
        route = self.route(out)
        tip = self.tip(route)
        return route, tip

3. Détection multi-échelles

Le processus de calcul que nous avons expliqué ci-dessus est basé sur la carte de caractéristiques P0 et sa foulée = 32. La taille de la carte des caractéristiques est relativement petite, le nombre de pixels est relativement petit, le champ de réception de chaque pixel est grand et il contient des informations sémantiques de haut niveau très riches, ce qui peut être plus facile pour détecter des cibles plus grandes. Afin de pouvoir détecter ces objets de plus petite taille, la sortie de prédiction doit être construite au-dessus de la carte d'entités de plus grande taille. Si nous générons directement une sortie de prédiction sur la carte d'entités au niveau de C2 ou C1, nous pouvons être confrontés à de nouveaux problèmes.Ils n'ont pas subi une extraction d'entités suffisante, et les informations sémantiques contenues dans les pixels ne sont pas assez riches, et il peut être difficile pour extraire des modèles de caractéristiques efficaces. . Dans la détection de cible, la façon de résoudre ce problème est d'agrandir la taille de la carte des caractéristiques de haut niveau, puis de la fusionner avec la carte des caractéristiques de bas niveau. La nouvelle carte des caractéristiques peut contenir des informations sémantiques riches et avoir plus de pixels. Les points peuvent décrire des structures plus fines.

La méthode est illustrée à la Figure 12 :



Figure 12 : Générer des cartes d'entités de sortie à plusieurs niveaux P0, P1, P2

YOLOv3 génère 3 boîtes d'ancrage au centre de chaque région, et les tailles des boîtes d'ancrage générées sur les cartes de caractéristiques des 3 niveaux sont P2 [(10×13),(16×30),(33×23)], P1 [(30×61),(62×45),(59×119)], P0[(116 × 90), (156 × 198), (373 × 326]. Il est utilisé sur la dernière carte des caractéristiques. plus la taille du cadre d'ancrage est grande, il peut capturer les informations des cibles de grande taille ; plus la taille du cadre d'ancrage sur la carte des caractéristiques est petite, il peut capturer les informations des cibles de petite taille.

Par conséquent, le calcul final de la fonction de perte et la prédiction du modèle sont effectués à ces trois niveaux. Ensuite, la définition de la structure complète du réseau peut être effectuée.


illustrer:

Dans YOLOv3, la fonction de perte comprend principalement trois parties, à savoir :

  • La fonction de perte indiquant si l'objet cible est inclus est calculée à l'aide de la fonction de perte d'entropie croisée binaire.

  • La fonction de perte représentant la position de l'objet, où, tx , ty t_x, t_ytx,tyCalculé à l'aide de la fonction de perte d'entropie croisée binaire, tw , th t_w, t_htw,thUtilisez la perte L1 pour le calcul.

  • La fonction de perte représentant la catégorie d'objet, calculée à l'aide de la fonction de perte d'entropie croisée binaire.


3.3 Formation de bout en bout

Le processus de formation de YOLOv3 est illustré à la figure 13. L'image d'entrée est soumise à une extraction de caractéristiques pour obtenir trois niveaux de cartes de caractéristiques de sortie P0 (foulée = 32), P1 (foulée = 16) et P2 (foulée = 8), utilisez en conséquence différentes tailles La petite zone carrée pour générer la boîte d'ancrage et la boîte de prédiction correspondantes, et marquer ces boîtes d'ancrage.

  • Carte des fonctionnalités de niveau P0, correspondant à l'utilisation de 32 × 32 32\times323 2×3 Des petits carrés de taille 2 sont générés au centre de chaque zone avec des tailles[116, 90] [116, 90][ 1 1 6 ,9 0 ] ,[ 156 , 198 ] [156, 198][ 1 5 6 ,1 9 8 ] ,[ 373 , 326 ] [373, 326][ 3 7 3 ,326]的三种锚框。

  • P1层级特征图,对应着使用 16 × 16 16\times16 16×16大小的小方块,在每个区域中心生成大小分别为 [ 30 , 61 ] [30, 61] [30,61], [ 62 , 45 ] [62, 45] [62,45], [ 59 , 119 ] [59, 119] [59,119]的三种锚框。

  • P2层级特征图,对应着使用 8 × 8 8\times8 8×8大小的小方块,在每个区域中心生成大小分别为 [ 10 , 13 ] [10, 13] [10,1 3 ] ,[ 16 , 30 ] [16, 30][ 1 6 ,3 0 ] ,[ 33 , 23 ] [33, 23][ 3 3 ,2 3 ] de trois types de boîtes d'ancrage.

Les cartes des caractéristiques des trois niveaux sont associées aux étiquettes entre les boîtes d'ancrage correspondantes et une fonction de perte est établie. La fonction de perte totale est égale à la somme des fonctions de perte des trois niveaux. En minimisant la fonction de perte, un processus de formation de bout en bout peut être lancé.



Figure 13 : Processus de formation de bout en bout

Lors de la définition d'un réseau complet, nous devons utiliser l'API paddle.vision.ops.yolo_loss pour calculer la fonction de perte. Cette API encapsule uniformément l'étiquetage des régions candidates ci-dessus et le calcul des fonctions de perte multi-échelles.

paddle.vision.ops.yolo_loss(x, gt_box, gt_label, anchors, anchor_mask, class_num, ignore_thresh, downsample_ratio, gt_score=None, use_label_smooth=True, name=None, scale_x_y=1.0)

Les paramètres clés sont décrits comme suit :

  • x : carte des caractéristiques de sortie.
  • gt_box : boîte de vérité terrain.
  • gt_label : étiquette de la boîte de vérité au sol.
  • ignore_thresh, lorsque le seuil IoU de la trame prédite et de la trame réelle dépasse ignore_thresh, il n'est pas utilisé comme échantillon négatif et il est défini sur 0,7 dans le modèle YOLOv3.
  • downsample_ratio, le taux de sous-échantillonnage de la carte de caractéristiques P0, qui est de 32 lors de l'utilisation du réseau fédérateur Darknet53.
  • gt_score, la confiance de la boîte réelle, utilisée lors de l'utilisation de la technique de mixup.
  • use_label_smooth, une technique d'entraînement, si elle n'est pas utilisée, définie sur False.
  • name, le nom de la couche, tel que 'yolov3_loss', la valeur par défaut est None, généralement pas besoin de définir.

4. Prédiction du modèle YOLOv3

L' organigramme 14 est le suivant :



Figure 14 : Processus de prévision

Le processus de prévision peut être divisé en deux étapes :

  1. La position de boîte prévue et le score de la catégorie sont calculés via la sortie du réseau.
  2. Utilisez une suppression non maximale pour éliminer les zones de prédiction avec de grands chevauchements.

4.1 Calcul de la trame de prédiction

Nous pouvons utiliser paddle.vision.ops.yolo_box pour calculer les boîtes de prédiction et les scores correspondant aux cartes de caractéristiques des trois niveaux.

paddle.vision.ops.yolo_box(x, img_size, anchors, class_num, conf_thresh, downsample_ratio, clip_bbox=True, name=None, scale_x_y=1.0)

Les paramètres clés ont les significations suivantes :

  • x, la carte des caractéristiques de sortie du réseau, telle que P0 ou P1, P2 mentionnée ci-dessus.
  • img_size, taille de l'image d'entrée.
  • ancres, la taille de l'ancre utilisée, telle que [10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326]
  • Anchor_mask : le masque de l'ancre utilisée à chaque niveau, [[6, 7, 8], [3, 4, 5], [0, 1, 2]]
  • class_num, le nombre de catégories d'objets.
  • conf_thresh, le seuil de confiance, la valeur de position de la boite de prédiction avec un score inférieur à ce seuil est directement mis à 0.0 sans calcul.
  • downsample_ratio, le taux de sous-échantillonnage de la carte d'entités, par exemple, P0 est 32, P1 est 16 et P2 est 8.
  • name=None, le nom, tel que 'yolo_box', n'a généralement pas besoin d'être défini et la valeur par défaut est None.

La valeur de retour comprend deux éléments, les cases et les scores, où les cases sont les valeurs de coordonnées de toutes les cases de prédiction, et les scores sont les scores de toutes les cases de prédiction.

La définition du score du cadre de prédiction est la probabilité de la catégorie multipliée par la probabilité d'objectivité de savoir si le cadre de prédiction contient l'objet cible, c'est-à-dire

score = P obj ⋅ P classement score = P_{obj} \cdot P_{classification}s c o r e=Po b jPc l a s s i f i c a t i o n

En appelant paddle.vision.ops.yolo_boxles cadres de prédiction et les scores correspondant aux cartes de caractéristiques des trois niveaux de P0, P1 et P2, et en les fusionnant, tous les cadres de prédiction et leurs scores appartenant à chaque catégorie peuvent être obtenus.

À ce stade, le réseau YOLOv3 complet peut être défini, et le code complet est le suivant :

# 定义上采样模块
class Upsample(paddle.nn.Layer):
    def __init__(self, scale=2):
        # 初始化函数
        super(Upsample,self).__init__()
        self.scale = scale

    def forward(self, inputs):
        # 前向计算
        # 获得动态的上采样输出形状
        shape_nchw = paddle.shape(inputs)
        shape_hw = paddle.slice(shape_nchw, axes=[0], starts=[2], ends=[4])
        shape_hw.stop_gradient = True
        in_shape = paddle.cast(shape_hw, dtype='int32')
        out_shape = in_shape * self.scale
        out_shape.stop_gradient = True

        # 上采样计算
        out = paddle.nn.functional.interpolate(x=inputs, scale_factor=self.scale, mode="NEAREST")
        return out
# 定义完整的YOLOv3模型
class YOLOv3(paddle.nn.Layer):
    def __init__(self, num_classes=20):
        # 初始化函数
        super(YOLOv3,self).__init__()

        self.num_classes = num_classes
        # 提取图像特征的骨干代码
        self.block = DarkNet53_conv_body()
        self.block_outputs = []
        self.yolo_blocks = []
        self.route_blocks_2 = []
        # 生成3个层级的特征图P0, P1, P2
        for i in range(3):
            # 添加从ci生成ri和ti的模块
            yolo_block = self.add_sublayer("yolo_detecton_block_%d" % (i),
                YoloDetectionBlock(ch_in=512//(2**i)*2 if i==0 else 512//(2**i)*2 + 512//(2**i), ch_out = 512//(2**i)))
            self.yolo_blocks.append(yolo_block)

            num_filters = 3 * (self.num_classes + 5)

            # 添加从ti生成pi的模块,这是一个Conv2D操作,输出通道数为3 * (num_classes + 5)
            block_out = self.add_sublayer("block_out_%d" % (i),
                paddle.nn.Conv2D(in_channels=512//(2**i)*2, out_channels=num_filters, kernel_size=1, stride=1, padding=0,
                       weight_attr=paddle.ParamAttr(initializer=paddle.nn.initializer.Normal(0., 0.02)),
                       bias_attr=paddle.ParamAttr(initializer=paddle.nn.initializer.Constant(0.0), regularizer=paddle.regularizer.L2Decay(0.))))
            self.block_outputs.append(block_out)
            if i < 2:
                # 对ri进行卷积
                route = self.add_sublayer("route2_%d"%i, ConvBNLayer(ch_in=512//(2**i), ch_out=256//(2**i), kernel_size=1, stride=1, padding=0))
                self.route_blocks_2.append(route)
            # 将ri放大以便跟c_{i+1}保持同样的尺寸
            self.upsample = Upsample()

    def forward(self, inputs):
        # 前向运算
        outputs = []
        blocks = self.block(inputs)
        for i, block in enumerate(blocks):
            if i > 0:
                # 将r_{i-1}经过卷积和上采样之后得到特征图,与这一级的ci进行拼接
                block = paddle.concat([route, block], axis=1)
            # 从ci生成ti和ri
            route, tip = self.yolo_blocks[i](block)
            # 从ti生成pi
            block_out = self.block_outputs[i](tip)
            # 将pi放入列表
            outputs.append(block_out)

            if i < 2:
                # 对ri进行卷积调整通道数
                route = self.route_blocks_2[i](route)
                # 对ri进行放大,使其尺寸和c_{i+1}保持一致
                route = self.upsample(route)

        return outputs

    def get_loss(self, outputs, gtbox, gtlabel, gtscore=None, anchors = [10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326],
                 anchor_masks = [[6, 7, 8], [3, 4, 5], [0, 1, 2]], ignore_thresh=0.7, use_label_smooth=False):
        # 损失计算函数
        self.losses = []
        downsample = 32
        # 对三个层级分别求损失函数
        for i, out in enumerate(outputs): 
            anchor_mask_i = anchor_masks[i]
            # 使用paddle.vision.ops.yolo_loss 直接计算损失函数
            loss = paddle.vision.ops.yolo_loss(x=out, gt_box=gtbox, gt_label=gtlabel, gt_score=gtscore, anchors=anchors, anchor_mask=anchor_mask_i, 
                    class_num=self.num_classes, ignore_thresh=ignore_thresh, downsample_ratio=downsample, use_label_smooth=False)
            self.losses.append(paddle.mean(loss)) 
            # 下一级特征图的缩放倍数会减半
            downsample = downsample // 2 
        # 对所有层级求和
        return sum(self.losses) 

    def get_pred(self, outputs, im_shape=None, anchors = [10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326],
                 anchor_masks = [[6, 7, 8], [3, 4, 5], [0, 1, 2]], valid_thresh = 0.01):
        # 预测函数
        downsample = 32
        total_boxes = []
        total_scores = []
        for i, out in enumerate(outputs):
            anchor_mask = anchor_masks[i]
            anchors_this_level = []
            for m in anchor_mask:
                anchors_this_level.append(anchors[2 * m])
                anchors_this_level.append(anchors[2 * m + 1])
            # 使用paddle.vision.ops.yolo_box 直接计算损失函数
            boxes, scores = paddle.vision.ops.yolo_box(x=out, img_size=im_shape, anchors=anchors_this_level, class_num=self.num_classes,
                   conf_thresh=valid_thresh, downsample_ratio=downsample, name="yolo_box" + str(i))
            total_boxes.append(boxes)
            total_scores.append(paddle.transpose( scores, perm=[0, 2, 1]))
            downsample = downsample // 2
        # 将三个层级的结果进行拼接
        yolo_boxes = paddle.concat(total_boxes, axis=1)
        yolo_scores = paddle.concat(total_scores, axis=2)
        return yolo_boxes, yolo_scores

4.2 Suppression maximale multicatégorie

Dans le processus de calcul précédent, le réseau peut effectuer plusieurs détections sur la même cible. Cela conduit également à plusieurs cadres de prédiction pour le même objet. Par conséquent, après avoir obtenu la sortie du modèle, une suppression non maximale (nms) doit être utilisée pour éliminer les trames redondantes. L'idée de base est que s'il existe plusieurs trames de prédiction correspondant au même objet, seule la trame de prédiction avec le score le plus élevé est sélectionnée et les trames de prédiction restantes sont ignorées.

Comment juger que les deux cases de prédiction correspondent au même objet, et comment établir la norme ?

Si les catégories des deux boîtes de prédiction sont les mêmes et que leur chevauchement de position est relativement important, on peut considérer qu'elles prédisent la même cible. La méthode de suppression de valeur non maximale consiste à sélectionner la trame de prédiction avec le score le plus élevé d'une certaine catégorie, puis à voir quelles trames de prédiction et son IoU sont supérieures au seuil, et à éliminer ces trames de prédiction. Le seuil d'IoU est ici un hyperparamètre, qui doit être fixé à l'avance. Le modèle YOLOv3 est fixé à 0,5.

Le code pour calculer l'IOU est indiqué ci-dessous.

# 计算IoU,其中边界框的坐标形式为xyxy
def box_iou_xyxy(box1, box2):
    # 获取box1左上角和右下角的坐标
    x1min, y1min, x1max, y1max = box1[0], box1[1], box1[2], box1[3]
    # 计算box1的面积
    s1 = (y1max - y1min + 1.) * (x1max - x1min + 1.)
    # 获取box2左上角和右下角的坐标
    x2min, y2min, x2max, y2max = box2[0], box2[1], box2[2], box2[3]
    # 计算box2的面积
    s2 = (y2max - y2min + 1.) * (x2max - x2min + 1.)
    
    # 计算相交矩形框的坐标
    xmin = np.maximum(x1min, x2min)
    ymin = np.maximum(y1min, y2min)
    xmax = np.minimum(x1max, x2max)
    ymax = np.minimum(y1max, y2max)
    # 计算相交矩形行的高度、宽度、面积
    inter_h = np.maximum(ymax - ymin + 1., 0.)
    inter_w = np.maximum(xmax - xmin + 1., 0.)
    intersection = inter_h * inter_w
    # 计算相并面积
    union = s1 + s2 - intersection
    # 计算交并比
    iou = intersection / union
    return iou

Le code d'implémentation spécifique de la suppression de valeur non maximale est tel que défini dans la nmsfonction suivante. Il convient de noter que l'ensemble de données contient des objets de plusieurs catégories, nous devons donc ici effectuer une suppression de valeur non maximale multicatégorie. Son principe de mise en œuvre est la même que celle de la suppression de la valeur non maximale La même chose, la différence est que la suppression non maximale est requise pour chaque catégorie, et le code de mise en œuvre est multiclass_nmsindiqué ci-dessous.

# 非极大值抑制
def nms(bboxes, scores, score_thresh, nms_thresh):
    # 对预测框得分进行排序
    inds = np.argsort(scores)
    inds = inds[::-1]
    keep_inds = []
    # 循环遍历预测框
    while(len(inds) > 0):
        cur_ind = inds[0]
        cur_score = scores[cur_ind]
        # 如果预测框得分低于阈值,则退出循环
        if cur_score < score_thresh:
            break
        # 计算当前预测框与保留列表中的预测框IOU,如果小于阈值则保留该预测框,否则丢弃该预测框
        keep = True
        for ind in keep_inds:
            current_box = bboxes[cur_ind]
            remain_box = bboxes[ind]
            # 计算当前预测框与保留列表中的预测框IOU
            iou = box_iou_xyxy(current_box, remain_box)
            if iou > nms_thresh:
                keep = False
                break
        if keep:
            keep_inds.append(cur_ind)
        inds = inds[1:]
    return np.array(keep_inds)

# 多分类非极大值抑制
def multiclass_nms(bboxes, scores, score_thresh=0.01, nms_thresh=0.45, pos_nms_topk=100):
    batch_size = bboxes.shape[0]
    class_num = scores.shape[1]
    rets = []
    for i in range(batch_size):
        bboxes_i = bboxes[i]
        scores_i = scores[i]
        ret = []
        # 遍历所有类别进行单分类非极大值抑制
        for c in range(class_num):
            scores_i_c = scores_i[c]
            # 单分类非极大值抑制
            keep_inds = nms(bboxes_i, scores_i_c, score_thresh, nms_thresh)
            if len(keep_inds) < 1:
                continue
            keep_bboxes = bboxes_i[keep_inds]
            keep_scores = scores_i_c[keep_inds]
            keep_results = np.zeros([keep_scores.shape[0], 6])
            keep_results[:, 0] = c
            keep_results[:, 1] = keep_scores[:]
            keep_results[:, 2:6] = keep_bboxes[:, :]
            ret.append(keep_results)
        if len(ret) < 1:
            rets.append(ret)
            continue
        ret_i = np.concatenate(ret, axis=0)
        scores_i = ret_i[:, 1]
        # 如果保留的预测框超过100个,只保留得分最高的100个
        if len(scores_i) > pos_nms_topk:
            inds = np.argsort(scores_i)[::-1]
            inds = inds[:pos_nms_topk]
            ret_i = ret_i[inds]
        rets.append(ret_i)
    return rets

Je suppose que tu aimes

Origine blog.csdn.net/weixin_43273742/article/details/122929070
conseillé
Classement