GuideAvancé

RAG sur images : Vision models et recherche visuelle

19 mars 2026
25 min de lecture
Équipe Ailog

Guide complet pour intégrer des images dans votre système RAG : modèles de vision, embeddings multimodaux, indexation et recherche visuelle avec GPT-4V, Claude Vision et CLIP.

RAG sur images : Vision models et recherche visuelle

Les systèmes RAG traditionnels se limitent au texte. Pourtant, une grande partie de l'information d'entreprise est visuelle : photos produits, captures d'écran, graphiques, schémas techniques. L'Image RAG permet d'indexer et de rechercher dans ces contenus visuels avec la même précision qu'un RAG textuel.

Pourquoi intégrer les images au RAG ?

Les données visuelles en entreprise

  • E-commerce : 70% des décisions d'achat sont influencées par les images produits
  • Support technique : Les captures d'écran accélèrent de 60% la résolution des tickets
  • Documentation : Un schéma vaut souvent mieux qu'une page de texte
  • Conformité : Photos de chantier, états des lieux, preuves visuelles

Cas d'usage concrets

SecteurUsageExemple de requête
E-commerceRecherche visuelle"Trouve des robes similaires à cette photo"
ImmobilierAnalyse de biens"Montre-moi des cuisines équipées modernes"
Support ITDiagnostic"Quel est ce message d'erreur ?"
ManufacturingContrôle qualité"Cette pièce présente-t-elle un défaut ?"

Architecture d'un Image RAG

Vue d'ensemble

┌─────────────────────────────────────────────────────────────┐
│                    IMAGE RAG PIPELINE                        │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────┐    ┌──────────────┐    ┌──────────────────┐  │
│  │  Image   │───▶│   Vision     │───▶│   Embedding      │  │
│  │  Input   │    │   Model      │    │   (CLIP/SigLIP)  │  │
│  └──────────┘    └──────────────┘    └──────────────────┘  │
│       │                │                      │             │
│       │                ▼                      ▼             │
│       │         ┌──────────────┐    ┌──────────────────┐   │
│       │         │  Description │    │  Vector Store    │   │
│       │         │  textuelle   │    │  (Qdrant/Pine)   │   │
│       │         └──────────────┘    └──────────────────┘   │
│       │                │                      │             │
│       │                ▼                      ▼             │
│       │         ┌─────────────────────────────────┐        │
│       └────────▶│      Retrieval multimodal       │        │
│                 └─────────────────────────────────┘        │
│                                │                            │
│                                ▼                            │
│                 ┌─────────────────────────────────┐        │
│                 │      Génération (VLM/LLM)       │        │
│                 └─────────────────────────────────┘        │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Les deux approches

1. Description + RAG textuel

  • Le vision model décrit l'image en texte
  • Le texte est indexé classiquement
  • Plus simple mais perd des informations visuelles

2. Embeddings multimodaux natifs

  • L'image est convertie directement en vecteur
  • Préserve l'information visuelle complète
  • Permet la recherche image-vers-image

Modèles de vision pour le RAG

Modèles propriétaires

ModèleRésolution maxCoûtForces
GPT-4V2048x2048$0.00765/image (low)Raisonnement complexe, OCR excellent
Claude 3.5 Sonnet Vision8192x8192$0.003/imageAnalyse détaillée, sécurité
Gemini 1.5 ProIllimité$0.001315/imageMulti-images, contexte long

Modèles open source

ModèleParamsVRAMUsage
LLaVA 1.634B24GBDescription générale
CogVLM219B16GBCompréhension fine
InternVL276B48GBPerformance SOTA
Qwen-VL-Max72B48GBMultilingue

Modèles d'embeddings multimodaux

ModèleDimensionLanguesOpen source
CLIP (OpenAI)512/768EN principalementOui
SigLIP384-1152MultilingueOui
Jina CLIP v2102489 languesOui
Cohere Embed v31024100+ languesNon

Implémentation pratique

Étape 1 : Extraction et description des images

