Computer-Vision-KI

Von den Informationen, die Menschen erwerben, stammen etwa 80% aus dem Sehen.

Wenn du ein Foto siehst, kannst du sofort erkennen, wie viele Personen darauf sind, was sie tun und ob der Hintergrund drinnen oder draußen ist.

Doch für einen Computer ist dieses Foto nur ein Haufen Zahlen—jedes Pixel wird durch drei Werte für Rot, Grün und Blau dargestellt, und das ist alles.

Computer Vision (CV) ist die Technologie, die es Computern ermöglicht, Bilder zu verstehen.

Vom Gesichtsentsperren auf Smartphones über die Erkennung von Straßenzuständen im autonomen Fahren bis hin zur Läsionsanalyse in der medizinischen Bildgebung ist Computer Vision allgegenwärtig geworden.

Dieses Modul führt dich von grundlegenden Faltungsnetzwerken bis hin zu den neuesten Vision-Language-Modellen.

Lernpfad: Faltungsnetzwerke → Vision Transformer → Objekterkennung → Bildsegmentierung → Diffusionsmodelle → CLIP → Vision-Language-Modelle. Jeder Schritt enthält ausführbare Codebeispiele.


Faltungsnetzwerke (CNN)

CNN ist eine grundlegende Technologie in der Computer Vision. Sie ahmt die Funktionsweise des menschlichen visuellen Kortex nach und extrahiert Bildmerkmale durch lokale rezeptive Felder.

Prinzipien der Faltungsoperation

Die Kernidee der Faltung ist:Verwende ein kleines gleitendes Fenster (Faltungskern), um das Bild zu scannen und lokale Merkmale zu extrahieren.。

Ein 3×3-Kern betrachtet beispielsweise 9 Pixel gleichzeitig, berechnet deren gewichtete Summe und erzeugt einen Ausgabewert.

Dieser Prozess wird wiederholt; der Faltungskern gleitet von links nach rechts und von oben nach unten über das Bild und erzeugt schließlich eine "Feature-Map".

Beispiel

# ============================================
# Implementiere die einfachste Faltungsoperation mit NumPy
# Zeige, wie die Faltung Kantenmerkmale extrahiert
# ============================================

import numpy as np


def simple_convolution(image: np.ndarray, kernel: np.ndarray) -> np.ndarray:
    """
Implementiere die einfachste 2D-Faltungsoperation (ohne Padding und Stride)

Parameter:
image: Eingabebild (H, W), einkanaliges Graustufenbild
kernel: Faltungskern (kH, kW)

Rückgabe:
Feature-Map nach der Faltung
    """

    # Ermittle die Dimensionen des Bildes und des Faltungskerns
    img_h, img_w = image.shape
    kernel_h, kernel_w = kernel.shape

    # Berechne die Größe der Ausgabe-Feature-Map
    # Ausgabegröße = Eingabegröße - Faltungskerngröße + 1
    out_h = img_h - kernel_h + 1
    out_w = img_w - kernel_w + 1

    # Initialisiere die Ausgabe-Feature-Map
    output = np.zeros((out_h, out_w))

    # Verschiebe den Faltungskern zur Berechnung
    for i in range(out_h):
        for j in range(out_w):
            # Extrahiere den lokalen Bereich des Bildes, der dem Faltungskern entspricht
            region = image[i:i+kernel_h, j:j+kernel_w]
            # Multipliziere entsprechende Elemente und summiere (dies ist die Faltungsoperation)
            output[i, j] = np.sum(region * kernel)

    return output


# Erstelle ein einfaches Testbild: ein weißes Quadrat in der Mitte, schwarz drumherum
# Form: 8×8 Graustufenbild
test_image = np.array([
    [0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 1, 1, 1, 0, 0],
    [0, 0, 1, 1, 1, 1, 0, 0],
    [0, 0, 1, 1, 1, 1, 0, 0],
    [0, 0, 1, 1, 1, 1, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0],
])
print("Originalbild:")
print(test_image)

# Definiere einen Faltungskern zur Kantenerkennung (vereinfachter Sobel-Operator)
# Dieser Faltungskern kann vertikale Kanten erkennen
edge_kernel = np.array([
    [-1, 0, 1],
    [-2, 0, 2],
    [-1, 0, 1],
])

# Führe die Faltung durch
feature_map = simple_convolution(test_image, edge_kernel)
print("\nFeature-Map nach der Faltung (vertikale Kanten erkannt):")
print(np.round(feature_map, 2))

Der Schlüssel zur Faltungsoperation ist:Die Parameter des Faltungskerns werden gelernt, nicht manuell entworfen.。

Während des Trainings passt das Modell die Faltungskernwerte automatisch an, sodass es Merkmale extrahieren kann, die für die Aufgabe nützlich sind.

Pooling

Pooling komprimiert die Größe der Feature-Map, reduziert den Rechenaufwand und bewahrt dabei wichtige Merkmale.

Am häufigsten wird Max-Pooling verwendet: Teile die Feature-Map in mehrere kleine Blöcke auf, und jeder Block behält nur den Maximalwert.

Beispiel

# ============================================
# Implementiere die Max-Pooling-Operation
# ============================================

def max_pooling(feature_map: np.ndarray, pool_size: int = 2) -> np.ndarray:
    """
Implementiere die Max-Pooling-Operation

Parameter:
feature_map: Eingabe-Feature-Map (H, W)
pool_size: Größe des Pooling-Fensters, Standard 2×2

Rückgabe:
Die gepoolte Feature-Map
    """

    h, w = feature_map.shape
    # Berechne die Ausgabegröße
    out_h = h // pool_size
    out_w = w // pool_size

    output = np.zeros((out_h, out_w))

    for i in range(out_h):
        for j in range(out_w):
            # Extrahiere den Bereich des Pooling-Fensters
            region = feature_map[
                i*pool_size:(i+1)*pool_size,
                j*pool_size:(j+1)*pool_size
            ]
            # Nimm den Maximalwert
            output[i, j] = np.max(region)

    return output


# Führe Pooling mit der vorherigen Feature-Map durch
print("Feature-Map vor dem Pooling:")
print(feature_map)

pooled = max_pooling(feature_map, pool_size=2)
print("\n"Feature-Map nach 2×2-Max-Pooling:")
print(pooled)

Klassischer Architekturvergleich

In der Geschichte der CNN-Entwicklung gibt es mehrere Meilenstein-Architekturen, die jeweils Designideen aus verschiedenen Epochen repräsentieren.

ArchitekturJahrKerninnovationEigenschaften
AlexNet2012ReLU-Aktivierung, Dropout, Daten-AugmentierungDer Pionier des Deep-Learning-Zeitalters, der als Erster traditionelle Methoden auf ImageNet deutlich übertraf.
VGG2014Einheitliche Verwendung von kleinen 3×3-FaltungskernenEinfache und elegante Struktur, leicht zu verstehen und zu implementieren
ResNet2015Residualverbindung (Skip-Connection)Löst das Problem des verschwindenden Gradienten in tiefen Netzwerken und ermöglicht Netzwerke mit Hunderten von Schichten.
EfficientNet2019Zusammengesetzte SkalierungsmethodeSkaliert Tiefe, Breite und Auflösung gleichzeitig, mit extrem hoher Parametereffizienz.

Residualverbindung (Skip-Connection)

Die Kerninnovation von ResNet ist die Residualverbindung, die das Problem löst, dass "je tiefer das Netzwerk, desto schwieriger ist es zu trainieren."

Das Lernziel einer traditionellen Netzwerkschicht ist es, direkt die Abbildung von Eingabe x auf Ausgabe y zu lernen.

Das Lernziel eines Residualnetzwerks ist:Die Differenz (das Residuum) zwischen Ausgabe y und Eingabe x lernen.。

Formel: y = F(x) + x, wobei F(x) das zu erlernende Residuum ist.

Intuition der Residualverbindung: Das Netzwerk lernen zu lassen, "wie viel sich basierend auf dem vorhandenen Zustand ändern muss", ist viel einfacher, als es "die vollständige Abbildung von Grund auf lernen" zu lassen.

Beispiel

# ============================================
# Implementiere einen vollständigen CNN-Bildklassifikator mit PyTorch
# Beinhaltet Faltung, Pooling, Residualverbindungen
# ============================================

import torch
import torch.nn as nn
import torch.nn.functional as F


class ResidualBlock(nn.Module):
    """Ein einfacher Residualblock"""

    def __init__(self, in_channels: int, out_channels: int, stride: int = 1):
        super().__init__()
        # Erste Faltungsschicht
        self.conv1 = nn.Conv2d(
            in_channels, out_channels,
            kernel_size=3, stride=stride, padding=1, bias=False
        )
        self.bn1 = nn.BatchNorm2d(out_channels)

        # Zweite Faltungsschicht
        self.conv2 = nn.Conv2d(
            out_channels, out_channels,
            kernel_size=3, stride=1, padding=1, bias=False
        )
        self.bn2 = nn.BatchNorm2d(out_channels)

        # Shortcut-Verbindung (zum Anpassen von Dimensionsänderungen)
        self.shortcut = nn.Sequential()
        if stride != 1 or in_channels != out_channels:
            self.shortcut = nn.Sequential(
                nn.Conv2d(
                    in_channels, out_channels,
                    kernel_size=1, stride=stride, bias=False
                ),
                nn.BatchNorm2d(out_channels)
            )

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # Hauptpfad: conv → BN → ReLU → conv → BN
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))

        # Residualverbindung: Addiere die Eingabe (oder die Eingabe nach der Dimensionstransformation)
        out += self.shortcut(x)

        # Abschließende ReLU
        out = F.relu(out)
        return out


