21 · Manipulation için object perception
| Bölüm | 5 · Perception ve estimation |
| Çalışma türü | CPU · Mac ya da Colab CPU |
| Önkoşul | 16, 20 |
| Önce öğren | Image Fundamentals — Pixels, Channels, Color Spaces · Object Detection — YOLO from Scratch · Open-Vocabulary Vision — CLIP · SAM 3 & Open-Vocabulary Segmentation |
| Süre | ~60 dk |
| Kod | experiments/21-object-perception/experiment.py |
| Durum | ✅ tam |
Bu derste yap
Her adımı yapınca işaretle. Yalnız bu tarayıcıda saklanır.
Grasping için perception tek bir sayıyla ölçülür: gripper nesnenin üstüne kapanıyor mu. Pixel’i değil, pick’i ölç.
Problem
Bölüm başlığı “Problem”Scripted bir pick (ders 16) nesnenin robot frame’indeki konumunu ister. Ders 20 pixel’leri masa koordinatlarına çevirdi; şimdi bir şeyin nesnenin pixel’lerini bulması gerekiyor. En basit detector, yani renk eşiği, ayarlandığı ışıkta çalışır. Bu ders döngüyü simülasyonda kapatır (render et, tespit et, back-project et, SO-101 ile al) ve ışığı sweep ederken başarılı kaldırışları sayar.
Okumadan önce · 2 soru. Önce tahmin et; nedenini ders anlatıyor.
1. Kırmızı bir küpün üstündeki ışığı kısıyorsun. Hue, saturation ve value’dan en çok hangisi değişir?
- Hue; küp önce kahverengiye, sonra siyaha döner.
- Saturation; loş renkler her zaman gri görünür.
- Value, yani parlaklık; hue olduğu yerde kalır.
Neden: HSV hangi renk olduğunu (hue) ne kadar parlak olduğundan (value) ayırır. Deney adım 2’de üst yüzün V’si ölçek 1,0’da 255’ten ölçek 0,1’de 72’ye iniyor; H ise her satırda 0’da kalıyor.
2. Perception küpü gerçek yerinden 8 mm uzakta raporluyor. Küp 30 mm genişliğinde. Scripted pick yine de çalışır mı?
- Çoğu zaman hayır; simülasyonda 8 mm nişan hatasında 30 pick’in 9’u başarısız oluyor.
- Her zaman; 8 mm, küpün 30 mm’sinin rahatça içinde.
- Asla; 1 mm’nin üstündeki her hata başarısız olur.
Neden: Deney adım 1 - 0, 4 ve 8 mm nişan hatası 30, 29 ve 21/30 pick veriyor. Hata bütçesi birkaç milimetre; ders bu yüzden pixel’i değil pick’i ölçüyor.
Ana fikir
Bölüm başlığı “Ana fikir”Sade anlatımla
Bölüm başlığı “Sade anlatımla”En basit detector elle yazılmış bir kuraldır: “bir pixel yeterince kırmızı, yeterince renkli ve yeterince parlaksa küpe aittir”. Koda gömülü bir if gibi ayarlandığı girdilerde çalışır; ışık kayınca birbirine yakın iki seviye arasında “çalışıyor”dan “hiçbir şey görmüyor”a atlar.
Kural RGB yerine HSV’de yazılır, çünkü HSV soruları birbirinden ayırır. Hue hangi renk olduğudur, renk çemberinde bir açı (kırmızı 0°‘de). Saturation gri yerine ne kadar renkli olduğudur. Value ne kadar parlak olduğudur. Kırmızı bir küpü karartmak çoğunlukla V’yi düşürür, hue’ya pek dokunmaz.
Kırmızı pixel maskesi sonra depth görüntüsüyle 3D bir konuma dönüşür (ders 20) ve kol oraya gider. Önemli olan skor bu zincirin sonundadır; bu yüzden pixel hatasını değil, pick başarısını ölçeriz.
Önce algıla, sonra al
Bölüm başlığı “Önce algıla, sonra al”flowchart TD
A["kamera görüntüsü<br/>RGB + depth"] --> B["HSV eşiği<br/>kırmızı hue, S ≥ S_min, V ≥ V_min"]
B --> C["maskeyi temizle<br/>opening, en büyük blob'u tut"]
C --> D["blob pixel'lerini depth ile<br/>back-project et (ders 20)"]
D --> E["üst yüzü tut<br/>x, y ortalamasını al"]
E --> F["IK + scripted grasp<br/>yaklaş, in, kapat, kaldır"]
F --> G["başarı mı?<br/>küp 5 cm'den fazla kalktı"]
Detector, kod olarak
Bölüm başlığı “Detector, kod olarak”hsv = rgb_to_hsv8(rgb) # H in 0..179 (degrees / 2), S and V in 0..255red = (hsv[..., 0] <= 10) | (hsv[..., 0] >= 170) # red wraps around hue 0mask = red & (hsv[..., 1] >= 120) & (hsv[..., 2] >= 70) # S_min, V_min tuned at nominal lightcube = largest_blob(open3x3(mask)) # drop speckles, keep the biggest group of pixelsxy = mask_to_table_xy(cube, depth, K, R_wc, c) # back-project, average the top face| Parça | Formül |
|---|---|
| Value | |
| Saturation | |
| “Kırmızı” testi | |
| Grasp noktası | 20 mm’den yüksek back-project edilmiş noktaların ortalaması |
| Metrik | pick başarısı = küp başlangıç yüksekliğinin 5 cm’den fazla üstünde |
pixel’in renk kanalları, ve bunların en büyüğü ve en küçüğü, OpenCV’nin 8-bit biriminde (derece/2) hue, ise ayarladığın iki eşiktir (S ve V 0–255’e ölçekli).
| Detector | Frame başına maliyet | Ayar | Tipik hata |
|---|---|---|---|
| HSV eşiği | birkaç dizi işlemi | renk başına 6 sayı | ışık, benzer renkli arka plan |
| Open-vocabulary (OWL-ViT, maske için + SAM) | bir ağ forward pass’i | bir metin prompt’u | yanlış nesne, belirsiz prompt; burada ölçülmedi |
Matematik ayrıntısı: eşikler neden uçurum gibi bozulur ve öğrenilmiş detector'ler neyi değiştirir
HSV eşikleme. Hue parlaklık değişimlerine karşı görece kararlıdır; S ve V değildir (bkz. AIEFS: Image Fundamentals). Morfolojik opening (önce erode, sonra dilate) kernel’den küçük benekleri siler; en büyük bağlı bileşen (birbirine değen pixel grubu) nesnedir.
Nerede bozulur. Az ışık: V ya da S kendi sınırının altına düşer. Fazla ışık: kanallar 255’te kırpılır; orada takılı kalırken artmaya devam ettikçe düşmek zorundadır. Yatay gelen ışık: kameranın gördüğü yüz karanlıktır, bir yan yüz ise parlaktır. Bir yüzün bütün pixel’leri hemen hemen aynı renktedir, dolayısıyla eşiği birlikte geçerler: hata ani olur. Nesnenin yalnızca bir kısmı geçerse ağırlık merkezi nesne boyunun bir kesri kadar kayar.
Öğrenilmiş detector’ler. OWL-ViT gibi open-vocabulary detector’ler (Minderer vd. 2022, arXiv 2205.06230) elle ayarlanmış bir renk penceresi yerine bir metin sorgusu alır (“red cube”) (bkz. AIEFS: Object Detection — YOLO from Scratch, Open-Vocabulary Vision — CLIP, SAM 3 & Open-Vocabulary Segmentation). Burada çalıştırmadık (bu ortamda transformers yok); bu ders onlar için sayı vermez.
Daha derine: yukarıdaki AIEFS dersleri; Minderer vd. 2022; OpenCV · color conversions.
Sıfırdan yaz
Bölüm başlığı “Sıfırdan yaz”Şimdi detector’ü yaklaşık 60 satır NumPy ile yazacaksın. Sahne, kamera, ışık ve pick rutini Deney script’inden geliyor; böylece Sıfırdan yaz aynı 30 frame’i görüyor, OpenCV ve SciPy yalnızca hakem. Adımları kendi dosyana yaz ve her adımdan sonra çalıştır. Bitmiş fonksiyonlar physai/vision.py içinde; uv run python experiments/21-object-perception/build.py beş adımı kontrolleriyle birlikte koşturur (çıktı build_output.txt dosyasında).
Adım 1 · RGB’den HSV’ye
Bölüm başlığı “Adım 1 · RGB’den HSV’ye”, hangi kanalın en büyük olduğunu ve bir sonrakine doğru ne kadar gittiğini OpenCV’nin yarım derece biriminde söyler:
def rgb_to_hsv8(rgb): """8-bit RGB image -> HSV in OpenCV's 8-bit units: H = degrees / 2 in 0..179, S and V in 0..255.""" x = rgb.astype(np.float64) r, g, b = x[..., 0], x[..., 1], x[..., 2] v = x.max(axis=-1) # value: the brightest channel diff = v - x.min(axis=-1) # 0 for a grey pixel s = np.where(v > 0, 255 * diff / np.maximum(v, 1), 0) # saturation: colourful vs grey dd = np.maximum(diff, 1) h = np.where(v == r, 30 * (g - b) / dd, # hue in half-degrees, sector by sector np.where(v == g, 60 + 30 * (b - r) / dd, 120 + 30 * (r - g) / dd)) h = np.floor(np.where(diff > 0, h, 0) + 0.5) # round first ... h = np.where(h < 0, h + 180, h) # ... then wrap: red sits at both ends return np.stack([h, np.floor(s + 0.5), v], axis=-1).astype(np.uint8)Önce yuvarla, sonra sar: −0,4 yarım derecelik bir hue 0’a yuvarlanır; önce sarsaydın 179,6 olurdu, o da aralığın dışındaki 180’e yuvarlanırdı.
== Step 1. RGB -> HSV (OpenCV 8-bit units) vs cv2.cvtColor ==1,000,000 random colours: H differs on 1.65 % (max 1), S on 0.75 % (max 1), V on 0.00 %rendered frame (position 1, light scale 1.0, 640 x 480): 49817 pixels off by 1, median OpenCV hue of those 106, 0 of them with a red hue scale top HSV ours top HSV OpenCV 0.1 (0,110,72) (0,110,72) 1.0 (0,177,255) (0,177,255) 3.0 (0,95,255) (0,95,255)OpenCV’nin 8-bit yolu tamsayı lookup tablolarıyla böler; bu yüzden hue’ların %1,65’i ve saturation’ların %0,75’i bir birim kayar. Frame’de bunlar mavimsi zemindir (hue 106), hiçbiri kırmızı pixel değildir. Üst yüzde ikisi de Deney’in “üst HSV” sütununu birebir verir.
Adım 2 · Eşik
Bölüm başlığı “Adım 2 · Eşik”def color_mask(hsv, ranges): """Pixels inside any of the (lo, hi) HSV boxes, bounds inclusive; red needs two boxes (hue wraps).""" mask = np.zeros(hsv.shape[:2], bool) for lo, hi in ranges: mask |= np.all((hsv >= np.asarray(lo)) & (hsv <= np.asarray(hi)), axis=-1) return mask== Step 2. The red threshold vs cv2.inRange ==same HSV input: our mask differs from inRange on 0 pixelsour HSV input: 742 red pixels, differs on 0 pixelsred pixels in the hue box near 0: 712, near 179: 30Kendi HSV’nle de maske birebir aynı. Son satır kırmızının neden iki kutu istediğini gösteriyor: küpün 742 pixel’inin 30’unun hue’su 179 civarında.
Adım 3 · Maskeyi temizle, en büyük blob’u tut
Bölüm başlığı “Adım 3 · Maskeyi temizle, en büyük blob’u tut”Görüntüyü her yöne bir pixel kaydır ve dokuz kopyayı birleştir: hepsi dolu ise erosion, herhangi biri dolu ise dilation, önce erosion sonra dilation ise opening. Etiketleme aynı kaydırmaları kullanır: her pixel, hiçbir şey değişmeyene kadar komşularının en büyük id’sini alır; böylece her 8-bağlı blob tek bir id ile biter.
def _shifts(a, fill): """The 9 copies of a 2D array shifted by -1, 0, +1 pixels in each direction; outside = fill.""" p = np.pad(a, 1, constant_values=fill) h, w = a.shape return [p[i:i + h, j:j + w] for i in range(3) for j in range(3)]def open3x3(mask): """Morphological opening with a 3x3 square: erode (keep a pixel if all 9 are set), then dilate.""" eroded = np.logical_and.reduce(_shifts(mask, True)) # outside the image counts as set, as in OpenCV return np.logical_or.reduce(_shifts(eroded, False))def label_blobs(mask): """Label 8-connected blobs: every pixel takes the largest id among its neighbours until nothing changes.""" lab = np.where(mask, np.arange(1, mask.size + 1).reshape(mask.shape), 0) while True: new = np.where(mask, np.maximum.reduce(_shifts(lab, 0)), 0) if np.array_equal(new, lab): return lab # one id per blob (not numbered 1, 2, 3 ...) lab = newdef largest_blob(mask, min_area=30): """The biggest blob as a boolean mask, or None if there is none of at least min_area pixels.""" if not mask.any(): return None rows, cols = np.nonzero(mask.any(axis=1))[0], np.nonzero(mask.any(axis=0))[0] box = (slice(rows[0], rows[-1] + 1), slice(cols[0], cols[-1] + 1)) # label only the bounding box lab = label_blobs(mask[box]) ids, area = np.unique(lab[lab > 0], return_counts=True) if area.max() < min_area: return None out = np.zeros_like(mask) out[box] = lab == ids[np.argmax(area)] return outKontrol, Adım 2’nin maskesine üç başıboş pixel ekliyor: 2 pixel’lik ve 1 pixel’lik bir benek:
== Step 3. Opening and the largest blob vs OpenCV and SciPy ==opening: 745 -> 740 pixels, differs from cv2.morphologyEx on 0blobs before opening: ours 3, OpenCV 3, scipy.ndimage.label 3largest blob: 740 pixels, differs from the Experiment's detect() on 0Opening iki beneği de (ve küpün iki pürüzlü pixel’ini) siliyor, üç etiketleyici de üç blob sayıyor ve en büyük blob pixel pixel Deney’in detect() maskesi.
Adım 4 · Blob’dan masa konumuna
Bölüm başlığı “Adım 4 · Blob’dan masa konumuna”Ağırlık merkezi ortalama pixel koordinatıdır; image moment’lerle . Masa konumu için her blob pixel’ini depth’iyle back-project et (ders 20) ve ortalamasını al:
def mask_centroid(mask): """Pixel centroid (u, v) from the image moments m00, m10, m01 of a binary mask.""" v, u = np.nonzero(mask) m00, m10, m01 = len(u), u.sum(), v.sum() return np.array([m10 / m00, m01 / m00])def mask_to_table_xy(mask, depth, K, R_wc, c, z_min=0.02): """Back-project every mask pixel with its depth (lesson 20) and average (x, y) of the points above z_min.""" v, u = np.nonzero(mask) rays = np.c_[u, v, np.ones(len(u))] @ np.linalg.inv(K).T @ R_wc.T # camera ray of each pixel, world frame P = c + depth[v, u][:, None] * rays # depth is the distance along the axis top = P[P[:, 2] > z_min] # the top face of the object return (top if len(top) else P)[:, :2].mean(axis=0)== Step 4. Centroid and table position ==centroid (u, v) = (326.092, 268.359) px, max |ours - cv2.moments| = 0.0e+0030 positions, light scale 1.0 median mm max mm centroid pixel, back-projected 0.57 1.09 mean of all blob points 0.32 0.47 mean of top-face points (z > 20 mm) 0.29 0.45max |ours - Experiment's locate()| = 0.0e+00 mone pixel at the top face (0.57 m from the camera) spans 1.11 mmAğırlık merkezi pixel’ini back-project etmek en kötüsü (en fazla 1,09 mm): tam bir pixel’e yuvarlanmak zorunda ve bir pixel üst yüzde 1,11 mm kaplıyor. Bütün blob noktalarının ortalaması yuvarlamayı ortadan kaldırıyor; yan yüz noktalarını ( mm) atmak 0,29 mm medyan ve 0,45 mm en büyük hata veriyor. Bu, Deney’in locate()’iyle birebir aynı; Deney tablosu aynı hataları 0,3 ve 0,4 mm’ye yuvarlıyor.
Adım 5 · Döngüyü kapat
Bölüm başlığı “Adım 5 · Döngüyü kapat”Tek bir fonksiyon pipeline’ı birleştiriyor:
def detect_color(rgb, ranges, min_area=30): """HSV threshold -> opening -> largest blob: the object's mask, or None.""" return largest_blob(open3x3(color_mask(rgb_to_hsv8(rgb), ranges)), min_area)pick ise Deney’in kendi rutini:
blob = detect_color(rgb, RED) # None if nothing passesxy = mask_to_table_xy(blob, depth, cam.K, cam.R_wc, cam.c) # top face, world frameok = exp.pick(m, d, xy) # IK, approach, close, lift > 5 cm== Step 5. Close the loop: our detector + the Experiment's pick == scale detected median err mm max err mm picks ok 0.1 0/30 - - 0/30 1.0 30/30 0.3 0.4 30/30 3.0 1/30 14.9 14.9 0/30Bunlar, Deney tablosunun ölçek 0,1, 1,0 ve 3,0 satırları, sayısı sayısına; 3,0’daki, pick’i başarısız olan tek 14,9 mm’lik tespit dahil.
Elinde kalan
Bölüm başlığı “Elinde kalan”physai.vision artık rgb_to_hsv8, color_mask, open3x3, label_blobs, largest_blob, mask_centroid, mask_to_table_xy ve detect_color içeriyor. Bitirme projesi (ders 44) üç küpünü aynı HSV maskesi ve back-projection ile buluyor. tests/test_21_object_perception.py onları SciPy’ye (OpenCV kuruluysa ona da) bağlıyor; bir şeyi değiştirdikten sonra uv run pytest tests/test_21_object_perception.py çalıştır.
Kütüphaneyle kullan
Bölüm başlığı “Kütüphaneyle kullan”Pratikte bütün detector, MuJoCo görüntüleri üzerinde beş OpenCV çağrısıdır:
renderer = mujoco.Renderer(m, 480, 640) # RGB, uint8, H x W x 3, channel order R, G, Brenderer.update_scene(d, "top"); rgb = renderer.render()hsv = cv2.cvtColor(rgb, cv2.COLOR_RGB2HSV) # RGB2HSV, not BGR2HSV, for MuJoCo imagesmask = cv2.inRange(hsv, (0, 120, 70), (10, 255, 255)) | cv2.inRange(hsv, (170, 120, 70), (179, 255, 255))mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, np.ones((3, 3), np.uint8))n, labels, stats, centroids = cv2.connectedComponentsWithStats(mask) # label 0 is the backgroundk = 1 + np.argmax(stats[1:, cv2.CC_STAT_AREA])cube = labels == k # centroids[k] is the pixel centroid of Step 4Önkoşullar: bir depth renderer (renderer.enable_depth_rendering(), kamera ekseni boyunca uzaklık), ve kamera pozu (ders 19–20), aynı kamera ayarlarıyla ayarlanmış eşikler. Sonra Adım 4’teki gibi back-project et.
Başka araçlar aynı renge başka sayılar verir. Saf mavi, her çağrının yazdırdığı gibi (OpenCV 5.0.0, Pillow 13.0.0, Matplotlib 3.11.2):
| Araç ve çağrı | Girdi | Saf mavinin hue’su | , aralığı |
|---|---|---|---|
physai rgb_to_hsv8, uint8 üzerinde OpenCV cvtColor | 0–255 | 120 (0–179 içinde) | 0–255 |
float32 üzerinde OpenCV cvtColor | 0–1 | 240 (0–360 içinde) | 0–1 |
Pillow Image.convert("HSV") | 0–255 | 170 (0–255 içinde) | 0–255 |
Matplotlib colors.rgb_to_hsv, Python colorsys.rgb_to_hsv | 0–1 | 0,667 (0–1 içinde) | 0–1 |
Kaynaklar: OpenCV · color conversions, Python colorsys, Matplotlib rgb_to_hsv, Pillow modes.
Tuzaklar:
- Kanal sırası. MuJoCo’nun renderer’ı RGB döndürür;
cv2.imreadvecv2.VideoCaptureBGR döndürür. RGB’yiCOLOR_BGR2HSVile çevirirsen saf kırmızı hue 120 (mavi) olarak çıkar: detector sessizce hiçbir şey görmez. - Hue birimi. Başka bir konvansiyonu kullanan araçtan kopyalanan eşik (yukarıdaki tablo) sessizce başka bir renk seçer.
- Kırmızı sarar. Tek bir kutusu 179 civarındaki pixel’leri kaçırır (Adım 2’de 742’nin 30’u).
- Bağlantı ve kenarlar.
cv2.connectedComponentsWithStatsvarsayılan olarak çapraz komşuları birleştirir (8-bağlantı);scipy.ndimage.label,structure=np.ones((3, 3))vermezsen birleştirmez. SciPy’ninbinary_erosion’ı görüntü dışını boş sayar (border_value=0) ve görüntü kenarına değen bir blob’u yer; OpenCV’nin varsayılanı yemez (scipy.ndimage.label,binary_erosion).
Yazdıktan sonra · 2 soru.
1. Bir MuJoCo görüntüsünü cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV)’ye veriyor ve kırmızı için eşikliyorsun. Ne olur?
- Hiçbir şey değişmez; HSV kanal sırasına bağlı değildir.
- Kırmızı ile mavi yer değiştirir, saf kırmızı bir pixel hue 120 alır ve detector sessizce hiçbir şey bulmaz.
- Görüntü BGR olmadığı için OpenCV hata verir.
Neden: Kütüphaneyle kullan, tuzaklar - MuJoCo’nun renderer’ı RGB, cv2.imread BGR döndürür. Dönüşüm hangisini aldığını bilemez; yanlış bayrak hata vermez.
2. Sıfırdan yaz, Adım 4’te blob’un ağırlık merkezi pixel’ini back-project etmek en fazla 1,09 mm, üst yüz noktalarının ortalaması 0,45 mm hata veriyor. Farkın çoğu nereden geliyor?
mask_centroid,cv2.momentsile uyuşmuyor.- Simülasyondaki depth görüntüsü gürültülü.
- Ağırlık merkezi tam bir pixel’e yuvarlanmak zorunda ve bir pixel üst yüzde 1,11 mm kaplıyor.
Neden: Adım 4 moments kontrolünü (0,0e+00) ve bir pixel’in kapladığı alanı (0,57 m’de 1,11 mm) yazdırıyor. Yüzlerce back-project edilmiş noktanın ortalaması yuvarlamayı, yan yüz noktalarını atmak da onların çekişini ortadan kaldırıyor.
Kurulum. Menagerie’den SO-101 (5 ms timestep) ve 30 rastgele konumda kırmızı, 30 mm’lik bir küp (0,05 kg), m, m; küp robota bakacak şekilde döndürülmüş (yalnızca konumu tahmin ediyoruz). Bir kamera 0,6 m’den dik aşağı bakıyor; fovy’den (ders 19), poz modelden. Görüntü alınırken kol görüş alanının dışında park hâlinde.
Pick rutini: çeneler arasındaki nokta üzerinde damped least squares IK (ders 10); gripper aşağı bakıyor, çene ekseni omuz–hedef doğrusu boyunca; 7 cm yukarıdan yaklaş, in, kapat, 10 cm kaldır. Başarı = küp başlangıç yüksekliğinin 5 cm’den fazla üstünde.
mask = detect(rgb) # HSV threshold + opening + largest blobxy = locate(mask, depth, K, R_wc, c) # back-project, mean of the top faceok = pick(m, d, xy) # IK + scripted grasp, lift testÇalıştır: uv run python experiments/21-object-perception/experiment.py (MuJoCo 3.17.0, OpenCV 5.0.0, NumPy 2.5.4, Apple M5, deterministic).
Adım 1: perception ne kadar doğru olmalı? Gerçek konuma, rastgele bir yönde sabit büyüklükte bir hata ekleyerek nişan al:
| Nişan hatası (mm) | 0 | 4 | 8 | 12 | 16 | 20 |
|---|---|---|---|---|---|---|
| Başarılı pick (30’da) | 30 | 29 | 21 | 15 | 11 | 7 |
Ground truth ile rutin 30/30 alır. Bu gripper ve küp için hata bütçesi birkaç milimetredir: 8 mm’de 30 pick’in 9’u başarısız.

