Skip to content

OpenCV

Vue d'ensemble

OpenCV (Open Source Computer Vision Library) est une bibliothèque de vision par ordinateur et d'apprentissage automatique open-source, optimisée pour les applications temps réel.

Philosophie

"Computer Vision for everyone - Rendre la vision par ordinateur accessible avec des outils performants et polyvalents."

Avantages clés

  • Performance : Optimisations C++ et parallélisation
  • Polyvalence : Images, vidéos, deep learning
  • Multi-plateforme : Windows, Linux, macOS, mobile
  • Intégrations : PyTorch, TensorFlow, MLflow

Installation et configuration

# Installation Python
pip install opencv-python opencv-contrib-python

# Vérification installation
python -c "import cv2; print(cv2.__version__)"

# Installation avec support GPU (CUDA)
pip install opencv-python[headless,contrib]

Traitement d'images de base

import cv2
import numpy as np
import matplotlib.pyplot as plt

# Lecture d'image
image = cv2.imread('image.jpg')
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# Redimensionnement
resized = cv2.resize(image, (800, 600))

# Filtrage
blurred = cv2.GaussianBlur(image, (15, 15), 0)
edges = cv2.Canny(image, 100, 200)

# Transformations géométriques
rows, cols = image.shape[:2]
M = cv2.getRotationMatrix2D((cols/2, rows/2), 45, 1)
rotated = cv2.warpAffine(image, M, (cols, rows))

# Seuillage
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# Morphologie
kernel = np.ones((5,5), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)

Détection d'objets

# Détection de contours
def detect_contours(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 50, 150)

    contours, hierarchy = cv2.findContours(
        edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
    )

    # Filtrage par aire
    min_area = 1000
    filtered_contours = [c for c in contours if cv2.contourArea(c) > min_area]

    # Dessin des contours
    result = image.copy()
    cv2.drawContours(result, filtered_contours, -1, (0, 255, 0), 2)

    return result, filtered_contours

# Détection de cercles
def detect_circles(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    circles = cv2.HoughCircles(
        gray,
        cv2.HOUGH_GRADIENT,
        dp=1,
        minDist=30,
        param1=50,
        param2=30,
        minRadius=0,
        maxRadius=0
    )

    if circles is not None:
        circles = np.round(circles[0, :]).astype("int")
        result = image.copy()

        for (x, y, r) in circles:
            cv2.circle(result, (x, y), r, (0, 255, 0), 4)
            cv2.rectangle(result, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)

        return result, circles

    return image, []

# Détection de formes géométriques
def detect_shapes(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 0)
    thresh = cv2.threshold(blurred, 60, 255, cv2.THRESH_BINARY)[1]

    contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]

    shapes = []
    for contour in contours:
        # Approximation polygonale
        epsilon = 0.02 * cv2.arcLength(contour, True)
        approx = cv2.approxPolyDP(contour, epsilon, True)

        # Classification par nombre de côtés
        vertices = len(approx)
        if vertices == 3:
            shape = "triangle"
        elif vertices == 4:
            shape = "rectangle"
        else:
            shape = "circle"

        shapes.append({
            'contour': contour,
            'shape': shape,
            'vertices': vertices,
            'area': cv2.contourArea(contour)
        })

    return shapes

Intégration avec PyTorch

import torch
import torchvision.transforms as transforms
from torch.utils.data import Dataset, DataLoader

class OpenCVDataset(Dataset):
    def __init__(self, image_paths, labels, transform=None):
        self.image_paths = image_paths
        self.labels = labels
        self.transform = transform

    def __len__(self):
        return len(self.image_paths)

    def __getitem__(self, idx):
        # Lecture avec OpenCV
        image = cv2.imread(self.image_paths[idx])
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

        # Preprocessing avec OpenCV
        image = self.preprocess_opencv(image)

        # Conversion vers tensor PyTorch
        if self.transform:
            image = self.transform(image)
        else:
            image = torch.from_numpy(image).float().permute(2, 0, 1) / 255.0

        return image, self.labels[idx]

    def preprocess_opencv(self, image):
        # Redimensionnement
        image = cv2.resize(image, (224, 224))

        # Égalisation d'histogramme
        lab = cv2.cvtColor(image, cv2.COLOR_RGB2LAB)
        lab[:,:,0] = cv2.equalizeHist(lab[:,:,0])
        image = cv2.cvtColor(lab, cv2.COLOR_LAB2RGB)

        # Débruitage
        image = cv2.fastNlMeansDenoisingColored(image, None, 10, 10, 7, 21)

        return image