class SimpleCNN(nn.Module):
    """Einfaches CNN zur Bildklassifizierung (mit Residualverbindungen)"""

    def __init__(self, num_classes: int = 10):
        super().__init__()
        # Initiale Faltungsschicht
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1)
        self.bn1 = nn.BatchNorm2d(32)

        # Residual-Schicht
        self.layer1 = ResidualBlock(32, 32)
        self.layer2 = ResidualBlock(32, 64, stride=2)
        self.layer3 = ResidualBlock(64, 64)

        # Globales Average-Pooling
        self.global_pool = nn.AdaptiveAvgPool2d((1, 1))

        # Klassifizierungskopf
        self.fc = nn.Linear(64, num_classes)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # x-Form: (batch_size, 3, 32, 32)
        x = F.relu(self.bn1(self.conv1(x)))

        x = self.layer1(x)  # (batch_size, 32, 32, 32)
        x = self.layer2(x)  # (batch_size, 64, 16, 16)
        x = self.layer3(x)  # (batch_size, 64, 16, 16)

        x = self.global_pool(x)  # (batch_size, 64, 1, 1)
        x = x.view(x.size(0), -1)  # (batch_size, 64)
        x = self.fc(x)  # (batch_size, num_classes)

        return x


# Modell erstellen und testen
model = SimpleCNN(num_classes=10)
print("Modellstruktur:")
print(model)

# Vorwärtsdurchlauf testen
# Erzeuge einen Fake-Batch: 4 Bilder, jeweils 3×32×32 (RGB 32×32)
test_input = torch.randn(4, 3, 32, 32)
test_output = model(test_input)
print(f"\nEingabeform: {test_input.shape}")
print(f"Ausgabeform: {test_output.shape} (batch_size, num_classes)")

Vision Transformer(ViT)

Im Jahr 2020 veröffentlichte Google das Paper "An Image is Worth 16x16 Words" und brachte die Transformer-Architektur aus dem Sprachbereich in den Vision-Bereich.

Bild-Patching (Patch-Embedding)

Transformer verarbeiten Sequenzdaten, aber Bilder sind 2D-Gitterdaten.

ViTs Lösung ist einfach:Zerlege das Bild in kleine Patches und behandle jeden Patch wie ein Wort。

Zum Beispiel ergibt ein 224×224-Bild, das in 16×16-Patches zerlegt wird, 14×14 = 196 Patches.

Jeder Patch wird zu einem 1D-Vektor abgeflacht und über eine lineare Schicht in ein Embedding projiziert, das dann in den Transformer eingespeist werden kann.

Beispiel

# ============================================
# Patch-Embedding implementieren (Bild-Patching)
# ============================================

class PatchEmbedding(nn.Module):
    """
Zerlege das Bild in Patches und bette sie ein

Parameter:
img_size: Größe des Eingabebilds (quadratisch)
patch_size: Größe jedes Patches (quadratisch)
in_channels: Anzahl der Eingabekanäle (3 für RGB)
embed_dim: Ausgabedimension des Embeddings
    """


    def __init__(
        self,
        img_size: int = 224,
        patch_size: int = 16,
        in_channels: int = 3,
        embed_dim: int = 768
    ):
        super().__init__()
        self.img_size = img_size
        self.patch_size = patch_size
        # Anzahl der Patches berechnen: (224/16)^2 = 14^2 = 196
        self.num_patches = (img_size // patch_size) ** 2

        # Faltung verwenden, um Patch-Aufteilung und Embedding zu implementieren
        # Dies entspricht: Patches ausschneiden → abflachen → lineare Projektion
        self.proj = nn.Conv2d(
            in_channels, embed_dim,
            kernel_size=patch_size, stride=patch_size
        )

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # x-Form: (batch_size, in_channels, img_size, img_size)
        x = self.proj(x)  # (batch_size, embed_dim, num_patches^0.5, num_patches^0.5)
        x = x.flatten(2)  # (batch_size, embed_dim, num_patches)
        x = x.transpose(1, 2)  # (batch_size, num_patches, embed_dim)
        return x


# Patch-Einbettung testen
patch_embed = PatchEmbedding(img_size=224, patch_size=16, embed_dim=768)
test_image = torch.randn(1, 3, 224, 224)  # 1 RGB-Bild mit 224×224
patches = patch_embed(test_image)
print(f"Form des Eingabebilds: {test_image.shape}")
print(f"Form der Patch-Sequenz: {patches.shape} (batch_size, num_patches, embed_dim)")

Vollständige ViT-Architektur

Die vollständige ViT-Architektur erfordert außerdem das Hinzufügen eines Klassen-Tokens und einer Positionskodierung.

Der Klassen-Token ist ein lernbarer Vektor, der dazu dient, Informationen aus allen Patches zu "aggregieren".

Die Positionskodierung ist ebenfalls lernbar; sie sagt dem Modell, "wo sich jeder Patch im Bild befindet".

Beispiel

# ============================================
# Einen vereinfachten Vision Transformer implementieren
# ============================================

class MultiHeadAttention(nn.Module):
    """Multi-Head-Selbstaufmerksamkeit"""

    def __init__(self, dim: int, num_heads: int):
        super().__init__()
        self.num_heads = num_heads
        self.head_dim = dim // num_heads
        assert self.head_dim * num_heads == dim, "Die Dimension muss durch die Anzahl der Heads teilbar sein"

        # Projektionsmatrizen für Q, K, V
        self.qkv = nn.Linear(dim, dim * 3)
        # Ausgabeprojektion
        self.proj = nn.Linear(dim, dim)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        batch_size, seq_len, dim = x.shape

        # Q, K, V berechnen
        qkv = self.qkv(x)  # (batch_size, seq_len, dim*3)
        qkv = qkv.reshape(
            batch_size, seq_len, 3, self.num_heads, self.head_dim
        )  # (batch_size, seq_len, 3, num_heads, head_dim)
        q, k, v = qkv.unbind(2)  # Jeweilige Form: (batch_size, seq_len, num_heads, head_dim)

        # Dimensionen umordnen, um Attention zu berechnen
        q = q.transpose(1, 2)  # (batch_size, num_heads, seq_len, head_dim)
        k = k.transpose(1, 2)
        v = v.transpose(1, 2)

        # Attention-Scores berechnen
        attention = q @ k.transpose(-2, -1) / (self.head_dim ** 0.5)
        attention = attention.softmax(dim=-1)

        # Werte aggregieren
        out = attention @ v  # (batch_size, num_heads, seq_len, head_dim)
        out = out.transpose(1, 2)  # (batch_size, seq_len, num_heads, head_dim)
        out = out.flatten(2)  # (batch_size, seq_len, dim)

        # Ausgabeprojektion
        out = self.proj(out)
        return out


class TransformerBlock(nn.Module):
    """Ein Transformer-Block"""

    def __init__(self, dim: int, num_heads: int, mlp_ratio: float = 4.0):
        super().__init__()
        self.norm1 = nn.LayerNorm(dim)
        self.attn = MultiHeadAttention(dim, num_heads)
        self.norm2 = nn.LayerNorm(dim)

        # MLP
        mlp_hidden = int(dim * mlp_ratio)
        self.mlp = nn.Sequential(
            nn.Linear(dim, mlp_hidden),
            nn.GELU(),
            nn.Linear(mlp_hidden, dim)
        )

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # Residual-Verbindung 1
        x = x + self.attn(self.norm1(x))
        # Residual-Verbindung 2
        x = x + self.mlp(self.norm2(x))
        return x


class SimpleViT(nn.Module):
    """Vereinfachter Vision Transformer"""

    def __init__(
        self,
        img_size: int = 224,
        patch_size: int = 16,
        in_channels: int = 3,
        num_classes: int = 10,
        embed_dim: int = 192,
        depth: int = 6,
        num_heads: int = 6
    ):
        super().__init__()
        # Patch-Einbettung
        self.patch_embed = PatchEmbedding(
            img_size, patch_size, in_channels, embed_dim
        )
        num_patches = self.patch_embed.num_patches

        # Klassen-Token
        self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))

        # Positionskodierung
        self.pos_embed = nn.Parameter(
            torch.zeros(1, num_patches + 1, embed_dim)
        )

        # Transformer-Schichten
        self.blocks = nn.ModuleList([
            TransformerBlock(embed_dim, num_heads)
            for _ in range(depth)
        ])

        # Abschließender LayerNorm und Klassifikationskopf
        self.norm = nn.LayerNorm(embed_dim)
        self.head = nn.Linear(embed_dim, num_classes)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        batch_size = x.shape[0]

        # Patch-Einbettung
        x = self.patch_embed(x)  # (batch_size, num_patches, embed_dim)

        # Klassen-Token hinzufügen
        cls_tokens = self.cls_token.expand(batch_size, -1, -1)
        x = torch.cat((cls_tokens, x), dim=1)  # (batch_size, 1+num_patches, embed_dim)

        # Positionskodierung hinzufügen
        x = x + self.pos_embed

        # Durch Transformer-Schichten leiten
        for block in self.blocks:
            x = block(x)

        # Nur das Klassen-Token für die Klassifikation verwenden
        x = self.norm(x)
        cls_output = x[:, 0]  # Erste Position nehmen (Klassen-Token)
        x = self.head(cls_output)

        return x


