Vision par ordinateur avec des Transformers#

Dans ce notebook, nous utilisons la bibliothÚque Transformers de Hugging Face pour traiter des images. Pour éviter de surcharger le notebook, certaines fonctions se trouvent dans utils/util.py.

DĂ©tection d’objets sans apprentissage (Zero-Shot)#

La dĂ©tection d’objets dans une image est une tĂąche clĂ© en vision par ordinateur. Les modĂšles zero-shot sont super pratiques car ils dĂ©tectent n’importe quel objet sans besoin de fine-tuning. Il suffit de leur donner une image et un prompt textuel avec les classes Ă  dĂ©tecter.

Mise en Ɠuvre#

On a opté pour le modÚle OWL-ViT de Google (google/owlvit-base-patch32) car il est compact et fonctionne sur la plupart des machines. Utilisons le pipeline de Hugging Face :

from transformers import pipeline
from PIL import Image
import cv2
import numpy as np
import matplotlib.pyplot as plt

zeroshot = pipeline("zero-shot-object-detection", model="google/owlvit-base-patch32")
/home/aquilae/anaconda3/envs/dev/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

Jetons un coup d’Ɠil à notre image.

image=Image.open("images/coco.jpg") # Image extraite de la base de données COCO (https://cocodataset.org/#home)

plt.imshow(image)
plt.axis('off')  
plt.show()
../_images/76ca40f16aeb642fd4a307029ce7027a99ad98462230df06da6465858783de38.png

Utilisons le modÚle pour dessiner les boßtes de détection prédites.

from utils.util import draw_box

text_prompt = "surfboard" # Vous pouvez changer la classe pour détecter autre chose "person" ou "surfboard"
output = zeroshot(image,candidate_labels = [text_prompt])
cv_image=draw_box(image,output)

plt.imshow(cv_image)
plt.axis('off') 
plt.show()
../_images/2cbf2496b1bfaccb7178df853786dd83da329f16392db46d6974d14622272d1a.png

Vous savez maintenant comment implĂ©menter un dĂ©tecteur d’objets zero-shot en quelques lignes de code.

Génération de légendes pour images#

L’image captionning consiste Ă  gĂ©nĂ©rer une description pour une image. Le modĂšle prend une image en entrĂ©e et produit une lĂ©gende.

Mise en Ɠuvre#

Comme précédemment, on utilise le pipeline de Hugging Face. Ici, on utilise le modÚle BLIP de Salesforce (Salesforce/blip-image-captioning-base).

captionner = pipeline("image-to-text", model="Salesforce/blip-image-captioning-base")

On utilise la mĂȘme image pour gĂ©nĂ©rer une description.

result=captionner(image)
print(result[0]['generated_text'])
a man holding a surfboard in a room

On a gĂ©nĂ©rĂ© la description “un homme qui tient une planche de surf dans une piĂšce”, ce qui est exact. Vous savez maintenant gĂ©nĂ©rer des descriptions d’images. C’est super utile pour crĂ©er automatiquement des datasets par exemple.

Classification d’images sans apprentissage (Zero-Shot)#

En plus de la dĂ©tection d’objets zero-shot, on peut faire de la classification d’images zero-shot. Le principe est similaire, mais cette fois on donne au moins deux phrases et le modĂšle nous donne la probabilitĂ© que l’image corresponde Ă  l’une ou l’autre.

Mise en Ɠuvre#

Utilisons une photo de mon chat pour déterminer sa race :

image=Image.open("images/tigrou.png") # Image extraite de la base de données COCO (https://cocodataset.org/#home)

plt.imshow(image)
plt.axis('off')  
plt.show()
../_images/1db913c7fb2a168b8ae65d034767e1691590eebb56d03abe007efadfd3a12c02.png

On va voir si le modĂšle peut dĂ©terminer s’il s’agit d’un Maine Coon ou d’un chat europĂ©en.

On utilise le modùle CLIP d’OpenAI (openai/clip-vit-base-patch32). Pour varier, on utilise d’autres fonctions de la bibliothùque Hugging Face au lieu du pipeline.

from transformers import AutoProcessor, AutoModelForZeroShotImageClassification

