zum Blättern · Enter öffnet · Esc schließt

Kontakt aufnehmen.

Projekte

Sichtprüfung per Computer Vision: gut oder Ausschuss?

Demo9. August 2026

Bereich
Industrie
Umsetzung
3 Tage
Kompetenz
Computer Vision · Machine Learning
Stack
Python · PyTorch · scikit-learn · matplotlib · Git

Ergebnis

  • Ein Foto der Flasche wird zu gut, Ausschuss oder menschlicher Nachprüfung
  • Offensichtliche Fälle automatisiert; die unsichere Zone bleibt beim Menschen
  • Gemessen: hohe Trefferquote bei Alarmen, aber feine Defekte können rutschen
  • Offenes Repo mit Code, Metriken und nachvollziehbarem Ablauf

In vier Sätzen: Auf dem Band soll ein Foto einer Flasche entscheiden helfen: gut oder Ausschuss. Statt ein Netz von null zu bauen, nehme ich ein Modell, das schon auf Millionen Alltagsfotos gelernt hat zu sehen — Kanten, Formen, Texturen — und richte es kurz auf diese Prüffrage aus. Das Modell liefert keine harte Ja/Nein-Antwort, sondern eine Zahl: wie stark das Bild nach Ausschuss aussieht. Daraus werden drei Wege: klar gut, klar Ausschuss — oder Mensch prüft, wenn die Zahl nahe der Entscheidungsgrenze liegt. Auf dem gehaltenen Test wirkt die Headline stark (93,3 % richtig insgesamt, 100 % der Alarme waren echte Defekte), aber nur 70 % der echten Defekte werden bei harter Schwelle gefunden — genau deshalb bleibt die Mensch-Zone Teil des Ablaufs, keine Notlösung für den Anfang.

Dieser Fall ist bewusst kein Kundenprojekt, sondern eine offene Demonstration: öffentlicher Datensatz, nachvollziehbarer Code, gemessene Zahlen. Die Beispielfotos unten stammen aus MVTec AD (MVTec Software GmbH, CC BY-NC-SA 4.0) und dienen nur der nicht-kommerziellen Erklärung der Aufgabe. Code und Metriken: cv-defect-inspection.

Das Problem: Augenarbeit mit teuren Fehlern

Manuelle Sichtprüfung ist langsam und ermüdend. Zwei Fehlerarten kosten unterschiedlich:

  • Ein übersehener Defekt geht weiter in die Kette — oft der teurere Fehler.
  • Ein Fehlalarm erzeugt Nacharbeit und Diskussion — auch teuer, aber anders.

Deshalb reicht «Gesamt-Trefferquote» allein nicht. Man muss zwei Fragen getrennt lesen:

  • Wie oft finden wir echte Defekte? (Fachwort: Recall — Anteil der echten Defekte, die das System erwischt.)
  • Wie oft stimmt ein Alarm? (Fachwort: Precision — Anteil der Alarme, die wirklich Defekte waren.)

Und die dritte Betriebsfrage oft wichtiger als beide: Was passiert mit den unsicheren Fällen? Genau dort entscheidet sich, ob man Vollautomatik verspricht oder ehrlich mit Mensch in der Schleife arbeitet.

So funktioniert der Ablauf

Der Ablauf ist absichtlich simpel:

  1. Foto der Flasche von oben (hier: Flaschenhals / Rand).
  2. Das Modell gibt eine Zahl zwischen 0 und 1 aus — grob: «Wie stark sieht das nach Ausschuss aus?»
  3. Eine Schwelle trennt Auto-Entscheidungen. In dieser Demo liegt sie sehr hoch (0,991): nur sehr sichere Scores werden automatisch Ausschuss.
  4. Um die Schwelle liegt eine Mensch-Zone (Human-Band): Scores, die zu nah an der Grenze sind, gehen nicht ungeprüft weiter. Ein Mensch schaut nach.

Die Mensch-Zone ist hier kein Trainings-Hilfsmittel auf Zeit, sondern Teil des Betriebs: das Modell filtert das Offensichtliche; der Mensch bleibt Richter für den Graubereich. Später kann man seine Entscheidungen speichern und das Modell nachschärfen — die Zone wird vielleicht schmaler, verschwindet aber selten ganz, weil feine Defekte schwer bleiben.

