"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.
Modifications corporelles de décembre 2017 à mars 2020
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.
Je m'inquiète de l'écart et je ne peux pas me concentrer sur mon enfant (corps).
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.
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).
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.
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.
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.
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.
Faites ces paires avec l'image physique.
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.
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).
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.
À partir du résultat de l'inférence ci-dessus, le post-traitement effectue les opérations suivantes:
Tronquez tous sauf les pixels avec une certitude claire pour l'étape suivante. Le seuil cette fois est fixé empiriquement à 0,995.
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.
À 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.
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.
É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)
Le laps de temps juste avant la correction est le suivant.
Le laps de temps corrigé est le suivant.
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