Görevleri Tek Bir İşlem Hattında Birleştir
Tespit edip ardından sınıflandırma yapan bir ardışık düzen kur, izlenen her nesneyi her karede değil yalnızca bir kez sınıflandır ve poz anahtar noktalarını kişi başına zaman serilerine dönüştür.
Tespitin ötesindeki en kullanışlı sistemler genellikle tek değil, iki modelden oluşur. Bir dedektör her çalışanı bulur, bir sınıflandırıcı da her kaskın doğru türde olup olmadığını belirler; bir poz modeli eklemleri bulur, bir izleyici de tekrarları sayabileceğin bir zaman çizelgesine dönüştürür. Kod kısadır. Bu sistemleri bozan şey, ikinci aşamanın veri kümesinin ilk aşamadaki kırpımlarla eşleşmemesi ve her karedeki her nesneyle katlanan maliyettir — gelişigüzel çalıştırıldığında, on kişinin bulunduğu 30 FPS'lik bir akış saniyede 300 sınıflandırmaya mal olur.
Uygulamalı
Çoğu ürünü kapsayan iki yaklaşım#
İkisi de aynı kare ve aynı Ultralytics API ile başlar; ikinci aşamanın neyi kullandığı konusunda ayrılır. Nesne izleme için izleyici seçimi, ayarlama ve akış yönetimi ByteTrack, BoT-SORT ve TrackTrack ile Nesneleri İzleme konusuna girer; burada görevleri birleştirmeye odaklanıyoruz.
Tespit et, ardından kırpımları sınıflandır#
from ultralytics import YOLO
detector = YOLO("yolo26n.pt")
classifier = YOLO("yolo26n-cls.pt") # swap in your own fine-tuned classifier
result = detector("https://ultralytics.com/images/bus.jpg", conf=0.5)[0]
crops, labels = [], []
for box in result.boxes:
x1, y1, x2, y2 = box.xyxy[0].int().tolist()
crops.append(result.orig_img[y1:y2, x1:x2]) # BGR numpy crop, same as the detector saw
labels.append(result.names[int(box.cls)])
for label, crop_result in zip(labels, classifier(crops, verbose=False)): # one batched call for all crops
probs = crop_result.probs
print(f"{label:6s} -> {crop_result.names[probs.top1]} ({float(probs.top1conf):.2f})")bus -> bullet_train (0.31)
person -> sweatshirt (0.24)
person -> abaya (0.50)
person -> loudspeaker (0.49)
person -> frilled_lizard (0.24)İşlem hattı doğru: kırpımlar orig_img üzerinden tam çözünürlükte ve dedektörün kendi piksel koordinatlarında alınır; sınıflandırıcı da hepsini tek bir toplu çağrıda işler. Etiketler bilerek anlamsız seçildi.
ImageNet sınıflandırıcısı, sıkı kırpılmış sokak figürleriyle hiç eğitilmedi; bu yüzden tahminde bulunuyor — 0.50'nin üzerinde güven değerinin olmadığını unutma. Temiz ürün fotoğraflarıyla eğitilmiş bir sınıflandırıcıya bulanık dedektör kırpımları verildiğinde de aynı durum yaşanır.
İkinci aşama veri kümesini kendi kırpımlarından oluştur#
Eşleşen veriler elde etmenin güvenilir yolu, dedektörün bu verileri üretmesini sağlamaktır:

