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:
- Foto der Flasche von oben (hier: Flaschenhals / Rand).
- Das Modell gibt eine Zahl zwischen 0 und 1 aus — grob: «Wie stark sieht das nach Ausschuss aus?»
- Eine Schwelle trennt Auto-Entscheidungen. In dieser Demo liegt sie sehr hoch (0,991): nur sehr sichere Scores werden automatisch Ausschuss.
- 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:
gut — intakter Rand
broken_large → defect
broken_small → defect
contamination → defect
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 |
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]].
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»:
contamination · P=0,003 → Auto: gut
contamination · P=0,938 → Mensch prüft
broken_small · P=0,980 → Mensch prüft
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
- Hohe Gesamtquote ≠ Freigabe für Vollautomatik. Hier waren die Alarme sauber, aber feine Defekte rutschten bei harter Schwelle durch.
- Die Mensch-Zone gehört zum Design. Sie entlastet den Prüfer (nur der Graubereich), ersetzt ihn aber nicht dauerhaft.
- 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.
- 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.