# ViT-Modell erstellen und testen
vit = SimpleViT(
    img_size=32,    # Kleinere Bilder für einfacheres Testen verwenden
    patch_size=8,   # 8×8-Patch
    embed_dim=128,
    depth=4,
    num_heads=4,
    num_classes=10
)
print("Vereinfachte ViT-Architektur:")
print(vit)

test_input = torch.randn(2, 3, 32, 32)  # 2 32×32-Bilder
test_output = vit(test_input)
print(f"\nEingabeform: {test_input.shape}")
print(f"Ausgabeform: {test_output.shape}")

CNN vs. ViT-Vergleich

Beide Architekturen haben jeweils ihre eigenen Vor- und Nachteile; welche man wählt, hängt vom konkreten Anwendungsfall ab.

EigenschaftenCNNViT
Induktiver BiasStark (Lokalität, Translationsinvarianz)Schwach (lernt aus Daten)
DatenanforderungenFunktioniert mit kleinen DatenmengenBenötigt große Datenmengen, um seine Vorteile zu zeigen
RecheneffizienzNormalerweise schneller, ausgereifte Hardware-OptimierungGroße Parameteranzahl, rechenintensiv
InterpretierbarkeitFaltungskerne lassen sich leicht visualisierenAufmerksamkeitskarten sind interpretierbar, aber das Gesamtmodell ist schwieriger zu interpretieren
Langreichweitige AbhängigkeitenErfordern mehrere Schichten zum AufbauUnterstützt natürlicherweise ein globales rezeptives Feld
Anwendbare SzenarienBegrenzte Daten, hohe EchtzeitanforderungenGroße Datensätze, komplexe visuelle Aufgaben

Faustregel: Verwenden Sie CNN (ResNet, EfficientNet), wenn die Daten begrenzt sind; ziehen Sie ViT in Betracht, wenn die Daten Millionen oder mehr erreichen. Es gibt auch hybride Architekturen (wie ConvNeXt), die die Vorteile beider kombinieren.


Objekterkennung

Die Bildklassifizierung beantwortet "Was ist auf diesem Bild," während die Objekterkennung beantwortet "Was ist auf diesem Bild und wo befinden sie sich."

Die Ausgabe der Objekterkennung ist eine Reihe von Bounding-Boxen, die jeweils einer Objektkategorie entsprechen.

Zweistufige Erkennung: R-CNN-Serie

Die Idee der zweistufigen Erkennung ist:Zuerst Regionen finden, die Objekte enthalten könnten, dann diese Regionen klassifizieren。

Erste Stufe: Regionsvorschläge generieren, möglicherweise Hunderte von Boxen, die anzeigen "Hier könnte etwas sein."

Zweite Stufe: Merkmale aus jeder Kandidatenregion extrahieren, bestimmen, um welches Objekt es sich handelt, und die Box-Position anpassen.

Evolution der R-CNN-Familie:

ModellWichtige VerbesserungenGeschwindigkeit
R-CNNErsetzt traditionelle Merkmale durch CNNLangsam (Dutzende Sekunden pro Bild)
Fast R-CNNROI-Pooling, gemeinsame MerkmalsberechnungMittel (einige Sekunden pro Bild)
Faster R-CNNRPN (Region Proposal Network)Schnell (Hunderte Millisekunden pro Bild)
Mask R-CNNFügt einen Segmentierungszweig hinzuMittel

Einstufige Erkennung: YOLO-Serie

Die Idee der einstufigen Erkennung ist:Direkt eine dichte Vorhersage auf dem Bild durchführen, ohne separate Kandidatenregionen zu generieren。

YOLO (You Only Look Once) ist ein Vertreter der einstufigen Erkennung, extrem schnell und für Echtzeitanwendungen geeignet.

YOLO teilt das Bild in ein S×S-Raster auf, wobei jede Rasterzelle für die Vorhersage von Objekten verantwortlich ist, deren Mittelpunkt in diese Zelle fällt.

Jede Rasterzelle sagt voraus: B Bounding-Boxen (mit Konfidenz) und Wahrscheinlichkeiten für C Klassen.

End-to-End-Erkennung: DETR

DETR (DEtection TRansformer) ist ein neues Paradigma, das von Facebook AI vorgeschlagen wurde und Transformer nutzt, um Objekterkennung in ein direktes Sequenzvorhersageproblem zu verwandeln.

Sein größtes Merkmal ist: keine Notwendigkeit für Non-Maximum Suppression (NMS), keine Notwendigkeit für Anchor-Boxen (Anchor), die Ausgabe ist das Endergebnis.

Praxis: YOLOv11-Objekterkennung

Jetzt verwenden wir Ultralytics YOLOv11, um ein vollständiges Beispiel für Objekterkennung zu erstellen.

Beispiel

# ============================================
# YOLOv11-Objekterkennung praktisch
# Enthält Modellladen, Erkennung, Visualisierung
# ============================================

import torch
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import matplotlib.pyplot as plt


