[PYTHON] L'apprentissage en profondeur facilite considérablement la visualisation du laps de temps des changements physiques

introduction

"Autoportrait (corps)" est une habitude de nombreux stagiaires (personnes qui aiment l'entraînement musculaire). C'est un moment idéal pour prendre une photo du corps pompé après l'entraînement et y revenir plus tard. De plus, si vous affichez les images capturées dans une animation comme un laps de temps, vous pouvez voir que la croissance musculaire est plus sélectionnable! Cet article utilise l'apprentissage en profondeur pour améliorer considérablement le laps de temps du corps.

D'abord du résultat

ezgif.com-optimize (3).gif Modifications corporelles de décembre 2017 à mars 2020

table des matières

* [2-5. Résultat](résultat # 2-5) * [Résumé](# Résumé) Contour: Un intervalle de temps a été créé à partir des images prises. Cependant, je m'inquiétais de l'écart entre les images, donc je l'ai corrigé manuellement pour créer un laps de temps fluide. De plus, afin d'éviter les problèmes de travail manuel, une correction a été effectuée automatiquement à l'aide de l'apprentissage en profondeur. 1. Correction manuelle 1-1. Affichage tel quel

Pour le moment, créons un laps de temps qui change simplement l'image telle qu'elle est en continu.

Code de création time lapse (partie)



#Vous pouvez faire des vidéos avec opencv,
#Pour créer un fichier mp4 pouvant être lu sur discord dans l'environnement google colab
#J'ai aimé utiliser skvideo.
import skvideo.io

def create_video(imgs, out_video_path, size_wh):
  video = []
  vid_out = skvideo.io.FFmpegWriter(out_video_path,
      inputdict={
          "-r": "10"
      },
      outputdict={
          "-r": "10"
      })
  
  for img in imgs:
    img = cv2.resize(img, size_wh)
    vid_out.writeFrame(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))

  vid_out.close()

imgs = load_images("images_dir")
create_video(imgs,  "video.mp4", (w,h))

Le résultat est le suivant.

ezgif.com-crop.gif

Je m'inquiète de l'écart et je ne peux pas me concentrer sur mon enfant (corps).

1-2. Fixation de la position

Je veux en quelque sorte éliminer facilement cet écart. Si je mets un point de référence quelque part sur mon corps et que je le répare, j'ai trouvé la solution du «mamelon» et du «nombril» en 0,1 seconde environ. Voici comment réparer vos mamelons et votre nombril.

1-2-1. Outil donnant les coordonnées du nombril du mamelon

Tout d'abord, créez un outil qui donne les coordonnées UV au mamelon et au nombril. Il est peut-être possible de le réaliser en utilisant cvat etc., mais quand j'ai estimé le temps pour le maîtriser et le temps pour créer mon propre outil, j'ai conclu qu'il était plus rapide de le fabriquer moi-même, alors je l'ai fait.

La spécification de l'outil est que si vous spécifiez un dossier, les images seront affichées en continu, donc pour chaque image, cliquez sur les trois points du mamelon et du nombril, et les coordonnées cliquées seront sorties dans le fichier csv. Devenir. J'ai utilisé tkinter pour l'interface graphique (la source est abrégée).

1-2-2. Création vidéo

L'emplacement du mamelon et du nombril est fixé par conversion affine selon la première image.

Correction du code de création de laps de temps (pièce)


def p3affine_img(img, src_p, dst_p):
    h, w, ch = img.shape
    pts1 = np.float32([src_p[0],src_p[1],src_p[2]])
    pts2 = np.float32([dst_p[0],dst_p[1],dst_p[2]])
    M = cv2.getAffineTransform(pts1,pts2)
    dst = cv2.warpAffine(img,M,(h, w))
    return dst


df = read_annotationd() #réduction

imgs = []
src_p = None
for index, row in df.iterrows():
    img = cv2.imread(row.file)
    dst_p = [ [row.p1x, row.p1y], #Mamelon gauche
              [row.p2x, row.p2y], #Mamelon droit
              [row.p3x, row.p3y]] #nombril
    if src_p is None:
      src_p = dst_p
    else:
      img = p3affine_img(img, dst_p, src_p)
    
    imgs.append(img)

write_video(imgs) #réduction

Les résultats sont les suivants.

ezgif.com-optimize.gif

J'ai pu faire le laps de temps auquel je m'attendais, félicitations. ** Ne pas! ** **

Le nombre de feuilles auxquelles les coordonnées sont données cette fois-ci est de 120 (la période va du 9 septembre 2019 à mars 2020). Cependant, j'ai encore 281 images que j'ai prises depuis décembre 2017 et dont les coordonnées n'ont pas été communiquées. De plus, nous devons faire de l'entraînement musculaire pour les décennies à venir, c'est-à-dire que nous devons continuer à donner des coordonnées pendant des décennies. Même en imaginant, le cortisol est sécrété et il tombe en catabolisme. J'ai pensé à ajouter du sucre pour résoudre ce problème.

C'est vrai ~~ Allons à la salle de gym ~~ Apprentissage en profondeur.

2. Correction automatique à l'aide de l'apprentissage en profondeur

Créez un modèle qui estime les positions du «mamelon» et du «nombril». Si cela est réalisé, tout ce que vous avez à faire est d'appliquer la conversion Affin comme auparavant. Nous abordons la détection des mamelons et du nombril comme une tâche de segmentation. La détection des points clés tels que l'estimation de la posture semble être meilleure, mais j'ai personnellement plus d'expérience avec les tâches de segmentation, alors j'ai choisi cela.

L'ensemble de données est le suivant. Depuis 2019/9 à 2020/3 ont déjà reçu des coordonnées, celles-ci seront utilisées pour les images de formation et les images de vérification afin d'obtenir automatiquement les coordonnées pour la période restante.

image.png

2-1. Création de données d'annotation

Il est concevable de résoudre par 4 classifications de "mamelon droit", "mamelon gauche", "nombril" et "arrière-plan", mais cette fois nous avons divisé en 2 classifications de "mamelon droit, mamelon gauche, nombril" et "arrière-plan". J'ai pensé qu'il serait facile de les classer sur une base de règles tant que je pourrais détecter trois points. Maintenant, faisons une image de masque. Sur la base des données de coordonnées créées précédemment, agrandissez un peu les points de coordonnées et remplissez-les avec 1. À part cela, c'est l'arrière-plan, alors définissez-le sur 0.

for index, row in df.iterrows():
  file = row.file
  mask = np.zeros((img_h, img_w), dtype=np.uint8)
  mask = cv2.circle(mask,(row.p1x, row.p1y,), 15, (1), -1)
  mask = cv2.circle(mask,(row.p2x, row.p2y,), 15, (1), -1)
  mask = cv2.circle(mask,(row.p3x, row.p3y,), 15, (1), -1)
  save_img(mask, row.file) #réduction

Visuellement (1 est blanc, 0 est noir), les données sont les suivantes.

image.png

Faites ces paires avec l'image physique.

2-2. Apprentissage

Pour apprendre, j'ai utilisé DeepLab v3 (vision de la torche). Les 120 images ont été divisées à 8: 2 pour la formation et la vérification. Bien que le nombre de feuilles soit assez petit, nous n'avons pas élargi les données pour les raisons suivantes.

Cependant, je pense qu'il vaut mieux étendre les données (ce n'est tout simplement pas ennuyeux).

