[PYTHON] Classification en temps réel de plusieurs objets dans les images de la caméra avec apprentissage en profondeur de Raspberry Pi 3 B + et PyTorch

Obtenez Razpai 3B + et picamera pour les cours universitaires. Comme j'ai du temps libre, j'ai décidé de laisser Razpai classer en utilisant le deep learning. Cependant, au lieu de classer les photos prises à l'avance, les objets de l'image en temps réel de picamera sont classés et affichés de manière agréable.

Ce sera peut-être au niveau des étudiants, mais j'espère que ce sera utile en partie.

Ce que j'ai envisagé

J'ai décidé de créer une «fonction qui ** classe et affiche ** en temps réel lorsque plusieurs effets personnels sont placés dans le champ visuel fixe de la picamera» dans Razpie.

Plus précisément, l'objet est extrait par ** différence d'arrière-plan ** (une méthode d'extraction de la partie modifiée de l'image d'arrière-plan) et l'apprentissage en profondeur est effectué par ** PyTorch [PyTorch] ** (similaire à Keras, TensorFlow). Nous adopterons une politique de classification par.

** (* YOLO, SSD, etc. ne sont pas gérés!) **

Je l'ai donc implémenté à l'étape suivante.

Raspeye est lent, j'ai donc appris ** sur mon propre PC et l'ai classé sur Raspai en utilisant le fichier de paramètres obtenu **. J'ai donc mis PyTorch sur PC et Raspberry.

Voici une série de processus. Notez les parties avec lesquelles vous avez eu du mal avec le symbole ** [⚠Remarque] **.

Première préparation

Préparez l'environnement d'exécution sur le PC et Raspeye.

Environnement d'exécution

Les versions du même package sont différentes pour PC et Raspeye, mais ne vous inquiétez pas. Votre propre PC est destiné à l'apprentissage.

Propre PC (Windows 10)

** * Torchvision ** est un ensemble avec PyTorch et est une bibliothèque utilisée pour ** le prétraitement d'images et la création de jeux de données **.

Raspberry Pi 3 Model B+ (Raspbian Stretch)

J'ai utilisé ** Raspberry Pi Camera Module V2 ** comme caméra à brancher sur le Raspberry Pi. J'ai également mis VNC Viewer sur mon PC et utilisé Raspeye avec ** connexion SSH **.

Comment construire

Mettez la version ci-dessus du package sur chaque ordinateur. J'omettrai les détails, mais je me suis référé à la zone de lien du site.

PyTorch / Torchvision

Installez sur ** PC ** en sélectionnant l'environnement depuis PyTorch Official.

** [⚠Remarque] ** Le GPU ne peut pas être utilisé sauf s'il est fabriqué par NVIDIA, donc si vous avez "Intel", sélectionnez ** CUDA → Aucun ** (utilise normalement le processeur).

À ** Raspberry Pi **, "Mettre PyTorch v1.3.0 dans Raspberry Pi 3" et "Code source de PyTorch Deep Learning Framework dans Raspberry Pi" Comment construire à partir de " est une bonne référence pour la construction.

** [⚠Remarque] ** Spécifiez la version comme git clone ~~~ -b v1.3.0 etc. ** [⚠Remarque] ** Dans PyTorch 1.4.0, erreur fatale: immintrin.h n'existe pas et la compilation s'est arrêtée à environ 80%. Un mystère. (2020/03/20)

OpenCV

"Installer OpenCV 3 sur Raspberry Pi + Python 3 aussi facilement que possible", etc., et installez-le sur ** Raspberry **.

Les deux prennent quelques heures à construire ...

Mis en œuvre immédiatement

Après de nombreux essais et erreurs, je viens de créer un script Python.

* Étape 1 *: Créez des données d'entraînement pour les images utilisées pour l'apprentissage par vous-même

J'ai créé les données d'image de mes effets personnels à utiliser pour l'apprentissage. On suppose que la picamera est insérée dans la tarte de la râpe et ** fixée de sorte que la picamera ne bouge pas **.

Programme pour créer

Après avoir tourné l'écran avec la touche "r", appuyez sur "p" pour photographier l'arrière-plan sans rien capturer. Si vous placez les effets personnels que vous souhaitez photographier et photographiez à nouveau avec "p", la différence d'arrière-plan sera faite et la photo dans le ** cadre vert ** sera enregistrée.

Cette fois, je vais classer les trois catégories de ** "certains téléphones", "regarder" et "portefeuille" **, donc je vais juste prendre ces trois photos.

take_photo.py


# coding: utf-8
import cv2
from datetime import datetime
import picamera
import picamera.array

MIN_LEN = 50  #Longueur minimale d'un côté du cadre de détection d'objet
GRAY_THR = 20  #Seuil de changement de concentration
CUT_MODE = True  # True:Couper et enregistrer l'objet détecté, False:Enregistrez l'image entière telle quelle


def imshow_rect(img, contour, minlen=0):
"""
Entourez tous les points de détection d'objet de l'image acquise avec un cadre carré
argument:
    img:Image de la caméra
    contour:Contour
    minlen:Seuil de taille de détection (à l'exclusion des zones où un côté du cadre est plus court que cela)
"""
    for pt in contour:
        x, y, w, h = cv2.boundingRect(pt)
        if w < minlen and h < minlen: continue
        cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
    cv2.imshow('Preview', img)


def save_cutimg(img, contour, minlen=0):
"""
Découpez et enregistrez tous les points de détection d'objet dans l'image acquise
argument:
Comme ci-dessus
"""
    #Obtenez la date et l'heure et utilisez-les pour le nom du fichier
    dt = datetime.now()
    f_name = '{}.jpg'.format(dt.strftime('%y%m%d%H%M%S'))
    imgs_cut = []
    for pt in contour:
        x, y, w, h = cv2.boundingRect(pt)
        if w < minlen and h < minlen: continue
        imgs_cut.append(img[y:y+h, x:x+w])

    #Découpez et enregistrez l'objet
    if not imgs_cut: return -1
    if len(imgs_cut) > 1:
        for i in range(len(imgs_cut)):
            cv2.imwrite(f_name[:-4]+'_'+str(i+1)+f_name[-4:], imgs_cut[i])
    else:
        cv2.imwrite(f_name, imgs_cut[0])
    return len(imgs_cut)


def save_img(img):
"""
Enregistrez l'image acquise telle quelle
argument:
Comme ci-dessus
"""
    dt = datetime.now()
    fname = '{}.jpg'.format(dt.strftime('%y%m%d%H%M%S'))
    cv2.imwrite(fname, img)


def take_photo():
"""
Prise de vue en arrière-plan->Photographie d'objets,sauvegarder
Entrée clé: 
    "p":prendre une photo
    "q":Arrêtez
    "r":Faire pivoter l'écran (lors de la prise de vue en arrière-plan)
    "i":Recommencer depuis le début (lors de la prise de vue d'un objet)
"""
    cnt = 0
    #Démarrer la picamera
    with picamera.PiCamera() as camera:
        camera.resolution = (480, 480)  #résolution
        camera.rotation = 0  #Angle de rotation de la caméra(À chaque fois)
        #Commencer le streaming
        with picamera.array.PiRGBArray(camera) as stream:
            print('Set background ... ', end='', flush=True)
            #Commencez par photographier l'arrière-plan
            while True:
                #Obtenir et afficher des images en streaming
                camera.capture(stream, 'bgr', use_video_port=True)
                cv2.imshow('Preview', stream.array)

                wkey = cv2.waitKey(5) & 0xFF  #Réception des entrées clés

                stream.seek(0)  #2 sorts pour capturer de nouveaux
                stream.truncate()

                if wkey == ord('q'):
                    cv2.destroyAllWindows()
                    return print()
                elif wkey == ord('r'):
                    camera.rotation += 90
                elif wkey == ord('p'):
                    camera.exposure_mode = 'off'  #Balance des blancs fixe
                    save_img(stream.array)
                    #Échelle de gris et définie comme image de fond
                    back_gray = cv2.cvtColor(stream.array, 
                                             cv2.COLOR_BGR2GRAY)
                    print('done')
                    break

            #Après avoir réglé l'arrière-plan,Photographier des objets sans déplacer la caméra
            print('Take photos!')
            while True:
                camera.capture(stream, 'bgr', use_video_port=True)
                #Échelle de gris du cadre actuel
                stream_gray = cv2.cvtColor(stream.array, 
                                           cv2.COLOR_BGR2GRAY)

                #Calculez la valeur absolue de la différence et binarisez-la,Fabrication de masques
                diff = cv2.absdiff(stream_gray, back_gray)
                mask = cv2.threshold(diff, GRAY_THR, 255, 
                                     cv2.THRESH_BINARY)[1]
                cv2.imshow('mask', mask)

                #Contour pour la détection d'objets,Fabrication de masques
                contour = cv2.findContours(mask,
                                           cv2.RETR_EXTERNAL,
                                           cv2.CHAIN_APPROX_SIMPLE)[1]

                #Tous les objets détectés sont affichés dans un carré
                stream_arr = stream.array.copy()
                imshow_rect(stream_arr, contour, MIN_LEN)

                wkey = cv2.waitKey(5) & 0xFF

                stream.seek(0)
                stream.truncate()

                if wkey == ord('q'):
                    cv2.destroyAllWindows()
                    return
                elif wkey == ord('i'):
                    break
                elif wkey == ord('p'):
                    if CUT_MODE:
                        num = save_cutimg(stream.array, contour, MIN_LEN)
                        if num > 0:
                            cnt += num
                            print('  Captured: {} (sum: {})'.format(num, cnt))
                    else:
                        save_img(stream.array)
                        cnt += 1
                        print('  Captured: 1 (sum: {})'.format(cnt))

    print('Initialized')
    take_photo()


if __name__ == '__main__':
    take_photo()

Courir

Je prends juste des photos. L'image recadrée pour chaque cadre vert est enregistrée comme ceci.

200328174638.jpg 200328174642_1.jpg 200328174642_2.jpg 200328174642_3.jpg

** [⚠Remarque] S'il y a trop peu de photos, cela n'apprendra pas bien. ** ** J'ai pris plus de 50 photos pour chaque classe pour les données d'entraînement, mais je me demande s'il y en a encore peu ... Pour le moment, divers bruits sont ajoutés lors de l'apprentissage et la quantité de données augmente.

Mettez les photos dans un dossier et utilisez Slack ou quelque chose pour ** les déplacer vers votre PC **. (Semi-analogique) Et stockez chaque photo personnelle dans la structure de dossiers ci-dessous **. ** **

image_data
├─train
│  ├─phone
│  │       191227013419.jpg
│  │       191227013424.jpg
│  │              :
│  ├─wallet
│  │       191227013300.jpg
│  │       191227013308.jpg
│  │              :
│  └─watch
│          191227013345.jpg
│          191227013351.jpg
|                 :
└─val
    ├─phone
    │       191227013441.jpg
    │       191227013448.jpg
    |              :
    ├─wallet
    │       191227013323.jpg
    │       191227013327.jpg
    |              :
    └─watch
            191227013355.jpg
            191227013400.jpg
                   :

* Étape 2 *: Apprentissage en profondeur avec PyTorch sur PC

Construisez un réseau et entraînez-vous avec l'image ci-dessus.

Programme pour créer

Lorsqu'il est exécuté, il lit l'image du dossier précédent, commence l'apprentissage et génère le fichier de progression, le diagramme de transition de perte et de précision et le fichier de paramètres final.

En le créant, je me suis référé au "PyTorch Neural Network Implementation Handbook" (Hidewa System).

Même si vous interrompez avec "Ctrl + C", la progression de l'apprentissage jusqu'à ce point est enregistrée sous ** "train_process.ckpt" **, et vous pouvez continuer à apprendre à partir de la prochaine exécution. Il est normal de changer les hyper paramètres en cours de route.

À propos, le ** dossier d'images ** de torchvsion crée un ensemble de données avec le nom du dossier contenant les photos comme nom de classe. Facile! !! Les photos du dossier train sont utilisées pour l'apprentissage et les photos du dossier val sont utilisées pour l'évaluation.

train_net.py


# coding: utf-8
import os
import re
import torch.nn as nn
import torch.optim as optim
import torch.utils
from torchvision import datasets, transforms
import matplotlib.pyplot as plt

DATA_DIR = 'image_data'  #Nom du dossier d'images
CKPT_PROCESS = 'train_process.ckpt'  #Progression de l'apprentissage enregistrer le nom du fichier
CKPT_NET = 'trained_net.ckpt'  #Nom du fichier de paramètres appris
NUM_CLASSES = 3  #Nombre de cours
NUM_EPOCHS = 100  #Nombre d'apprentissage

#Hyper paramètres qui changent fréquemment
LEARNING_RATE = 0.001  #Taux d'apprentissage
MOMENTUM = 0.5  #inertie

checkpoint = {}  #Variables pour enregistrer la progression


#Définition de la conversion des données d'image (volumineux)
#Avec la taille de Redimensionner,Lié à la première taille d'entrée linéaire du classificateur
data_transforms = transforms.Compose([
    transforms.Resize((112, 112)),  #redimensionner
    transforms.RandomRotation(30),  #Faire une rotation aléatoire
    transforms.Grayscale(),  #Binarisation
    transforms.ToTensor(),  #Tensolisation
    transforms.Normalize(mean=[0.5], std=[0.5])  #Normalisation (les nombres sont texto)
])

val_transforms = transforms.Compose([
    transforms.Resize((112, 112)),
    transforms.Grayscale(),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.5], std=[0.5])
])

