
FIG. 01
KAYIT KÜNYESİ
Kategori
Yazılım
TARİH
İlk kez bir saha testinde fark ettik: tespit kutusu ekranda, İHA hedefin çoktan üzerinden geçtikten uzun süre sonra belirdi. Model çalışıyordu, kutu da doğru yerdeydi; ama o "doğru yer" birkaç saniye öncesinin dünyasıydı. Uçuş ilerledikçe fark büyümeye devam etti; ilk birkaç saniyede göze batmıyordu, birkaç dakika sonra ise video tamamen geçmişte yaşıyordu.
Bu, tespit modelinin değil, video okuma katmanının sorunu. RTSP akışı kareleri sabit bir hızda üretir; sizin döngünüz ise model çıkarımı yaptığı için daha yavaş dönebilir. Aradaki fark bir yerde birikmek zorunda; o yer de siz istesiniz ya da istemeyin var olan bir kuyruk. Kuyruk hiç boşalmadığı sürece cap.read() size sıradaki en yeni kareyi değil, en eski kareyi verir.
Bu yazıda SUAS 2026 hava aracımızda kullandığımız çözümü anlatıyoruz: SIYI A8 mini gimbal kameradan gelen 1080p RTSP akışını Jetson Orin NX üzerinde gecikmeyi biriktirmeden nasıl okuyoruz, hangi ayarlar gerçekten bir işe yarıyor ve hangileri sessizce hiçbir şey yapmıyor.
RTSP nedir, İHA kameralarında neden karşımıza çıkar?
RTSP (Real Time Streaming Protocol) video verisinin kendisini taşımaz; oturumu yönetir: "oynat", "duraklat" ve "bana şu akışı gönder" gibi komutların protokolüdür. Asıl video paketleri RTP (Real-time Transport Protocol) üzerinden akar. Kamera bir RTSP sunucusu çalıştırır, siz de ona bir URL ile bağlanırsınız.
İHA kameralarında bu kadar yaygın olmasının nedeni pratik: gimbal kamera companion bilgisayara Ethernet üzerinden bağlanır ve aynı akışı tek bir adresten hem yer kontrol istasyonuna hem de tespit koduna sunar. ArduPilot dokümantasyonunun SIYI A8 mini ve ZR10 için verdiği adres rtsp://192.168.144.25:8554/main.264, varsayılan IP ise 192.168.144.25.
Akışı OpenCV ile açmak
OpenCV'nin FFmpeg arka ucu RTSP'yi doğrudan destekler. FFmpeg'e gidecek seçenekler OPENCV_FFMPEG_CAPTURE_OPTIONS ortam değişkeni üzerinden aktarılır. Biçim alışılmadıktır: anahtarı değerinden bir noktalı virgül, bir çifti diğerinden bir dikey çizgi ayırır.
probesize ve analyzeduration varsayılanları akışın açılmasını uzatır; bu değerleri düşürmek ilk kareye kadar geçen süreyi kısaltır. Fazla düşürürseniz FFmpeg akış biçimini çözemez; o yüzden kademeli olarak azaltın ve isOpened() ne döndürüyor, ona bakın.
Asıl sorun: kuyruk büyür, video geride kalır
Basit aritmetik: akış 30 FPS üretiyorsa (kare başına ~33 ms) ve döngünüz saniyede 15 kare işliyorsa (kare başına ~67 ms), her saniye 15 kare geriye düşersiniz. 10 saniye sonra kuyrukta 150 kare birikir; bu da kabaca 5 saniyelik gecikme demektir. Bizim uçuş konfigürasyonumuzda YOLO11m, 640 piksel tam-kare çıkarımda 15-20 FPS çalışıyor; akış hızı bunun üzerinde kaldığı sürece fark kaçınılmaz olarak birikiyor.
Önemli olan şu: bu sabit bir gecikme değil, büyüyen bir gecikme. Sabit 200 ms'lik bir gecikmeyle yaşayabilirsiniz; her dakika büyüyen bir gecikmeyle otonom bırakma görevi uçuramazsınız.

