5.33. ImageIO akışları

save() ve to_jpeg(), tek çerçeveli G/Ç durumunu kapsar: bir uygulama bir çerçeve yakalar, onu kodlar ve bir yere gönderir. Farklı bir uygulama sınıfı ise dizi durumuna ihtiyaç duyar: doğal yakalama hızında arka arkaya birçok çerçeveyi kaydetmek, bunları daha sonra geri alınabilecekleri bir yerde saklamak ve doğru hızda geri oynatmak. Bir eğitim verisi toplama betiği, bir makine öğrenimi işlem hattı için birkaç yüz örnek çerçeve yakalar; bir denetim istasyonu kaydı, izlenebilirlik için yakalanan her parçayı kaydeder; bir geliştirme betiği, daha önce canlı olarak yakalanmış verilere karşı yeni bir algoritmayı test etmek üzere saklanmış bir diziyi yeniden oynatır.

ImageIO sınıfı, image modülünün kaydedicisi / oynatıcısıdır. Tek bir akış, bir Image çerçeveleri dizisini – muhtemelen farklı boyut ve piksel formatlarında – her birinin çerçeveler arası aralığıyla birlikte tutar; böylece oynatma, orijinal çerçeve hızını yeniden oluşturabilir. İki destekleyici depolama mevcuttur: dosya sistemindeki bir dosya veya RAM’deki sabit boyutlu bir arabellek.

5.33.1. İki destekleyici depolama

Bir dosya akışı, kaydı güç döngüleri arasında kalıcı kılar ve yalnızca onu destekleyen depolama tarafından boyutlandırılır. 16 baytlık bir sihirli başlık OMV IMG STR Vx.y ile başlar, ardından çerçeve başına bir yığın gelir; mevcut yazıcı V2.0 üretir ve okuyucu, geriye dönük uyumluluk için V1.0 ve V1.1 dosyalarını hâlâ kabul eder. Dosya yolu, yapıcı argümanıdır; mod ise dosya açma modudur (mevcut bir akışı okumak için 'r', kesip yeniden yazmak için 'w').

# Recording to /sdcard/run.bin
stream = image.ImageIO("/sdcard/run.bin", "w")
for _ in range(120):
    img = csi0.snapshot()
    stream.write(img)
stream.close()

Bir bellek akışı, oluşturulurken ayrılan bir RAM arabelleğinde yer alır. Yapıcı, bir yol yerine bir (w, h, pixformat) 3’lü demeti alır ve mode argümanı önceden ayrılan çerçeve yuvası sayısı hâline gelir. Arabellek, sağlanan boyutlarda tam olarak o kadar çerçeve için boyutlandırılır ve bir kez ayrıldıktan sonra büyümesine izin verilmez – son yuvanın ötesine yazmak EOFError üretir ve yuva başına arabellekten daha büyük bir çerçeve yazmak ValueError üretir. Bellek akışları, uygulamanın dosya sistemine girmeden bir aşağı akış aşamasına bir kayıt iletmesi gerektiğinde doğru araçtır (örneğin, bir tetikle-ve-yeniden-oynat deseni için son çerçevelerden oluşan kısa bir halka arabelleği).

# Pre-allocate space for 32 QVGA RGB565 frames in RAM
stream = image.ImageIO((320, 240, image.RGB565), 32)
for _ in range(32):
    stream.write(csi0.snapshot())

Sıkıştırılmış piksel formatları (image.JPEG, image.PNG) için yuva başına boyut, piksel başına 2 bit olarak tahmin edilir; tahminden daha büyük bir kodlanmış çerçeve, yazma sırasında ValueError üretir; bu nedenle yüksek kaliteli JPEG’ler saklamayı bekleyen bir uygulamanın ya yuva sayısını fazladan ayırması ya da önce daha düşük kalitede kodlaması gerekir.

type(), image.ImageIO.FILE_STREAM veya image.ImageIO.MEMORY_STREAM döndürür; böylece aşağı akış kodu, kendisine verilen destekleyici depolamaya uyum sağlayabilir.

5.33.2. Kaydetme

write(), yakalanan bir Image öğesini bir dosya akışına ekler (veya bir bellek akışının mevcut yuvasında saklar) ve ofseti bir artırır. Aynı çağrı, son yazmadan bu yana geçen çerçeveler arası aralığı da kaydeder; böylece oynatma tarafı çerçeveler arasında doğru süre kadar duraklatabilir ve kaydın doğal çerçeve hızı korunur.