Daten: bottle, bewusst binär

Für die Demo nutze ich die öffentliche Kategorie bottle. Es gibt intakte Flaschen und mehrere Defektarten (broken_large, broken_small, contamination, …). Alle Defektarten falte ich zu einer Klasse defect zusammen. Für den Betriebstag 1 ist die Frage «gut oder Ausschuss?» lesbarer als fünf seltene Untertypen.

So sieht die Kamera den Flaschenhals von oben — gut versus drei typische Defekte:

Intakter Flaschenrand von oben, kein Defekt

gut — intakter Rand

Großer Ausbruch am Flaschenrand

broken_large → defect

Kleiner Chip am Flaschenrand

broken_small → defect

Fremdkörper im Flaschenhals

contamination → defect

Beispielbilder aus MVTec AD, Kategorie bottle (MVTec Software GmbH, CC BY-NC-SA 4.0). Nur zur nicht-kommerziellen Demonstration; das Modell sieht zwei Klassen: gut / defect.

Die gelabelten Bilder gehen in Trainings-, Prüf- und Testteil (fester Zufallskeim, damit der Lauf wiederholbar bleibt):

Split n gut defect
train 204 160 44
val 43 34 9
test 45 35 10
Von Foto zu Entscheidung Produktfoto MVTec bottle 224×224 Eingabe ResNet18 ImageNet + Fine-Tune P(defect) Schwelle aus Val-PR-Kurve Ziel-Recall 0,95 Auto: gut Mensch prüft Auto: defect Human-Band um die Schwelle: unsichere Scores gehen nicht ungeprüft in die Linie. Code und Metriken: github.com/KyryloY/cv-defect-inspection Beispielfotos: MVTec AD bottle, CC BY-NC-SA 4.0 (Non-Commercial).
Der Betriebsablauf: Modell schätzt P(defect), die Schwelle trennt Auto und Mensch.

Ansatz: fertiges Sehen, kurze Nachschulung

Warum nicht von null? Ein Netz von null bräuchte sehr viele Bilder und viel Zeit. Hier starte ich mit ResNet18, vortrainiert auf ImageNet — einer großen Sammlung von Alltagsfotos. Dort hat das Modell gelernt zu sehen, aber noch nicht «Flaschenrand = gut/Ausschuss».

Nachschulung (Fine-Tuning): Ich friere die frühen Schichten ein (das allgemeine Sehen bleibt) und lerne nur den hinteren Teil plus den neuen Kopf mit zwei Klassen. So reicht eine kleine, gelabelte Menge — und Minuten statt Tage.

def build_model(pretrained: bool = True, unfreeze_from: str = "layer4"):
    # Start with everyday-photo vision (ImageNet), not from random weights.
    weights = models.ResNet18_Weights.IMAGENET1K_V1 if pretrained else None
    model = models.resnet18(weights=weights)

    # Replace the final head: two answers only — good vs defect.
    model.fc = nn.Linear(model.fc.in_features, 2)

    # Freeze everything, then unfreeze only the last block + head.
    for param in model.parameters():
        param.requires_grad = False
    for param in model.layer4.parameters():
        param.requires_grad = True
    for param in model.fc.parameters():
        param.requires_grad = True
    return model

Was sind «Epochen»? Eine Epoche ist ein voller Durchgang über die Trainingsfotos. Ein Durchgang reicht selten; das Modell verbessert sich über mehrere Runden. Hier stoppt Early Stopping: sobald die Prüfungsmessung nicht mehr besser wird, bricht das Training ab — damit es die Trainingsbilder nicht «auswendig lernt». In diesem Lauf waren das 16 Epochen, rund 4,7 Minuten auf CPU.

Danach kommt der betriebliche Teil: die Schwelle. Ziel auf der Validation: möglichst viele echte Defekte finden (Recall ≥ 0,95) — und unter diesen Kandidaten die höchste Schwelle wählen, damit Fehlalarme möglichst selten bleiben.