class SimpleYOLO:
    """Vereinfachte YOLO-Demo (mit vorab trainiertem Modell)"""

    def __init__(self, model_name: str = "yolo11n.pt"):
        """
YOLO-Modell initialisieren

Parameter:
model_name: Modellname, yolo11n.pt (nano, klein), yolo11s.pt (klein), usw.
        """

        self.model_name = model_name
        self.model = None
        # 80 Kategorien des COCO-Datensatzes
        self.coco_names = [
            'Person', 'Fahrrad', 'Auto', 'Motorrad', 'Flugzeug', 'Bus',
            'Zug', 'LKW', 'Boot', 'Ampel', 'Feuerhydrant',
            'Stoppschild', 'Parkuhr', 'Bank', 'Vogel', 'Katze', 'Hund',
            'Pferd', 'Schaf', 'Kuh', 'Elefant', 'Bär', 'zebra', 'giraffe',
            'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
            'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat',
            'baseball glove', 'skateboard', 'surfboard', 'tennis racket',
            'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl',
            'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot',
            'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
            'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop',
            'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven',
            'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase',
            'scissors', 'teddy bear', 'hair drier', 'toothbrush'
        ]
        # Farbe für jede Kategorie
        self.colors = np.random.randint(0, 255, (80, 3))

    def load_model(self):
        """Lade vorab trainiertes Modell (hier wird eine simulierte Implementierung verwendet, um das Konzept zu demonstrieren)"""
        print(f"Lade Modell {self.model_name}...")
        print("Hinweis: In der tatsächlichen Verwendung bitte ultralytics installieren und aufrufen:")
        print(" from ultralytics import YOLO")
        print(" model = YOLO('yolo11n.pt')")
        self.model = "pretrained_model_loaded"
        print("Modell erfolgreich geladen!")
        return self

    def predict_dummy(self, image: np.ndarray) -> list:
        """
Simulierte Erkennungsergebnisse (zur Demonstration)
In der tatsächlichen Verwendung model(image) aufrufen

Rückgabe:
Liste der Erkennungsergebnisse, jedes Element ist ein Dictionary:
            {'bbox': [x1, y1, x2, y2], 'class_id': int, 'score': float, 'class_name': str}
        """

        # Hier simulieren wir die Erkennung mehrerer Objekte
        h, w = image.shape[:2]
        dummy_results = [
            {'bbox': [w*0.1, h*0.2, w*0.3, h*0.6], 'class_id': 0, 'score': 0.92, 'class_name': 'person'},
            {'bbox': [w*0.35, h*0.4, w*0.7, h*0.8], 'class_id': 2, 'score': 0.85, 'class_name': 'car'},
            {'bbox': [w*0.75, h*0.3, w*0.9, h*0.55], 'class_id': 16, 'score': 0.78, 'class_name': 'dog'},
        ]
        return dummy_results

    def visualize(self, image: np.ndarray, results: list) -> np.ndarray:
        """
Erkennungsboxen und Beschriftungen auf das Bild zeichnen

Parameter:
image: Originalbild (H, W, 3)
results: Liste der Erkennungsergebnisse

Rückgabe:
Annotiertes Bild
        """

        if isinstance(image, np.ndarray):
            image = Image.fromarray(image)

        draw = ImageDraw.Draw(image)

        for result in results:
            bbox = result['bbox']
            class_id = result['class_id']
            score = result['score']
            class_name = result['class_name']

            color = tuple(self.colors[class_id % 80].tolist())

            # Box zeichnen
            draw.rectangle(bbox, outline=color, width=3)

            # Label-Hintergrund
            label = f"{class_name} {score:.2f}"
            try:
                font = ImageFont.truetype("arial.ttf", 16)
            except:
                font = ImageFont.load_default()

            # Textgröße berechnen
            bbox_label = draw.textbbox((0, 0), label, font=font)
            text_w = bbox_label[2] - bbox_label[0]
            text_h = bbox_label[3] - bbox_label[1]

            # Label-Hintergrund zeichnen
            label_bg = [bbox[0], bbox[1]-text_h-5, bbox[0]+text_w+5, bbox[1]]
            draw.rectangle(label_bg, fill=color)

            # Text zeichnen
            draw.text((bbox[0]+2, bbox[1]-text_h-3), label, fill=(255,255,255), font=font)

        return np.array(image)


# Simuliertes Bild erstellen (zur Demonstration)
def create_test_image() -> np.ndarray:
    """Erstellt ein Testbild"""
    h, w = 480, 640
    image = np.zeros((h, w, 3), dtype=np.uint8)
    # Verlaufshintergrund
    image[:, :, 0] = np.linspace(100, 200, w)[np.newaxis, :]
    image[:, :, 1] = np.linspace(150, 100, h)[:, np.newaxis]
    image[:, :, 2] = 180
    return image


# YOLO-Erkennungsprozess demonstrieren
print("=" * 60)
print("BEISPIEL YOLOv11 Objekterkennungs-Demo")
print("=" * 60)

# Initialisieren
detector = SimpleYOLO().load_model()

# Testbild erstellen
test_image = create_test_image()
print(f"\nEingabebildgröße: {test_image.shape}")

