À quoi ça sert

Là où Pillow ouvre et enregistre des fichiers image, OpenCV fait du traitement de pixels et de la vision par ordinateur :

  • Lire/écrire vidéo — depuis un fichier, une webcam, un flux IP, et générer une vidéo en sortie.
  • Filtrage et convolutions — flou gaussien, détection de bords (Canny), morphologie (érosion, dilatation), seuillage adaptatif.
  • Transformations géométriques — rotation, perspective, warping, calibration de caméra.
  • Détection « classique » — visages (Haar cascades), couleurs (HSV), formes (Hough), features (ORB, SIFT).
  • Suivi d'objets — algorithmes de tracking intégrés (KCF, CSRT…).
  • Pré/post-traitement pour le deep learning — souvent utilisé avant ou après YOLO / PyTorch pour préparer les frames et dessiner les sorties.
Le piège BGR (à savoir avant de débugger 1h)

cv2.imread() renvoie une image au format BGR (Bleu, Vert, Rouge), pas RGB. C'est un héritage historique d'OpenCV (les capteurs caméra de l'époque). Conséquence : si tu affiches une image OpenCV avec matplotlib ou la passes à un modèle qui attend du RGB (la grande majorité), les couleurs sont inversées. Solution : cv2.cvtColor(img, cv2.COLOR_BGR2RGB).

Un exemple d'usage

Lire une vidéo, détecter les contours frame par frame, sauver le résultat. C'est l'archétype d'usage OpenCV — chaîne lecture → traitement → écriture.

python
import cv2

cap = cv2.VideoCapture("input.mp4")
fps = int(cap.get(cv2.CAP_PROP_FPS))
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

fourcc = cv2.VideoWriter_fourcc(*"mp4v")
out = cv2.VideoWriter("edges.mp4", fourcc, fps, (w, h))

while True:
    ok, frame = cap.read()
    if not ok:
        break
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 100, 200)            # détection de bords
    color = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)   # remettre 3 canaux
    out.write(color)

cap.release(); out.release()

Le pattern read → traiter → write est universel en OpenCV. Tu peux substituer Canny par n'importe quoi — un appel à YOLO, un modèle PyTorch, un seuillage couleur — et garder la même structure.

How-to : installer et utiliser OpenCV

  1. Installer OpenCV

    Le paquet pip s'appelle opencv-python, mais l'import reste cv2. Avec UV :

    bash
    uv add opencv-python
    Sur un serveur sans interface graphique

    Sur un container Docker ou un serveur headless, préfère opencv-python-headless : même API mais sans les dépendances GUI (Qt) → installation plus légère, image Docker plus petite. Ne pas installer les deux, sinon conflit.

    python
    import cv2
    print(cv2.__version__)
  2. Lire et afficher une image

    python
    import cv2
    
    img = cv2.imread("photo.jpg")        # renvoie un array NumPy BGR
    print(img.shape, img.dtype)         # (H, W, 3) uint8
    
    # Convertir en RGB pour afficher avec matplotlib
    rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    # … ou en niveaux de gris pour des algos
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    cv2.imwrite("out.png", img)             # sauvegarder
  3. Redimensionner, recadrer, transformer

    python
    # Resize (attention : (largeur, hauteur), inverse de PIL)
    small = cv2.resize(img, (300, 200))
    
    # Crop avec slicing NumPy (img est un ndarray)
    crop = img[50:450, 100:500]
    
    # Rotation autour du centre
    h, w = img.shape[:2]
    M = cv2.getRotationMatrix2D((w/2, h/2), angle=45, scale=1.0)
    rotated = cv2.warpAffine(img, M, (w, h))
  4. Filtrage et seuillage

    python
    # Flou gaussien (lisse l'image)
    blur = cv2.GaussianBlur(img, (5, 5), sigmaX=0)
    
    # Détection de bords Canny
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 100, 200)
    
    # Seuillage : tout pixel > 127 devient 255, sinon 0
    _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
    
    # Seuillage adaptatif (ajusté par zone, meilleur sur photo)
    adaptive = cv2.adaptiveThreshold(
        gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
        cv2.THRESH_BINARY, 11, 2)
  5. Lire une vidéo ou la webcam

    python
    # Fichier vidéo
    cap = cv2.VideoCapture("video.mp4")
    
    # Webcam (0 = première caméra)
    cap = cv2.VideoCapture(0)
    
    while True:
        ok, frame = cap.read()
        if not ok:
            break
        # … traiter frame …
        cv2.imshow("webcam", frame)
        if cv2.waitKey(1) & 0xFF == ord("q"):
            break
    
    cap.release()
    cv2.destroyAllWindows()
  6. Dessiner des annotations

    Particulièrement utile après une détection YOLO pour visualiser le résultat :

    python
    # Rectangle (BGR : ici rouge en BGR = (0, 0, 255))
    cv2.rectangle(img, (50, 50), (200, 150),
                  color=(0, 0, 255), thickness=2)
    
    # Texte
    cv2.putText(img, "chien", (55, 45),
                cv2.FONT_HERSHEY_SIMPLEX, 0.8,
                (0, 255, 0), 2)
    
    # Cercle
    cv2.circle(img, center=(150, 100),
               radius=30, color=(255, 0, 0), thickness=-1)  # rempli
  7. Détection couleur (segmentation HSV)

    Classique : isoler tout ce qui est d'une certaine couleur dans une image. On passe en HSV pour que la « couleur » soit un seul paramètre stable :

    python
    import numpy as np
    
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    
    # Masque pour tout ce qui est "rouge" (deux plages en HSV)
    lower1 = np.array([0,   120, 70])
    upper1 = np.array([10,  255, 255])
    mask = cv2.inRange(hsv, lower1, upper1)
    
    # Appliquer le masque sur l'image originale
    result = cv2.bitwise_and(img, img, mask=mask)

