Utiliser l’IA pour trouver votre styliste de célébrités (Partie I)
L’article a été initialement publié sur le site Web de l’AI Accelerator Institute et est republié ici avec autorisation.
J’adore porter des pantalons roses pendant mes conférences, ce qui est devenu l’un de mes looks emblématiques. J’en possède plusieurs paires, ce qui a suscité de nombreuses discussions sur la mode et le style. Récemment, j’ai participé à un projet appelé "Fashion AI", dans lequel nous utilisons un modèle affiné pour segmenter les vêtements dans les images. Nous recadrons ensuite chaque article étiqueté et redimensionnons les images à la même taille. Enfin, nous stockons les embeddings générés à partir de ces images dans Milvus, une base de données vectorielle open-source capable de stocker et d’interroger des milliards d’embeddings vectoriels.
Pour trouver les articles les plus proches dans notre base de données, nous appliquons les mêmes transformations à l’image et à la requête le long des mêmes vecteurs. Pour chaque requête, ce projet renvoie trois résultats. Vous pouvez interpréter les résultats selon vos préférences. Vous pouvez également déterminer quelle célébrité est la plus proche de vous. Vous pouvez choisir la première place la plus fréquente, la distance agrégée la plus faible ou le résultat global le plus fréquent.
Vous pouvez trouver les images ici. En plus des images, vous aurez besoin d’une version de Python mise à niveau et de pip install milvus pymilvus torch torchvision matplotlib. Nous utilisons le modèle de segmentation de vêtements de Mateusz Dziemian sur Hugging Face, ainsi que ce modèle ResNet50 de Nvidia sur PyTorch pour la segmentation d’images et les embeddings.
Dans cet article, nous verrons comment générer une segmentation d’image pour des articles de mode, ajouter vos données d’image à Milvus et découvrir à quelle célébrité votre robe ressemble le plus.
Segmentation d’image pour les articles vestimentaires
Pour effectuer une segmentation d’image, j’ai trouvé trois modèles à examiner sur Hugging Face.
Le modèle Fashion-CLIP de Patrick John Chia
J’ai finalement choisi le modèle "segformer". Il fournit une segmentation précise pour différents articles vestimentaires et identifie 18 types d’"objets". Par exemple, il détecte les "upper clothes" pour n’importe quel type de haut, "dress", "left shoe", "right shoe", "hat", et de nombreux autres articles vestimentaires. De plus, il peut détecter des éléments comme "face", "hair", "right leg" et "left leg". Vous pouvez trouver l’ensemble complet des 18 types d’objets ici.
Nous commençons par importer les packages nécessaires à la manipulation d’images dans ce projet. Ceux-ci incluent torch pour l’extraction de caractéristiques, l’objet segformer de transformers, matplotlib, ainsi que certains imports de torchvision tels que Resize, masks_to_boxes et crop.
import torch
from torch import nn, tensor
from transformers import AutoFeatureExtractor, SegformerForSemanticSegmentation
import matplotlib.pyplot as plt
from torchvision.transforms import Resize
import torchvision.transforms as T
from torchvision.ops import masks_to_boxes
from torchvision.transforms.functional import crop
Générer des masques de segmentation avec Hugging Face
Il existe de nombreuses approches pour segmenter votre image, selon le modèle que vous utilisez et ce qu’il détecte. Pour cet exemple, notre modèle renvoie une image à 18 couches, une pour chaque type d’objet, y compris l’arrière-plan. La première fonction que nous devons écrire est celle qui génère cette image.
La fonction get_segmentation nécessite trois paramètres : un extracteur de caractéristiques, un modèle et une image. Tout d’abord, elle génère des caractéristiques d’entrée à l’aide de l’image et de l’extracteur. Ensuite, elle obtient la sortie du modèle et la convertit en logits. Puis, elle suréchantillonne les logits via une interpolation bilinéaire PyTorch. Enfin, la fonction ne conserve que la prédiction maximale pour chaque pixel dans les échantillons suréchantillonnés afin de créer un masque de segmentation.
def get_segmentation(extractor, model, image):
inputs = extractor(images=image, return_tensors="pt")
outputs = model(**inputs)
logits = outputs.logits.cpu()
upsampled_logits = nn.functional.interpolate(
logits,
size=image.size[::-1],
mode="bilinear",
align_corners=False,
)
pred_seg = upsampled_logits.argmax(dim=1)[0]
return pred_seg
À titre de référence, les images dans upsampled_logits ressemblent à ceci :
Tandis que l’image pred_seg ressemble à ceci : (ce sont deux images différentes, bien que les deux représentent Andre 3000).
Obtenir les masques de segmentation est simple à partir de là. Nous obtenons toutes les valeurs uniques dans la segmentation ; dans ce modèle, il ne peut y en avoir que jusqu’à 18. Nous supprimons la première entrée, qui représente l’arrière-plan. Pour créer les masques, nous extrayons les pixels de la segmentation qui ont la même valeur que l’ID de l’objet. Je fais en sorte que cette fonction renvoie à la fois les masques et les ID afin que nous puissions garder une trace des deux.
# returns two lists masks (tensor) and obj_ids (int)
# "mattmdjaga/segformer_b2_clothes" from hugging face
def get_masks(segmentation):
obj_ids = torch.unique(segmentation)
obj_ids = obj_ids[1:]
masks = segmentation == obj_ids[:, None, None]
return masks, obj_ids
Cette fonction crée des masques qui ressemblent à ceci (masques des cheveux et des vêtements du haut affichés) :
Rogner et redimensionner vos images avec les transformations Pytorch
Nous pouvons maintenant créer une nouvelle image pour chaque objet détecté à l’aide des masques et des idées d’objets de la fonction get_masks, ainsi que de l’image d’origine. Ensuite, nous appelons la fonction magique masks_to_boxes, que nous avons importée plus tôt depuis torchvision.ops, pour convertir les masques créés en boîtes englobantes.
Ensuite, nous créons une liste de boîtes à rogner et convertissons le système de coordonnées des boîtes dans le système de coordonnées de crop. Les boîtes sont renvoyées sous forme de valeurs au format (x1, x2, y1, y2). En revanche, la fonction crop attend une entrée au format (top, left, height, width).
Avant de rogner les images, nous définissons également une fonction de prétraitement. Nous voulons redimensionner chaque image en 256x256 et les convertir en Tensors PyTorch (actuellement des Images PIL). Il est maintenant temps de rogner les images. Nous parcourons les boîtes de rognage et appelons la fonction crop sur l’image en utilisant les valeurs obtenues précédemment. Ensuite, nous ajoutons l’image prétraitée comme valeur correspondant à la valeur de clé de l’id de segmentation dans un dictionnaire. À la fin de la fonction, nous renvoyons ce dictionnaire.
def crop_images(masks, obj_ids, img):
boxes = masks_to_boxes(masks)
crop_boxes = []
for box in boxes:
crop_box = tensor([box[0], box[1], box[2]-box[0], box[3]-box[1]])
crop_boxes.append(crop_box)
preprocess = T.Compose([
T.Resize(size=(256, 256)),
T.ToTensor()
])
cropped_images = {}
for i in range(len(crop_boxes)):
crop_box = crop_boxes[i]
cropped = crop(img, crop_box[1].item(), crop_box[0].item(), crop_box[3].item(), crop_box[2].item())
cropped_images[obj_ids[i].item()] = preprocess(cropped)
return cropped_images
Voici ci-dessous un exemple des boîtes que nous rognons et pour lesquelles nous créons des images séparées en utilisant Drake dans une sortie de feu.
Ajouter vos données d’image à une base de données vectorielle
Maintenant que toutes les images sont segmentées et recadrées, ajoutons-les à Milvus, notre base de données vectorielle. Pour vous aider à démarrer rapidement avec Milvus, nous utilisons Milvus Lite, une version légère de Milvus, dans cet exemple afin d’exécuter une instance de Milvus dans notre notebook. Ensuite, nous utilisons pymilvus pour nous connecter au serveur par défaut fourni par Milvus Lite.
Nous utilisons également cette section pour configurer certaines constantes. Définissons le nombre de dimensions dans un vecteur (à partir du modèle Nvidia ResNet50), la taille du lot, le nom de notre collection et le nombre de résultats à renvoyer. Enfin, nous exécutons une fonction ssl pour créer un contexte non vérifié afin d’obtenir le modèle depuis PyTorch.
from milvus import default_server
from pymilvus import utility, connections
default_server.start()
connections.connect(host="127.0.0.1", port=default_server.listen_port)
DIMENSION = 2048
BATCH_SIZE = 128
COLLECTION_NAME = "fashion"
TOP_K = 3
# run this before importing th resnet50 model if you run into an SSL certificate URLError
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
Définir votre schéma pour stocker des métadonnées dans une base de données vectorielle
Première étape : définir votre schéma. Le schéma est utilisé pour organiser les données enregistrées dans la base de données vectorielle. Le champ id est un identifiant de clé standard dans les bases de données SQL ou NoSQL, tandis que les autres champs ont des définitions de type SQL pour leurs types de données (int64, varchar, float, etc).
Pour cet exemple, nous enregistrons le chemin du fichier, le nom de la célébrité et l’ID de segmentation comme métadonnées. À l’avenir, nous pourrons ajouter davantage de champs, comme l’emplacement des boîtes englobantes ou des masques. Une fois le FieldSchema défini, nous définissons un CollectionSchema, puis créons une Collection dans Milvus sur la base du schéma et du nom de collection donnés.
Maintenant que nous avons une collection, définissons son index. Ces paramètres d’index sont assez basiques. Nous utilisons IVF Flat avec 128 centroïdes et L2 comme métrique de distance. Nous créons l’index dans notre collection, en précisant que le champ embedding est celui sur lequel opérer. Ensuite, nous chargeons la collection en mémoire afin qu’elle soit prête à être utilisée.
from pymilvus import FieldSchema, CollectionSchema, Collection, DataType
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name='filepath', dtype=DataType.VARCHAR, max_length=200),
FieldSchema(name="name", dtype=DataType.VARCHAR, max_length=200),
FieldSchema(name="seg_id", dtype=DataType.INT64),
FieldSchema(name='embedding', dtype=DataType.FLOAT_VECTOR, dim=DIMENSION)
]
schema = CollectionSchema(fields=fields)
collection = Collection(name=COLLECTION_NAME, schema=schema)
index_params = {
"index_type": "IVF_FLAT",
"metric_type": "L2",
"params": {"nlist": 128},
}
collection.create_index(field_name="embedding", index_params=index_params)
collection.load()
Obtenir vos embeddings vectoriels à partir du ResNet50 de Nvidia
La première étape de cette section consiste à charger le modèle. Nous chargeons le modèle ResNet50 de Nvidia depuis PyTorch, puis nous supprimons la couche de sortie. Les embeddings vectoriels sont la sortie de l’avant-dernière couche d’un modèle.
# Load the embedding model with the last layer removed
embeddings_model = torch.hub.load('NVIDIA/DeepLearningExamples:torchhub', 'nvidia_resnet50', pretrained=True)
embeddings_model = torch.nn.Sequential(*(list(embeddings_model.children())[:-1]))
embeddings_model.eval()
Cette fonction est chargée de recevoir les embeddings vectoriels et d’insérer les données dans Milvus. Elle accepte trois paramètres : les données, l’objet collection et un modèle, qui dans ce cas est le modèle d’embedding. Pour suivre la manière dont les données sont traitées au fur et à mesure que nous les ajoutons à notre base de données vectorielle, j’ai ajouté plusieurs instructions print.
En plus d’imprimer les données de débogage, nous empilons toutes les valeurs dans data[0] dans un seul tenseur, puis nous supprimons toutes les dimensions de taille 1 de la sortie à l’aide de la fonction squeeze. Ensuite, nous insérons une nouvelle liste composée des trois dernières entrées du lot de données d’origine, suivies du tenseur de sortie converti en liste. Celles-ci correspondent au chemin du fichier, au nom, à l’ID de segmentation et à l’embedding de 2048 dimensions.
def embed_insert(data, collection, model):
with torch.no_grad():
print(len(data[0]))
print(data[0][0].size())
output = model(torch.stack(data[0])).squeeze()
print(type(output))
print(len(output))
print(len(output[0]))
print(output[0])
collection.insert([data[1], data[2], data[3], output.tolist()])
Les données imprimées ressemblent à l’image ci-dessous. Chaque lot de données a une taille de 128 jusqu’à la fin, chaque entrée étant de taille 3x256x256. La sortie est un tenseur PyTorch de longueur 128, chaque entrée de la sortie étant de longueur 2048. Le tenseur imprimé est la sortie de la première entrée du lot de données.
Stocker vos données d’image dans une base de données vectorielle
Vous vous souvenez de cet extracteur et de ce modèle de segmentation dont nous avons parlé plus tôt ? C’est ici que nous les utilisons. Nous utilisons ce modèle segformer pré-entraîné de Hugging Face. Après avoir chargé les modèles, nous mettons tous les chemins de fichiers dans une liste pour les parcourir.
extractor = AutoFeatureExtractor.from_pretrained("mattmdjaga/segformer_b2_clothes")
model = SegformerForSemanticSegmentation.from_pretrained("mattmdjaga/segformer_b2_clothes")
import os
image_paths = []
for celeb in os.listdir("./photos"):
for image in os.listdir(f"./photos/{celeb}/"):
# print(image)
image_paths.append(f"./photos/{celeb}/{image}")
Milvus attend en entrée une liste de listes. Dans cet exemple, nous utilisons une liste de 4 listes, qui correspondent à l’image, au chemin du fichier, au nom et à l’ID de segmentation. Dans la fonction embed_insert, nous convertissons l’image en embedding vectoriel. Nous parcourons ensuite chaque chemin de fichier vers les images, récupérons leurs masques de segmentation et les recadrons. Enfin, nous ajoutons les images avec leurs métadonnées au lot de données.
Toutes les 128 images, nous les transformons en embeddings et les insérons dans Milvus, puis nous vidons le lot de données. À la fin de la boucle, nous transformons en embeddings et insérons le reste du lot de données dans Milvus, puis nous effectuons un flush pour terminer l’indexation. Sur un Mac M1 2021 avec 16 Go de RAM, ce processus prend environ 8 minutes à s’exécuter.
from PIL import Image
data_batch = [[], [], [], []]
for path in image_paths:
image = Image.open(path)
path_split = path.split("/")
name = " ".join(path_split[2].split("_"))
segmentation = get_segmentation(extractor, model, image)
masks, ids = get_masks(segmentation)
cropped_images = crop_images(masks, ids, image)
for key, image in cropped_images.items():
data_batch[0].append(image)
data_batch[1].append(path)
data_batch[2].append(name)
data_batch[3].append(key)
if len(data_batch[0]) % BATCH_SIZE == 0:
embed_insert(data_batch, collection, embeddings_model)
data_batch = [[], [], [], []]
if len(data_batch[0]) != 0:
embed_insert(data_batch, collection, embeddings_model)
collection.flush()
Découvrez à quelles célébrités votre style vestimentaire ressemble le plus
Vous pouvez faire beaucoup de choses avec cette configuration. Je fournirai des méthodes supplémentaires pour faire correspondre et évaluer vos choix de mode dans un prochain article. Dans cet exemple, nous obtiendrons les trois meilleures images en fonction de chaque article de vêtement segmenté. Nous utilisons quelques exemples de Taylor Swift et obtenons un rappel parfait.
Générer des embeddings pour vos images d’entrée
De manière similaire à la façon dont nous chargeons les images dans la base de données, nous devons traiter les images d’entrée. La fonction d’intégration des images de recherche prend deux paramètres : les données et le modèle (d’intégration). Nous utilisons le modèle pour obtenir les embeddings, les aplatissons ou les réduisons selon le nombre d’images interrogées, les convertissons en liste, puis les renvoyons.
def embed_search_images(data, model):
with torch.no_grad():
print(len(data[0]))
print(data[0][0].size())
output = model(torch.stack(data))
print(type(output))
print(len(output))
print(len(output[0]))
print(output[0])
if len(output) > 1:
return output.squeeze().tolist()
Else:
return torch.flatten(output, start_dim=1).tolist()
De manière similaire à la fonction embed_insert, j’ai ajouté ici plusieurs instructions print pour suivre les données. Comme indiqué ci-dessous, les data transmises à cette fonction correspondent essentiellement à l’objet data[0], par comparaison avec la fonction embed_insert.
Pour interroger la base de données, nous n’avons besoin que des embeddings vectoriels, que nous pouvons obtenir de manière similaire à lorsque nous avons ajouté des images à Milvus. Cependant, il est utile de garder ces autres variables en mémoire afin de faciliter les comparaisons ultérieures.
# data_batch[0] is a list of tensors
# data_batch[1] is a list of filepaths to the images (string)
# data_batch[2] is a list of the names of the people in the images (string)
# data_batch[3] is a list of segmentation keys (int)
data_batch = [[], [], [], []]
search_paths = ["./photos/Taylor_Swift/Taylor_Swift_3.jpg", "./photos/Taylor_Swift/Taylor_Swift_8.jpg"]
for path in search_paths:
image = Image.open(path)
path_split = path.split("/")
name = " ".join(path_split[2].split("_"))
segmentation = get_segmentation(extractor, model, image)
masks, ids = get_masks(segmentation)
cropped_images = crop_images(masks, ids, image)
for key, image in cropped_images.items():
data_batch[0].append(image)
data_batch[1].append(path)
data_batch[2].append(name)
data_batch[3].append(key)
embeds = embed_search_images(data_batch[0], embeddings_model)
Interrogation de la base de données vectorielle
Maintenant que nous avons les embeddings, nous pouvons interroger la base de données. Pour le plaisir, j’ajoute le module time afin de suivre la durée de ces requêtes. Dans cet exemple, nous mesurons le temps de requête pour 23 vecteurs à 2048 dimensions. Pour interroger Milvus, nous utilisons simplement la fonction search avec les embeddings générés ci-dessus.
import time
start = time.time()
res = collection.search(embeds,
anns_field='embedding',
param={"metric_type": "L2",
"params": {"nprobe": 10}},
limit=TOP_K,
output_fields=['filepath'])
finish = time.time()
print(finish - start)
Après avoir parcouru les résultats, nous pouvons voir la réponse générée, affichée dans l’image sous le code.
for index, result in enumerate(res):
print(index)
print(result)
Résumé
C’est tout pour cette section. Vous êtes maintenant prêt à comparer n’importe quelles images de vous-même ou de vos amis (avec leur permission !) à certaines célébrités, notamment Taylor Swift, Drake et Andre 3000. Pour ce faire, commencez par obtenir une segmentation des articles vestimentaires dans l’image à l’aide d’un modèle trouvé sur Hugging Face.
Une fois les segmentations en main, récupérez chaque segmentation unique de l’image et recadrez-les en images séparées. Avant de placer ces images recadrées dans la base de données vectorielle, redimensionnez-les et transformez-les en tenseurs. Ensuite, faites-les passer dans un modèle d’embeddings ResNet50 de Nvidia afin d’obtenir les embeddings vectoriels à stocker.
Pour l’interrogation, effectuez une procédure similaire au chargement des vecteurs. Dans cet exemple, nous nous sommes arrêtés aux résultats de requête. Pour aller plus loin, enregistrez les boîtes englobantes ou les masques dans la base de données vectorielle et extrayez-les pour afficher les correspondances spécifiques. Vous pouvez également faire repasser les images d’entrée dans le modèle et procéder de la même manière. Puisque nous faisons tout localement, nous pouvons utiliser la mémoire locale.
J’espère que cela vous a plu. N’hésitez pas à me contacter et à partager vos commentaires. Dites-moi également à quelle célébrité votre style vestimentaire ressemble le plus !
Et n’oubliez pas de consulter la partie II de cette série !
Continuer à lire

A Few Notes from Databricks Data + AI Summit 2026: Why the Data Layer Matters Again
James Luan shares notes from Databricks Data + AI Summit 2026 on why production AI is pushing the data layer back to the center of infrastructure.

Zilliz Cloud Now Available in AWS Asia Pacific (Seoul)
Zilliz Cloud is now available in AWS Seoul — low-latency vector search, in-country data residency, and one-step migration for Korean AI teams. 31 regions across 5 clouds.

Zilliz Cloud BYOC Now Available Across AWS, GCP, and Azure
Zilliz Cloud BYOC is now generally available on all three major clouds. Deploy fully managed vector search in your own AWS, GCP, or Azure account — your data never leaves your VPC.