Fonctions liées à la classe / à l'apprentissage


class MaskDataset(Dataset):
  def __init__(self, imgs_dir, masks_dir, scale=1, transforms=None):
    self.imgs_dir = imgs_dir
    self.masks_dir = masks_dir

    self.imgs = list(sorted(glob.glob(os.path.join(imgs_dir, "*.jpg "))))
    self.msks = list(sorted(glob.glob(os.path.join(masks_dir, "*.png "))))
    self.transforms = transforms
    self.scale = scale

  def __len__(self):
      return len(self.imgs_dir)

  @classmethod
  def preprocess(cls, pil_img, scale):

    #Cela semble bon à l'échelle de gris, mais c'est gênant donc je ne le ferai pas
    # pil_img = pil_img.convert("L") 

    w, h = pil_img.size
    newW, newH = int(scale * w), int(scale * h)
    pil_img = pil_img.resize((newW, newH))

    img_nd = np.array(pil_img)

    if len(img_nd.shape) == 2:
      img_nd = np.expand_dims(img_nd, axis=2)

    # HWC to CHW
    img_trans = img_nd.transpose((2, 0, 1))
    if img_trans.max() > 1:
        img_trans = img_trans / 255

    return img_trans

  def __getitem__(self, i):
      
    mask_file = self.msks[i]
    img_file = self.imgs[i]

    mask = Image.open(mask_file)
    img = Image.open(img_file)

    img = self.preprocess(img, self.scale)
    mask = self.preprocess(mask, self.scale)

    item = {"image": torch.from_numpy(img), "mask": torch.from_numpy(mask)}
    if self.transforms:
      item = self.transforms(item)
    return item

from torchvision.models.segmentation.deeplabv3 import DeepLabHead