Adım 2: ışık şiddeti (headlight, yani bakış noktasına bağlı ışık, ve directional light birlikte ölçekleniyor; eşikler , ‘te dondurulmuş; “üst HSV”, 1. konumda üst yüzün merkezindeki pixel):
| Ölçek | Üst HSV | Tespit | Medyan hata (mm) | En büyük hata (mm) | Başarılı pick |
|---|---|---|---|---|---|
| 0,1 | (0; 110; 72) | 0/30 | – | – | 0/30 |
| 0,2 | (0; 145; 107) | 30/30 | 0,1 | 0,2 | 30/30 |
| 0,35 | (0; 173; 158) | 30/30 | 0,1 | 0,2 | 30/30 |
| 0,5 | (0; 186; 211) | 30/30 | 0,2 | 0,3 | 30/30 |
| 1,0 | (0; 177; 255) | 30/30 | 0,3 | 0,4 | 30/30 |
| 1,5 | (0; 157; 255) | 30/30 | 0,3 | 0,7 | 30/30 |
| 2,0 | (0; 136; 255) | 30/30 | 0,5 | 0,8 | 30/30 |
| 3,0 | (0; 95; 255) | 1/30 | 14,9 | 14,9 | 0/30 |
Gözlem.
- Küp tespit edildiğinde hata 1 mm’nin altında kalıyor (en fazla 0,8 mm) ve her pick başarılı: simülasyonda calibration ve depth kusursuz olduğundan darboğaz konumlandırma değil.
- Hata bir uçurum: ölçek 0,2 ile 0,1 arasında 30/30’dan 0/30’a, 3,0’da 1/30’a. Loş ışık S’yi 110’a düşürüyor (< 120); aşırı pozlama V’yi 255’te doyuruyor ve S’yi 95’e kadar solduruyor.
- Ölçek 3,0’daki tek tespit 14,9 mm sapmış: eşiği yalnızca daha koyu bir yan yüz geçebildi, ağırlık merkezi yaklaşık yarım küp ötede ve pick başarısız. Kısmi tespit hiç tespitten kötüdür, çünkü robot ona göre davranır.
Headlight’ı kapat (ambient ışık 0,1) ve güneşi (directional light’ı) tepeden yataya, robotun ters tarafından gelecek şekilde alçalt:
| Yükseklik açısı (°) | Üst HSV | Tespit | Medyan hata (mm) | En büyük hata (mm) | Başarılı pick |
|---|---|---|---|---|---|
| 90 | (0; 185; 211) | 30/30 | 0,1 | 0,2 | 30/30 |
| 45 | (0; 224; 130) | 30/30 | 0,1 | 0,5 | 30/30 |
| 20 | (0; 224; 74) | 30/30 | 0,3 | 0,8 | 30/30 |
| 10 | (0; 223; 48) | 1/30 | 15,8 | 15,8 | 0/30 |
| 5 | (0; 219; 35) | 0/30 | – | – | 0/30 |
Hue 0’da kalıyor, saturation hatta artıyor; ama üst yüzün V’si güneşle birlikte düşüyor: 20°‘de 74, 10°‘de 48, yani ‘in altında. Aydınlanan yan yüz kameraya neredeyse kenardan görünüyor. Detector ya hiçbir şey raporlamıyor ya da 15,8 mm sapmış bir şerit.
Aynı sahnelerde ‘i 70’ten 25’e indirmek her şeyi geri getirir:
| Yükseklik açısı (°) | Tespit | Medyan hata (mm) | En büyük hata (mm) | Başarılı pick |
|---|---|---|---|---|
| 20 | 30/30 | 0,2 | 0,6 | 30/30 |
| 10 | 30/30 | 0,2 | 0,6 | 30/30 |
| 5 | 30/30 | 0,3 | 0,6 | 30/30 |
| nominal (ölçek 1,0) | 30/30 | 0,3 | 0,7 | 30/30 |
Bu bedava bir çözüm değil: daha düşük bir tabanı koyu kırmızı-kahverengi pixel’leri de kabul eder; bu sahnede dikkat dağıtıcı yok, dolayısıyla false positive’leri (küp diye raporlanan yanlış nesneleri) ölçmedik. Gerçek bir masada kahverengi bir kutuyu ya da ahşap bir yüzeyi de yakalamasını bekle; öğrenilmiş bir detector’ün hesap maliyetini hak ettiği yer burasıdır.
Gerçek robotta
Bölüm başlığı “Gerçek robotta”- Webcam’ler auto-exposure ve auto-white-balance çalıştırır; bunlar V’yi ve hue’yu frame’den frame’e değiştirir. İkisini de kilitle, sonra robotun göreceği en kötü ışıkta ayarla. Gerçek kolda henüz ölçülmedi.
- Gerçek hata kaynakları yukarıdaki birkaç milimetrelik bütçeye karşı toplanır: pixel noise’u ve izdüşümü (ders 20: simülasyonda 0,6 m’den 1 px’te medyan 1,60 mm), extrinsic ya da hand-eye calibration hatası (ders 19), depth noise’u ve kolun kendi eklem hatası. Milimetre altı sayılarımız, kusursuz calibration ve depth ile alınmış simülasyon sonuçlarıdır.
- Başarısız her pick’i görüntüsü ve nesnedeki HSV değerleriyle birlikte logla; bir eşik hatası orada görünür.
Kendini dene
Bölüm başlığı “Kendini dene”Hızlı kontrol · 2 soru. Bir cevaba tıkla.
1. Işık ölçeği 3,0’da kırmızı küp hiç olmadığı kadar parlak, ama HSV detector onu 30 sahnenin yalnızca 1’inde buluyor. Neden?
- Işık parlakken hue kırmızıdan uzaklaşır.
- Kanallar 255’te kırpılır, renk beyaza doğru solar ve saturation ‘in altına düşer (95 < 120).
- V, detector’ün bir üst eşiğini aşar.
Neden: 255’te takılıp artmaya devam edince düşmek zorunda. Adım 2’nin her satırında hue 0’da kalıyor.
2. ‘i 70’ten 25’e indirmek yatay güneş ışığında 30/30 pick’i geri getiriyor. Bunun bedeli ne?
- Normal ışıktaki pick’ler 21/30’a düşüyor.
- Konumlandırma hatası 5 mm’nin üstüne çıkıyor.
- Daha düşük bir V tabanı koyu kırmızı-kahverengi pixel’leri de kabul ediyor ve bu sahnede false positive’ler ölçülmedi.
Neden: ile normal satır yine 30/30, en fazla 0,7 mm hata. Sahnede dikkat dağıtıcı nesne yok; kahverengi bir kutu ya da ahşap bir masa da geçebilir.
- Kolay. OpenCV’de kırmızı neden iki hue aralığı ister?
- Orta. Ölçek 3,0’da ışık daha parlak olduğu hâlde küp neden daha az doygun görünüyor?
- Zor. Küpün yanına koyu kırmızı bir dikkat dağıtıcı kutu (rgba 0,4; 0,15; 0,1) ekle ve Adım 2’yi ve ile tekrar çalıştır. Hangi eşik artık yanlış nesneyi alıyor ve onu nasıl elersin (boyut, şekil, depth)?
Cevaplar
Bölüm başlığı “Cevaplar”- Hue bir açıdır; kırmızı 0°/360°‘tedir ve OpenCV bunu 0–179’un iki ucuna eşler (Sıfırdan yaz, Adım 2: küpün 742 pixel’inin 30’u 179 civarında).
- Renderer her kanalı 255’te kırpar. Önce kırmızı kanal doyar, yeşil ve mavi ise artmaya devam eder; renk beyaza kayar ve düşer (ölçek 3,0’da 95).
- Burada hesaplanmadı; çalıştır. Dikkat dağıtıcının temel renginin hue’su 10° (OpenCV’de H = 5), S’si 0,75 (8 bit’te 191); ikisi de pencerelerin içinde, yani onu yalnızca V ayırır; V = 70’in üstünde kararı en büyük blob kuralı verir. Blob boyutu, karelik ya da üst yüzünün yüksekliği onu eleyebilir.
Akılda kalanlar ve kaynaklar
Bölüm başlığı “Akılda kalanlar ve kaynaklar”- Döngüyü kapat: metrik pick başarısıdır. Burada 4 mm nişan hatası hâlâ 29/30 pick verdi, 8 mm yalnızca 21/30.
- Renk eşiği, V ya da S’nin sınırını geçtiği ışıkta aniden bozulur (ölçek 0,1, ölçek 3,0, 10°‘de güneş); tespit ettiğinde simülasyondaki konumlandırma milimetrenin altındadır.
- Open-vocabulary detector’ler, tek bir renk penceresine bağlı olmamak için frame başına hesap maliyeti öder; hızları ve sağlamlıkları burada ölçülmedi.
Sıradaki: ders 22, motion planning.
Kaynaklar:
- M. Minderer vd., “Simple Open-Vocabulary Object Detection with Vision Transformers”, ECCV 2022. arXiv:2205.06230
- OpenCV · color conversions: HSV formülleri; 8-bit görüntülerde H, H/2 olarak saklanır, yani hue 0–179 aralığındadır.
- MuJoCo Menagerie · robotstudio_so101 (Apache-2.0).
- Back-projection ve depth: ders 20. Calibration: ders 19.
- Yukarıdaki tüm sayılar:
experiment.py, çıktıoutput.txt; Sıfırdan yaz:build.py,build_output.txt.