# Pipeline d'augmentation combiné
class CVAugmentation:
    def __init__(self):
        self.opencv_transforms = [
            self.random_brightness,
            self.random_contrast,
            self.random_gaussian_noise,
            self.random_motion_blur
        ]

    def random_brightness(self, image, prob=0.5):
        if np.random.random() < prob:
            value = np.random.randint(-50, 50)
            hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
            hsv[:,:,2] = cv2.add(hsv[:,:,2], value)
            image = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
        return image

    def random_contrast(self, image, prob=0.5):
        if np.random.random() < prob:
            alpha = np.random.uniform(0.5, 1.5)
            image = cv2.convertScaleAbs(image, alpha=alpha, beta=0)
        return image

    def random_gaussian_noise(self, image, prob=0.3):
        if np.random.random() < prob:
            noise = np.random.randint(0, 50, image.shape, dtype=np.uint8)
            image = cv2.add(image, noise)
        return image

    def random_motion_blur(self, image, prob=0.3):
        if np.random.random() < prob:
            size = np.random.randint(10, 20)
            kernel = np.zeros((size, size))
            kernel[int((size-1)/2), :] = np.ones(size)
            kernel = kernel / size
            image = cv2.filter2D(image, -1, kernel)
        return image

    def __call__(self, image):
        for transform in self.opencv_transforms:
            image = transform(image)
        return image