#Création de l'ensemble de données
train_dataset = datasets.ImageFolder(
    root=os.path.join(DATA_DIR, 'train'),
    transform=train_transforms
)

val_dataset = datasets.ImageFolder(
    root=os.path.join(DATA_DIR, 'val'),
    transform=val_transforms
)

#Obtenez un mini lot
train_loader = torch.utils.data.DataLoader(
    dataset=train_dataset,
    batch_size=10,  #Taille du lot au moment de l'apprentissage
    shuffle=True  #Mélanger les données d'entraînement
)

val_loader = torch.utils.data.DataLoader(
    dataset=val_dataset,
    batch_size=10,
    shuffle=True
)


class NeuralNet(nn.Module):
    """Définition du réseau. nn.Héritage du module"""
    def __init__(self, num_classes):
        super(NeuralNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(1, 8, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(8, 16, kernel_size=5, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),
            nn.Linear(400, 200),
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),
            nn.Linear(200, num_classes)
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x


def main():
    """Lire les données pendant l'entraînement->Apprentissage(->Sauvegarde des données pendant l'entraînement)->Illustration des résultats"""
    global checkpoint
    print('[Settings]')
    #Réglages de l'appareil
    device = 'cuda' if torch.cuda.is_available() else 'cpu'

    #réseau,Fonction d'évaluation,Réglage de la fonction d'optimisation
    net = NeuralNet(NUM_CLASSES).to(device)
    criterion = nn.CrossEntropyLoss()  #Fonction d'évaluation
    optimizer = optim.SGD(  #Algorithme d'optimisation
        net.parameters(),
        lr=LEARNING_RATE,
        momentum=MOMENTUM,
        weight_decay=5e-4
    )

    #Paramètres d'affichage
    # print('  Device               :', device)
    # print('  Dataset Class-Index  :', train_dataset.class_to_idx)
    # print('  Network Model        :', re.findall('(.*)\(', str(net))[0])
    # print('  Criterion            :', re.findall('(.*)\(', str(criterion))[0])
    # print('  Optimizer            :', re.findall('(.*)\(', str(optimizer))[0])
    # print('    -Learning Rate     :', LEARNING_RATE)
    # print('    -Momentum          :', MOMENTUM)

    t_loss_list = []
    t_acc_list = []
    v_loss_list = []
    v_acc_list = []
    epoch_pre = -1

    #Acquisition de données de formation (en cours de route)
    if os.path.isfile(CKPT_PROCESS):
        checkpoint = torch.load(CKPT_PROCESS)
        net.load_state_dict(checkpoint['net'])
        optimizer.load_state_dict(checkpoint['optimizer'])
        t_loss_list = checkpoint['t_loss_list']
        t_acc_list = checkpoint['t_acc_list']
        v_loss_list = checkpoint['v_loss_list']
        v_acc_list = checkpoint['v_acc_list']
        epoch_pre = checkpoint['epoch']
        print("Progress until last time = {}/{} epochs"\
              .format(epoch_pre+1, NUM_EPOCHS))

    print('[Main process]')
    for epoch in range(epoch_pre+1, NUM_EPOCHS):
        t_loss, t_acc, v_loss, v_acc = 0, 0, 0, 0

        #Apprentissage---------------------------------------------------------
        net.train()  #Mode d'apprentissage
        for _, (images, labels) in enumerate(train_loader):
            images, labels = images.to(device), labels.to(device)
            optimizer.zero_grad()
            outputs = net(images)
            loss = criterion(outputs, labels)
            t_loss += loss.item()
            t_acc += (outputs.max(1)[1] == labels).sum().item()
            loss.backward()
            optimizer.step()
        avg_t_loss = t_loss / len(train_loader.dataset)
        avg_t_acc = t_acc / len(train_loader.dataset)

        #Évaluation---------------------------------------------------------
        net.eval()  #Mode d'évaluation
        with torch.no_grad():  #Arrêter de mettre à jour le dégradé
            for images, labels in val_loader:
                images, labels = images.to(device), labels.to(device)
                images = images.to(device)
                labels = labels.to(device)
                outputs = net(images)
                loss = criterion(outputs, labels)
                v_loss += loss.item()
                v_acc += (outputs.max(1)[1] == labels).sum().item()
        avg_v_loss = v_loss / len(val_loader.dataset)
        avg_v_acc = v_acc / len(val_loader.dataset)
        # --------------------------------------------------------------
        print('\rEpoch [{}/{}] | Train [oss:{:.3f}, acc:{:.3f}] | Val [loss:{:.3f}, acc:{:.3f}]'\
              .format(epoch+1, NUM_EPOCHS, avg_t_loss, avg_t_acc, avg_v_loss, avg_v_acc), end='')

        #perte,Record de précision
        t_loss_list.append(avg_t_loss)
        t_acc_list.append(avg_t_acc)
        v_loss_list.append(avg_v_loss)
        v_acc_list.append(avg_v_acc)

        #Processus de sauvegarde des progrès
        checkpoint['net'] = net.state_dict()
        checkpoint['optimizer'] = optimizer.state_dict()
        checkpoint['t_loss_list'] = t_loss_list
        checkpoint['t_acc_list'] = t_acc_list
        checkpoint['v_loss_list'] = v_loss_list
        checkpoint['v_acc_list'] = v_acc_list
        checkpoint['epoch'] = epoch

    graph()
    save_process()
    save_net()


def save_process():
    """Enregistrer la progression"""
    global checkpoint
    if not checkpoint: return
    torch.save(checkpoint, CKPT_PROCESS)


def save_net():
    """Enregistrer uniquement les informations du réseau"""
    global checkpoint
    if not checkpoint: return
    torch.save(checkpoint['net'], CKPT_NET)


def graph():
    """perte,Précision graphique"""
    global checkpoint
    if not checkpoint: return
    t_loss_list = checkpoint['t_loss_list']
    t_acc_list = checkpoint['t_acc_list']
    v_loss_list = checkpoint['v_loss_list']
    v_acc_list = checkpoint['v_acc_list']

    plt.figure(figsize=(10, 4))
    plt.subplot(1, 2, 1)
    plt.plot(range(len(t_loss_list)), t_loss_list,
             color='blue', linestyle='-', label='t_loss')
    plt.plot(range(len(v_loss_list)), v_loss_list,
             color='green', linestyle='--', label='v_loss')
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.title('Training and validation loss')
    plt.grid()

    plt.subplot(1, 2, 2)
    plt.plot(range(len(t_acc_list)), t_acc_list,
             color='blue', linestyle='-', label='t_acc')
    plt.plot(range(len(v_acc_list)), v_acc_list,
             color='green', linestyle='--', label='v_acc')
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('acc')
    plt.title('Training and validation accuracy')
    plt.grid()
    plt.show()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print()
        graph()
        save_process()

** [⚠Note] L'échelle du réseau est modérée. ** ** Si vous augmentez trop le nombre de couches et de nœuds, vous obtiendrez l'erreur DefaultCPUAllocator: impossible d'allouer de la mémoire: vous avez essayé d'allouer 685198800 octets. ..

Courir

Cliquez ici pour la progression de l'apprentissage. La gauche est la perte et la droite est la précision. La ligne bleue correspond aux données d'entraînement et la ligne verte en pointillé aux données de vérification. 0.png La précision des données de vérification est d'environ 72%. Il y a place à amélioration ...

Lorsque vous aurez terminé l'entraînement, vous aurez un fichier ** "" training_net.ckpt "" ** qui ne stockera que les paramètres entraînés, et vous le renverrez à Rasppie avec Slack ou quelque chose **.

* Step3 *: classification en temps réel des images de la caméra avec Raspeye et affichage des résultats

Dans un but, les objets de l'image de la caméra sont classés en temps réel et affichés de manière agréable.

Programme pour créer

Commencez par photographier l'arrière-plan, puis divisez l'arrière-plan des images suivantes, découpez les objets émergents et créez un lot de tenseur en 4 dimensions grâce à un prétraitement défini. Le lot entier est passé à travers le réseau, converti en probabilité de chaque classe, et la classe (nom de l'objet) avec la probabilité la plus élevée est superposée et affichée dans la fenêtre.

Chargez le "training_net.ckpt" créé précédemment.

** [⚠Remarque] Si vous ne définissez pas de limite supérieure sur la taille du lot (le nombre d'objets à détecter en même temps), la tarte aux râpes peut geler lorsque vous essayez de traiter une grande quantité de zones détectées à la fois. ** **

raltime_classification.py


# coding: utf-8
import os
from PIL import Image
from time import sleep
import cv2
import picamera
import picamera.array
import torch
#Dans le répertoire pytorch"export OMP_NUM_THREADS=1 or 2 or 3"Obligatoire(La valeur par défaut est 4)
#Le nombre de cœurs de traitement parallèles"print(torch.__config__.parallel_info())"Confirmer avec
import torch.nn as nn
import torch.utils
from torchvision import transforms

CKPT_NET = 'trained_net.ckpt'  #Fichier de paramètres formés
OBJ_NAMES = ['Phone', 'Wallet', 'Watch']  #Afficher le nom de chaque classe
MIN_LEN = 50
GRAY_THR = 20
CONTOUR_COUNT_MAX = 3  #Taille du lot(Nombre d'objets à détecter à la fois)Limite supérieure de
SHOW_COLOR = (255, 191, 0)  #Couleur du cadre(B,G,R)

NUM_CLASSES = 3
PIXEL_LEN = 112  #Taille après redimensionnement(1 côté)
CHANNELS = 1  #Nombre de canaux de couleur(BGR:3,niveaux de gris:1)


#Définition de la conversion des données d'image
#Avec redimensionner,Lié à la première entrée linéaire du classificateur
data_transforms = transforms.Compose([
    transforms.Resize((PIXEL_LEN, PIXEL_LEN)),
    transforms.Grayscale(),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.5], std=[0.5])
])


class NeuralNet(nn.Module):
    """Définition du réseau.Doit être le même que celui utilisé pour l'apprentissage"""
    def __init__(self, num_classes):
        super(NeuralNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(1, 8, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(8, 16, kernel_size=5, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),
            nn.Linear(400, 200),
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),
            nn.Linear(200, num_classes)
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x


def detect_obj(back, target):
    """
Avec le traitement des différences de fond OpenCV,Créer un tapple d'objets détectés
argument:
        back:Image d'arrière-plan d'entrée
Image couleur
        target:Image pour la différence de fond
Image couleur.Découpez plusieurs objets,Assembler dans une image couleur taple
    """
    print('Detecting objects ...')
    #Binarisation
    b_gray = cv2.cvtColor(back, cv2.COLOR_BGR2GRAY)
    t_gray = cv2.cvtColor(target, cv2.COLOR_BGR2GRAY)
    #Calculez la différence
    diff = cv2.absdiff(t_gray, b_gray)

    #Contour selon le seuil,Créer un masque,Extraire des objets
    #L'index de findContours est, cv2.__version__ == 4.2.0->[0], 3.4.7->[1]
    mask = cv2.threshold(diff, GRAY_THR, 255, cv2.THRESH_BINARY)[1]
    cv2.imshow('mask', mask)
    contour = cv2.findContours(mask,
                               cv2.RETR_EXTERNAL,
                               cv2.CHAIN_APPROX_SIMPLE)[1]

    #Coordonnées de la zone de changement détectées au-dessus d'une certaine hauteur et largeur,Créer un lot de taille
    pt_list = list(filter(
        lambda x: x[2] > MIN_LEN and x[3] > MIN_LEN,
        [cv2.boundingRect(pt) for pt in contour]
    ))[:CONTOUR_COUNT_MAX]

    #Découpez le cadre en fonction des informations de position,Convertir en un tuple d'image PIL et retourner
    obj_imgaes = tuple(map(
        lambda x: Image.fromarray(target[x[1]:x[1]+x[3], x[0]:x[0]+x[2]]),
        pt_list
    ))
    return (obj_imgaes, pt_list)


def batch_maker(tuple_images, transform):
    """
Transformez le tuple d'une image au format PIL,Convertir en un lot de tenseurs pouvant être traité sur le réseau
argument:
        tuple_images:Image PIL taple
        transform:définition de conversion d'image torchvision
    """
    return torch.cat([transform(img) for img
                      in tuple_images]).view(-1, CHANNELS, PIXEL_LEN, PIXEL_LEN)


def judge_what(img, probs_list, pos_list):
    """
Déterminer l'objet à partir de la probabilité d'appartenir à chaque classe,Afficher le cadre et le nom à cette position,Renvoie l'index de la classe
argument:
        probs_list:Tableau secondaire de probabilités.Format de lot
        pos_list:Tableau secondaire de positions.Format de lot
    """
    print('Judging objects ...')
    #Convertir en une liste des probabilités les plus élevées et leurs index
    ip_list = list(map(lambda x: max(enumerate(x), key = lambda y:y[1]),
                       F.softmax(probs_list, dim=-1)))  # <- 4/30 corrections

    #Convertir l'index en nom d'objet,Ecrire et afficher le nom et la certitude de l'objet à la position de l'objet
    for (idx, prob), pos in zip(ip_list, pos_list):
        cv2.rectangle(img, (pos[0], pos[1]), (pos[0]+pos[2], pos[1]+pos[3]), SHOW_COLOR, 2)
        cv2.putText(img, '%s:%.1f%%'%(OBJ_NAMES[idx], prob*100), (pos[0]+5, pos[1]+20),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.8, SHOW_COLOR, thickness=2)
    return ip_list


def realtime_classify():
    """Chargement du modèle formé->Lire les données de test->Classification->Afficher le résultat superposé sur l'image"""
    #Réglages de l'appareil
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    #paramètres réseau
    net = NeuralNet(NUM_CLASSES).to(device)

    #Acquisition de données formée
    if os.path.isfile(CKPT_NET):
        checkpoint = torch.load(CKPT_NET)
        net.load_state_dict(checkpoint)
    else:
        raise FileNotFoundError('No trained network file: {}'.format(CKPT_NET))

    #Mode d'évaluation
    net.eval()
    #Démarrer la picamera
    with picamera.PiCamera() as camera:
        camera.resolution = (480, 480)
        #Commencer le streaming
        with picamera.array.PiRGBArray(camera) as stream:
            print('Setting background ...')
            sleep(2)
    
            camera.exposure_mode = 'off'  #Balance des blancs fixe
            camera.capture(stream, 'bgr', use_video_port=True)
            #Définir comme arrière-plan
            img_back = stream.array

            stream.seek(0)
            stream.truncate()
            
            print('Start!')
            with torch.no_grad():
                while True:
                    camera.capture(stream, 'bgr', use_video_port=True)
                    #Différence d'arrière-plan pour les futures images d'entrée
                    img_target = stream.array
                    #Détecte les objets et leurs positions
                    obj_imgs, positions = detect_obj(img_back, img_target)
                    if obj_imgs:
                        #Convertir les objets détectés au format d'entrée réseau
                        obj_batch = batch_maker(obj_imgs, data_transforms)
                        #Classification
                        outputs = net(obj_batch)
                        #Jugement
                        result = judge_what(img_target, outputs, positions)
                        print('  Result:', result)

                    #afficher
                    cv2.imshow('detection', img_target)

                    if cv2.waitKey(200) == ord('q'):
                        cv2.destroyAllWindows()
                        return

                    stream.seek(0)
                    stream.truncate()


if __name__ == "__main__":
    try:
        realtime_classify()
    except KeyboardInterrupt:
        cv2.destroyAllWindows()

Courir

Apportez le précédent "training_net.ckpt" sur la tarte râpe et exécutez-le dans le même répertoire. Le nom de l'objet détecté et sa certitude s'affichent.

Le résultat de l'exécution est ... Je suis satisfait du classement de haute précision à partir du moment où je l'ai mis! !!

0.jpg 1.jpg 2.jpg

** [⚠Remarque] Il est recommandé de changer le nombre de cœurs utilisés pour l'exécution (par défaut 4). ** ** Il y a un grand risque de gel lorsqu'il est utilisé avec 4 cœurs pleins. Dans le répertoire pytorch, changez la commande comme ʻexport OMP_NUM_THREADS = 2(en utilisant 2 cœurs). Vous pouvez vérifier le nombre de cœurs avecprint (torch .__ config __. Parallel_info ()). Cependant, lorsque le shell est fermé, les modifications sont annulées, donc pour le rendre persistant, sous le bas ... ~ fi de **". Profile "** in / home / pi`, ʻexport OMP_NUM_THREADS Écrivez = 2 et redémarrez.

Résumé

J'ai pu faire ce que je voulais faire! (Je suis désolé pour le manque de lisibilité ...) Si vous utilisez la détection de visage OpenCV, il semble que vous puissiez l'appliquer immédiatement à une reconnaissance de visage très simple.

Au départ, je pensais implémenter un SSD, mais je pensais qu'il serait difficile de créer un ensemble de données avec des informations de localisation, et j'ai abandonné car je ne pouvais pas résoudre l'erreur que j'avais en essayant de m'entraîner avec des échantillons de données. ..

Contrairement aux SSD, l'inconvénient est que la différence d'arrière-plan cette fois ne permet pas de séparer les objets qui se chevauchent et est considérée comme un.

C'était une bonne étude ~

Recommended Posts

Classification en temps réel de plusieurs objets dans les images de la caméra avec apprentissage en profondeur de Raspberry Pi 3 B + et PyTorch
L'histoire de l'apprentissage profond avec TPU
Comptez le nombre de paramètres dans le modèle d'apprentissage en profondeur
Prenez la valeur du thermo-hygromètre SwitchBot avec Raspberry Pi
Changer les valeurs du thermo-hygromètre Bot avec Raspberry Pi
Une histoire à laquelle j'ai pensé en essayant d'identifier les informations de plénitude du parking en utilisant l'image obtenue par la caméra Web et Razpai et le deep learning.
Notez ce que vous voulez faire à l'avenir avec Razpai
Contrôlez librement l'affichage du tableau d'affichage RGB LED Matirix Lightning avec Raspberry Pi 3B +
Jouez avec le module de caméra Raspberry Pi Zero WH Partie 1
[Apprentissage en profondeur] Classification d'images avec un réseau neuronal convolutif [DW jour 4]
[PyTorch] Classification des images du CIFAR-10
J'ai essayé d'automatiser l'arrosage du pot avec Raspberry Pi
Consigner périodiquement les valeurs des capteurs d'environnement Omron avec Raspberry Pi
Graphique de l'historique du nombre de couches de deep learning et du changement de précision
Mettez vos propres données d'image dans Deep Learning et jouez avec
Enregistrement des valeurs du capteur d'environnement Omron avec Raspberry Pi (type USB)
Classification multi-étiquette d'images multi-classes avec pytorch
Deep learning 2 appris par l'implémentation (classification d'images)
Othello-De la troisième ligne de "Implementation Deep Learning" (3)
Modèle de reconnaissance d'image utilisant l'apprentissage profond en 2016
Accélérez l'apprentissage en profondeur avec le processeur Rasperry Pi 4
Analyse émotionnelle des tweets avec apprentissage en profondeur
Othello-De la troisième ligne de "Implementation Deep Learning" (2)
J'ai tweeté l'éclairement de la pièce avec Raspberry Pi, Arduino et un capteur optique
Extraire la couleur de l'objet dans l'image avec le clustering Mask R-CNN et K-Means
Deep Learning from scratch La théorie et la mise en œuvre de l'apprentissage profond appris avec Python Chapitre 3
Informer périodiquement l'état de traitement de Raspberry Pi avec python → Google Spreadsheet → LINE
Comment installer le framework d'apprentissage en profondeur Tensorflow 1.0 dans l'environnement Windows Anaconda