def choose_threshold_for_recall(y_true, y_prob, target_recall=0.95):
    """Pick the highest threshold that still meets target defect recall on val."""
    precision, recall, thresholds = precision_recall_curve(y_true, y_prob)
    candidates = []
    for thr, prec, rec in zip(thresholds, precision[:-1], recall[:-1]):
        if rec >= target_recall:
            candidates.append((float(thr), float(prec), float(rec)))
    # Highest threshold among those meeting recall -> fewer false alarms.
    thr, prec, rec = max(candidates, key=lambda x: x[0])
    return {"threshold": thr, "precision": prec, "recall": rec}

In Worten: erst die Qualitätsregel («finde genug Defekte»), dann so streng wie möglich schneiden, damit nicht jede Kleinigkeit Alarm auslöst.

Ergebnis: starke Headline, ehrlicher Haken

Auf dem Test (45 Bilder):

Metrik Wert In Klartext
Accuracy 93,3 % Gesamt oft richtig
Precision (defect) 100 % Jeder Auto-Alarm war ein echter Defekt
Recall (defect) 70 % 3 von 10 echten Defekten rutschen unter die harte Schwelle
Gewählte Schwelle (Val) 0,991 Sehr hohe Hürde für Auto-Ausschuss
Anteil Human-Band 15,6 % (7/45) Etwa jedes sechste Bild geht an den Menschen

Confusion matrix [[TN, FP], [FN, TP]]: [[35, 0], [3, 7]].

Confusion Matrix: 35 echte Gutteile korrekt, 0 Fehlalarme, 3 übersehene Defekte, 7 erkannte Defekte
Test-Confusion: keine Fehlalarme, aber drei übersehene Defekte bei harter Schwelle.
Precision-Recall-Kurve der Defektklasse mit markiertem Schwellenwert
PR-Kurve der Defektklasse. Die Schwelle kommt von der Validation, die Kurve hier zeigt den Test.

Auf der Validation wirkte die Schwelle perfekt. Auf dem Test fällt der Recall auf 0,7. Das ist kein peinlicher Bug, sondern der Punkt der Demo: eine schöne Prüfzahl ersetzt keinen Betriebsversuch.

Genau deshalb gibt die Vorhersage drei Antworten — nicht zwei:

# threshold ≈ 0.991, band = 0.15  →  Mensch-Zone ab ca. 0.841
if abs(prob - threshold) < band:
    decision = "human_review"   # too close to the cut — person checks
elif prob >= threshold:
    decision = "defect"         # clear enough for auto scrap
else:
    decision = "good"           # clear enough for auto pass

Was die drei False Negatives waren

Die drei übersehenen Defekte sind die feineren Fälle. Harte Schwelle P(defect) ≥ 0,991; die Mensch-Zone darunter beginnt bei etwa 0,841. Zwei Scores landen noch dort — einer wirkt sicher «gut»:

Sehr feines Faserstück am Flaschenrand, vom Modell sicher als gut bewertet

contamination · P=0,003 → Auto: gut

Feiner Defekt am Flaschenrand mit Score im Human-Band

contamination · P=0,938 → Mensch prüft

Kleiner Chip am inneren Flaschenrand mit Score im Human-Band

broken_small · P=0,980 → Mensch prüft

Die drei False Negatives des Testlaufs (Seed 42). Quelle: MVTec AD bottle (MVTec Software GmbH, CC BY-NC-SA 4.0), nicht-kommerzielle Demonstration.

Ohne Mensch-Zone wäre Fall 1 still durchgerutscht. Mit Zone bleiben zwei von drei Misses sichtbar für Nachprüfung — genau der Betriebsnutzen.

Fazit: was man mitnehmen kann

  1. Hohe Gesamtquote ≠ Freigabe für Vollautomatik. Hier waren die Alarme sauber, aber feine Defekte rutschten bei harter Schwelle durch.
  2. Die Mensch-Zone gehört zum Design. Sie entlastet den Prüfer (nur der Graubereich), ersetzt ihn aber nicht dauerhaft.
  3. Nachschulen hilft, löscht die Zone selten. Menschliche Entscheidungen aus der Zone kann man später wieder ins Training stecken — die Last sinkt oft, die harte Garantie «nie wieder Mensch» bleibt unrealistisch.
  4. Betriebsfrage zuerst: Welche Fehlerart ist teurer — übersehener Ausschuss oder Fehlalarm — und was passiert mit Unsicherheit?

Quellcode, Metriken und Checkpoint-Release: github.com/KyryloY/cv-defect-inspection.

← Alle Projekte