Spaces:
Running on Zero
Running on Zero
Download app.py from mguven61/sameproductdetect: direct link, hf CLI and curl.
- Browser
- Download file 32.8 kB
-
https://huggingface.co/spaces/mguven61/sameproductdetect/resolve/main/app.py
- Command line
-
hf download hf://spaces/mguven61/sameproductdetect/app.py
-
curl -L -o app.py https://huggingface.co/spaces/mguven61/sameproductdetect/resolve/main/app.py
32.8 kB
| """ | |
| Nesne ekle/cikar sistemi - Hugging Face Space surumu (Gradio). | |
| benzerlikurunv3.py'nin ayni mantigi (hazir MobileNetV3-Small ile nesne tanima), ancak kamera kullanicinin | |
| TARAYICISINDA calisir: goruntu tarayicidan akis olarak gelir, sunucuda islenir. Her ziyaretcinin kendi | |
| oturumu vardir (kendi katalogu, sepeti ve arka plani); kimse baskasinin verisini goremez. Hicbir goruntu | |
| diske yazilmaz. | |
| Yerelde deneme: | |
| pip install -r requirements.txt | |
| python app.py | |
| """ | |
| import threading | |
| import time | |
| try: # HF Space'te CUDA'dan once yuklenmezse 'spaces' paketi hata verir | |
| import spaces | |
| except ImportError: | |
| spaces = None | |
| import cv2 | |
| import gradio as gr | |
| import numpy as np | |
| import torch | |
| import torch.nn.functional as F | |
| import torchvision.models as models | |
| # ---------------------------------------------------------------------------- | |
| # AYARLAR | |
| # ---------------------------------------------------------------------------- | |
| GENISLIK, YUKSEKLIK = 640, 480 # gelen goruntu 4:3 kirpilip bu boyuta getirilir | |
| ROI_ORANI = 0.6 # ROI kenari = kisa kenarin bu orani | |
| SIM_ESIK = 0.80 # kosinus benzerligi bundan buyukse (ve renk uyuyorsa) AYNI nesne | |
| GENISLET_SIM = 0.93 # tanininca bundan dusuk benzerlikse yeni bakis acisi olarak saklanir | |
| KILIT_FARK = 0.12 # kilitli nesneyle benzerlik (esik - KILIT_FARK) altina inerse nesne degisti | |
| MAX_GORUNUM = 12 # nesne basina en fazla bakis acisi | |
| RENK_MESAFE = 0.55 # renk dagilimi mesafesi bundan buyukse FARKLI RENK | |
| PARLAK_ESIK = 0.18 # renksiz nesnelerde parlaklik farki bundan buyukse FARKLI nesne | |
| NOTR_KROMA = 8 # Lab a,b'nin nötrden uzakligi bundan azsa piksel 'renksiz' | |
| NOTR_ORAN = 0.35 # renkli piksel orani bundan azsa nesne 'renksiz' kabul edilir | |
| SAHNE_FARK = 30 # sahneyle piksel farki bundan buyukse 'nesne bolgesi' | |
| SAHNE_MIN_ORAN = 0.02 # nesne bolgesi ROI'nin bundan azsa nesne yok say | |
| GRI = 127 # nesnenin disindaki zemin rengi (model girisi) | |
| # Tarayici akisi saniyede birkac kare oldugundan (yerel kameradan cok daha az) sayaclar kucuk tutulur. | |
| EL_MIN_ORAN = 0.01 | |
| EL_MAX_ORAN = 0.50 | |
| HAREKET_ESIK = 6.0 | |
| BASLAT_FRAME = 3 | |
| ORNEK_FRAME = 5 | |
| BOSALT_FRAME = 5 | |
| SAHNE_ARALIK = 2 | |
| KILIT_SURE = 1.5 | |
| DEGISIM_KONTROL = 3 | |
| MESAJ_SURESI = 2.5 | |
| FOTO_BOYUT = 128 | |
| PANEL_GENISLIK = 320 | |
| BOS, ALGILANIYOR, KILITLI = 0, 1, 2 | |
| MOD_EKLE, MOD_CIKAR = 0, 1 | |
| # ---------------------------------------------------------------------------- | |
| # Hazir model (MobileNetV3-Small, siniflandirma katmani cikarilmis -> 576 boyutlu embedding) | |
| # ---------------------------------------------------------------------------- | |
| CIHAZ = torch.device("cpu") # model kucuk; CPU yeterli, CUDA baslatilmaz (ZeroGPU/spaces ile cakismaz) | |
| torch.set_num_threads(max(1, min(4, torch.get_num_threads()))) | |
| def model_yukle(): | |
| agirliklar = models.MobileNet_V3_Small_Weights.DEFAULT | |
| m = models.mobilenet_v3_small(weights=agirliklar) | |
| m.classifier = torch.nn.Identity() | |
| return m.to(CIHAZ).eval() | |
| MODEL = model_yukle() | |
| if spaces is not None: | |
| def _zerogpu_gerekli(): | |
| """ZeroGPU donanimi en az bir @spaces.GPU fonksiyonu ister; uygulama CPU kullandigindan cagrilmaz.""" | |
| MODEL_KILIDI = threading.Lock() # ayni anda tek kullanici modeli calistirir (CPU'yu bogmamak icin) | |
| ORT = torch.tensor([0.485, 0.456, 0.406], device=CIHAZ).view(3, 1, 1) | |
| STD = torch.tensor([0.229, 0.224, 0.225], device=CIHAZ).view(3, 1, 1) | |
| def embedding_al(gorseller): | |
| """Kare BGR goruntu listesinden L2-normalize (n, 576) ozellik vektorleri.""" | |
| kutu = [] | |
| for g in gorseller: | |
| rgb = cv2.cvtColor(cv2.resize(g, (224, 224), interpolation=cv2.INTER_AREA), cv2.COLOR_BGR2RGB) | |
| kutu.append(torch.from_numpy(rgb).to(CIHAZ).permute(2, 0, 1).float().div(255.0)) | |
| t = (torch.stack(kutu) - ORT) / STD | |
| with MODEL_KILIDI: | |
| return F.normalize(MODEL(t), p=2, dim=1).cpu().numpy() | |
| def benzerlik(q, g): | |
| """Sorgu (0 derece) ile katalog gorunumunun 4 donusu arasindaki en yuksek kosinus benzerligi.""" | |
| return float((g["embr"] @ q["emb"]).max()) | |
| # ---------------------------------------------------------------------------- | |
| # El / hareket | |
| # ---------------------------------------------------------------------------- | |
| def roi_gri(roi): | |
| return cv2.GaussianBlur(cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY), (21, 21), 0) | |
| def deri_maskesi(roi): | |
| """YCrCb uzayinda ten rengi maskesi (bool dizi).""" | |
| ycrcb = cv2.cvtColor(roi, cv2.COLOR_BGR2YCrCb) | |
| m = cv2.inRange(ycrcb, (0, 133, 77), (255, 173, 127)) | |
| k = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7)) | |
| m = cv2.morphologyEx(m, cv2.MORPH_OPEN, k) | |
| m = cv2.morphologyEx(m, cv2.MORPH_CLOSE, k) | |
| return m > 0 | |
| # ---------------------------------------------------------------------------- | |
| # Nesneyi ayirma: sahneden farkli olan, ten rengi olmayan en buyuk bolge | |
| # ---------------------------------------------------------------------------- | |
| def nesne_cikar(roi, deri, sahne): | |
| """Doner: dict(gorsel, maske, alan, kontur, kutu) ya da nesne yoksa None.""" | |
| if sahne is None: | |
| return None | |
| h, w = roi.shape[:2] | |
| fark = (cv2.GaussianBlur(roi, (7, 7), 0).astype(np.int16) | |
| - cv2.GaussianBlur(sahne, (7, 7), 0).astype(np.int16)) | |
| kayma = np.clip(np.median(fark.reshape(-1, 3), axis=0), -25, 25).astype(np.int16) # genel pozlama degisimi | |
| fark = np.abs(fark - kayma).max(axis=2) | |
| degisen = (fark > SAHNE_FARK).astype(np.uint8) * 255 | |
| yasak = cv2.dilate(deri.astype(np.uint8) * 255, np.ones((15, 15), np.uint8)) | |
| degisen[yasak > 0] = 0 | |
| degisen = cv2.morphologyEx(degisen, cv2.MORPH_OPEN, np.ones((5, 5), np.uint8)) | |
| degisen = cv2.morphologyEx(degisen, cv2.MORPH_CLOSE, np.ones((15, 15), np.uint8)) | |
| konturlar, _ = cv2.findContours(degisen, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
| if not konturlar: | |
| return None | |
| kontur = max(konturlar, key=cv2.contourArea) | |
| alan = cv2.contourArea(kontur) / float(h * w) | |
| if alan < SAHNE_MIN_ORAN: | |
| return None | |
| maske = np.zeros((h, w), np.uint8) | |
| cv2.drawContours(maske, [kontur], -1, 255, -1) | |
| maske[yasak > 0] = 0 | |
| x, y, bw, bh = cv2.boundingRect(kontur) | |
| pad = int(0.08 * max(bw, bh)) | |
| kenar = max(bw, bh) + 2 * pad | |
| gorsel = np.full((kenar, kenar, 3), GRI, np.uint8) | |
| kmaske = np.zeros((kenar, kenar), np.uint8) | |
| parca = roi[y:y + bh, x:x + bw].copy() | |
| pm = maske[y:y + bh, x:x + bw] | |
| parca[pm == 0] = GRI | |
| ox, oy = (kenar - bw) // 2, (kenar - bh) // 2 | |
| gorsel[oy:oy + bh, ox:ox + bw] = parca | |
| kmaske[oy:oy + bh, ox:ox + bw] = pm | |
| return {"gorsel": gorsel, "maske": kmaske, "alan": alan, "kontur": kontur, "kutu": (x, y, bw, bh)} | |
| # ---------------------------------------------------------------------------- | |
| # Renk imzasi ve renk karsilastirma | |
| # ---------------------------------------------------------------------------- | |
| def renk_imzasi(gorsel, maske): | |
| """Nesne pikselleri icin: renkli piksel orani, (a,b) histogrami, ortalama parlaklik. Yetersizse None.""" | |
| m = maske > 0 | |
| n = int(m.sum()) | |
| if n < 30: | |
| return None | |
| lab = cv2.cvtColor(gorsel, cv2.COLOR_BGR2LAB) | |
| a = np.ascontiguousarray(lab[..., 1]) | |
| b = np.ascontiguousarray(lab[..., 2]) | |
| kroma = np.hypot(a.astype(np.float32) - 128.0, b.astype(np.float32) - 128.0) | |
| renkli = m & (kroma >= NOTR_KROMA) | |
| sayi = int(renkli.sum()) | |
| hist = None | |
| if sayi >= 20: | |
| hist = cv2.calcHist([a, b], [0, 1], renkli.astype(np.uint8) * 255, [16, 16], [0, 256, 0, 256]) | |
| hist = cv2.GaussianBlur(hist, (3, 3), 0) | |
| hist = (hist / (hist.sum() + 1e-9)).astype(np.float32) | |
| return {"oran": sayi / n, "hist": hist, "parlak": float(lab[..., 0][m].mean()) / 255.0} | |
| def renk_farki(r1, r2): | |
| """(renk mesafesi, parlaklik farki). Parlaklik sadece iki nesne de renksizse dikkate alinir.""" | |
| if r1 is None or r2 is None: | |
| return 0.0, 0.0 | |
| mesafe = abs(r1["oran"] - r2["oran"]) | |
| if r1["hist"] is not None and r2["hist"] is not None: | |
| mesafe = max(mesafe, float(cv2.compareHist(r1["hist"], r2["hist"], cv2.HISTCMP_BHATTACHARYYA))) | |
| parlak = 0.0 | |
| if max(r1["oran"], r2["oran"]) < NOTR_ORAN: | |
| parlak = abs(r1["parlak"] - r2["parlak"]) | |
| return mesafe, parlak | |
| def renk_uyuyor(mesafe, parlak, renk_esik): | |
| return mesafe < renk_esik and parlak < PARLAK_ESIK | |
| # ---------------------------------------------------------------------------- | |
| # Ozellik cikarma ve eslestirme | |
| # ---------------------------------------------------------------------------- | |
| def ozellik_cikar(roi, deri, sahne): | |
| """ | |
| ROI'den nesne + embedding + renk imzasi (dict) ya da nesne yoksa None. | |
| emb: nesnenin oldugu gibi embedding'i; embr: 0/90/180/270 derece donmus halleri (emb = embr[0]). | |
| """ | |
| n = nesne_cikar(roi, deri, sahne) | |
| if n is None: | |
| return None | |
| n["embr"] = embedding_al([np.rot90(n["gorsel"], k) for k in range(4)]) | |
| n["emb"] = n["embr"][0] | |
| n["renk"] = renk_imzasi(n["gorsel"], n["maske"]) | |
| return n | |
| def en_iyi_nesne(sorgular, katalog, esik, renk_esik): | |
| """ | |
| Her katalog nesnesi icin en iyi bakis acisi bulunur; skor = sorgu karelerinin ortalama kosinus benzerligi. | |
| Doner: (ad, skor, en iyi sorgu karesi, ham_ad, ham_skor, ham_renk_mesafesi) | |
| ad : benzerlik VE renk esigini gecen en benzer nesne (yoksa None) | |
| ham_ad : renge bakilmadan en benzer nesne (skor esigi gectiyse ama renk uymadiysa = ayni urunun farkli rengi) | |
| """ | |
| adaylar = [] | |
| for ad, gorunumler in katalog.items(): | |
| en = None | |
| for g in gorunumler: | |
| sims = np.array([benzerlik(q, g) for q in sorgular]) | |
| skor = float(sims.mean()) | |
| if en is None or skor > en["skor"]: | |
| farklar = [renk_farki(q["renk"], g["renk"]) for q in sorgular] | |
| en = {"ad": ad, "skor": skor, "kare": sorgular[int(sims.argmax())], | |
| "mes": float(np.mean([f[0] for f in farklar])), | |
| "parlak": float(np.mean([f[1] for f in farklar]))} | |
| adaylar.append(en) | |
| if not adaylar: | |
| return None, 0.0, None, None, 0.0, 0.0 | |
| ham = max(adaylar, key=lambda a: a["skor"]) | |
| uyan = [a for a in adaylar if a["skor"] >= esik and renk_uyuyor(a["mes"], a["parlak"], renk_esik)] | |
| if uyan: | |
| en = max(uyan, key=lambda a: a["skor"]) | |
| return en["ad"], en["skor"], en["kare"], ham["ad"], ham["skor"], ham["mes"] | |
| return None, ham["skor"], None, ham["ad"], ham["skor"], ham["mes"] | |
| def karar_ver(mod, iyi, st, esik, renk_esik): | |
| """Gosterilen nesneye moda gore karar verir; oturumun katalog, sepet ve adetlerini gunceller.""" | |
| katalog, sepet, adetler = st["katalog"], st["sepet"], st["adetler"] | |
| ad, skor, kare, ham_ad, ham_skor, ham_mes = en_iyi_nesne(iyi[:4], katalog, esik, renk_esik) | |
| taninan = ad is not None | |
| varyant = (not taninan) and ham_ad is not None and ham_skor >= esik # yapi ayni, renk farkli | |
| sonuc = {"skor": skor, "kayit": None, "kilit_ref": None, "renk": ham_mes if ham_ad is not None else None} | |
| if taninan: | |
| sonuc["kilit_ref"] = katalog[ad] | |
| if skor < GENISLET_SIM and len(katalog[ad]) < MAX_GORUNUM: | |
| katalog[ad].append(kare) # yeni bakis acisi | |
| if mod == MOD_EKLE: | |
| if ad in sepet: | |
| adetler[ad] = adetler.get(ad, 1) + 1 | |
| mesaj = f"ADET ARTTI: {ad} (adet {adetler[ad]}, benzerlik {skor:.2f})" | |
| else: | |
| sepet.append(ad) | |
| adetler[ad] = 1 | |
| mesaj = f"EKLENDI: {ad} (adet 1, benzerlik {skor:.2f})" | |
| else: | |
| if ad in sepet: | |
| if adetler.get(ad, 1) > 1: | |
| adetler[ad] -= 1 | |
| mesaj = f"ADET AZALDI: {ad} (kalan {adetler[ad]}, benzerlik {skor:.2f})" | |
| else: | |
| sepet.remove(ad) | |
| adetler.pop(ad, None) | |
| mesaj = f"CIKARILDI: {ad} (benzerlik {skor:.2f})" | |
| else: | |
| mesaj = f"LISTEDE YOK: {ad}" | |
| else: | |
| if mod == MOD_EKLE: | |
| st["sayac"] += 1 | |
| ad = f"Nesne-{st['sayac']}" | |
| katalog[ad] = list(iyi[:2]) | |
| sepet.append(ad) | |
| adetler[ad] = 1 | |
| sonuc.update(kayit=ad, kilit_ref=katalog[ad]) | |
| mesaj = f"YENI NESNE EKLENDI: {ad}" | |
| if varyant: | |
| mesaj += f" ({ham_ad} ile ayni urun, FARKLI RENK)" | |
| else: | |
| sonuc["kilit_ref"] = [iyi[0]] | |
| mesaj = "BILINMEYEN NESNE - listede yok" | |
| if varyant: | |
| mesaj += f" ({ham_ad} ile ayni urun, farkli renk)" | |
| sonuc["mesaj"] = mesaj | |
| return sonuc | |
| # ---------------------------------------------------------------------------- | |
| # Fotograflar ve cizim | |
| # ---------------------------------------------------------------------------- | |
| def foto_hazirla(gorsel): | |
| return cv2.resize(gorsel, (FOTO_BOYUT, FOTO_BOYUT), interpolation=cv2.INTER_AREA).copy() | |
| def kayit_adi(katalog, ref): | |
| """kilit_ref'in (gorunum listesi) hangi katalog nesnesine ait oldugunu bulur.""" | |
| if ref is None: | |
| return None | |
| for ad, gorunumler in katalog.items(): | |
| if gorunumler is ref: | |
| return ad | |
| return None | |
| def son_islem_olustur(mesaj, foto, eslesen_ad, yeni_kayit, fotolar): | |
| ana = mesaj.split(" (")[0] | |
| if ana.startswith(("EKLENDI", "YENI NESNE", "CIKARILDI", "ADET")): | |
| renk = (80, 200, 80) | |
| elif ana.startswith("BILINMEYEN"): | |
| renk = (60, 60, 230) | |
| else: | |
| renk = (0, 170, 255) | |
| if yeni_kayit: | |
| eslesen_foto, etiket = foto, "yeni kayit" | |
| elif eslesen_ad: | |
| eslesen_foto, etiket = fotolar.get(eslesen_ad), eslesen_ad | |
| else: | |
| eslesen_foto, etiket = None, "eslesen yok" | |
| return {"foto": foto, "eslesen_foto": eslesen_foto, "eslesen_etiket": etiket, | |
| "metin": mesaj.replace(" (", " ("), "renk": renk} | |
| def satirlara_bol(metin, en=19): | |
| satirlar, simdiki = [], "" | |
| for kelime in metin.split(): | |
| if simdiki and len(simdiki) + 1 + len(kelime) > en: | |
| satirlar.append(simdiki) | |
| simdiki = kelime | |
| else: | |
| simdiki = f"{simdiki} {kelime}" if simdiki else kelime | |
| if simdiki: | |
| satirlar.append(simdiki) | |
| return satirlar | |
| def foto_yapistir(img, foto, x, y, boyut, cerceve=(110, 110, 110), kalinlik=1): | |
| if y < 0 or x < 0 or y + boyut > img.shape[0] or x + boyut > img.shape[1]: | |
| return | |
| if foto is None: | |
| img[y:y + boyut, x:x + boyut] = (60, 60, 60) | |
| cv2.putText(img, "?", (x + boyut // 2 - 5, y + boyut // 2 + 6), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.6, (150, 150, 150), 1) | |
| else: | |
| img[y:y + boyut, x:x + boyut] = cv2.resize(foto, (boyut, boyut), interpolation=cv2.INTER_AREA) | |
| cv2.rectangle(img, (x - 1, y - 1), (x + boyut, y + boyut), cerceve, kalinlik) | |
| def adet_rozeti(img, x, y, boyut, adet, olcek=0.4): | |
| yazi = f"x{adet}" | |
| (tw, th), _ = cv2.getTextSize(yazi, cv2.FONT_HERSHEY_SIMPLEX, olcek, 1) | |
| x1 = max(x, x + boyut - tw - 8) | |
| cv2.rectangle(img, (x1, y), (x + boyut, y + th + 6), (0, 0, 0), -1) | |
| cv2.putText(img, yazi, (x1 + 4, y + th + 2), cv2.FONT_HERSHEY_SIMPLEX, olcek, (0, 255, 255), 1) | |
| def foto_listesi_ciz(p, st, yukseklik, alt): | |
| """Sepeti kucuk resimlerle cizer; altina 'SON ISLEM' (gosterilen | eslesen) kutusunu koyar.""" | |
| sepet, fotolar, adetler, son_islem = st["sepet"], st["fotolar"], st["adetler"], st["son_islem"] | |
| f = cv2.FONT_HERSHEY_SIMPLEX | |
| cizgi = alt - 12 | |
| kutu_ust = cizgi - 108 - 4 | |
| boyut, aralik = 48, 8 | |
| kolon = max(1, (PANEL_GENISLIK - 24 + aralik) // (boyut + aralik)) | |
| satir_h = boyut + 16 | |
| y0 = 60 | |
| satir = max(1, (kutu_ust - 6 - y0) // satir_h) | |
| gorunen = kolon * satir | |
| if not sepet: | |
| cv2.putText(p, "(bos)", (12, 80), f, 0.55, (150, 150, 150), 1) | |
| for i, ad in enumerate(sepet[:gorunen]): | |
| r, c = divmod(i, kolon) | |
| x, y = 12 + c * (boyut + aralik), y0 + r * satir_h | |
| foto_yapistir(p, fotolar.get(ad), x, y, boyut, (120, 200, 120)) | |
| adet_rozeti(p, x, y, boyut, adetler.get(ad, 1)) | |
| cv2.putText(p, ad.replace("Nesne-", "#"), (x, y + boyut + 12), f, 0.4, (200, 255, 200), 1) | |
| if len(sepet) > gorunen: | |
| cv2.putText(p, f"+{len(sepet) - gorunen} daha", (182, 50), f, 0.42, (200, 200, 200), 1) | |
| cv2.rectangle(p, (6, kutu_ust), (PANEL_GENISLIK - 6, cizgi - 4), (45, 45, 45), -1) | |
| cv2.putText(p, "SON ISLEM", (12, kutu_ust + 14), f, 0.42, (200, 200, 200), 1) | |
| if son_islem is None: | |
| cv2.putText(p, "(henuz yok)", (12, kutu_ust + 55), f, 0.45, (130, 130, 130), 1) | |
| return | |
| renk = son_islem["renk"] | |
| ty = kutu_ust + 22 | |
| foto_yapistir(p, son_islem["foto"], 12, ty, 64, renk, 2) | |
| cv2.putText(p, "gosterilen", (12, ty + 64 + 13), f, 0.36, (200, 200, 200), 1) | |
| cv2.arrowedLine(p, (80, ty + 32), (94, ty + 32), (200, 200, 200), 1, tipLength=0.5) | |
| foto_yapistir(p, son_islem["eslesen_foto"], 98, ty, 64, renk, 2) | |
| cv2.putText(p, son_islem["eslesen_etiket"][:14], (98, ty + 64 + 13), f, 0.36, (200, 200, 200), 1) | |
| for i, satir_metni in enumerate(satirlara_bol(son_islem["metin"], 19)[:5]): | |
| cv2.putText(p, satir_metni, (172, ty + 12 + i * 14), f, 0.4, renk, 1) | |
| def panel_ciz(yukseklik, st, mod, bilgiler): | |
| p = np.full((yukseklik, PANEL_GENISLIK, 3), 30, np.uint8) | |
| f = cv2.FONT_HERSHEY_SIMPLEX | |
| adet = sum(st["adetler"].get(a, 1) for a in st["sepet"]) | |
| cv2.putText(p, f"SEPET ({adet} adet)", (12, 28), f, 0.6, (255, 255, 255), 2) | |
| mod_renk = (60, 170, 60) if mod == MOD_EKLE else (60, 60, 210) | |
| cv2.rectangle(p, (200, 10), (310, 38), mod_renk, -1) | |
| cv2.putText(p, "EKLE" if mod == MOD_EKLE else "CIKAR", (218, 30), f, 0.55, (255, 255, 255), 2) | |
| alt = yukseklik - 125 | |
| foto_listesi_ciz(p, st, yukseklik, alt) | |
| cv2.line(p, (0, alt - 12), (PANEL_GENISLIK, alt - 12), (80, 80, 80), 1) | |
| for i, (yazi, renk) in enumerate(bilgiler): | |
| cv2.putText(p, yazi, (12, alt + 8 + i * 22), f, 0.48, renk, 1) | |
| return p | |
| def katalog_resmi(st): | |
| """Tum kayitli nesnelerin buyuk fotograflari (listede olanlar yesil cerceveli). RGB dondurur.""" | |
| katalog, sepet, fotolar, adetler = st["katalog"], st["sepet"], st["fotolar"], st["adetler"] | |
| boyut, kolon, bosluk = 128, 4, 12 | |
| f = cv2.FONT_HERSHEY_SIMPLEX | |
| genislik = kolon * (boyut + bosluk) + bosluk | |
| adlar = list(katalog) | |
| if not adlar: | |
| img = np.full((70, genislik, 3), 30, np.uint8) | |
| cv2.putText(img, "KATALOG BOS - once bir nesne ekle", (bosluk, 40), f, 0.5, (170, 170, 170), 1) | |
| return cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| satir_h = boyut + 34 | |
| satirlar = (len(adlar) + kolon - 1) // kolon | |
| img = np.full((34 + satirlar * satir_h + bosluk, genislik, 3), 30, np.uint8) | |
| cv2.putText(img, f"KATALOG ({len(adlar)} nesne) yesil cerceve = listede", (bosluk, 22), | |
| f, 0.5, (255, 255, 255), 1) | |
| for i, ad in enumerate(adlar): | |
| r, c = divmod(i, kolon) | |
| x, y = bosluk + c * (boyut + bosluk), 34 + r * satir_h | |
| listede = ad in sepet | |
| foto_yapistir(img, fotolar.get(ad), x, y, boyut, | |
| (80, 200, 80) if listede else (110, 110, 110), 3 if listede else 1) | |
| if listede: | |
| adet_rozeti(img, x, y, boyut, adetler.get(ad, 1), 0.7) | |
| cv2.putText(img, ad, (x, y + boyut + 16), f, 0.5, | |
| (255, 255, 255) if listede else (170, 170, 170), 1) | |
| cv2.putText(img, f"gorunum: {len(katalog[ad])}" + (" LISTEDE" if listede else ""), | |
| (x, y + boyut + 30), f, 0.38, (120, 220, 120) if listede else (140, 140, 140), 1) | |
| return cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| # ---------------------------------------------------------------------------- | |
| # Oturum durumu ve kare isleme | |
| # ---------------------------------------------------------------------------- | |
| def yeni_durum(): | |
| return { | |
| "katalog": {}, # ad -> [ozellik dict, ...] | |
| "sepet": [], # listedeki nesne adlari | |
| "fotolar": {}, # ad -> ilk kayit fotografi | |
| "adetler": {}, # ad -> adet | |
| "sayac": 0, "son_islem": None, | |
| "mod": MOD_EKLE, "durum": BOS, | |
| "uygun_say": 0, "el_yok_say": 0, "kareler": [], "deneme": 0, "zorla": False, | |
| "sahne": None, "onceki_gri": None, "kare_no": 0, | |
| "kilit_ref": None, "kilit_zaman": 0.0, "degisim_say": 0, | |
| "mesaj": "", "mesaj_zaman": 0.0, "son_skor": None, "son_renk": None, | |
| "goster": None, "uyari": "", | |
| } | |
| def kare_hazirla(bgr): | |
| """Goruntuyu ortadan 4:3 kirpar ve GENISLIK x YUKSEKLIK boyuta getirir.""" | |
| h, w = bgr.shape[:2] | |
| if w * YUKSEKLIK > h * GENISLIK: # cok genis -> yanlardan kirp | |
| yeni_w = h * GENISLIK // YUKSEKLIK | |
| x0 = (w - yeni_w) // 2 | |
| bgr = bgr[:, x0:x0 + yeni_w] | |
| else: # cok uzun -> alt/ustten kirp | |
| yeni_h = w * YUKSEKLIK // GENISLIK | |
| y0 = (h - yeni_h) // 2 | |
| bgr = bgr[y0:y0 + yeni_h] | |
| return cv2.resize(bgr, (GENISLIK, YUKSEKLIK), interpolation=cv2.INTER_AREA) | |
| def durum_sifirla(st): | |
| st["durum"], st["kareler"], st["zorla"], st["uygun_say"] = BOS, [], False, 0 | |
| st["goster"] = None | |
| def isle(kare, st, esik, renk_esik): | |
| """Tarayicidan gelen her kare icin: durum makinesini ilerlet, cizimi + durum metnini dondur.""" | |
| if kare is None: | |
| return gr.skip(), gr.skip() | |
| frame = kare_hazirla(cv2.cvtColor(kare, cv2.COLOR_RGB2BGR)) | |
| h, w = frame.shape[:2] | |
| mod = st["mod"] | |
| st["kare_no"] += 1 | |
| kare_no = st["kare_no"] | |
| kenar = int(min(h, w) * ROI_ORANI) | |
| x1, y1 = (w - kenar) // 2, (h - kenar) // 2 | |
| x2, y2 = x1 + kenar, y1 + kenar | |
| roi = frame[y1:y2, x1:x2] | |
| # ---- Kosullar ---- | |
| gri = roi_gri(roi) | |
| deri = deri_maskesi(roi) | |
| deri_oran = float(deri.mean()) | |
| el_var = deri_oran >= EL_MIN_ORAN | |
| cok_ten = deri_oran > EL_MAX_ORAN | |
| hareket = 0.0 if st["onceki_gri"] is None else float(cv2.absdiff(gri, st["onceki_gri"]).mean()) | |
| st["onceki_gri"] = gri | |
| sakin = hareket < HAREKET_ESIK | |
| uygun = el_var and not cok_ten and sakin | |
| st["uygun_say"] = st["uygun_say"] + 1 if uygun else 0 | |
| st["el_yok_say"] = st["el_yok_say"] + 1 if not el_var else 0 | |
| # El yokken sahneyi (arka plani) guncelle | |
| if not el_var and st["durum"] != ALGILANIYOR and kare_no % SAHNE_ARALIK == 0: | |
| st["sahne"] = roi.copy() | |
| # ---- Durum makinesi ---- | |
| if st["durum"] == BOS: | |
| if st["uygun_say"] >= BASLAT_FRAME: | |
| st["durum"], st["kareler"], st["deneme"] = ALGILANIYOR, [], 0 | |
| elif st["durum"] == ALGILANIYOR: | |
| if not (uygun or st["zorla"]): # el cekildi / hareket basladi | |
| st["durum"], st["kareler"] = BOS, [] | |
| else: | |
| q = ozellik_cikar(roi, deri, st["sahne"]) | |
| st["goster"] = q | |
| if q is not None: | |
| st["kareler"].append(q) | |
| st["deneme"] += 1 | |
| if len(st["kareler"]) >= ORNEK_FRAME or st["deneme"] >= ORNEK_FRAME * 2: | |
| iyi = sorted(st["kareler"], key=lambda k: -k["alan"]) | |
| if len(iyi) < max(2, ORNEK_FRAME // 3): | |
| st["mesaj"] = "BOS SAHNE ALINMADI" if st["sahne"] is None else "NESNE BULUNAMADI" | |
| st["uyari"] = "NESNE YOK" | |
| st["kilit_ref"], st["son_skor"] = None, None | |
| else: | |
| st["uyari"] = "" | |
| sonuc = karar_ver(mod, iyi, st, esik, renk_esik) | |
| st["son_skor"], st["son_renk"] = sonuc["skor"], sonuc["renk"] | |
| st["kilit_ref"], st["mesaj"] = sonuc["kilit_ref"], sonuc["mesaj"] | |
| foto_simdi = foto_hazirla(iyi[0]["gorsel"]) | |
| if sonuc["kayit"]: | |
| st["fotolar"][sonuc["kayit"]] = foto_simdi | |
| st["son_islem"] = son_islem_olustur( | |
| st["mesaj"], foto_simdi, kayit_adi(st["katalog"], sonuc["kilit_ref"]), | |
| sonuc["kayit"], st["fotolar"]) | |
| st["mesaj_zaman"] = time.time() | |
| st["durum"] = KILITLI | |
| st["kilit_zaman"] = time.time() | |
| st["degisim_say"] = 0 | |
| st["kareler"] = [] | |
| st["zorla"] = False | |
| elif st["durum"] == KILITLI: | |
| if st["el_yok_say"] >= BOSALT_FRAME: # el tamamen gitti | |
| st["durum"], st["goster"] = BOS, None | |
| elif time.time() - st["kilit_zaman"] > KILIT_SURE: | |
| # El ROI'de kalsa da: gosterilen nesne degisti mi / bir nesne belirdi mi? | |
| q = ozellik_cikar(roi, deri, st["sahne"]) | |
| st["goster"] = q | |
| if st["kilit_ref"] is None: | |
| degisti = q is not None # nesne yoktu, simdi var | |
| elif q is None: | |
| degisti = True # kilitli nesne artik yok | |
| else: | |
| degisti = max(benzerlik(q, g) for g in st["kilit_ref"]) < esik - KILIT_FARK | |
| st["degisim_say"] = st["degisim_say"] + 1 if degisti else 0 | |
| if st["degisim_say"] >= DEGISIM_KONTROL: | |
| st["durum"], st["degisim_say"], st["goster"] = BOS, 0, None | |
| durum = st["durum"] | |
| # ---- Cizim ---- | |
| renk = {BOS: (0, 200, 0), ALGILANIYOR: (0, 200, 255), KILITLI: (0, 0, 255)}[durum] | |
| goruntu = frame.copy() | |
| bolge = goruntu[y1:y2, x1:x2] # ten rengi = mavi ton | |
| bolge[deri] = (bolge[deri] * 0.6 + np.array([255, 80, 0]) * 0.4).astype(np.uint8) | |
| goster = st["goster"] | |
| if goster is not None and durum != BOS: # modele giden nesne = yesil kontur | |
| kx, ky, kw, kh = goster["kutu"] | |
| cv2.drawContours(goruntu, [goster["kontur"]], -1, (0, 255, 0), 2, offset=(x1, y1)) | |
| cv2.rectangle(goruntu, (x1 + kx, y1 + ky), (x1 + kx + kw, y1 + ky + kh), (0, 255, 0), 1) | |
| cv2.rectangle(goruntu, (x1, y1), (x2, y2), renk, 3) | |
| if durum == KILITLI: | |
| durum_yazi = "TAMAM - yeni nesneyi gosterebilirsin" | |
| elif durum == ALGILANIYOR: | |
| durum_yazi = "ALGILANIYOR - sabit tut" | |
| elif st["sahne"] is None: | |
| durum_yazi = "BOS SAHNE ALINIYOR - elini cek" | |
| elif cok_ten: | |
| durum_yazi = "COK FAZLA TEN - biraz uzaklas" | |
| elif el_var and not sakin: | |
| durum_yazi = "HAREKET VAR - sabit tut" | |
| else: | |
| durum_yazi = "HAZIR - nesneyi elinle kareye tut" | |
| cv2.putText(goruntu, durum_yazi, (10, 28), cv2.FONT_HERSHEY_SIMPLEX, 0.65, renk, 2) | |
| if durum == ALGILANIYOR: | |
| ilerleme = int(kenar * min(1.0, len(st["kareler"]) / ORNEK_FRAME)) | |
| cv2.rectangle(goruntu, (x1, y2 + 8), (x1 + ilerleme, y2 + 16), renk, -1) | |
| mesaj_gorunur = st["mesaj"] and time.time() - st["mesaj_zaman"] < MESAJ_SURESI | |
| if mesaj_gorunur: | |
| cv2.putText(goruntu, st["mesaj"].split(" (")[0], (10, h - 16), | |
| cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) | |
| son_skor = f"{st['son_skor']:.2f}" if st["son_skor"] is not None else "-" | |
| bilgiler = [ | |
| (f"Esik: {esik:.2f} Son benzerlik: {son_skor}", (0, 220, 255)), | |
| (f"Katalog: {len(st['katalog'])} nesne", (180, 180, 180)), | |
| (f"Renk mesafesi: {st['son_renk']:.2f} (esik {renk_esik:.2f})" if st["son_renk"] is not None | |
| else f"Renk esigi: {renk_esik:.2f}", (180, 180, 180)), | |
| (f"Ten: {deri_oran * 100:.1f}% Hareket: {hareket:.1f}", (180, 180, 180)), | |
| (st["uyari"], (0, 120, 255)), | |
| ] | |
| birlesik = np.hstack([goruntu, panel_ciz(h, st, mod, bilgiler)]) | |
| metin = st["mesaj"] if mesaj_gorunur else durum_yazi | |
| return cv2.cvtColor(birlesik, cv2.COLOR_BGR2RGB), metin | |
| def mod_dugmeleri(mod): | |
| """Aktif modun dugmesi vurgulanir: (EKLE dugmesi, CIKAR dugmesi).""" | |
| return (gr.Button(variant="primary" if mod == MOD_EKLE else "secondary"), | |
| gr.Button(variant="primary" if mod == MOD_CIKAR else "secondary")) | |
| def mod_sec(st, mod): | |
| """EKLE / CIKAR dugmesi: modu degistirir, o anki tarama durumunu sifirlar.""" | |
| if st["mod"] != mod: | |
| st["mod"] = mod | |
| durum_sifirla(st) | |
| st["mesaj"] = "MOD: NESNE EKLEME" if mod == MOD_EKLE else "MOD: NESNE CIKARMA" | |
| st["mesaj_zaman"] = time.time() | |
| return (st["mesaj"], *mod_dugmeleri(mod)) | |
| def zorla_tara(st): | |
| """Otomatik tetik calismazsa: o an gosterileni el olmasa da tara.""" | |
| st["durum"], st["kareler"], st["deneme"], st["zorla"] = ALGILANIYOR, [], 0, True | |
| return "Zorla tarama basladi - nesneyi sabit tut" | |
| def sifirla(st): | |
| """Bu oturumun katalog, sepet ve arka planini temizler (mod EKLE'ye doner).""" | |
| st.clear() | |
| st.update(yeni_durum()) | |
| return ("Sifirlandi", gr.skip(), *mod_dugmeleri(MOD_EKLE)) | |
| def son_kaydi_sil(st): | |
| ad = max(st["katalog"], key=lambda a: int(a.split("-")[1]), default=None) | |
| if ad is None: | |
| return "Silinecek kayit yok" | |
| st["katalog"].pop(ad, None) | |
| st["fotolar"].pop(ad, None) | |
| st["adetler"].pop(ad, None) | |
| if ad in st["sepet"]: | |
| st["sepet"].remove(ad) | |
| st["son_islem"] = None | |
| return f"SILINDI: {ad}" | |
| def katalogu_goster(st): | |
| return katalog_resmi(st) | |
| # ---------------------------------------------------------------------------- | |
| # Arayuz | |
| # ---------------------------------------------------------------------------- | |
| ACIKLAMA = """ | |
| # Nesne ekle / çıkar (hazır model: MobileNetV3) | |
| Kamerada bir ürünü elinle göster; sistem onu tanır, **EKLE** modunda sepete adet ekler, **ÇIKAR** modunda adet düşürür. | |
| Her ziyaretçinin kendi kataloğu ve sepeti vardır; görüntüler kaydedilmez. | |
| **Nasıl kullanılır** | |
| 1. Sayfanın altındaki **Kamera girişi** kutusunda kamera izni ver ve turuncu **Kaydet** düğmesine bas (analiz | |
| akışı böyle başlar, 15 dakika sonra durursa tekrar bas). Sonra **elini çek** (boş arka plan alınır, "HAZIR" yazısını bekle). | |
| 2. Ürünü elinle yeşil çerçevenin ortasında ~1 saniye **sabit** tut. | |
| 3. Aynı ürünü tekrar göstermek adeti artırır; **ÇIKAR** modunda göstermek adeti azaltır. | |
| 4. Ürün tanınmıyorsa **Benzerlik eşiği**ni biraz düşür; farklı ürünler karışıyorsa artır. | |
| """ | |
| with gr.Blocks(title="Nesne Takip - MobileNetV3") as demo: | |
| gr.Markdown(ACIKLAMA) | |
| st = gr.State(yeni_durum()) | |
| # Ana gorunum (masaustu surumundeki pencere gibi): kamera + ROI cercevesi + sag panel tek resimde | |
| cikti = gr.Image(label="Nesne Takip", interactive=False, format="jpeg", show_label=False) | |
| with gr.Row(): | |
| ekle_btn = gr.Button("EKLE", variant="primary", size="lg") | |
| cikar_btn = gr.Button("CIKAR", variant="secondary", size="lg") | |
| zorla_btn = gr.Button("Zorla tara", size="lg") | |
| sil_btn = gr.Button("Son kaydı sil", size="lg") | |
| sifirla_btn = gr.Button("Sıfırla", variant="stop", size="lg") | |
| durum_metni = gr.Textbox(label="Durum", interactive=False) | |
| with gr.Row(): | |
| kamera = gr.Image( | |
| sources=["webcam"], streaming=True, type="numpy", height=240, | |
| label="Kamera girişi: izin ver ve altındaki Kaydet düğmesine bas", | |
| webcam_options=gr.WebcamOptions( | |
| mirror=True, constraints={"width": {"ideal": 640}, "height": {"ideal": 480}}), | |
| ) | |
| with gr.Column(): | |
| with gr.Accordion("Ayarlar", open=False): | |
| esik = gr.Slider(0.50, 0.99, value=SIM_ESIK, step=0.01, label="Benzerlik eşiği") | |
| renk_esik = gr.Slider(0.05, 1.0, value=RENK_MESAFE, step=0.05, | |
| label="Renk eşiği (küçük = daha sıkı)") | |
| with gr.Accordion("Katalog (görülen tüm nesneler)", open=False): | |
| katalog_btn = gr.Button("Kataloğu göster / yenile") | |
| katalog_img = gr.Image(label="Katalog", interactive=False) | |
| kamera.stream( | |
| isle, [kamera, st, esik, renk_esik], [cikti, durum_metni], | |
| stream_every=0.2, time_limit=900, concurrency_limit=8, show_progress="hidden", | |
| ) | |
| mod_cikti = [durum_metni, ekle_btn, cikar_btn] | |
| ekle_btn.click(lambda s: mod_sec(s, MOD_EKLE), [st], mod_cikti, queue=False) | |
| cikar_btn.click(lambda s: mod_sec(s, MOD_CIKAR), [st], mod_cikti, queue=False) | |
| zorla_btn.click(zorla_tara, [st], [durum_metni], queue=False) | |
| sil_btn.click(son_kaydi_sil, [st], [durum_metni], queue=False) | |
| sifirla_btn.click(sifirla, [st], [durum_metni, katalog_img, ekle_btn, cikar_btn], queue=False) | |
| katalog_btn.click(katalogu_goster, [st], [katalog_img]) | |
| demo.queue(max_size=32) | |
| if __name__ == "__main__": | |
| demo.launch() | |