Çözüm: yalnızca en yeni kareyi tutan bir okuyucu thread
Doğru zihinsel model şu: akışa bir kuyruk gibi değil, tek gözlü bir kutu gibi davranın. Arka planda çalışan bir thread sürekli okur ve her yeni kareyle bir öncekinin üzerine yazar. İşleme döngüsü kutuya her baktığında en güncel kareyi bulur; arada kaçırdığı kareler sessizce düşer. Bir tespit sisteminde tam olarak istediğimiz davranış budur: tazeliği kaybetmektense kare kaybetmeyi tercih ederiz.
Kullanımı, kare yaşı da ölçülerek:
Bu yazının en değerli satırı belki de şu age_ms satırı: gecikmeyi tahmin etmeyi bırakıp ölçmeye başlıyorsunuz. Sayı sabit kalıyorsa sorun yok; tırmanmaya devam ediyorsa bir yerde hâlâ kuyruk var.
CAP_PROP_BUFFERSIZE çoğu zaman neden hiçbir işe yaramaz?
İnternette en sık önerilen çözüm cap.set(cv2.CAP_PROP_BUFFERSIZE, 1). Oysa OpenCV 4.x kaynağında FFmpeg arka ucunun setProperty fonksiyonu bu özelliği hiç ele almaz. Yalnızca CAP_PROP_POS_MSEC, CAP_PROP_POS_FRAMES, CAP_PROP_POS_AVI_RATIO, CAP_PROP_FORMAT ve CAP_PROP_CONVERT_RGB işlenir. Yani RTSP + FFmpeg ikilisinde bu çağrı sessizce hiçbir şey yapmaz ve set() geriye False döner.
Yine de yukarıdaki sınıfta bıraktık: aynı kod bir USB kamerada (V4L2) ya da GStreamer arka ucunda çalıştığında etkisini gösteriyor. Kural şu: her set() çağrısının dönüş değerini mutlaka kontrol edin. OpenCV desteklenmeyen bir özellik için hata fırlatmaz, sadece False döner.
FFmpeg seçenekleri ve TCP/UDP kararı
Seçenek | Ne yapar | Ne zaman kullanılır |
|---|---|---|
| Taşıma katmanını seçer: | Varsayılanı bilerek ezmek istediğinizde |
| Mümkün olduğunda önce TCP'yi dener | OpenCV zaten varsayılan olarak bunu yapıyor |
| Akış analizi sırasındaki tamponlamayı azaltır | Her canlı akışta |
| Demux gecikmesinin üst sınırı (mikrosaniye) | Kayıp bir pakette takılıp kalmasını istemediğinizde |
| Sırasız paketleri yeniden sıralamak için tamponlanan paket sayısı | UDP'de tazelik isteyip kaybı göze alabildiğinizde |
| Soket tamponunun bayt cinsinden üst sınırı | Yüksek bit hızlı akışlarda paket düşerken |
| Soket TCP G/Ç zaman aşımı (mikrosaniye) | Ölü bir bağlantı sonsuza kadar bloklamasın diye |
rtsp:// adresleri için OpenCV, libavformat sürümüne göre varsayılan olarak ya rtsp_flags=prefer_tcp (yeni libavformat) ya da rtsp_transport=tcp (eskiler) uygular. Yani hiçbir şey yapmasanız da zaten TCP üzerindesiniz.
TCP | UDP | |
|---|---|---|
Paket kaybı | Yeniden gönderilir, kare bozulmaz | Kayıp yerinde kalır, blok bozulmaları görürsünüz |
Gecikme davranışı | Paket kaybolduğu anda gecikme sıçrar | Kayba rağmen gecikme sabit kalır |
Ağ geçidi/NAT | Genelde sorun çıkarmaz | Engellenebilir |
Ne zaman | Kaybın az olduğu kablolu Ethernet'te; ayrıca kayıt alırken | Telsiz ya da paylaşımlı linklerde, tazelik kare bütünlüğünden daha önemliyken |
Hava aracında gimbal ile companion bilgisayar arasındaki bağlantı kablolu Ethernet olduğu için TCP'de kaldık; kayıp zaten sıfıra yakınken UDP'nin sunduğu avantaj ortadan kalkıyor.
Yeniden bağlanma: uçuşta kaçınılmaz
Kamera yeniden başlar, bir kabloda mikro kopma olur, switch bir paketi yutar. Yukarıdaki _loop bunu hallediyor: art arda 5 başarısız read() çağrısından sonra capture nesnesini bırakıp bir sonraki turda yeniden açıyor. Tek bir False dönüşünün bağlantıyı yıkmaması önemli; geçici kare hataları normaldir.
Bir uyarı: OpenCV'nin read() ve açma çağrıları bloklayıcıdır ve varsayılan bekleme süreleri uzundur. CAP_PROP_OPEN_TIMEOUT_MSEC ve CAP_PROP_READ_TIMEOUT_MSEC bunları kısaltır, ama ikisi de yalnızca açılış anında geçerlidir: parametre listesinde verilmeleri gerekir, örneğin cv2.VideoCapture(url, cv2.CAP_FFMPEG, [cv2.CAP_PROP_OPEN_TIMEOUT_MSEC, 5000]) şeklinde; sonradan set() ile değiştirilemezler. Bazı opencv-python sürümleri bu sabitleri sunmaz, o yüzden hasattr(cv2, "CAP_PROP_OPEN_TIMEOUT_MSEC") ile kontrol edin.
Sık düşülen tuzaklar
cap.set(CAP_PROP_BUFFERSIZE, 1): bunu yazıp sorunun çözüldüğünü sanmak. FFmpeg arka ucunda hiçbir etkisi yok; dönüş değerini yazdırın.Ortam değişkenini
cv2.VideoCaptureçağrısından sonra ayarlamak. Değişken akış açılırken okunur; sonradan yapılan bir değişiklik o oturumu etkilemez.Ayraçları karıştırmak. Doğrusu
rtsp_transport;tcp|fflags;nobuffer; virgül ya da=kullanmak seçeneğin sessizce yok sayılmasına yol açar.Kodek seviyesindeki bayrakları demuxer sözlüğüne koymak.
OPENCV_FFMPEG_CAPTURE_OPTIONSiçeriği akış açılışına (avformat_open_input) gider.ffplay -flags low_delaygibi kodek bayraklarının bu yoldan geçtiğini varsaymayın; ölçmeden etkisine inanmayın.Kuyruğu bir
grab()döngüsüyle "boşaltmaya" çalışmak. Sabit sayıda kareyi atmak yalnızca o andaki hız farkına denk gelir; hız değiştiğinde ya gecikme geri gelir ya da boşuna CPU yakarsınız. Thread yaklaşımı ise kendini ayarlar.Kare yaşını hiç ölçmemek. Göz kararı "gecikmeli görünüyor" demek yerine tek satırlık bir zaman damgası ekleyin; hangi değişikliğin gerçekten işe yaradığını ancak böyle görürsünüz.
Jetson'da yazılım kod çözücüde kalmak. Orin NX'te donanım kod çözücüyü kullanan bir GStreamer pipeline'ı (
rtspsrc latency=0 ! rtph264depay ! h264parse ! nvv4l2decoder ! nvvidconv ! video/x-raw,format=BGRx ! videoconvert ! video/x-raw,format=BGR ! appsink drop=true max-buffers=1) CPU'yu tespit modeline bırakır;appsinküzerindekidrop=true max-buffers=1ise aynı "en yeni kare" mantığını pipeline'ın içine taşır.
Pratik özet
Akışı ana döngünüzde okumayın. Arka planda okuyup en yeni kareyi tek gözlü bir kutuda tutan bir thread kurun; kare düşürmek gecikme biriktirmekten iyidir.
Her karenin yakalanma zamanını saklayın ve işleme başlarken yaşını milisaniye cinsinden yazdırın. Sabit bir sayı iyidir; artan bir sayı hâlâ kuyruk olduğu anlamına gelir.
FFmpeg seçeneklerini, capture nesnesini oluşturmadan önce
OPENCV_FFMPEG_CAPTURE_OPTIONSüzerindenkey;value|key;valuebiçiminde geçirin.probesizeveanalyzedurationdeğerlerini düşürmek ilk kareye daha hızlı ulaşmanızı sağlar.Kablolu Ethernet üzerinde TCP'de kalın (OpenCV zaten varsayılan olarak TCP'yi tercih ediyor). UDP'yi yalnızca telsiz ya da paylaşımlı bir linkte ve yalnızca kare bütünlüğünden vazgeçebiliyorsanız düşünün.
Yeniden bağlanmayı en baştan yazın: capture'ı tek bir
read()hatasında değil, art arda birkaç başarısızlıktan sonra bırakıp yeniden açın; açma/okuma zaman aşımlarını constructor parametresi üzerinden kısaltın.