Tek bir dosya akışı içinde heterojen çerçevelere izin verilir: bir kayıt, RGB565 yakalamaları, gri tonlamalı kırpmaları ve JPEG ile kodlanmış küçük resimleri özgürce harmanlayabilir ve okuyucu her birini orijinal boyutunda ve formatında çözer. Bellek akışları homojendir (tüm yuvalar yapıcıyla sağlanan (w, h, pixformat) değerini paylaşır), bu nedenle bir bellek kaydı tek bir çerçeve yapılandırmasıyla sınırlıdır.

write(), çağrıların zincirlenebilmesi için akış nesnesini döndürür. Bir dosya akışının son olmayan bir ofsetine yazmak, dosyanın geri kalanını keser – saklanan bir diziyi düzenlemek için yararlıdır, ancak bir sonraki yazma konumu daha önceki bir seek() tarafından istemeden taşındıysa risklidir.

sync(), dosya akışları için bekleyen yazmaları diske aktarır (bellek akışlarında işlem yapmaz) ve kayıt uzun sürdüğünde, dosya kapatılmadan önce kamera yeniden başlatılırsa kaydın sonunu kaybetmemek için düzenli olarak çağrılmalıdır. ImageIO kapsam dışına çıktığında yıkıcı, akışı otomatik olarak kapatır, ancak açık bir close() çağrısı doğru disiplindir.

5.33.3. Oynatma

read(), mevcut ofsetteki çerçeveyi okur, ofseti ilerletir ve yeni Image öğesini döndürür. copy_to_fb=True (varsayılan) olduğunda alıcı çerçeve arabelleğinde kalır; böylece döndürülen görüntü IDE önizlemesi aracılığıyla çizilebilir; copy_to_fb=False ile çerçeve MicroPython yığınına iner.

# Loop a recorded stream at its natural frame rate
stream = image.ImageIO("/sdcard/run.bin", "r")
while True:
    img = stream.read()
    # img is now in the frame buffer; the IDE shows it
    # and the script can run any analysis it likes

İki anahtar sözcük oynatma davranışını denetler. loop=True (dosya akışları için varsayılan), kaydın sonuna ulaşıldığında okuma işaretçisini başa döndürür; böylece çağrı asla None döndürmez; loop=False, kayıt tükendikten sonra None döndürür ve çağıranın döngüsü sonlanır. pause=True (varsayılan), yazma sırasında kaydedilen çerçeveler arası aralık geçene kadar çağrıyı engeller; böylece oynatma çerçeve hızı orijinal yakalama çerçeve hızıyla eşleşir; pause=False ise hemen döner ve orijinal zamanlamaya uymaksızın kaydı mümkün olduğunca hızlı işlemek isteyen analiz işlem hatları için yararlıdır.

Aynı döngü deseni bellek akışları için de çalışır, ancak loop yok sayılır – bir bellek akışının sonunun ötesinde okuma yapmak EOFError üretir. Bir bellek halkası için beklenen desen, sarma istendiğinde açıkça sıfıra seek() yapmaktır.

5.33.5. Ana bilgisayarda oynatılabilir kayıtlar

ImageIO akışları, kayıt kamerada geri oynatılacaksa doğru araçtır – yakalanan her çerçeveyi kendi yerel piksel formatında korurlar, çerçeveler arası aralık tam olarak kaydedilir ve bir aşağı akış betiği bunlar arasında adım adım ilerleyebilir, arama yapabilir ve hiç kayıp olmadan yeniden analiz edebilir. Ancak kaydın bir ana bilgisayarda – bir iş istasyonu, telefon veya web oynatıcı – oynatılabilir olması gerektiğinde doğru araç değillerdir. Bir ana bilgisayar, OpenMV diskteki sihirli başlık formatını değil, standart bir video kapsayıcısı bekler.

Ana bilgisayarda oynatılabilir durumu iki ayrı modül kapsar. mjpeg modülü Motion JPEG kaydeder: VLC, QuickTime, ffmpeg ve standart web video etiketinin doğrudan oynattığı, tek bir AVI tarzı kapsayıcıya paketlenmiş JPEG ile sıkıştırılmış çerçeveler dizisi. gif modülü ise animasyonlu bir GIF kaydeder: açık çerçeve başına gecikmelere sahip, sıkıştırılmamış (veya palet ile sıkıştırılmış) çerçeveler dizisi; animasyonlu GIF’leri işleyen herhangi bir web tarayıcısında veya görüntü görüntüleyicisinde oynatılabilir.