DEVELOPERpython
import base64 from openai import OpenAI def describe_image_for_rag(image_path: str, context: str = "") -> dict: """ Génère une description RAG-optimisée d'une image. """ client = OpenAI() # Encoder l'image en base64 with open(image_path, "rb") as f: image_data = base64.b64encode(f.read()).decode("utf-8") # Déterminer le type MIME mime_type = "image/jpeg" if image_path.endswith((".jpg", ".jpeg")) else "image/png" prompt = """Analyse cette image pour un système RAG. Fournis: 1. **Description générale** (2-3 phrases) 2. **Éléments clés** (liste à puces des objets/concepts importants) 3. **Texte visible** (tout texte lisible dans l'image) 4. **Métadonnées suggérées** (catégorie, tags pertinents) Sois exhaustif mais concis. L'objectif est de permettre la recherche textuelle sur cette image.""" if context: prompt += f"\n\nContexte additionnel: {context}" response = client.chat.completions.create( model="gpt-4o", messages=[ { "role": "user", "content": [ {"type": "text", "text": prompt}, { "type": "image_url", "image_url": { "url": f"data:{mime_type};base64,{image_data}", "detail": "high" } } ] } ], max_tokens=1000 ) description = response.choices[0].message.content return { "image_path": image_path, "description": description, "model": "gpt-4o", "tokens_used": response.usage.total_tokens }

Étape 2 : Embeddings multimodaux avec CLIP

DEVELOPERpython
import torch from PIL import Image from transformers import CLIPProcessor, CLIPModel class MultimodalEmbedder: def __init__(self, model_name: str = "openai/clip-vit-large-patch14"): self.device = "cuda" if torch.cuda.is_available() else "cpu" self.model = CLIPModel.from_pretrained(model_name).to(self.device) self.processor = CLIPProcessor.from_pretrained(model_name) def embed_image(self, image_path: str) -> list[float]: """Génère un embedding pour une image.""" image = Image.open(image_path).convert("RGB") inputs = self.processor(images=image, return_tensors="pt").to(self.device) with torch.no_grad(): embedding = self.model.get_image_features(**inputs) # Normaliser pour la similarité cosinus embedding = embedding / embedding.norm(dim=-1, keepdim=True) return embedding.cpu().squeeze().tolist() def embed_text(self, text: str) -> list[float]: """Génère un embedding pour du texte (même espace que les images).""" inputs = self.processor(text=[text], return_tensors="pt", padding=True).to(self.device) with torch.no_grad(): embedding = self.model.get_text_features(**inputs) embedding = embedding / embedding.norm(dim=-1, keepdim=True) return embedding.cpu().squeeze().tolist() def compute_similarity(self, image_path: str, text: str) -> float: """Calcule la similarité image-texte.""" img_emb = torch.tensor(self.embed_image(image_path)) txt_emb = torch.tensor(self.embed_text(text)) return (img_emb @ txt_emb).item()

Étape 3 : Indexation dans Qdrant

