""" 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: @spaces.GPU 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) @torch.no_grad() 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()