# Utilisation avec PyTorch
transform = transforms.Compose([
    CVAugmentation(),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

dataset = OpenCVDataset(image_paths, labels, transform=transform)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True)

Traitement vidéo temps réel

class VideoProcessor:
    def __init__(self, source=0):
        self.cap = cv2.VideoCapture(source)
        self.fps = self.cap.get(cv2.CAP_PROP_FPS)

        # Détecteurs
        self.face_cascade = cv2.CascadeClassifier(
            cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
        )
        self.body_cascade = cv2.CascadeClassifier(
            cv2.data.haarcascades + 'haarcascade_fullbody.xml'
        )

    def detect_faces(self, frame):
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faces = self.face_cascade.detectMultiScale(
            gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30)
        )

        for (x, y, w, h) in faces:
            cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
            cv2.putText(frame, 'Face', (x, y-10), 
                       cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 0, 0), 2)

        return frame, len(faces)

    def optical_flow_tracking(self, frame):
        if not hasattr(self, 'old_frame'):
            self.old_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            return frame

        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # Détection de points caractéristiques
        corners = cv2.goodFeaturesToTrack(
            self.old_frame, maxCorners=100, qualityLevel=0.3, 
            minDistance=7, blockSize=7
        )

        if corners is not None:
            # Calcul du flot optique
            new_corners, status, error = cv2.calcOpticalFlowPyrLK(
                self.old_frame, gray, corners, None
            )

            # Dessin des trajectoires
            for i, (new, old) in enumerate(zip(new_corners, corners)):
                if status[i] == 1:
                    a, b = new.ravel().astype(int)
                    c, d = old.ravel().astype(int)
                    cv2.line(frame, (a, b), (c, d), (0, 255, 0), 2)
                    cv2.circle(frame, (a, b), 5, (0, 0, 255), -1)

        self.old_frame = gray.copy()
        return frame

    def process_stream(self):
        while True:
            ret, frame = self.cap.read()
            if not ret:
                break

            # Traitement du frame
            frame, face_count = self.detect_faces(frame)
            frame = self.optical_flow_tracking(frame)

            # Affichage des informations
            cv2.putText(frame, f'Faces: {face_count}', (10, 30),
                       cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
            cv2.putText(frame, f'FPS: {self.fps:.1f}', (10, 70),
                       cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

            # Affichage
            cv2.imshow('Video Processing', frame)

            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

        self.cap.release()
        cv2.destroyAllWindows()

# Utilisation
processor = VideoProcessor()
processor.process_stream()

Intégration avec MLflow

import mlflow
import mlflow.opencv

class ComputerVisionExperiment:
    def __init__(self, experiment_name):
        mlflow.set_experiment(experiment_name)
        self.metrics = {}

    def preprocess_dataset(self, image_paths, params):
        with mlflow.start_run(run_name="preprocessing"):
            mlflow.log_params(params)

            processed_images = []
            processing_times = []

            for path in image_paths:
                start_time = time.time()

                # Lecture et preprocessing
                image = cv2.imread(path)
                image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

                # Redimensionnement
                if params.get('resize'):
                    image = cv2.resize(image, params['target_size'])

                # Débruitage
                if params.get('denoise'):
                    image = cv2.fastNlMeansDenoisingColored(image)

                # Égalisation
                if params.get('equalize'):
                    lab = cv2.cvtColor(image, cv2.COLOR_RGB2LAB)
                    lab[:,:,0] = cv2.equalizeHist(lab[:,:,0])
                    image = cv2.cvtColor(lab, cv2.COLOR_LAB2RGB)

                processed_images.append(image)
                processing_times.append(time.time() - start_time)

            # Métriques de preprocessing
            avg_time = np.mean(processing_times)
            mlflow.log_metrics({
                "avg_preprocessing_time": avg_time,
                "total_images": len(processed_images),
                "images_per_second": 1.0 / avg_time
            })

            # Sauvegarde d'exemples
            sample_before = cv2.imread(image_paths[0])
            sample_after = processed_images[0]

            cv2.imwrite("sample_before.jpg", sample_before)
            cv2.imwrite("sample_after.jpg", sample_after)

            mlflow.log_artifact("sample_before.jpg")
            mlflow.log_artifact("sample_after.jpg")

            return processed_images

    def evaluate_detection_algorithm(self, algorithm, test_images, ground_truth):
        with mlflow.start_run(run_name=f"detection_{algorithm.__name__}"):
            true_positives = 0
            false_positives = 0
            false_negatives = 0
            detection_times = []

            for image, gt in zip(test_images, ground_truth):
                start_time = time.time()
                detections = algorithm(image)
                detection_time = time.time() - start_time
                detection_times.append(detection_time)

                # Calcul métriques de détection
                tp, fp, fn = self.calculate_detection_metrics(detections, gt)
                true_positives += tp
                false_positives += fp
                false_negatives += fn

            # Métriques finales
            precision = true_positives / (true_positives + false_positives) if (true_positives + false_positives) > 0 else 0
            recall = true_positives / (true_positives + false_negatives) if (true_positives + false_negatives) > 0 else 0
            f1_score = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
            avg_detection_time = np.mean(detection_times)

            metrics = {
                "precision": precision,
                "recall": recall,
                "f1_score": f1_score,
                "avg_detection_time": avg_detection_time,
                "detections_per_second": 1.0 / avg_detection_time
            }

            mlflow.log_metrics(metrics)

            return metrics

Applications avec drones

# Vision pour drones - détection de terrain d'atterrissage
class DroneVision:
    def __init__(self):
        self.landing_detector = self.setup_landing_detector()

    def setup_landing_detector(self):
        # Détecteur de zones planes pour atterrissage
        return cv2.HoughCircles

    def detect_landing_zones(self, aerial_image):
        """Détection de zones d'atterrissage sûres"""
        gray = cv2.cvtColor(aerial_image, cv2.COLOR_BGR2GRAY)

        # Détection de zones planes (faible gradient)
        sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
        sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
        gradient_magnitude = np.sqrt(sobelx**2 + sobely**2)

        # Zones avec faible gradient = terrain plat
        flat_mask = gradient_magnitude < 30

        # Morphologie pour nettoyer
        kernel = np.ones((20, 20), np.uint8)
        flat_mask = cv2.morphologyEx(flat_mask.astype(np.uint8), cv2.MORPH_OPEN, kernel)

        # Détection de contours des zones plates
        contours, _ = cv2.findContours(flat_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

        landing_zones = []
        for contour in contours:
            area = cv2.contourArea(contour)
            if area > 10000:  # Zone suffisamment grande
                # Calculer centroïde
                M = cv2.moments(contour)
                if M["m00"] != 0:
                    cx = int(M["m10"] / M["m00"])
                    cy = int(M["m01"] / M["m00"])

                    landing_zones.append({
                        'center': (cx, cy),
                        'area': area,
                        'contour': contour,
                        'safety_score': self.calculate_safety_score(aerial_image, contour)
                    })

        return sorted(landing_zones, key=lambda x: x['safety_score'], reverse=True)

    def calculate_safety_score(self, image, contour):
        """Calcul du score de sécurité d'une zone d'atterrissage"""
        mask = np.zeros(image.shape[:2], dtype=np.uint8)
        cv2.fillPoly(mask, [contour], 255)

        # Analyse de la zone
        roi = cv2.bitwise_and(image, image, mask=mask)
        gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)

        # Critères de sécurité
        uniformity = 1.0 - (np.std(gray_roi[gray_roi > 0]) / 255.0)  # Uniformité
        area_score = min(cv2.contourArea(contour) / 50000.0, 1.0)    # Taille

        return (uniformity * 0.6 + area_score * 0.4) * 100

# Exemple d'utilisation
drone_vision = DroneVision()
aerial_img = cv2.imread('drone_view.jpg')
safe_zones = drone_vision.detect_landing_zones(aerial_img)

print(f"Zones d'atterrissage détectées: {len(safe_zones)}")
for i, zone in enumerate(safe_zones[:3]):  # Top 3
    print(f"Zone {i+1}: Score {zone['safety_score']:.1f}, Centre {zone['center']}")

Ressources