Aide-mémoire

python (lecture/écriture)
import cv2
img = cv2.imread("in.jpg")            # BGR uint8
cv2.imwrite("out.png", img)
cap = cv2.VideoCapture("v.mp4")
ok, frame = cap.read(); cap.release()
python (conversions)
cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
python (traitements)
cv2.resize(img, (w, h))
cv2.GaussianBlur(img, (5, 5), 0)
cv2.Canny(gray, 100, 200)
cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
python (dessin BGR)
cv2.rectangle(img, p1, p2, (b, g, r), thickness)
cv2.putText(img, txt, org, font, scale, color, thick)
cv2.circle(img, center, r, color, thick)

OpenCV et le reste de l'écosystème

  • NumPy — toute image OpenCV est un array NumPy. Toutes les opérations NumPy (slicing, masques, broadcasting) sont valides sur une image.
  • Pillow — complémentaire. Pillow est plus simple pour ouvrir/sauver/redimensionner ; OpenCV prend le relais pour le traitement d'image et la vidéo. Aller-retour avec np.array(pil_img) et cv2.cvtColor(arr, cv2.COLOR_RGB2BGR).
  • YOLO / PyTorch — OpenCV est souvent le préprocesseur (resize, normalisation) et le post-processeur (dessiner les bounding boxes) du pipeline. YOLO Ultralytics accepte directement une frame OpenCV en input.
  • matplotlib — pour afficher une image OpenCV dans un notebook, il faut convertir BGR → RGB avant plt.imshow, sinon les couleurs sont inversées.
  • FastAPI — pour recevoir une image en POST et la traiter avec OpenCV : on lit les bytes et on décode avec cv2.imdecode(np.frombuffer(...), 1).
  • Streamlit — affiche directement une image OpenCV avec st.image(img, channels="BGR") (sinon couleurs inversées, encore et toujours).
Choisir entre Pillow et OpenCV pour un projet vision

Si tu te poses la question, fais un test mental : « j'ai besoin de manipuler des fichiers image (resize, format) » → Pillow suffit. « J'ai besoin de filtres, de détection, de vidéo, de masques » → OpenCV. En pratique sur un projet ML vision, les deux cohabitent : Pillow pour le chargement avant un modèle PyTorch, OpenCV pour annoter et lire des vidéos.

Pour aller plus loin