Uporedni sistem za detekciju objekata
Ista slika kroz dva ekosistema mašinskog učenja, sa odgovorom u jednom formatu.
- Godina
- Kontekst
- Domen
Kako prolazi jedan zahtev
- Ulaz: React frontend - React
- Slika, multipart/form-data
- Orkestracija: NestJS gateway - NestJS
- Paralelno
- FastAPI servis - Ultralytics YOLOv8m - Python, FastAPI
- ASP.NET Core servis - ML.NET model - C#, ASP.NET Core
- Jedan JSON format: okviri, klase, pouzdanost
Demo
Pregled
Rad istražuje šta se menja kada se isti zadatak detekcije napravi dva puta: jednom u Python-u, koji je standard u istraživanju, i jednom u .NET-u, na kom već radi backend. Sistem obučava YOLOv8m i ML.NET model na istim anotiranim slikama, stavlja oba iza jednog API-ja i pušta sliku kroz jedan ili kroz oba.
Domen je lična zaštitna oprema na gradilištu, sa šest klasa: šlem, prsluk i rukavice, svaka kao prisutna ili odsutna. Ključne su negativne klase: sistem je koristan samo ako može da kaže da neko ne nosi šlem, a ne samo da se šlem nalazi negde u kadru.
Arhitektura
Frontend šalje sliku
React šalje fajl kao multipart/form-data, a izabrani model ide u posebnom polju. Kada se traže oba, dva zahteva idu paralelno, pa se modeli porede na istoj slici u istom trenutku.
Gateway prosleđuje zahtev
NestJS je jedina adresa koju frontend poznaje. Prima upload i prosleđuje ga FastAPI ili ASP.NET Core servisu, pa se ML servisi mogu menjati nezavisno od klijenta.
Servis radi inferenciju
Python učitava sliku u RGB i predaje je YOLOv8m modelu. Težine modela ostaju učitane u memoriji, pa se .pt fajl ne čita pri svakom zahtevu. .NET strana pretvara sliku u MLImage i bira ML.NET model po id-u.
Oba servisa vraćaju isti format
Klasa, score i okvir kao x1, y1, x2, y2, uz id modela, skup anotacija na kom je obučen i originalne dimenzije slike. .NET strana prvo ograničava koordinate na te dimenzije, pa okvir nikad ne izlazi van slike.
Frontend iscrtava rezultat
Pošto je odgovor isti u oba slučaja, interfejs nema posebnu granu ni za YOLO ni za ML.NET. Koordinate se skaliraju sa originalne na prikazanu veličinu, a okviri, klase i pouzdanosti se iscrtavaju preko slike.
Šta radi
Jedan model ili oba
Jedan servis ili oba istovremeno, u dva panela nad istom slikom, uz izbor bilo kog od četiri obučena modela.
Podesiv prag pouzdanosti
Slajder skriva sve ispod praga, kao što i pravi sistem reaguje samo na predikcije iznad granice, a ne na sve što model vrati.
Lista svih detekcija
Klasa i score za svaku detekciju. Kada se jedna izabere, izdvaja se na slici, što pomaže kada se u kadru preklapa osam okvira.
Okviri, ispuna, zoom i pan
Okviri kao linije ili kao ispunjene površine, jedna detekcija ili sve, uz zoom i pan da se okvir uporedi sa pikselima ispod njega.
Dva odgovora uporedo
Trajanje zahteva, broj detekcija, prosečna pouzdanost, najjača detekcija i koji servis je prvi odgovorio.
Provera stanja servisa
Gateway, Python API i .NET API javljaju status i vreme odgovora. To su tri odvojena procesa, pa kada jedan ne radi, interfejs pokazuje koji.
Skup podataka i obučavanje
Skup podataka je sastavljen u Roboflow-u od dva javna skupa, koje sam pregledao i spojio u jedan: 2.911 slika, od čega 2.374 za trening, 290 za validaciju i 247 za test, automatski orijentisanih i svedenih na 640x640. Python strana ga koristi u YOLO formatu, a .NET strana u COCO formatu, jer Model Builder za detekciju očekuje COCO. Tako oba alata dobijaju svoj format, nad istim slikama i istim klasama.
Onda je došao deo koji nisam planirao. Prva verzija je imala 8.813 anotacija, a posle ponovnog pregleda taj broj je narastao na 17.942, što znači da više od polovine objekata nije bilo označeno. Objekat koji postoji na slici, a nema ga u oznakama, tokom obučavanja uči model da je to pozadina, a tokom evaluacije mu se računa kao greška kada ga model ipak detektuje.
Oba modela su obučavana 50 epoha na 640x640, polazeći od unapred obučenih težina, sa istim parametrima za obe verzije skupa, da bi razlika u rezultatima poticala od kvaliteta anotacija, a ne od konfiguracije. Model Builder se zaustavlja na obučenom modelu, pa je za .NET stranu trebao poseban servis za evaluaciju: učitava COCO anotacije, radi predikciju nad validacionim skupom i računa precision, recall, F1 i mAP@0.5, uz matrice konfuzije i precision-recall krive. Tako ML.NET dobija iste metrike koje Ultralytics prikazuje sam.
Rezultati
| Model | Anotacije | Precision | Recall | F1 | mAP@0.5 | mAP@0.5:0.95 |
|---|---|---|---|---|---|---|
| YOLOv8m | 8.813 | 0,738 | 0,800 | - | 0,790 | 0,455 |
| YOLOv8m | 17.942 | 0,894 | 0,867 | - | 0,916 | 0,552 |
| ML.NET | 8.813 | 0,669 | 0,645 | 0,657 | 0,580 | - |
| ML.NET | 17.942 | 0,862 | 0,709 | 0,778 | 0,748 | - |
Ponovno anotiranje popravilo je svaku metriku kod oba modela. YOLOv8m je porastao sa 0,790 na 0,916 mAP@0.5, a ML.NET sa 0,580 na 0,748, pa je slabiji model relativno dobio više, 29 procenata prema 16. Loše anotacije su njega najviše i kočile.
YOLOv8m je ispred po brojevima, a najvažnija razlika je u recall-u: 0,867 prema 0,709 na ispravljenom skupu, uz skoro isti precision. Kod zaštitne opreme to je presudno, jer propuštena detekcija znači da sistem čoveka bez opreme prijavi kao ispravno opremljenog, a precision to ne može da pokaže.
Poređenje je fer jer su uslovi bili isti na obe strane: isti domen slika, istih šest klasa i uporedivi parametri, bez posebnog podešavanja za svaki model.
Šta sam naučio
Na rezultat su više uticali podaci nego izbor frameworka. Ponovno anotiranje istih 2.911 slika popravilo je oba modela više nego što iznosi razlika između dva ekosistema, i to nije zaključak koji sam očekivao.
Sa inženjerske strane, zaključci su praktičniji. Python je dao prostor za eksperimentisanje i sam generisao materijal za evaluaciju, .NET je dao model koji se direktno uklapa u ASP.NET Core servis, a zajednički format odgovora omogućava da jedan frontend radi sa oba modela bez ikakve razlike.