mjpeg modülü, uzun kayıtlar için doğal tercihtir. JPEG sıkıştırması dosya boyutunu yönetilebilir tutar – yapılandırılan kalitede to_jpeg() ile karşılaştırılabilir, çerçeve çerçeve – böylece uzun bir yakalama oturumu SD kartının bütçesi içinde kalır. Kullanım, ImageIO kaydını yakından yansıtır:

import mjpeg

m = mjpeg.Mjpeg("/sdcard/run.mjpeg")
while running:
    m.add_frame(csi0.snapshot(), quality=85)
m.close()

mjpeg.Mjpeg, diğer image yöntemlerinin aldığı aynı çizim stilinde konumsal ve ölçek anahtar sözcüklerini kabul eder; böylece bir kayıt, içeri girerken çerçeve başına ölçeklenebilir, kırpılabilir veya palet ile eşlenebilir. Yapıcının width ve height argümanları varsayılan olarak ana çerçeve arabelleğinin boyutlarına ayarlanır ve çıkış çözünürlüğünü sabitler; eklenen her çerçeve sığacak şekilde (en boy oranı korunarak) ölçeklenir. sync(), uzun bir kayıt sırasında dosyayı diske aktarır ve close(), kapsayıcıyı sonlandırır – temiz bir şekilde kapatılmamış bir Motion JPEG dosyası oynatılamaz, bu nedenle disiplin önemlidir.

gif modülü, teknik olmayan bir izleyiciyle olduğu gibi paylaşılan kısa kayıtlar için doğal tercihtir – bir demo için yakalanan birkaç saniyelik eylem, dokümantasyon için animasyonlu bir illüstrasyon, bir sohbet mesajına gömülü bir olay klibi. GIF çerçeveleri sıkıştırılmamış olarak (veya 7 bit renk derinliğinde palet ile sıkıştırılmış olarak) saklanır; bu da dosyaları saniye başına Motion JPEG’den çok daha büyük yapar ve formatı birkaç saniyeden uzun kayıtlar için elemeye alır, ancak sonuç doğrudan herhangi bir tarayıcıya bırakılır:

import gif

g = gif.Gif("/sdcard/clip.gif")
while running:
    g.add_frame(csi0.snapshot(), delay=10)
g.close()

add_frame() üzerindeki delay argümanı, santisaniye cinsinden çerçeve başına görüntüleme süresidir (10, çerçeve başına 100 ms’dir, yani 10 fps), ki bu standart GIF oynatma denetimidir. Yapıcının loop anahtar sözcüğü, ortaya çıkan klibin görüntüleyicilerde otomatik olarak döngüye girip girmeyeceğini ayarlar (varsayılan True olup, geleneksel “animasyonlu GIF” beklentisiyle eşleşir).

Üç kayıt yolu, aralarında yaygın durumları kapsar: kamerada yeniden işleme için ImageIO, uzun ana bilgisayarda oynatılabilir kayıtlar için Motion JPEG, kısa ana bilgisayarda oynatılabilir klipler için animasyonlu GIF. Aralarındaki seçim, kaydı kimin geri oynatacağına bağlıdır. Kameranın kendisinde çalışan bir aşağı akış aşaması ImageIO okur; bir ana bilgisayar iş istasyonu veya web görüntüleyicisi MJPEG veya GIF okur.

5.33.6. Bir tetikle-ve-yeniden-oynat deseni

Yararlı bir desen, bir bellek akışını bir tetik koşuluyla birleştirir. Kamera, count yuvalı bir bellek halka arabelleğine sürekli olarak kaydeder ve her turda en eski yuvanın üzerine yazar. Bir tetik koşulu tetiklendiğinde (bir nokta çerçeveye girer, bir hareket olayı eşiği aşar, bir düğmeye basılır) uygulama, halkanın içeriğinin – en son count çerçevenin – anlık görüntüsünü alır ve bunları SD karttaki bir dosya akışına yazar. Sonuç, kameranın gerçekten fark ettiği olaydan sonraki saniyeleri değil, önceki saniyeleri de yakalayan bir ön tetik kaydıdır; bu, naif bir “tetiklenince yakala” kaydedicisinin klasik sınırlamasıdır.

Akış sınıfları elde edildiğinde uygulama basittir: sabit boyutlu bir bellek akışı halka görevi görür (ofset yuva sayısına ulaştığında açıkça sıfıra seek() ile), ana döngü her yinelemede içine yakalar ve tetik işleyicisi bellek akışını çerçeve çerçeve okur ve her birini tetiğin zaman damgasıyla adlandırılan bir dosya akışına yazar.