processor = AutoProcessor.from_pretrained("openai/clip-vit-base-patch32")
model = AutoModelForZeroShotImageClassification.from_pretrained("openai/clip-vit-base-patch32")
/home/aquilae/anaconda3/envs/dev/lib/python3.11/site-packages/huggingface_hub/file_download.py:1132: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.
  warnings.warn(
labels = ["a photo of a european shorthair", "a photo of maine coon"]
inputs = processor(text=labels,images=image,return_tensors="pt",padding=True)
outputs = model(**inputs)

# Transformation des outputs pour obtenir des probabilités
print("Probabilité de a photo of a european shorthair : ",outputs.logits_per_image.softmax(dim=1)[0][0].item())
print("Probabilité de a photo of maine coon : ",outputs.logits_per_image.softmax(dim=1)[0][1].item())
Probabilité de a photo of a european shorthair :  0.9104425311088562
Probabilité de a photo of maine coon :  0.08955750614404678

Le modĂšle est plutĂŽt sĂ»r qu’il s’agit d’un chat europĂ©en, et effectivement, il a raison.

Segmentation d’images#

Pour cet exemple, on utilise le modùle SAM de Meta qui permet de segmenter n’importe quel objet.

Mise en Ɠuvre#

sam = pipeline("mask-generation","Zigeng/SlimSAM-uniform-77")
image=Image.open("images/coco.jpg") # Image extraite de la base de données COCO (https://cocodataset.org/#home)

plt.imshow(image)
plt.axis('off')  
plt.show()
../_images/76ca40f16aeb642fd4a307029ce7027a99ad98462230df06da6465858783de38.png
# ATTENTION : le traitement peut prendre plusieurs minutes
output=sam(image, points_per_batch=32)
masks=output["masks"]
from utils.util import draw_masks
image_np=draw_masks(image,masks)

plt.imshow(image_np)
plt.axis('off') 
plt.show()
../_images/45ead8efc685aee9ac3b5c07bac8335d3440deba032bf79e11dd98165534ad10.png

Comme vous pouvez le voir, on a segmentĂ© tous les objets de l’image. Par contre, le temps de traitement Ă©tait assez long
 Pour un temps d’infĂ©rence plus raisonnable, on utilise un prompt de coordonnĂ©es d’un point de l’image. Cela permet de spĂ©cifier le traitement et d’obtenir un rĂ©sultat plus rapidement. On ne peut pas utiliser le pipeline pour cette tĂąche.

from transformers import SamModel, SamProcessor
model = SamModel.from_pretrained("Zigeng/SlimSAM-uniform-77")

processor = SamProcessor.from_pretrained("Zigeng/SlimSAM-uniform-77")

Créons notre prompt de coordonnées et visualisons le point :

input_points = [[[300, 200]]]
image_np= np.array(image)
cv2.circle(image_np,input_points[0][0],radius=3,color=(255,0,0),thickness=5)
plt.imshow(image_np)
plt.axis('off')
plt.show()
../_images/f1a32f5e1dacfe7ac05a71016c00b71a202607b7a5198cfc988a51a286020ad5.png
inputs = processor(image,input_points=input_points,return_tensors="pt")
outputs = model(**inputs)
predicted_masks = processor.image_processor.post_process_masks(
  outputs.pred_masks,
  inputs["original_sizes"],
  inputs["reshaped_input_sizes"]
)

Le traitement est bien plus rapide ! SAM produit 3 masques par dĂ©faut. Chaque masque reprĂ©sente une possibilitĂ© de segmentation de l’image. Vous pouvez changer la valeur mask_number pour visualiser les diffĂ©rents masques.

mask_number=2 # 0,1 or 2
mask=predicted_masks[0][:, mask_number] 
image_np=draw_masks(image,mask)
plt.imshow(image_np)
plt.axis('off') 
plt.show()
../_images/b0065d116a0a1b01c869cdc83b867921ac3cdeb25211c3b3e0add8fe0f7aa5f3.png

Dans cet exemple, on voit que les 3 masques sont pertinents : le premier segmente la personne entiĂšre, le second segmente les vĂȘtements et le troisiĂšme segmente uniquement le t-shirt. Vous pouvez essayer de changer les coordonnĂ©es du point et de visualiser les masques gĂ©nĂ©rĂ©s.

Estimation de la profondeur#

L’estimation de la profondeur est une tĂąche clĂ© en vision par ordinateur. C’est super utile pour des applications comme les voitures autonomes oĂč on estime la distance par rapport au vĂ©hicule devant nous. Pour l’industrie, c’est aussi intĂ©ressant pour organiser les objets dans un colis en fonction de l’espace restant. Pour cet exemple, on utilise le modĂšle DPT (Intel/dpt-hybrid-midas) qui prend une image en entrĂ©e et renvoie une carte de profondeur.

Mise en Ɠuvre#

depth_estimator = pipeline(task="depth-estimation",model="Intel/dpt-hybrid-midas")
image=Image.open("images/coco2.jpg") # Image extraite de la base de données COCO (https://cocodataset.org/#home)

plt.imshow(image)
plt.axis('off')  
plt.show()
../_images/fe35ac60bafdc2175fc907f00694fb9f9c8f0cd14ebc48b37a22aa7c050be71f.png
outputs = depth_estimator(image)
outputs["predicted_depth"].shape
torch.Size([1, 384, 384])

On utilise PyTorch pour adapter la dimension de la carte de profondeur prédite à celle de notre image de base, puis on génÚre une image de la carte de profondeur.

import torch 
prediction = torch.nn.functional.interpolate(outputs["predicted_depth"].unsqueeze(1),size=image.size[::-1],
                                             mode="bicubic",align_corners=False)
output = prediction.squeeze().numpy()
formatted = (output * 255 / np.max(output)).astype("uint8")
depth = Image.fromarray(formatted)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6)) 
ax1.imshow(image)
ax1.axis('off')  
ax2.imshow(depth)
ax2.axis('off')  
plt.show()
../_images/af58ffbc75ba63fbd7ccae7383d511f3fb019fe2e806d4858f1e2bccf3bc8851.png

Sur la carte de profondeur, les couleurs vives représentent les objets les plus proches. On voit bien la route proche en couleur trÚs vive et le bus en couleur assez vive. La carte de profondeur est donc précise.