DEVELOPERpython
from qdrant_client import QdrantClient from qdrant_client.models import ( VectorParams, Distance, PointStruct, Filter, FieldCondition, MatchValue ) class ImageRAGIndex: def __init__(self, collection_name: str = "image_rag"): self.client = QdrantClient(url="http://localhost:6333") self.collection_name = collection_name self.embedder = MultimodalEmbedder() def create_collection(self, vector_size: int = 768): """Crée la collection avec deux espaces vectoriels.""" self.client.recreate_collection( collection_name=self.collection_name, vectors_config={ # Embedding visuel (CLIP) "visual": VectorParams( size=vector_size, distance=Distance.COSINE ), # Embedding textuel (description) "textual": VectorParams( size=1536, # Ada-002 ou similaire distance=Distance.COSINE ) } ) def index_image( self, image_id: str, image_path: str, description: str, text_embedding: list[float], metadata: dict = None ): """Indexe une image avec ses deux embeddings.""" visual_embedding = self.embedder.embed_image(image_path) point = PointStruct( id=hash(image_id) % (2**63), vector={ "visual": visual_embedding, "textual": text_embedding }, payload={ "image_id": image_id, "image_path": image_path, "description": description, **(metadata or {}) } ) self.client.upsert( collection_name=self.collection_name, points=[point] ) def search_by_text(self, query: str, limit: int = 5) -> list[dict]: """Recherche par requête textuelle.""" query_embedding = self.embedder.embed_text(query) results = self.client.search( collection_name=self.collection_name, query_vector=("visual", query_embedding), # CLIP text -> visual limit=limit ) return [ { "image_path": r.payload["image_path"], "description": r.payload["description"], "score": r.score } for r in results ] def search_by_image(self, image_path: str, limit: int = 5) -> list[dict]: """Recherche d'images similaires.""" query_embedding = self.embedder.embed_image(image_path) results = self.client.search( collection_name=self.collection_name, query_vector=("visual", query_embedding), limit=limit ) return [ { "image_path": r.payload["image_path"], "description": r.payload["description"], "score": r.score } for r in results ]

Étape 4 : Génération avec contexte visuel

DEVELOPERpython
def generate_with_images( query: str, retrieved_images: list[dict], client: OpenAI ) -> str: """ Génère une réponse en utilisant les images récupérées comme contexte. """ # Préparer le contenu multimodal content = [ { "type": "text", "text": f"""Tu es un assistant qui répond aux questions en utilisant les images fournies comme source d'information. Question de l'utilisateur: {query} Images disponibles:""" } ] # Ajouter chaque image avec sa description for i, img in enumerate(retrieved_images[:3], 1): # Max 3 images with open(img["image_path"], "rb") as f: img_data = base64.b64encode(f.read()).decode("utf-8") content.append({ "type": "text", "text": f"\n**Image {i}** (score: {img['score']:.2f}):\n{img['description']}" }) content.append({ "type": "image_url", "image_url": { "url": f"data:image/jpeg;base64,{img_data}", "detail": "low" # Économiser les tokens } }) content.append({ "type": "text", "text": "\n\nRéponds à la question en te basant uniquement sur ces images. Si les images ne permettent pas de répondre, dis-le clairement." }) response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": content}], max_tokens=1000 ) return response.choices[0].message.content

Optimisations avancées

Chunking d'images haute résolution

Pour les grandes images (plans, schémas), découpez en tuiles :

DEVELOPERpython
from PIL import Image def tile_large_image(image_path: str, tile_size: int = 512, overlap: int = 64): """Découpe une grande image en tuiles avec chevauchement.""" img = Image.open(image_path) width, height = img.size tiles = [] for y in range(0, height - overlap, tile_size - overlap): for x in range(0, width - overlap, tile_size - overlap): box = (x, y, min(x + tile_size, width), min(y + tile_size, height)) tile = img.crop(box) tiles.append({ "tile": tile, "position": (x, y), "original_size": (width, height) }) return tiles

Hybrid search image + texte

DEVELOPERpython
def hybrid_image_search( query: str, text_embedding: list[float], index: ImageRAGIndex, alpha: float = 0.7 # Poids du visuel vs textuel ) -> list[dict]: """Combine recherche visuelle et textuelle.""" # Recherche visuelle (CLIP) visual_results = index.search_by_text(query, limit=20) # Recherche textuelle (sur les descriptions) text_results = index.client.search( collection_name=index.collection_name, query_vector=("textual", text_embedding), limit=20 ) # Fusion des scores avec RRF combined_scores = {} for rank, r in enumerate(visual_results): img_id = r["image_path"] combined_scores[img_id] = combined_scores.get(img_id, 0) + alpha / (rank + 60) for rank, r in enumerate(text_results): img_id = r.payload["image_path"] combined_scores[img_id] = combined_scores.get(img_id, 0) + (1 - alpha) / (rank + 60) # Trier par score combiné sorted_results = sorted(combined_scores.items(), key=lambda x: x[1], reverse=True) return [{"image_path": path, "score": score} for path, score in sorted_results[:5]]

Benchmarks et coûts

Performance de retrieval

MéthodePrecision@5Recall@10Latence
Description seule0.720.8150ms
CLIP seul0.780.8530ms
Hybride0.840.9180ms

Coûts par image indexée

ÉtapeCoût estiméNotes
Description GPT-4V$0.01-0.03Selon taille et détail
Embedding CLIP$0 (local)GPU recommandé
Stockage Qdrant~$0.0001Par vecteur/mois

Comparaison des modèles d'embedding

ModèleZero-shot accuracyMultilingueVitesse
CLIP ViT-L/1475.5%NonRapide
SigLIP So400m83.1%OuiMoyen
Jina CLIP v281.2%OuiRapide

Pièges et solutions

Problème 1 : Images avec peu de contenu visuel

Symptôme : Les captures d'écran de texte sont mal indexées par CLIP.

Solution : OCR explicite + indexation textuelle.

DEVELOPERpython
import pytesseract def extract_text_from_image(image_path: str) -> str: """Extrait le texte d'une image via OCR.""" img = Image.open(image_path) text = pytesseract.image_to_string(img, lang='fra+eng') return text.strip()

Problème 2 : Doublons visuels

Symptôme : Plusieurs images quasi-identiques polluent les résultats.

Solution : Déduplication par similarité.

DEVELOPERpython
def deduplicate_images(embeddings: list, threshold: float = 0.95): """Supprime les images trop similaires.""" keep = [] for i, emb in enumerate(embeddings): is_duplicate = False for j in keep: similarity = cosine_similarity(emb, embeddings[j]) if similarity > threshold: is_duplicate = True break if not is_duplicate: keep.append(i) return keep

Problème 3 : Contexte visuel vs textuel contradictoire

Symptôme : La description générée contredit l'image.

Solution : Validation croisée et score de confiance.

Intégration avec Ailog

Ailog supporte nativement l'indexation d'images dans vos knowledge bases :

  1. Upload : Glissez-déposez vos images dans l'interface
  2. Analyse automatique : Vision model pour extraction de contenu
  3. Indexation hybride : Embeddings visuels + textuels
  4. Recherche unifiée : Une seule requête pour texte et images

Essayez l'Image RAG sur Ailog - Aucune configuration requise.

FAQ

CLIP génère des embeddings multimodaux permettant la recherche image-vers-image et texte-vers-image directement. GPT-4V analyse et décrit les images en texte détaillé. En pratique, combinez les deux : CLIP pour le retrieval rapide et GPT-4V pour l'analyse approfondie et la génération de réponses contextuelles.
Comptez environ 10-30 dollars pour 1000 images avec GPT-4V (description) plus les embeddings CLIP (gratuit en local). Le coût principal vient de la description initiale. Une fois indexées, les recherches ne coûtent que le prix des embeddings de requête et de la génération de réponse.
Oui, mais avec des limitations. CLIP peut tourner sur CPU (plus lent), et vous pouvez utiliser les APIs cloud (OpenAI, Anthropic) pour les vision models. Pour la production avec beaucoup d'images, un GPU accélère significativement l'indexation initiale et réduit les coûts à long terme.
Les vision models comme GPT-4V ont un excellent OCR intégré, mais pour les documents textuels, combinez l'approche : extrayez le texte via OCR dédié (Tesseract, Azure Document Intelligence) et indexez-le séparément. Cela améliore la recherche textuelle tout en conservant le contexte visuel.
Absolument. C'est même un cas d'usage idéal. Indexez les photos produits avec leurs descriptions générées, puis permettez aux clients de rechercher visuellement ("trouve des robes similaires"). Pour les grands catalogues, utilisez Qdrant ou Pinecone avec des index optimisés pour les recherches à grande échelle.

Guides connexes

Tags

RAGmultimodalvisionimagesGPT-4VClaude VisionCLIPembeddings

Articles connexes

Ailog Assistant

Ici pour vous aider

Salut ! Pose-moi des questions sur Ailog et comment intégrer votre RAG dans vos projets !