12.8. Çerçeve akışı (streaming)¶
Özel bir kanalın en yaygın gerçek kullanımı, görüntü çerçevelerini kameranın çerçeve hızında kameradan bir ana bilgisayar (host) programına akıtmaktır. Mekanik, göründüğünden daha inceliklidir: bir JPEG 25 KB veya daha fazlasına ulaşabilir, bu nedenle host onu birkaç parça hâlinde okur ve kameranın yakalama döngüsünün arabelleği okuma ortasında üzerine yazması engellenmelidir. Doğru desen – burada gösterilen ve openmv-projects/tools/ içindeki araçların kullandığı – host son baytı çekmeyi bitirene kadar arabelleği kilitler (latch).
12.8.1. Kamera tarafı¶
Tek bir çerçeve arabelleğine (frame buffer) yakalama yapan, host’un ilk okumasında onu kilitleyen (latch) ve yalnızca host tüm görüntüyü tükettikten sonra bir sonraki anlık görüntüyü alan bir çerçeve kanalı:
import csi
import protocol
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)
csi0.framebuffers(1)
img = csi0.snapshot()
img.compress(quality=85)
img_mv = memoryview(img.bytearray())
img_size = len(img_mv)
frame_available = True
class FrameChannel:
def poll(self):
return frame_available
def size(self):
return img_size
def readp(self, offset, size):
global frame_available
end = offset + size
mv = img_mv[offset:end]
if end == img_size:
# Host has just read the last byte of this frame --
# release the buffer so the capture loop can refresh.
frame_available = False
return mv
ch = protocol.register(name='frame', backend=FrameChannel())
while True:
if not frame_available:
img = csi0.snapshot()
img.compress(quality=85)
img_mv = memoryview(img.bytearray())
img_size = len(img_mv)
frame_available = True
ch.send_event(0x01) # notify host that a new frame is ready
Burada gerçek işi yapan dört parça var:
frame_availablekilittir (latch). Yakalama döngüsü yalnızca oFalseolduğunda – yani host önceki çerçevenin son baytını çektiğinde – yeni bir anlık görüntü alır. Host’un okuması, son ofset sunulduktan sonrareadpiçinden onu yenidenFalseolarak ayarlar. Bu koruma olmadan, bir sonrakicsi0.snapshot()arabelleği okuma ortasında üzerine yazar ve host iki yakalamadan birleştirilmiş bir çerçeve alırdı.Arka ucun (backend) uyguladığı
readdeğilreadpyöntemidir. Protokol kütüphanesi döndürülen arabelleği yetkili sayar ve baytlarını doğrudan giden pakete okur – kopyalama yapmaz. Çerçeve boyutundaki yükler içinreadp, bir ara kopyalamaya zorlayanread‘den belirgin biçimde daha hızlıdır.size, hiçbir şeyi yeniden hesaplamadan önbelleğe alınmış JPEG uzunluğunu döndürür; yakalama döngüsü arabelleği her tazelediğinde bunu günceller. Host, kaç bayt çekeceğini bilmek içinpollilereadparasındasize‘ı çağırır.send_event(), yeni bir çerçeve geldiği an host’a bildirir; böylece host yoklama (polling) yapmadan çekmeye başlayabilir. Olay kimliği0x01uygulama tanımlıdır (bu durumda “çerçeve hazır”); her bildirim türü için farklı küçük bir tam sayı kullanın.
12.8.2. Parçalama (fragmentation)¶
JPEG kalitesi 85’te QVGA RGB565, sahneye bağlı olarak kabaca 10-25 KB’a sıkışır – herhangi bir kameradaki anlaşılan maksimum yükten çok daha büyük (kart başına tabloya bakın: protocol.init()). Tek bir JPEG okuması tek bir pakete sığmaz ve bu sorun değildir, çünkü protokol kütüphanesi onu şeffaf biçimde parçalara böler.
Host channel_read('frame', 12000) istediğinde:
Kameranın
readp‘ıoffset=0ve 12000 baytlık tam istekle bir kez çağrılır. Tüm aralığı kapsayan tek bir memoryview döndürür.Protokol kütüphanesi bu memoryview’i kablo üzerinde maksimum yük boyutunda parçalara böler; her parça için kendi başlığı ve CRC’si olan bir
CHANNEL_READyanıt paketi gönderir. Baytlar doğrudan arka ucun arabelleğinden akıtılır – kopyalama yoktur.Host parçaları sırayla alır, güvenilirlik katmanı CRC’sini geçemeyen herhangi bir parçayı yeniden iletir ve host SDK’sı parçaları, çağırana döndürülen 12000 baytlık sonuca yapıştırır.
Not
Bu, readp ile read arasındaki temel pratik farktır. readp host isteği başına bir kez çağrılır; protokol katmanı, döndürülen tek arabellekten parçalara böler ve iletir. read ise parça başına bir kez çağrılır ve kütüphane döndürülen her parçayı kendi paket arabelleğine kopyalar. Çerçeve boyutundaki yükler için readp, hem parça başına Python düzeyindeki çağrı yükünü hem de kopyalamayı ortadan kaldırır.
Tüyo
Farkı kendiniz görmek ister misiniz? Arka ucun readp yöntemini read olarak yeniden adlandırın – başka hiçbir şey değişmez; kütüphane bunun yerine read yeteneğini alacaktır – ve host’un çerçeve hızı sayacını öncesi ve sonrasıyla karşılaştırın. Daha yavaş sayı, readp kullanarak kaçındığınız parça başına kopyalama ve Python çağrısı maliyetidir.
FrameChannel.readp içindeki kilit (latch), offset + size == img_size olduğunda – host’un son baytı çektiği anda – arabelleği serbest bırakır. O ana kadar arabellek geçerli kalmalıdır; bu nedenle yakalama döngüsü yalnızca frame_available yeniden False olduğunda bir sonraki anlık görüntüyü alır.
12.8.3. Host tarafı¶
Host, çerçeveleri sıkı bir döngüde çeker:
import io
from PIL import Image
from openmv.camera import Camera
with Camera('/dev/ttyACM0', baudrate=921600) as cam:
cam.update_channels()
while True:
size = cam.channel_size('frame')
if not size:
continue
data = cam.channel_read('frame', size)
img = Image.open(io.BytesIO(data))
img.show() # or feed to a GUI
channel_size() çağrısı aynı zamanda bir “hazır bir şey var mı” denetimi işlevi görür – sıfır, kameranın henüz yakalama yapmadığı anlamına gelir – böylece döngü boş bir arabellekte okuma denemelerini atlar. Zaten bir zamanlayıcı üzerinde yoklama yapan GUI uygulamaları için bu doğal desendir.
Pillow’un Image.open‘ı JPEG’i çözer; kamera onu zaten JPEG ile sıkıştırdığı için host, RGB565 üzerinde pahalı bit paketlemeyi yeniden yapmak zorunda kalmaz. Host betiği baytları diske kaydedebilir, OpenCV’ye verebilir veya bir web görünümüne aktarabilir; hepsi aynı kolaylıkta.
12.8.4. İş hacmi açısından düşünmek¶
Erişilebilir çerçeve hızını üç şey sınırlar:
Kameranın yakalama hızı. Protokol, çerçeveleri sensörün ürettiğinden daha hızlı teslim edemez; seçilen piksel biçiminin ve çerçeve boyutunun yakalamaya uyguladığı her sınır tavandır.
Anlaşılan maksimum yük. Daha büyük yükler, çerçeve başına daha az parça ve daha az çerçeveleme yükü anlamına gelir; bu nedenle daha büyük protokol arabellekleri olan kameralar baytları daha küçük olanlardan daha hızlı taşır.
CRC ve ACK yükü. Her paketin maliyeti 14 baytlık çerçeveleme artı bir ACK gidiş-dönüşüdür. Uzun parçalar için yük başına ek yük küçüktür; küçük yükler için ise baskındır.
Çoğu kameradan dizüstüne GUI çalışmasında sınırlayıcı etken, protokol yığını değil, kameranın yakalama ve JPEG sıkıştırma süresidir. Protokolün darboğaza dönüştüğü yerlerde – örneğin sıkıştırılmamış ham çerçeveleri yüksek çerçeve hızlarında akıtarken – kullanılabilecek kaldıraçlar şunlardır: ACK’leri kapatmak (protocol.init(ack=False)), kamera destekliyorsa protokol arabelleğini büyütmek veya GRAYSCALE’de yakalamak; böylece sıkıştırılmış her JPEG üç yerine tek kanal taşır ve kodlanmış çerçeve kabloda belirgin biçimde küçülür.
Çerçeve kanalı, kanonik kameradan host’a veri akışıdır. Aynı arka uç arabirimi, eklenen bir write yöntemiyle, host’un veriyi diğer yöne de göndermesine olanak tanır – ki bu, operatör yalnızca izlemek yerine bir şeyi değiştirmek istediği anda etkileşimli bir kamera aracının ihtiyaç duyduğu şeydir.