def create_deeplabv3(num_classes):
  model = models.segmentation.deeplabv3_resnet101(pretrained=True, progress=True)
  model.classifier = DeepLabHead(2048, num_classes)

  #Cela semble bon à l'échelle de gris, mais c'est gênant donc je ne le ferai pas
  #model.backbone.conv1 = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)

  return model

def train_model(model, criterion, optimizer, dataloaders, device, num_epochs=25, print_freq=1):
  since = time.time()

  best_model_wts = copy.deepcopy(model.state_dict())
  best_loss = 1e15

  for epoch in range(num_epochs):
    print('Epoch {}/{}'.format(epoch+1, num_epochs))
    print('-' * 10)

    loss_history = {"train": [], "val": []}
    
    for phase in ["train", "val"]:
        
      if phase == "train":
        model.train()
      else:
        model.eval()

      for sample in tqdm(iter(dataloaders[phase])):
        imgs = sample["image"].to(device, dtype=torch.float)
        msks = sample["mask"].to(device, dtype=torch.float)

        optimizer.zero_grad()

        with torch.set_grad_enabled(phase == "train"):
          outputs = model(imgs)
          loss = criterion(outputs["out"], msks)

          if phase == "train":
            loss.backward()
            optimizer.step()

      epoch_loss = np.float(loss.data)
      if (epoch + 1) % print_freq == 0:
        print("Epoch: [%d/%d], Loss: %.4f" %(epoch+1, num_epochs, epoch_loss))
        loss_history[phase].append(epoch_loss)

      # deep copy the model
      if phase == "val" and epoch_loss < best_loss:
        best_loss = epoch_loss
        best_model_wts = copy.deepcopy(model.state_dict())

  time_elapsed = time.time() - since
  print("Training complete in {:.0f}m {:.0f}s".format(time_elapsed // 60, time_elapsed % 60))
  print("Best val Acc: {:4f}".format(best_loss))

  model.load_state_dict(best_model_wts)
  
  return model, loss_history

Exécution de l'apprentissage



dataset = MaskDataset("images_dir", "masks_dir", 0.5, transforms=None)

#Séparé pour la formation et la vérification
val_percent= 0.2
batch_size=4
n_val = int(len(dataset) * val_percent)
n_train = len(dataset) - n_val
train, val = random_split(dataset, [n_train, n_val])
train_loader = DataLoader(train, batch_size=batch_size, shuffle=True, num_workers=8, pin_memory=True, drop_last=True )
val_loader = DataLoader(val, batch_size=batch_size, shuffle=False, num_workers=8, pin_memory=True, drop_last=True )

dataloaders = {"train": train_loader, "val": val_loader}

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

#Lors de l'utilisation de BCEWithLogitsLoss, spécifiez 1 pour la classification binaire
num_classes = 1 

model = create_deeplabv3(num_classes)

#Pour pré-formé
#model.load_state_dict(torch.load("model.pth"))

model.to(device)

#Depuis l'arrière-plan est extrêmement nombreux, pos_Ajuster avec le poids
criterion = nn.BCEWithLogitsLoss(pos_weight=torch.tensor(10000.0).to(device))

params = [p for p in model.parameters() if p.requires_grad]

#optimizer = torch.optim.SGD(params, lr=0.005,momentum=0.9, weight_decay=0.0005)
optimizer = optim.Adam(params)

total_epoch = 50

model, loss_dict = train_model(model, criterion, optimizer, dataloaders, device, total_epoch)

Cette fois, quand j'ai tourné environ 50 époques, l'apprentissage a convergé dans une certaine mesure.

2-3. Application aux images inconnues

En conséquence, il était généralement bon, et 3 points ont répondu correctement, mais parfois les résultats suivants ont également été obtenus (expression de la carte thermique).

image.png

Bien sûr, il n'y a jamais deux mamelons à gauche, donc le petit point en haut à droite est Faux positif. À propos, il n'y avait pas de faux négatif.

2-4. Post-traitement

À partir du résultat de l'inférence ci-dessus, le post-traitement effectue les opérations suivantes:

  1. Tronquez la valeur de sortie de chaque pixel en dessous du seuil
  2. Divisez l'objet
  3. S'il y a 4 clusters ou plus, sélectionnez 3 dans l'ordre décroissant de zone et jetez le reste.
  4. Trouvez le centre de gravité de chaque cluster
  5. Trier en coordonnée x ascendante du centre de gravité de chaque groupe (mamelon droit → nombril → mamelon gauche)

2-4-1. Tronquer si la valeur de sortie de chaque pixel est inférieure au seuil

Tronquez tous sauf les pixels avec une certitude claire pour l'étape suivante. Le seuil cette fois est fixé empiriquement à 0,995.

2-4-2. Diviser l'objet

Utilisez cv2.connectedComponents pour le partitionnement d'objets (division en clusters). Pour plus de détails, reportez-vous à OpenCV - Comment étiqueter les composants connectés avec connectedComponents --pynote.

2-4-3. S'il y a 4 groupes ou plus, sélectionnez 3 dans l'ordre décroissant de la zone et jetez le reste.

À partir de l'étude de cas, il a été constaté que la zone des faux positifs autres que le mamelon et le nombril était petite. Par conséquent, nous en sélectionnerons trois avec une grande surface. En fait, je ne pense pas que ce genre de contre-mesure soit très robuste, mais cette fois, cela a fonctionné, alors je vais l'adopter.

2-4-4. Trouvez le centre de gravité de chaque groupe

Utilisez cv2.moments pour trouver le centre de gravité de chaque cluster. Pour plus de détails, reportez-vous à Calcul du centre de gravité avec Python + OpenCV - Introduction à l'analyse d'images CV.

2-4-5. Trier en abscisse croissante du centre de gravité de chaque groupe (mamelon droit → nombril → mamelon gauche)

Étant donné que les points doivent correspondre lors de la conversion en affine, il est nécessaire d'unifier l'ordre des coordonnées du mamelon et du nombril entre les images. Toutes les images cette fois-ci ont été prises debout, et il ne fait aucun doute que les mamelons → le nombril → les mamelons apparaîtront dans la direction de l'axe horizontal, alors triez simplement par abscisse.

Au moment de l'inférence



#3 points détectés depuis le masque
def triangle_pt(heatmask, thresh=0.995):
  mask = heatmask.copy()

  # 2-4-1.Si la valeur de sortie de chaque pixel est inférieure au seuil, elle sera tronquée.
  mask[mask>thresh] = 255
  mask[mask<=thresh] = 0
  mask = mask.astype(np.uint8)
  # 2-4-2.Objet divisé
  nlabels, labels = cv2.connectedComponents(mask)

  pt = []
  if nlabels != 4:

    #Si moins, ne fais rien
    #Je veux vraiment baisser le seuil, mais c'est ennuyeux
    if nlabels < 4:
      return None
    
    # 2-4-3.S'il y a 4 clusters ou plus, sélectionnez 3 dans l'ordre décroissant de la zone et jetez le reste
    elif nlabels > 4:
      sum_px = []
      for i in range(1, nlabels):
        sum_px.append((labels==i).sum())
      #Contexte+1
      indices = [ x+1 for x in np.argsort(-np.array(sum_px))[:3]]

  else:
    indices = [x for x in range(1, nlabels)]

  # 2-4-4.Trouvez le centre de gravité de chaque cluster
  for i in indices:
    base = np.zeros_like(mask, dtype=np.uint8)
    base[labels==i] = 255
    mu = cv2.moments(base, False)
    x,y= int(mu["m10"]/mu["m00"]) , int(mu["m01"]/mu["m00"])
    pt.append([x,y])

  # 2-4-5.Trier en abscisse croissante du centre de gravité de chaque groupe (mamelon droit → nombril → mamelon gauche)
  sort_key = lambda v: v[0]
  pt.sort(key=sort_key)
  return np.array(pt)


def correct_img(model, device, in_dir, out_dir, 
                draw_heatmap=True, draw_triangle=True, correct=True):

  imgs = []

  base_3p = None
  model.eval()
  with torch.no_grad():
    imglist = sorted(glob.glob(os.path.join(in_dir, "*.jpg ")))
    
    for idx, img_path in enumerate(imglist):

      #C'est ennuyeux, donc la taille du lot 1
      full_img = Image.open(img_path)
      img = torch.from_numpy(BasicDataset.preprocess(full_img, 0.5))
      img = img.unsqueeze(0)
      img = img.to(device=device, dtype=torch.float32)

      output = model(img)["out"]
      probs = torch.sigmoid(output)
      probs = probs.squeeze(0)

      tf = transforms.Compose(
                [
                    transforms.ToPILImage(),
                    transforms.Resize(full_img.size[0]),
                    transforms.ToTensor()
                ]
            )
      
      probs = tf(probs.cpu())
      full_mask = probs.squeeze().cpu().numpy()

      full_img = np.asarray(full_img).astype(np.uint8)
      full_img = cv2.cvtColor(full_img, cv2.COLOR_RGB2BGR)

      #Triangle
      triangle = triangle_pt(full_mask)
      if draw_triangle and triangle is not None:
        cv2.drawContours(full_img, [triangle], 0, (0, 0, 255), 5)

      #Carte de chaleur
      if draw_heatmap:
        full_mask = (full_mask*255).astype(np.uint8)
        jet = cv2.applyColorMap(full_mask, cv2.COLORMAP_JET)

        alpha = 0.7
        full_img = cv2.addWeighted(full_img, alpha, jet, 1 - alpha, 0)

      #Conversion d'affine
      if correct:
        if base_3p is None and triangle is not None:
          base_3p = triangle
        elif triangle is not None:
          full_img = p3affine_img(full_img, triangle, base_3p)

      if out_dir is not None:
        cv2.imwrite(os.path.join(out_dir, os.path.basename(img_path)), full_img)

      imgs.append(full_img)

  return imgs

imgs = correct_img(model, device,
                   "images_dir", None,
                    draw_heatmap=False, draw_triangle=False, correct=True)

2-5. Résultats

Le laps de temps juste avant la correction est le suivant.

ezgif.com-optimize (1).gif

Le laps de temps corrigé est le suivant.

ezgif.com-optimize (2).gif

Résumé

En détectant le mamelon et le nombril à l'aide de l'apprentissage en profondeur et en corrigeant automatiquement l'image, le laps de temps est considérablement plus facile à voir. Cela m'a motivé davantage à m'entraîner. Bien sûr, certaines personnes peuvent penser ** "N'est-ce pas possible avec un CV aussi peu profond?" **, mais dans mon cas, si j'avais le temps de réfléchir aux règles, j'aimerais monter la barre. On a l'impression qu'il a été résolu par la force brute. Tout le développement a été fait avec google colab à l'exception de l'outil d'affectation des coordonnées, 3150 Uu! Le défi est

Cependant, le cortisol est sécrété, alors ne vous inquiétez pas qu'il soit trop dur!

Ayons une vie d'entraînement musculaire amusante!

Recommended Posts

L'apprentissage en profondeur facilite considérablement la visualisation du laps de temps des changements physiques
Vous qui coloriez le journal pour le rendre plus facile à voir
Une histoire qui rend le débogage de modèle plus facile à voir dans l'environnement Django + SQLAlchemy
Othello-De la troisième ligne de "Implementation Deep Learning" (3)
Visualisez les effets de l'apprentissage profond / de la régularisation
J'ai essayé l'histoire courante de l'utilisation du Deep Learning pour prédire la moyenne Nikkei
Othello-De la troisième ligne de "Implementation Deep Learning" (2)
Créez un environnement python pour apprendre la théorie et la mise en œuvre de l'apprentissage profond
Comment installer le framework d'apprentissage en profondeur Tensorflow 1.0 dans l'environnement Windows Anaconda
L'histoire de l'apprentissage profond avec TPU
L'arrière-plan des caractères de l'image texte est surexposé pour faciliter la lecture.
Comptez le nombre de paramètres dans le modèle d'apprentissage en profondeur
Techniques pour comprendre la base des décisions d'apprentissage en profondeur
Othello ~ De la troisième ligne de "Implementation Deep Learning" (4) [Fin]
[Apprentissage en profondeur] Découvrez comment utiliser chaque fonction du réseau neuronal convolutif [DW jour 3]
À en juger par l'image du chien Shiba en apprenant en profondeur si c'est mon enfant (1)
Deep learning 1 Pratique du deep learning
[Partie 4] Utilisez le Deep Learning pour prévoir la météo à partir d'images météorologiques
[Partie 1] Utilisez le Deep Learning pour prévoir la météo à partir d'images météorologiques
Essayez d'évaluer les performances du modèle d'apprentissage automatique / de régression
[Partie 3] Utilisez le Deep Learning pour prévoir la météo à partir d'images météorologiques
Facilitez la compréhension de l'affichage des exceptions du module Python
Une doublure qui formate JSON pour le rendre plus facile à voir
Essayez d'évaluer les performances du modèle d'apprentissage automatique / de classification
Comment augmenter le nombre d'images de jeux de données d'apprentissage automatique
Tentative d'ajuster automatiquement la vitesse des vidéos accélérées (partie 2)
[Partie 2] Utilisez le Deep Learning pour prévoir la météo à partir d'images météorologiques
Comment voir le contenu du fichier ipynb du notebook Jupyter
[Apprentissage automatique] J'ai essayé de résumer la théorie d'Adaboost
J'ai capturé le projet Toho avec Deep Learning ... je le voulais.
Je veux connaître la légende du monde des technologies informatiques
Chapitre 1 Introduction à Python Découpez uniquement les bons points de Deeplearning à partir de zéro
Le framework de Deep Learning de Microsoft "CNTK" est désormais compatible avec Python, ce qui le rend beaucoup plus facile à utiliser