NEU-CLS steel defect classifier (ResNet-18, ONNX)

ImageNet-pretrained ResNet-18 fine-tuned on the six classes of the NEU-CLS steel surface defect database: crazing, inclusion, patches, pitted surface, rolled-in scale, scratches. Exported to a single ONNX file for CPU or in-browser serving.

Try it in the browser: huggingface.co/spaces/senanurcetin/neu-steel-defect-demo · project and the operator review-queue design it plugs into: github.com/senanurcetin/visual-qc-project.

Results, and what they do not mean

model split accuracy macro F1
Random Forest on HOG + Gabor + grid features (the project's model) random 80/20, seed 42 0.939 0.939
this ResNet-18 random 80/20, seed 42 1.000 (360/360) 1.000
this ResNet-18 blocked: images 241–300 of every class held out 1.000 (360/360) 1.000

The blocked split exists because NEU images are crops of the same steel strips: neighbouring indices can be near-duplicates, which a random split puts on both sides. The score did not move.

A perfect score means the benchmark is saturated, not that the model is ready for a line. NEU-CLS images are 200×200 grayscale crops taken under one setup. It no longer separates good models from better ones, and it says nothing about different lighting, cameras, steel grades or defect sizes. Validate on your own line's images before relying on it, and keep the project's confidence-based review queue (route low-confidence parts to an operator).

Where the 100% ends. In the Kaggle notebook NEU Steel Defects: 100% Accuracy and Where It Ends, a ResNet-18 trained the same way is tested on the same 360 images, each degraded at test time only:

test-time condition accuracy (two GPU runs)
clean 1.000
darker lighting (x0.5) 1.000
low contrast (x0.5) 0.947–0.967
brighter lighting (x1.6) 0.822–0.828
sensor noise, sd 0.05 0.692–0.808
JPEG quality 15 0.678–0.767
half resolution 0.642–0.661
slight blur (radius 1.5) 0.556–0.631
quarter resolution 0.514–0.594
sensor noise, sd 0.12 0.344–0.386
strong blur (radius 3) 0.356–0.375

GPU training is not bit-for-bit reproducible, so the figures move by a few points between runs; the large losses repeat.

Focus, resolution and sensor noise are the conditions to control on the line (or to add to training as augmentation); the clean-image score does not show them.

Use

Input: 1×3×224×224 float32. Convert the grayscale image to 3 channels, resize to 224×224, scale to [0, 1] and normalise with ImageNet mean/std (labels.json holds the exact values and class order). Output: logits over the six classes.

import json, numpy as np, onnxruntime as ort
from PIL import Image
meta = json.load(open("labels.json"))
x = np.asarray(Image.open("part.jpg").convert("L").convert("RGB").resize((224, 224)), np.float32) / 255
x = ((x - meta["mean"]) / meta["std"]).transpose(2, 0, 1)[None].astype(np.float32)
logits = ort.InferenceSession("neu_resnet18.onnx").run(None, {"image": x})[0][0]
print(meta["classes"][int(logits.argmax())])

train_cnn.py reproduces both splits (12 epochs, AdamW, flips and small rotations; ~5 min on one GPU).

Data

NEU surface defect database (K. Song and Y. Yan, Northeastern University), 1,800 images, 300 per class. The images are not redistributed here.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Space using senanurcetin/neu-steel-defect-resnet18 1