# Simulierte Erkennung
results = detector.predict_dummy(test_image)
print(f"\n"Erkannte {len(results)} Objekte:")
for i, r in enumerate(results, 1):
    print(f" {i}. {r['class_name']} (Konfidenz: {r['score']:.2f})")

# Visualisierung
visualized = detector.visualize(test_image, results)
print(f"\nVisualisierung abgeschlossen, Ausgabebildgröße: {visualized.shape}")


# ============================================
# Tatsächlicher Code für die Verwendung von YOLOv11 (erfordert Installation von ultralytics)
# ============================================
print("\n" + "=" * 60)
print("Beispielcode für die tatsächliche Verwendung von YOLOv11:")
print("=" * 60)
print("""
# Installation: pip install ultralytics

from ultralytics import YOLO

# 1. Modell laden
model = YOLO('yolo11n.pt') # Sie können auch 'yolo11s.pt', 'yolo11m.pt' usw. verwenden.

# 2. Vorhersage
results = model('test_image.jpg') # Kann ein Bildpfad, Video oder 0 (Webcam) sein

# 3. Ergebnisse verarbeiten
for result in results:
# Erkennungsboxen
    boxes = result.boxes
# Segmentierungsmasken (bei Verwendung eines Segmentierungsmodells)
    masks = result.masks
# Ergebnisse anzeigen
    result.show()
# Ergebnisse speichern
    result.save('result.jpg')

# 4. Training (optional)
# model.train(data='coco128.yaml', epochs=100, imgsz=640)
"""
)

Bildsegmentierung

Objekterkennung markiert Objekte mit rechteckigen Boxen, während Bildsegmentierung bis auf die Pixelebene geht und kennzeichnet, zu welchem Objekt jedes Pixel gehört.

Semantische Segmentierung vs. Instanzsegmentierung

Bildsegmentierung hat zwei große Zweige: semantische Segmentierung und Instanzsegmentierung.

AufgabeZielBeispielRepräsentative Modelle
Semantische SegmentierungJedes Pixel klassifizieren (unabhängig von der Instanz)Alle Personen rot markieren, alle Autos blauFCN、U-Net、DeepLab
InstanzsegmentierungJedes Pixel klassifizieren und verschiedene Instanzen unterscheidenPerson 1 rot markieren, Person 2 orangeMask R-CNN、YOLOv8-Seg
Panoptische SegmentierungSemantisch + Instanz, einheitliche DarstellungGleichzeitig alle 'Dinge' und 'Hintergrund' kennzeichnenPanoptic FPN、MaskFormer

Ein klassisches Beispiel: Angenommen, es gibt zwei Personen und zwei Hunde.

Semantische Segmentierung sagt: Hier gibt es 'Personen' und 'Hunde' (nur Kategorien, keine Anzahl).

Instanzsegmentierung sagt: Hier gibt es 'Person 1', 'Person 2', 'Hund 1', 'Hund 2' (jedes Individuum ist eigenständig).

Segment Anything Model(SAM)

Im Jahr 2023 veröffentlichte Meta AI SAM (Segment Anything Model), wodurch Segmentierung zu einer prompt-gesteuerten interaktiven Aufgabe wurde.

SAMs Eingabe kann sein: ein Punkt, eine Box, ein Textstück oder eine grobe Maske.

SAMs Ausgabe ist: die präzise Segmentierungsmaske des entsprechenden Objekts.

Die Designphilosophie von SAM ist:Promptfähig, generalisierbar, Zero-Shot。

Beispiel

# ============================================
# SAM-Segmentierungskonzept-Demo
# Zeigt, wie man mit Prompts wie Punkten, Boxen usw. segmentiert.
# ============================================

class SimpleSAM:
    """Vereinfachte SAM-Konzeptdemo"""

    def __init__(self):
        self.image_embedding = None
        print("SAM-Konzeptdemo-Initialisierung")

    def set_image(self, image: np.ndarray):
        """Bild kodieren (nur einmal)"""
        print("Bild wird zu Embedding kodiert...")
        self.image_embedding = "image_encoded"
        return self

    def predict_from_point(self, point: tuple, point_label: int = 1):
        """
Segmentieren von einem Punkt

Parameter:
point: (x, y)-Koordinaten
point_label: 1 für Vordergrund, 0 für Hintergrund
        """

        print(f"Segmentieren von Punkt {point} ({'Vordergrund' if point_label == 1 else 'Hintergrund'})")
        return "mask_from_point"

    def predict_from_box(self, box: tuple):
        """Segmentieren anhand einer Bounding-Box"""
        x1, y1, x2, y2 = box
        print(f"Segmentieren aus Box [{x1}, {y1}, {x2}, {y2}]")
        return "mask_from_box"

    def predict_from_points(self, points: list, labels: Liste):
        """Segmentieren aus mehreren Punkten"""
        print(f"Segmentieren aus {len(points)} Punkten")
        return "mask_from_points"


# SAM-Nutzungsworkflow demonstrieren
print("=" * 60)
print("BEISPIEL SAM-Segmentierungskonzept-Demo")
print("=" * 60)

sam = SimpleSAM()

# 1. Bild vorbereiten (einmal kodieren)
test_img = create_test_image()
sam.set_image(test_img)

# 2. Segmentierung mit verschiedenen Prompts
print()
mask1 = sam.predict_from_point((100, 200), point_label=1)

print()
mask2 = sam.predict_from_box((50, 50, 200, 300))

print()
mask3 = sam.predict_from_points([(100, 100), (150, 150)], [1, 1])


# ============================================
# Codebeispiel für die tatsächliche SAM-Verwendung
# ============================================
print("\n" + "=" * 60)
print("Codebeispiel für die tatsächliche SAM-Verwendung:")
print("=" * 60)
print("""
# Installation: pip install segment-anything torch torchvision opencv-python

import cv2
import numpy as np
from segment_anything import sam_model_registry, SamPredictor

# 1. Modell laden
# Checkpoint herunterladen: https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth
sam = sam_model_registry["vit_h"](checkpoint="sam_vit_h_4b8939.pth")
sam.to(device="cuda")
predictor = SamPredictor(sam)

# 2. Bild vorbereiten
image = cv2.imread("test_image.jpg")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
predictor.set_image(image)

# 3. Segmentierung mit Punkt-Prompts
input_point = np.array([[300, 250]]) # Koordinaten des Prompt-Punkts
input_label = np.array([1]) # 1 = Vordergrund, 0 = Hintergrund

masks, scores, logits = predictor.predict(
    point_coords=input_point,
    point_labels=input_label,
    multimask_output=True
)

# 4. Ergebnisse visualisieren
for i, mask in enumerate(masks):
    color = np.array([30, 144, 255])
    h, w = mask.shape
    mask_image = mask.reshape(h, w, 1) * color.reshape(1, 1, -1)
    cv2.imwrite(f"mask_{i}.jpg", mask_image)
"""
)

Anwendungen der Segmentierung in der Industrie

Die Bildsegmentierung ist eine Kerntechnologie in vielen industriellen Anwendungen:

FeldAnwendungsszenarioAufgabentyp
Medizinische BildgebungTumorsegmentierung, OrganvermessungSemantische Segmentierung
Autonomes FahrenBefahrbarer Bereich, FahrspurerkennungSemantische Segmentierung
FernerkundungsbilderLandbedeckungsklassifikation, GebäudeextraktionSemantische Segmentierung
Intelligente FertigungDefekterkennung, TeilevermessungInstanzsegmentierung
VideobearbeitungPersonenfreistellung, HintergrundaustauschInstanzsegmentierung

Diffusionsmodelle

Diffusionsmodelle sind die Kerntechnologie für die KI-Bildgenerierung; Stable Diffusion, DALL-E 3 und Midjourney basieren alle darauf.

Vorwärtsdiffusionsprozess (Rauschen hinzufügen)

Die Vorwärtsdiffusion ist ein einfacher Prozess:Fügen Sie dem Bild schrittweise Gaußsches Rauschen hinzu, bis es reines Rauschen ist.。

Dies ist ein deterministischer Prozess; jeder Schritt fügt nur ein wenig Rauschen hinzu, und wir können mathematische Formeln verwenden, um die Rauschmenge zu jedem Zeitpunkt präzise zu berechnen.

Beispiel

# ============================================
# Vorwärtsprozess des Diffusionsmodells demonstrieren (Rauschen hinzufügen)
# ============================================

import numpy as np
import matplotlib.pyplot as plt


class ForwardDiffusion:
    """Demo des Vorwärtsdiffusionsprozesses"""

    def __init__(self, num_timesteps: int = 1000):
        self.num_timesteps = num_timesteps

        # Beta-Zeitplan definieren (linear ansteigend von beta_start bis beta_end)
        self.beta_start = 0.0001
        self.beta_end = 0.02
        self.betas = np.linspace(self.beta_start, self.beta_end, num_timesteps)

        # Berechne Alpha und alpha_bar
        self.alphas = 1.0 - self.betas
        self.alphas_bar = np.cumprod(self.alphas)

    def q_sample(self, x0: np.ndarray, t: int) -> tuple:
        """
Berechne xt zum Zeitpunkt t direkt aus x0

Parameter:
x0: Originalbild (H, W, 3)
t: Zeitschritt (0 <= t < num_timesteps)

Rückgabe:
xt: Bild zum Zeitpunkt t
noise: das hinzugefügte Rauschen
        """

        sqrt_alpha_bar_t = np.sqrt(self.alphas_bar[t])
        sqrt_one_minus_alpha_bar_t = np.sqrt(1 - self.alphas_bar[t])

        # Erzeuge zufälliges Rauschen derselben Form
        noise = np.random.randn(*x0.shape)

        # Berechne xt in einem Schritt
        xt = sqrt_alpha_bar_t * x0 + sqrt_one_minus_alpha_bar_t * noise

        return xt, noise


# Erzeuge ein einfaches Testbild: ein heller Kreis in der Mitte
def create_diffusion_test_image():
    h, w = 64, 64
    x = np.linspace(-1, 1, w)
    y = np.linspace(-1, 1, h)
    xx, yy = np.meshgrid(x, y)
    r = np.sqrt(xx**2 + yy**2)
    image = np.exp(-r**2 / 0.1)  # Ein heller Kreis mit Gauß-Verteilung
    image = (image - image.min()) / (image.max() - image.min())  # Auf [0, 1] normalisieren
    image = np.stack([image, image*0.8, image*0.5], axis=-1)  # In 3 Kanäle umwandeln
    return image


print("=" * 60)
print("BEISPIEL Demo des Vorwärtsdiffusionsprozesses")
print("=" * 60)

# Diffusionsprozess initialisieren
diffusion = ForwardDiffusion(num_timesteps=1000)

# Testbild erstellen
x0 = create_diffusion_test_image()
print(f"Form des Originalbilds: {x0.shape}")

# Ergebnisse der Rauschhinzufügung bei verschiedenen Zeitschritten anzeigen
timesteps_to_show = [0, 100, 200, 300, 500, 1000]

print("\nRauschniveau bei verschiedenen Zeitschritten: ")
for t in timesteps_to_show:
    if t < 1000:
        xt, noise = diffusion.q_sample(x0, t)
        alpha_bar = diffusion.alphas_bar[t]
        print(f" t={t:4d}: alpha_bar={alpha_bar:.4f}, SNR={alpha_bar/(1-alpha_bar):.4f}")
    else:
        print(f" t={t:4d}: reines Rauschen")

print("\nDie Kernformel der Vorwärtsdiffusion: ")
print(" q(x_t | x_0) = N(x_t; sqrt(alpha_bar_t) * x0, (1 - alpha_bar_t) * I)")
print("\nDiese Formel erlaubt es uns, x_t zu jedem Zeitpunkt t aus x0 in einem Schritt zu berechnen, ohne schrittweise Iteration!)

Umgekehrter Entrauschungsprozess

Der umgekehrte Prozess ist die Umkehrung des Vorwärtsprozesses:Ausgehend von reinem Rauschen wird schrittweise Rauschen vorhergesagt und entfernt, um schließlich ein Bild zu erhalten。

Das muss unser Modell lernen: gegeben xt und t, vorhersagen, was das hinzugefügte Rauschen ist.

Trainingsziel: den mittleren quadratischen Fehler zwischen dem vom Modell vorhergesagten Rauschen und dem wahren Rauschen minimieren.

U-Net-Architektur

Der Kern eines Diffusionsmodells ist ein U-Net-Netzwerk, dessen Form dem Buchstaben "U" ähnelt: links Downsampling und Komprimierung, rechts Upsampling und Wiederherstellung.

Der Schlüssel zum U-Net ist die Skip-Verbindung, die Merkmale direkt vom Downsampling an die entsprechenden Upsampling-Schichten weitergibt und dabei detaillierte Informationen bewahrt.

DDPM vs. DDIM Sampling

Nach dem Trainieren des Modells müssen wir aus Rauschen sampeln, um Bilder zu erzeugen. Verschiedene Sampler unterscheiden sich in Geschwindigkeit und Qualität.

SamplerVollständiger NameSchritteEigenschaften
DDPMDenoising Diffusion Probabilistic Models1000Ursprüngliche Methode, gute Qualität, aber langsam
DDIMDenoising Diffusion Implicit Models50-100Deterministisches Sampling, schnell
LMSLineares Mehrschrittverfahren30-50Schnell, gute Qualität
DPM-SolverDPM-Solver10-20Ultraschnell, erzielt gute Ergebnisse in nur wenigen Schritten

Das aktuelle Stable Diffusion verwendet standardmäßig DPM-Solver++ und erzielt normalerweise hochwertige Ergebnisse in 20-30 Schritten, Dutzende Male schneller als das ursprüngliche DDPM.

Stable-Diffusion-Architektur

Stable Diffusion arbeitet nicht direkt im Pixelraum, sondern im latenten Raum (Latent Space), was es schneller und effizienter macht.

Das vollständige Stable Diffusion besteht aus drei Teilen:

1. VAE (Variational Autoencoder): komprimiert Bilder in latente Vektoren oder rekonstruiert Bilder aus latenten Vektoren.

2. UNet: das zentrale Entrauschungsnetzwerk, nimmt latente Vektoren, Zeitschritte und Prompt-Einbettungen und sagt Rauschen voraus.

3. Text-Encoder (CLIP): wandelt Text-Prompts in Einbettungen um und teilt dem Modell mit, was generiert werden soll.

Beispiel

# ============================================
# Stable Diffusion-Konzeptdemonstration
# Vollständige Generierungspipeline anzeigen
# ============================================

class SimpleStableDiffusion:
    """Vereinfachte Stable Diffusion-Konzeptdemonstration"""

    def __init__(self):
        print("Initialisierung der Stable Diffusion-Konzeptdemonstration")

    def encode_text(self, prompt: str):
        """Text-Prompt kodieren"""
        print(f"Prompt kodieren: '{prompt}'")
        return "text_embedding"

    def encode_image(self, image: np.ndarray):
        """VAE kodiert Bild in den latenten Raum"""
        print(Bild in den latenten Raum kodieren (VAE-Encoder))
        return "latent_code"

    def decode_latent(self, latent: str):
        VAE dekodiert Bild aus dem latenten Raum
        print(Bild aus dem latenten Raum dekodieren (VAE-Decoder))
        return "decoded_image"

    def denoise_step(self, latent: str, text_embedding: str, t: int):
        Ein-Schritt-Entrauschung
        print(fEntrauschungsschritt t={t})
        return "denoised_latent"

    def generate(self, prompt: str, num_inference_steps: int = 50):
        Vollständige Generierungspipeline
        print("=" * 60)
        print(fStarte Generierung: '{prompt}')
        print("=" * 60)

        # 1. Text kodieren
        text_emb = self.encode_text(prompt)

        # 2. Mit zufälligem Rauschen starten
        print("\nInitialisiere zufälliges Rauschen...)
        latent = "random_noise"

        # 3. Schrittweise entrauschen
        print(f"\nStarte Entrauschungsschleife ({num_inference_steps} Schritte):)
        for t in reversed(range(num_inference_steps)):
            latent = self.denoise_step(latent, text_emb, t)

        # 4. Dekodieren, um Bild zu erhalten
        print("\nDekodiere latenten Vektor zu einem Bild...)
        image = self.decode_latent(latent)

        print("\nGenerierung abgeschlossen!)
        return image


# Generierungspipeline demonstrieren
sd = SimpleStableDiffusion()
image = sd.generate(
    prompt=Eine süße orange Katze im Garten,
    num_inference_steps=20
)


# ============================================
# Tatsächliches Codebeispiel mit Stable Diffusion
# ============================================
print("\n" + "=" * 60)
print(Tatsächliches Codebeispiel mit Stable Diffusion:)
print("=" * 60)
print("""
# Installation: pip install diffusers transformers accelerate torch

from diffusers import StableDiffusionPipeline
import torch

# 1. Modell laden
model_id = "runwayml/stable-diffusion-v1-5"
pipe = StableDiffusionPipeline.from_pretrained(
    model_id,
    torch_dtype=torch.float16
)
pipe = pipe.to("cuda")

# 2. Text-zu-Bild
prompt = "eine niedliche orangefarbene Katze in einem Garten"
image = pipe(prompt).images[0]

# 3. Ergebnis speichern
image.save("cat_garden.png")

# 4. Bild-zu-Bild
from diffusers import StableDiffusionImg2ImgPipeline
from PIL import Image

img_pipe = StableDiffusionImg2ImgPipeline.from_pretrained(
    model_id,
    torch_dtype=torch.float16
).to("cuda")

init_image = Image.open("input.jpg").convert("RGB")
init_image = init_image.resize((512, 512))

image = img_pipe(
    prompt="lass es wie ein Gemälde aussehen",
    image=init_image,
strength=0.75 # Grad der Variation
).images[0]
image.save("painted.png")

# 5. Inpainting (lokale Änderung)
from diffusers import StableDiffusionInpaintPipeline

inpaint_pipe = StableDiffusionInpaintPipeline.from_pretrained(
    model_id,
    torch_dtype=torch.float16
).to("cuda")

image = inpaint_pipe(
    prompt="setze der Katze einen Hut auf",
    image=init_image,
mask_image=mask_image # Maskenbild, weiße Bereiche sind die zu ändernden Teile
).images[0]
"""
)

CLIP: Grundlagen der multimodalen Ausrichtung

CLIP (Contrastive Language-Image Pre-training) ist eine 2021 von OpenAI veröffentlichte Arbeit, die erstmals Text und Bilder wirklich im selben Vektorraum ausrichtete.

Prinzipien des kontrastiven Lernens

CLIPs Trainingsmethode ist kontrastives Lernen:Korrekte Bild-Text-Paare sollen eine hohe Ähnlichkeit haben, und falsche Paare eine geringe Ähnlichkeit.。

Während des Trainings enthält ein Batch N Bilder und N Textbeschreibungen (in Eins-zu-eins-Entsprechung), die N×N mögliche Bild-Text-Paare bilden.

Die Diagonale enthält übereinstimmende positive Proben, und der Rest sind nicht übereinstimmende negative Proben.

Das Trainingsziel ist: die Ähnlichkeit positiver Proben zu maximieren und die Ähnlichkeit negativer Proben zu minimieren.

Training zur Bild-Text-Ausrichtung

CLIP besteht aus zwei Encodern:

  • 1. Bild-Encoder: kodiert ein Bild in einen Vektor (kann ResNet oder ViT sein).

  • 2. Text-Encoder: kodiert Text in Vektoren (Transformer).

Nachdem die beiden Vektoren in denselben Raum projiziert wurden, wird der Grad der Bild-Text-Übereinstimmung über die Kosinusähnlichkeit berechnet.

Beispiel

# ============================================
# CLIP-Konzeptdemo: kontrastives Lernen und Bild-Text-Ausrichtung
# ============================================

import torch
import torch.nn.functional as F


class SimpleCLIP:
    """Vereinfachte CLIP-Konzeptdemo"""

    def __init__(self, embed_dim: int = 512):
        self.embed_dim = embed_dim
        # Einfache Simulation: keine echte Kodierung, nur zufällige Projektion
        print("Initialisierung der CLIP-Konzeptdemo")
        print(f"Einbettungsdimension: {embed_dim}")

    def encode_image(self, image: str) -> torch.Tensor:
        """Bildkodierung simulieren"""
        # In der Praxis wäre dies ein Vision Transformer oder ResNet
        return torch.randn(1, self.embed_dim)

    def encode_text(self, text: str) -> torch.Tensor:
        """Textkodierung simulieren"""
        # In der Praxis wäre dies ein Text-Transformer
        return torch.randn(1, self.embed_dim)

    def compute_similarity(self, image_emb: torch.Tensor, text_emb: torch.Tensor) -> float:
        """Kosinusähnlichkeit berechnen"""
        image_emb = F.normalize(image_emb, dim=-1)
        text_emb = F.normalize(text_emb, dim=-1)
        return (image_emb @ text_emb.T).item()


# CLIP-Inferenzablauf demonstrieren
print("=" * 60)
print("BEISPIEL CLIP Zero-Shot-Klassifikations-Demo")
print("=" * 60)

clip = SimpleCLIP(embed_dim=512)

# Angenommen, es gibt ein Bild
print("\n1. Bild wird kodiert...")
image_emb = clip.encode_image("Ein Foto einer Katze")

# Angenommen, es gibt mehrere Kandidatenkategorien
candidates = ["Ein Foto eines Hundes", "Ein Foto einer Katze", "Ein Auto", "Eine Person"]
print(f"\n2. Kandidatentexte werden kodiert: {candidates}")

# Ähnlichkeiten berechnen
print("\n3. Bild-Text-Ähnlichkeit wird berechnet:")
for text in candidates:
    text_emb = clip.encode_text(text)
    sim = clip.compute_similarity(image_emb, text_emb)
    print(f" '{text}' → Ähnlichkeit: {sim:.3f}")

print("\n(In der Praxis wird 'Ein Foto einer Katze' die höchste Ähnlichkeit haben)")


# ============================================
# Praxisbeispiel für die Verwendung von CLIP
# ============================================
print("\n" + "=" * 60)
print("Praxisbeispiel für die Verwendung von CLIP:")
print("=" * 60)
print("""
# Installation: pip install clip torch torchvision

import clip
import torch
from PIL import Image

# 1. Modell laden
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device)

# 2. Bild vorbereiten
image = preprocess(Image.open("cat.jpg")).unsqueeze(0).to(device)

# 3. Text vorbereiten
text = clip.tokenize([
    "Ein Foto eines Hundes",
    "Ein Foto einer Katze",
    "Ein Foto eines Autos",
    "Ein Foto einer Person"
]).to(device)

# 4. Inferenz
with torch.no_grad():
    image_features = model.encode_image(image)
    text_features = model.encode_text(text)

    logits_per_image, logits_per_text = model(image, text)
    probs = logits_per_image.softmax(dim=-1).cpu().numpy()

# 5. Ergebnisse ausgeben
print("Vorhersagewahrscheinlichkeiten:")
for i, p in enumerate(probs[0]):
    print(f" {i+1}. {p:.2%}")
"""
)

Zero-Shot-Klassifizierungsfähigkeit

Die spannendste Fähigkeit von CLIP ist die Zero-Shot-Klassifikation:Es kann direkt klassifizieren, ohne auf dem Zieldatensatz trainiert zu werden。

Auf traditionellem ImageNet trainierte Modelle können nur die 1000 Klassen von ImageNet klassifizieren; das Hinzufügen neuer Klassen erfordert ein erneutes Training.

CLIP kann beliebigen Text verwenden, um Kategorien zu beschreiben, zum Beispiel: "Ein Foto einer Katze", "eine orange Katze, die auf dem Gras sitzt", "eine Skizze einer Katze".

Die Bedeutung von CLIP liegt nicht nur in der Zero-Shot-Klassifikation, sondern auch darin, dass es eine allgemeine "Vision-Language"-Schnittstelle etabliert; viele nachfolgende Modelle (wie Stable Diffusion, Vision-Language-Modelle) bauen auf seiner Grundlage auf.


Vision-Language-Modelle (VLM)

Ein Vision-Language-Modell (VLM) kombiniert Bildverständnis und Sprachgenerierung, wodurch das Modell "Bilder anschauen und sprechen" kann.

LLaVA-Architektur

LLaVA (Large Language and Vision Assistant) ist ein Open-Source-VLM. Sein Design ist einfach und klar, wodurch es sich sehr gut für Anfänger eignet.

Die Architektur von LLaVA ist in drei Teile gegliedert:

  • 1. Vision-Encoder: CLIPs Vision-Encoder, der Bilder in Embeddings umwandelt.

  • 2. Projektionsschicht: projiziert die visuellen Embeddings in dieselbe Dimension wie die LLM-Eingabe.

  • 3. LLM: ein großes Sprachmodell (z. B. Vicuna, LLaMA), das die verkettete Bild-Text-Eingabe empfängt und Antworten generiert.

Das Training ist in zwei Schritte unterteilt:

  • 1. Pretraining-Phase: Nur die Projektionsschicht wird trainiert, wobei eine große Anzahl von Bild-Text-Paaren zur Ausrichtung verwendet wird.

  • 2. Instruction-Fine-Tuning-Phase: Das gesamte Modell wird mit visuellen Instruction-Datensätzen trainiert (z. B. LLaVA-Instruct).

Verbindung zwischen visuellem Encoder und LLM

Der Schlüssel zur Verbindung von Vision und Sprache liegt darin:Bilder als Token-Sequenzen darzustellen, die Sprachmodelle verstehen können。

Es gibt mehrere gängige Verbindungsmethoden:

MethodeEigenschaftenRepräsentative Modelle
Spezielles TokenEin [IMG]-Token verwenden, um das gesamte Bild darzustellenFrühe Modelle
SequenzverkettungBild-Patch-Einbettungen direkt vor dem Text verkettenLLaVA、GPT-4V
Cross-AttentionCross-Attention zu visuellen Merkmalen im LLM hinzufügenBLIP-2、Flamingo

Beispiel

# ============================================
# Konzept-Demo für Vision-Language-Modell (VLM)
# Zeigt, wie visuelle und sprachliche Eingaben verkettet werden
# ============================================

class SimpleVLM:
    """Vereinfachtes Vision-Language-Modell"""

    def __init__(self, vocab_size: int = 50000, hidden_size: int = 768):
        self.vocab_size = vocab_size
        self.hidden_size = hidden_size
        print("Vereinfachte VLM-Initialisierung")

    def encode_vision(self, image: str) -> torch.Tensor:
        """Bild als Sequenz kodieren"""
        # Angenommen, es gibt 256 visuelle Token
        return torch.randn(1, 256, self.hidden_size)

    def encode_text(self, text: str) -> torch.Tensor:
        """Text als Sequenz kodieren"""
        # Angenommen, der Text hat 16 Token
        return torch.randn(1, 16, self.hidden_size)

    def generate(self, image: str, question: str) -> str:
        """Bild-Text-Verkettung erzeugt Antwort"""
        print("=" * 60)
        print(f"Frage: {question}")
        print("=" * 60)

        # 1. Bild und Text kodieren
        print("\n1. Bild kodieren...)
        vision_emb = self.encode_vision(image)

        print("2. Text kodieren...")
        text_emb = self.encode_text(question)

        # 3. Verketten: [Vision-Sequenz] + [Text-Sequenz]
        print("\n3. Vision- und Textsequenzen verketten...)
        print(f" Form der Vision-Einbettung: {vision_emb.shape}")
        print(f" Form der Text-Einbettung: {text_emb.shape}")
        combined = torch.cat([vision_emb, text_emb], dim=1)
        print(f" Form nach der Verkettung: {combined.shape}")

        # 4. LLM erzeugt Antwort
        print("\n4. LLM erzeugt Antwort...)

        return "Das ist eine süße orangefarbene Katze, sie sieht sehr glücklich aus."


# VLM-Fragen und -Antworten demonstrieren
vlm = SimpleVLM()
answer = vlm.generate(
    image="Ein Foto einer orangefarbenen Katze",
    question="Was ist auf diesem Bild? Bitte beschreibe es."
)

print(f"\nAntwort: {answer}")


# ============================================
# Codebeispiel für die tatsächliche Verwendung von LLaVA
# ============================================
print("\n" + "=" * 60)
print("Codebeispiel für die tatsächliche Verwendung von LLaVA:")
print("=" * 60)
print("""
# LLaVA benötigt viel GPU-Speicher, es wird empfohlen, die Bibliothek transformers oder llava zu verwenden

from transformers import AutoProcessor, AutoModelForVisionAndLanguageGeneration
import torch
from PIL import Image

# 1. Modell und Prozessor laden
model_id = "llava-hf/llava-1.5-7b-hf"
processor = AutoProcessor.from_pretrained(model_id)
model = AutoModelForVisionAndLanguageGeneration.from_pretrained(
    model_id,
    torch_dtype=torch.float16,
    device_map="auto"
)

# 2. Eingabe vorbereiten
image = Image.open("cat.jpg")
prompt = "USER: <image>\\nWas ist auf diesem Bild? Bitte beschreiben Sie es. ASSISTANT:"

inputs = processor(images=image, text=prompt, return_tensors="pt").to("cuda")

# 3. Generieren
with torch.no_grad():
    output = model.generate(**inputs, max_new_tokens=500)
answer = processor.decode(output[0], skip_special_tokens=True)

print(answer)
"""
)

Praxis: Vollständiges Projekt für Bildklassifizierung + Objekterkennung

Jetzt setzen wir das Gelernte zusammen, um ein vollständiges Praxisprojekt zu erstellen.

Beispiel

# ============================================
# Praxisprojekt: Vollständige Vision-KI-Pipeline
# Enthält Bildklassifizierung, Objekterkennung und Ergebnisvisualisierung
# ============================================

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
import numpy as np
from PIL import Image
import json
from pathlib import Path


class VisionPipeline:
    """
Vollständige Vision-KI-Pipeline

Funktionen:
1. Bildklassifizierung
2. Objekterkennung
3. Ergebniszusammenfassung und Visualisierung
    """


    def __init__(self, device: str = "cpu"):
        self.device = device
        self.classifier = None
        self.detector = None
        print(f"BEISPIEL Vision Pipeline initialisiert (Gerät: {device})")

    def build_classifier(self, num_classes: int = 10):
        """Bildklassifizierer erstellen"""
        print("\nBildklassifizierer wird erstellt...)
        # Verwende das zuvor definierte SimpleCNN
        self.classifier = SimpleCNN(num_classes=num_classes).to(self.device)
        print("Bildklassifizierer erfolgreich erstellt!")
        return self

    def build_detector(self):
        """Objektdetektor erstellen"""
        print("\nObjektdetektor wird erstellt...)
        self.detector = SimpleYOLO()
        print("Detektor erfolgreich erstellt!")
        return self

    def load_pretrained_weights(self, classifier_path: str = None):
        """Vortrainierte Gewichte laden"""
        if classifier_path and self.classifier:
            print(f"\nKlassifizierer-Gewichte laden: {classifier_path}")
            # In der Praxis: self.classifier.load_state_dict(torch.load(classifier_path))
        return self

    def classify_image(self, image: np.ndarray) -> tuple:
        """
Bildklassifizierung

Rückgabe:
            (pred_class_id, pred_class_name, confidence)
        """

        if self.classifier is None:
            raise ValueError("Bitte zuerst den Klassifizierer erstellen: build_classifier()")

        # Vorverarbeitung
        if isinstance(image, np.ndarray):
            if image.ndim == 2:  # Graustufen
                image = np.stack([image]*3, axis=-1)
            image = torch.from_numpy(image).permute(2, 0, 1).float() / 255.0
            image = F.interpolate(image.unsqueeze(0), size=(32, 32), mode='bilinear')
            image = image.to(self.device)

        # Inferenz
        self.classifier.eval()
        with torch.no_grad():
            logits = self.classifier(image)
            probs = F.softmax(logits, dim=-1)
            confidence, pred_idx = probs.max(dim=-1)

        # Ergebnis zurückgeben
        class_names = [
            'Flugzeug', 'Auto', 'Vogel', 'Katze', 'Reh',
            'Hund', 'Frosch', 'Pferd', 'Schiff', 'LKW'
        ]
        pred_class = class_names[pred_idx.item()]

        return pred_idx.item(), pred_class, confidence.item()

    def detect_objects(self, image: np.ndarray) -> list:
        """Objekterkennung"""
        if self.detector is None:
            raise ValueError("Bitte bauen Sie zuerst den Detektor: build_detector()")

        # Simulierte Erkennung verwenden
        return self.detector.predict_dummy(image)

    def process_image(self, image: np.ndarray) -> dict:
        """
Ein ganzes Bild verarbeiten

Rückgabe:
Wörterbuch mit allen Ergebnissen
        """

        print("\n" + "=" * 60)
        print("Starte Bildverarbeitung")
        print("=" * 60)

        results = {}

        # 1. Bildklassifizierung
        if self.classifier is not None:
            print("\n[1/3] Bildklassifizierung wird ausgeführt...")
            class_id, class_name, conf = self.classify_image(image)
            results['classification'] = {
                'class_id': class_id,
                'class_name': class_name,
                'confidence': conf
            }
            print(f" → Klassifizierungsergebnis: {class_name} (Konfidenz: {conf:.2%})")

        # 2. Objekterkennung
        if self.detector is not None:
            print("\n[2/3] Objekterkennung wird ausgeführt...")
            detections = self.detect_objects(image)
            results['detections'] = detections
            print(f" → {len(detections)} Objekte erkannt:")
            for det in detections:
                print(f" - {det['class_name']} ({det['score']:.2f})")

        # 3. Visualisierung
        print("\n[3/3] Visualisierung wird erstellt...")
        if self.detector is not None:
            vis_image = self.detector.visualize(image, detections)
            results['visualization'] = vis_image
            print(" → Visualisierung abgeschlossen")

        print("\n" + "=" * 60)
        print("Bildverarbeitung abgeschlossen!")
        print("=" * 60)

        return results

    def save_results(self, results: dict, output_dir: str = "output"):
        """Ergebnisse in Datei speichern"""
        output_path = Path(output_dir)
        output_path.mkdir(exist_ok=True)

        # JSON-Ergebnisse speichern
        json_result = {
            'classification': results.get('classification'),
            'detections': [
                {k: v for k, v in det.items() if k != 'bbox'}
                for det in results.get('detections', [])
            ]
        }
        with open(output_path / "results.json", "w") as f:
            json.dump(json_result, f, indent=2, ensure_ascii=False)

        # Visualisierungsbild speichern
        if 'visualization' in results:
            vis = Image.fromarray(results['visualization'])
            vis.save(output_path / "visualization.jpg")

        print(f"\n"Ergebnisse gespeichert unter: {output_path}/")


# Vollständiges Trainingsbeispiel
class SimpleDataset(Dataset):
    """Einfacher simulierter Datensatz"""

    def __init__(self, num_samples: int = 100):
        self.num_samples = num_samples
        # Fake-Daten generieren
        self.images = torch.randn(num_samples, 3, 32, 32)
        self.labels = torch.randint(0, 10, (num_samples,))

    def __len__(self):
        return self.num_samples

    def __getitem__(self, idx):
        return self.images[idx], self.labels[idx]


def train_classifier(pipeline: VisionPipeline, num_epochs: int = 5):
    """Vollständiger Workflow zum Trainieren des Klassifikators"""
    print("\n" + "=" * 60)
    print("Starte Training des Klassifikators")
    print("=" * 60)

    # 1. Daten vorbereiten
    print("\nDatensatz wird vorbereitet...")
    train_dataset = SimpleDataset(1000)
    val_dataset = SimpleDataset(200)

    train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
    val_loader = DataLoader(val_dataset, batch_size=32)

    print(f"Trainingsset: {len(train_dataset)} Beispiele")
    print(f"Validierungsset: {len(val_dataset)} Beispiele")

    # 2. Optimierer und Verlustfunktion
    optimizer = torch.optim.Adam(pipeline.classifier.parameters(), lr=1e-3)
    criterion = nn.CrossEntropyLoss()

    # 3. Trainingsschleife
    print("\nStarte Training...")
    for epoch in range(num_epochs):
        pipeline.classifier.train()
        total_loss = 0.0
        correct = 0
        total = 0

        for images, labels in train_loader:
            images, labels = images.to(pipeline.device), labels.to(pipeline.device)

            optimizer.zero_grad()
            outputs = pipeline.classifier(images)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()

            total_loss += loss.item()
            _, predicted = outputs.max(1)
            total += labels.size(0)
            correct += predicted.eq(labels).sum().item()

        train_acc = correct / total
        avg_loss = total_loss / len(train_loader)

        # Validierung
        pipeline.classifier.eval()
        correct = 0
        total = 0
        with torch.no_grad():
            for images, labels in val_loader:
                images, labels = images.to(pipeline.device), labels.to(pipeline.device)
                outputs = pipeline.classifier(images)
                _, predicted = outputs.max(1)
                total += labels.size(0)
                correct += predicted.eq(labels).sum().item()

        val_acc = correct / total

        print(f"Epoche {epoch+1}/{num_epochs} - "
              f"Verlust: {avg_loss:.4f}, "
              f"Trainingsgenauigkeit: {train_acc:.2%}, "
              fValidierungsgenauigkeit: {val_acc:.2%})

    print("\nTraining abgeschlossen!)


# ============================================
Führe das vollständige praktische Projekt aus.
# ============================================

print("=" * 60)
print(BEISPIEL Visual AI Vollständiges praktisches Projekt)
print("=" * 60)

# 1. Pipeline erstellen
pipeline = VisionPipeline(device="cuda" if torch.cuda.is_available() else "cpu")

# 2. Modell erstellen
pipeline.build_classifier(num_classes=10)
pipeline.build_detector()

3. Training des Klassifikators
train_classifier(pipeline, num_epochs=3)

# 4. Bilder verarbeiten
test_image = create_test_image()
results = pipeline.process_image(test_image)

5. Ergebnis speichern
pipeline.save_results(results, output_dir="example_output")

print("\n" + "=" * 60)
print(Projektlauf abgeschlossen!)
print("=" * 60)
print("\nVorschläge für die nächsten Lernschritte:)
print(1. Versuchen Sie, mit einem echten Datensatz zu trainieren (z. B. CIFAR-10).)
print(2. Fine-Tuning mit einem vortrainierten Modell)
print(3. Probieren Sie komplexere Modellarchitekturen aus.)
print(4. In praktischen Anwendungen einsetzen.)
Weitere Erweiterungen