mguven61's picture
Update app.py
5bb88b2 verified
Raw History Blame Contribute Delete
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:
@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()