- Temsili görüntüler üzerinde dedektörü
save_crop=Trueile çalıştır; örneğinyolo predict model=best.pt source=footage/ save_crop=Truekullan. Kırpımlarruns/detect/predict/crops/<class>/içine kaydedilir. - Her sınıfa ait kırpımları ayrıntılı sınıf klasörlerine yerleştir (
helmet_ok/,helmet_missing/) — bu, önceki derste gördüğün sınıflandırma düzeninin aynısıdır. - Sınıflandırıcıyı bu klasörler üzerinde eğit ve farklı bir gün ya da kameradan alınan kırpımlarla doğrula.
Dedektör kırpımları oluşturduğu için bunlar gerçek çerçeveleme, bulanıklık ve örtülme özelliklerini taşır — yani sınıflandırıcının üretimde göreceği görüntüleri yansıtır — ve bunlar üzerinde ince ayar yapmak olağan bir aktarım öğrenimi uygulamasıdır.
Her izleme kimliği için bir kez sınıflandır#
Kask kareler arasında değişmediğinden her karedeki her kutuyu sınıflandırmak boşa harcanan iştir. Tespitleri izle ve her yeni izleme kimliğini bir kez sınıflandır:
from ultralytics import YOLO
detector = YOLO("yolo26n.pt")
classifier = YOLO("yolo26n-cls.pt") # swap in your own fine-tuned classifier
source = "https://github.com/ultralytics/assets/releases/download/v0.0.0/decelera_portrait_min.mov"
labels = {} # track id -> (label, confidence), filled once per person
frames = boxes_seen = 0
for result in detector.track(source, classes=[0], stream=True, persist=True, verbose=False): # people only
frames += 1
if result.boxes.id is None:
continue
for track_id, (x1, y1, x2, y2) in zip(result.boxes.id.int().tolist(), result.boxes.xyxy.int().tolist()):
boxes_seen += 1
if track_id not in labels: # first sighting: classify once, then reuse
probs = classifier(result.orig_img[y1:y2, x1:x2], verbose=False)[0].probs
labels[track_id] = (classifier.names[probs.top1], round(float(probs.top1conf), 2))
print(f"{frames} frames, {boxes_seen} person boxes, {len(labels)} classifier calls")
print(labels)11 frames, 33 person boxes, 3 classifier calls
{1: ('basketball', 0.51), 2: ('maillot_(tights)', 0.14), 3: ('bulletproof_vest', 0.23)}11 karelik bir klipte 33 yerine üç sınıflandırıcı çağrısı; uzun bir akışta kazanç, kişi görüntüde kaldığı her kareyle birlikte artar. Etiketler hâlâ ImageNet tahminleridir — ince ayar yaptığın sınıflandırıcıyı kullanırsan kişi başına yanıta dönüşürler.
Nitelik, nesne görüntüdeyken değişemiyorsa (kask türü, araç markası, ürün SKU'su) her iz için bir kez sınıflandır. Değişebiliyorsa (kapının açık olması, elin kaldırılması) her N karede bir yeniden sınıflandır. İlk görünümler genellikle küçük ve bulanıksa bunun yerine, izin kutusunun en büyük olduğu karede sınıflandır.
Poz ve izleme#

İzleme modu, kişi başına kalıcı bir result.boxes.id ekler; böylece anahtar noktalar zaman serisine dönüşür. Bu betik, aynı kısa videoda her kişinin sol diz açısını kaydeder:
from collections import defaultdict
import numpy as np
from ultralytics import YOLO
def angle(a, b, c):
"""Angle at b, in degrees, formed by points a-b-c."""
ba, bc = a - b, c - b
return np.degrees(np.arccos(np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))))
model = YOLO("yolo26n-pose.pt")
source = "https://github.com/ultralytics/assets/releases/download/v0.0.0/decelera_portrait_min.mov"
HIP, KNEE, ANKLE = 11, 13, 15 # left leg, COCO keypoint order
knee_angles = defaultdict(list) # track id -> knee angle per frame
for result in model.track(source, stream=True, persist=True, verbose=False):
if result.boxes.id is None: # no confirmed tracks in this frame
continue
kpts = result.keypoints.cpu().numpy()
for track_id, xy, conf in zip(result.boxes.id.int().tolist(), kpts.xy, kpts.conf):
if conf[[HIP, KNEE, ANKLE]].min() > 0.5: # skip occluded legs
knee_angles[track_id].append(angle(xy[HIP], xy[KNEE], xy[ANKLE]))
for track_id, angles in sorted(knee_angles.items()):
print(f"id {track_id}: {len(angles)} frames, left knee {min(angles):.0f}-{max(angles):.0f} deg")id 1: 11 frames, left knee 170-176 deg
id 2: 11 frames, left knee 96-118 deg
id 3: 11 frames, left knee 177-180 deg2 numaralı kimliğin bükülü dizi klipteki oturan kişiyle eşleşiyor; diğer iki kişi yaklaşık 180° açıyla bacaklarını düz tutarak ayakta duruyor. Buradan tekrar sayacı, her kimliğin serisindeki eşik geçişlerini sayar — bir açının altına inip başka bir açının üzerine tekrar çıkmayı — bu özellik antrenman izleme çözümünde kullanıma hazır hâle getirilmiştir.
Zincirin maliyetini hesapla#
| Maliyet etkeni | Tespit et, sonra sınıflandır | Poz ve izleme |
|---|---|---|
| Şuna göre artar | kare başına nesne sayısı | kareler |
| Temel kaldıraç | kırpımları toplu işle; her track ID için bir kez sınıflandır ve sonucu önbelleğe al | Hareket yavaşsa imgsz veya vid_stride değerini düşür |
| Hata modu | sınıflandırıcı, eğitim verilerindekilere benzemeyen kırpımları görür | ID değişimleri, tek bir kişinin geçmişini ikiye böler |
Belirti: Tekrar sayacı setin ortasında sıfırlanıyor veya aynı kişi iki farklı ID ile görünüyor. Neden: İzleyici kişiyi kaybetti (örtüşme, hızlı dönüş) ve yeni bir ID atadı. Çözüm: Üretim kursunda anlatıldığı gibi izleyiciyi ayarla ve ID başına mantığın kısa kesintileri tolere etmesini sağla — önbelleğe alınmış sınıflandırıcı etiketi yeni ID için yeniden hesaplanır.
Çözümü göster
Dedektör çağrısına save_crop=True ekle:
result = detector("https://ultralytics.com/images/bus.jpg", conf=0.5, save_crop=True)[0]Kırpımlar runs/detect/predict*/crops/person/ ve crops/bus/ altında görünür (tahmin çalıştırmalarında klasör adı her seferinde artar). Track başına önbelleğe alma, 11 karelik bir klipte 33 kişi kutusu için 3 sınıflandırıcı çağrısı yaptı ve 30 çağrı tasarruf sağladı.
Diz sayımı için pose_track.py kodunun son döngüsünü değiştir:
for track_id, angles in sorted(knee_angles.items()):
bent = sum(a < 120 for a in angles)
print(f"id {track_id}: {len(angles)} frames, {bent} with knee below 120 deg")id 1: 11 frames, 0 with knee below 120 deg
id 2: 11 frames, 11 with knee below 120 deg
id 3: 11 frames, 0 with knee below 120 degOturan kişi olan ID 2, 11 karenin tamamında 120°'nin altındadır; ID 1 ve 3 hiçbir karede bu durumda değildir.