12.8. Kehysten suoratoisto¶
Mukautetun kanavan yleisin käytännön sovellus on kuvakehysten suoratoisto kamerasta isäntäohjelmaan kameran kehysnopeudella. Mekaniikka on hienovaraisempaa kuin miltä näyttää: JPEG voi olla 25 KB tai enemmän, joten isäntä lukee sen useina paloina, ja kameran kaappaussilmukka on estettävä ylikirjoittamasta puskuria kesken luvun. Oikea malli – joka esitetään tässä ja jota työkalut hakemistossa openmv-projects/tools/ käyttävät – lukitsee puskurin, kunnes isäntä on hakenut viimeisen tavun.
12.8.1. Kameran puoli¶
Kehyskanava, joka kaappaa yhteen kehyspuskuriin, lukitsee sen isännän ensimmäisellä lukukerralla ja ottaa seuraavan tilannekuvan vasta kun isäntä on kuluttanut koko kuvan:
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
Neljä osaa tekee tässä varsinaisen työn:
frame_availableon lukko. Kaappaussilmukka ottaa uuden tilannekuvan vain, kun se onFalse– eli kun isäntä on hakenut edellisen kehyksen viimeisen tavun. Isännän luku palauttaa sen takaisin arvoonFalsereadp-metodin sisältä, kun viimeinen siirtymä on toimitettu. Ilman tätä suojausta seuraavacsi0.snapshot()ylikirjoittaisi puskurin kesken luvun, ja isäntä saisi kahdesta kaappauksesta yhteen ommellun kehyksen.Backend toteuttaa
readp-metodin eikäread-metodia. Protokollakirjasto kohtelee palautettua puskuria auktoritatiivisena ja lukee sen tavut suoraan lähtevään pakettiin – ilman kopiointia. Kehyksen kokoisille hyötykuormillereadpon huomattavasti nopeampi kuinread, joka pakottaa välikopion.sizepalauttaa välimuistissa olevan JPEG-pituuden laskematta mitään uudelleen; kaappaussilmukka ylläpitää sitä aina kun se päivittää puskurin. Isäntä kutsuusize-metodiapoll- jareadp-kutsujen välissä tietääkseen, montako tavua hakea.send_event()ilmoittaa isännälle heti uuden kehyksen saapuessa, jotta se voi alkaa hakea ilman pollausta. Tapahtuman tunnus0x01on sovelluskohtainen (tässä tapauksessa ”kehys valmis”); käytä kullekin ilmoitustyypille eri pientä kokonaislukua.
12.8.2. Pirstoutuminen¶
QVGA RGB565 JPEG-laadulla 85 pakkautuu noin 10-25 KB:hen näkymästä riippuen – paljon suuremmaksi kuin neuvoteltu enimmäishyötykuorma millään kameralla (katso laitekohtainen taulukko protocol.init()). Yksi JPEG-luku ei mahdu yhteen pakettiin, ja se on ihan hyvä, koska protokollakirjasto pirstoo sen läpinäkyvästi.
Kun isäntä pyytää channel_read('frame', 12000):
Kameran
readpkutsutaan kerran arvoillaoffset=0ja koko 12000 tavun pyynnöllä. Se palauttaa yhden memoryview-objektin, joka kattaa koko alueen.Protokollakirjasto pilkkoo tuon memoryview-objektin enimmäishyötykuorman kokoisiksi paloiksi linjalle, yksi
CHANNEL_READ-vastauspaketti palaa kohti, kullakin oma otsikko ja CRC. Tavut suoratoistetaan suoraan backendin puskurista – ilman kopiointia.Isäntä vastaanottaa palat järjestyksessä, luotettavuuskerros lähettää uudelleen jokaisen palan, joka epäonnistuu CRC-tarkistuksessaan, ja isännän SDK liimaa palat 12000 tavun tulokseksi, joka palautetaan kutsujalle.
Muista
Tämä on keskeinen käytännön ero readp- ja read-metodien välillä. readp kutsutaan kerran isäntäpyyntöä kohti; protokollakerros pirstoo ja lähettää yhdestä palautetusta puskurista. read kutsutaan kerran palaa kohti, ja kirjasto kopioi jokaisen palautetun palan omaan pakettipuskuriinsa. Kehyksen kokoisille hyötykuormille readp säästää sekä palakohtaisen Python-tason kutsun yleiskustannuksen että kopioinnin.
Vihje
Haluatko nähdä eron itse? Nimeä backendin readp-metodi uudelleen muotoon read – mikään muu ei muutu; kirjasto poimii sen sijaan read-kyvyn – ja vertaa isännän kehysnopeuslaskuria ennen ja jälkeen. Hitaampi luku on palakohtainen kopiointi- ja Python-kutsukustannus, jonka vältät käyttämällä readp-metodia.
FrameChannel.readp-metodin lukko vapauttaa puskurin, kun offset + size == img_size – sillä hetkellä, kun isäntä on hakenut viimeisen tavun. Siihen asti puskurin täytyy pysyä voimassa, minkä vuoksi kaappaussilmukka ottaa seuraavan tilannekuvan vasta kun frame_available kääntyy takaisin arvoon False.
12.8.3. Isännän puoli¶
Isäntä hakee kehyksiä tiukassa silmukassa:
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()-kutsu toimii samalla ”onko mitään valmiina” -tarkistuksena – nolla tarkoittaa, ettei kamera ole vielä kaapannut – joten silmukka ohittaa lukuyritykset tyhjällä puskurilla. GUI-sovelluksille, jotka jo pollaavat ajastimella, tämä on luonteva malli.
Pillow’n Image.open purkaa JPEG:n; kamera on jo JPEG-pakannut sen, joten isännän ei tarvitse tehdä uudelleen kallista bittien pakkausta RGB565:lle. Isäntäskripti voisi yhtä helposti tallentaa tavut levylle, antaa ne OpenCV:lle tai työntää ne web-näkymän läpi.
12.8.4. Läpäisykyvyn pohdintaa¶
Kolme asiaa rajoittaa saavutettavaa kehysnopeutta:
Kameran kaappausnopeus. Protokolla ei voi toimittaa kehyksiä nopeammin kuin sensori tuottaa niitä; mikä tahansa katto, jonka valittu pikseliformaatti ja kehyskoko asettavat kaappaukselle, on yläraja.
Neuvoteltu enimmäishyötykuorma. Suuremmat hyötykuormat tarkoittavat vähemmän paloja kehystä kohti ja vähemmän kehystyksen yleiskustannusta, joten kamerat, joilla on suuremmat protokollapuskurit, siirtävät tavuja nopeammin kuin pienemmät.
CRC:n ja ACK:n yleiskustannus. Jokainen paketti maksaa 14 tavua kehystystä plus yhden ACK-edestakaisen kierroksen. Pitkille paloille hyötykuormakohtainen yleiskustannus on pieni; pienille hyötykuormille se hallitsee.
Useimmassa kamerasta-kannettavalle GUI-työssä rajoittava tekijä on kameran kaappaus- ja JPEG-pakkausaika, ei protokollapino. Siellä missä protokollasta tulee pullonkaula – esimerkiksi pakkaamattomien raakojen kehysten suoratoistossa korkeilla kehysnopeuksilla – vivut ovat ACK:ien sammuttaminen (protocol.init(ack=False)), protokollapuskurin kasvattaminen jos kamera tukee sitä, tai kaappaus GRAYSCALE-tilassa, jolloin jokainen pakattu JPEG sisältää yhden kanavan kolmen sijaan ja koodattu kehys päätyy huomattavasti pienemmäksi linjalla.
Kehyskanava on kanoninen kamerasta-isännälle datavirta. Sama backend-rajapinta, johon on lisätty write-metodi, antaa isännän työntää dataa myös toiseen suuntaan – mikä on juuri se mitä interaktiivinen kameratyökalu tarvitsee heti kun käyttäjä haluaa muuttaa jotain eikä vain katsoa.