Object detection for electronic components on printed circuit boards — capacitors, resistors, ICs, diodes, LEDs, connectors and friends — trained on merged public PCB datasets and benchmarked on Apple Silicon.
Undergraduate project at RMUTT (Rajamangala University of Technology Thanyaburi).
Most of the inline documentation in the scripts is written in Thai. เอกสารในสคริปต์ส่วนใหญ่เขียนเป็นภาษาไทย
This is a research repo, not a library. Alongside the training pipelines it carries the tooling built to answer specific questions about the data and the hardware:
| Area | Scripts |
|---|---|
| Training | src/train_smd.py, src/train_pcb2.py, src/train_merged*.py, train_t7.py, train_filtered.py, train_mlx.py |
| RF-DETR comparison | src/train_merged_T6_rfdetr.py, notebooks/train_merged_T6_rfdetr_colab.ipynb |
| Dataset merging | src/merge_datasets.py, merge_datasets_v2.py, merge_datasets_v3.py |
| Data leakage | group_split.py |
| Duplicate detection | find_duplicates.py, dup_lab.py, dataset_checker_gui.py |
| Preprocessing research | apply_fpic_filter.py, ab_filter_experiment.py, filter_lab.py, make_variants.py |
| Backend benchmark | benchmark_backends.py → bench_out.txt, bench_results.json |
| Inference GUIs | src/detect_gui.py, src/detect_viewer.py, src/preview_detect.py |
| Label Studio | label_config.xml, make_ls_preview.py, ndjson_clone_gui.py, fix_polygon_labels.py |
| Live dashboard | training-dashboard/ (Next.js + Recharts) |
Two Roboflow exports form the base, later merged with four more sources into merged_T6:
| Config | Classes | Notes |
|---|---|---|
data_smd_absolute.yaml |
8 | CAPACITOR DIODE EC IC LED RESISTOR SCAPACITOR ZENER |
data_pcb2_absolute.yaml |
23 | Superset taxonomy — adds Connector, Ferrite Bead, Test Point, Transistor, jumpers … |
data_merged_absolute.yaml |
18 | The merged_V1 label space |
merged_T6 |
16 | Six sources: smd · i2 · pccomp · pcbdetv2 · compdetect · pcbcomp |
merged_T6 classes: IC LED battery buzzer capacitor clock connector diode display fuse inductor potentiometer relay resistor switch transistor
Base export: PCB Electronic components (CC BY 4.0) — 5,363 images, YOLO26 format, resized 640×640 with grayscale CRT-phosphor preprocessing and 3 augmented versions per source image.
Dataset directories and runs/ weights are not committed — the data_*.yaml files
hold absolute paths and need rewriting for your machine.
merged_T6 stitches together six sources that photograph the same physical boards.
Splitting train/valid/test per image scattered one board across all three splits:
images in valid whose board also appears in train .... 55.5%
images in test whose board also appears in train .... 65.9%
The model was being scored on boards it had already memorised. group_split.py regroups
the split by board identity so the reported numbers mean something.
PCBSegClassNet (Table VII) reports large IoU gains from an HSI + CLAHE colour transform
— but for their segmentation network, at 512×512, on their data. Different
architecture, different task, different dataset.
apply_fpic_filter.py reproduces their filter chain exactly (BGR→HLS, CLAHE
clip_limit=4.0, tile 8×8), make_variants.py builds four matched dataset variants
(rgb / clahe-only / hsi / fpic), and ab_filter_experiment.py runs the A/B on
YOLO directly rather than assuming the gain transfers. filter_lab.py is a live GUI for
sweeping the parameters the paper never specifies.
yolo-mlx advertises 2.07× inference and 2.65× training over PyTorch MPS — but
measured on an M4 Pro against COCO, which is neither this machine nor this dataset.
benchmark_backends.py re-measures it here (imgsz=640, nc=16, compile/fuse on):
Inference — batch=1, median ms (FPS)
| scale | MLX | MPS | CPU | MLX/MPS |
|---|---|---|---|---|
| n | 3.3 (300) | 4.2 (240) | 38.5 (26) | 1.25× |
| m | 15.5 (64) | 15.3 (65) | 169.6 (6) | 0.98× |
| l | 20.0 (50) | 19.1 (52) | 213.7 (5) | 0.96× |
| x | 40.4 (25) | 35.6 (28) | 351.6 (3) | 0.88× |
Training step — batch=2, median ms (img/s), network fwd+bwd+optimizer only
| scale | MLX | MPS | CPU | MLX/MPS |
|---|---|---|---|---|
| n | 48 (41.3) | 45 (44.2) | 292 (6.8) | 0.93× |
| m | 207 (9.7) | 163 (12.2) | 1421 (1.4) | 0.79× |
| l | 257 (7.8) | 193 (10.4) | 1698 (1.2) | 0.75× |
| x | 441 (4.5) | 379 (5.3) | 2857 (0.7) | 0.86× |
MLX only wins at n. From m upward MPS is ahead, and the gap widens with scale.
The benchmark also pins down the classic measurement trap — not waiting for the GPU:
| scale | backend | real (synced) | fake (no sync) |
|---|---|---|---|
| m | MLX | 15.5 ms | 0.1 ms — 105× too fast |
| x | MLX | 40.4 ms | 0.3 ms — 132× too fast |
| x | MPS | 35.6 ms | 25.9 ms |
Lazy MLX evaluation means an unsynced timer measures queueing, not compute.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtrequirements.txt: ultralytics · PyQt5 · opencv-python · numpy · reportlab · PyYAML
Extras installed per experiment: rfdetr[train] for the RF-DETR pipeline, yolo-mlx for
the MLX backend (setup_mlx.sh).
Training targets device='mps' (Apple Silicon) throughout.
source .venv/bin/activate
# 1. SMD model — trains from scratch
python src/train_smd.py
# 2. PCB2 model — fine-tunes from the SMD output, so run step 1 first
python src/train_pcb2.py
# 3. Merged model
python src/merge_datasets.py
python src/train_merged.py
# resume an interrupted run
python src/resume_train_smd.py # or resume_train_pcb2.py / resume_train_merged.pyEach resume_train_*.py loads that pipeline's last.pt and calls model.train(resume=True)
— for continuing an interrupted run, not extending a finished one.
python src/detect_gui.pyPyQt5 viewer with debounced confidence/IoU sliders, per-class toggles that re-filter without re-running inference, a confidence-sorted detection list, scroll-zoom and drag-pan, live camera inference, and saving annotated frames.
cd training-dashboard
npm install
npm run dev # http://localhost:3000Next.js 14 + Recharts monitor that compares the v1 / T5 / T6 runs side by side and refreshes every 10 seconds.
- PCB_DETECT_RMUTT_GUI — the standalone desktop inspection app
No license file yet. The Roboflow base dataset is CC BY 4.0.