Issue when image loading on raspberry pi

382 views
Skip to first unread message

Federico daniele

unread,
Feb 6, 2014, 4:40:54 PM2/6/14
to kivy-...@googlegroups.com
I'm trying to make some photo slideshow. I'm working on a Raspberry pi model B (512mb RAM). The thing is that with my code, wich use 2 Image widgets, one to show the image and the other to load the image that will be showed next, on a scatter layout, my code only show 6 images, and then it shows black squares. i had the raspberry configured with the memory split 128 RAM for de GPU and the rest for the CPU. Then I changed that split to 256 to GPU and 256 to the CPU and it shows 12 images (1920x1080 images). That makes me think that the issue is a memory issue. Something like kivy didn't unload the images from RAM when I load a new source for a given Image widget.
My code is pretty messy, So i've tried the Picture demo in the examples folder, copy more photos to the image folder, and I get the same behavior. I've tried using the nocache=True property and it makes no diference. When I resized the images making them smaller, both programs loads more images, but the problem persist.
So, do anyone have an idea of how to deal with this?, is there a manual way to explicitly unload images from the GPU RAM?
This problem don't seems to appear when i run the code on my linux PC.
Thanks in advance.

ps: pardon my english

skeezix

unread,
Feb 6, 2014, 4:56:35 PM2/6/14
to kivy-...@googlegroups.com
On Thu, 6 Feb 2014, Federico daniele wrote:

# My code is pretty messy, So i've tried the Picture demo in the examples folder, copy more photos to the image folder, and I get the same behavior. I've tried using
# the nocache=True property and it makes no diference. When I resized the images making them smaller, both programs loads more images, but the problem persist.
# So, do anyone have an idea of how to deal with this?, is there a manual way to explicitly unload images from the GPU RAM?

Can you share any of the code?

Almost certainly a memory leak, but it will be hard to assist
without seeing whats what. It is easy to get stuck in a memory leak,
imho..

jeff

--
If everyone would put barbecue sauce on their food, there would be no war.

Federico daniele

unread,
Feb 6, 2014, 5:08:54 PM2/6/14
to kivy-...@googlegroups.com
I do know that this code is hideous, but there it is


import kivy
from kivy.uix.scatter import Scatter
from kivy.app import App
import os
from kivy.properties import ListProperty
from kivy.uix.image import Image
from kivy.animation import Animation
from kivy.properties import StringProperty
from kivy.clock import Clock
import random
from kivy.uix.widget import Widget
import re
## IMPORTS PARA LOS BOTONES

import RPi.GPIO as GPIO
import atexit
import time

# setup de los gpio

def gpio_init():
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    atexit.register(GPIO.cleanup)

class MyScatter(Scatter):
    fuente = StringProperty()

class Picture(Image):
    source = StringProperty(None)
 

class BoothApp(App):
   
    lista_fotos = ListProperty()
    lista_carpetas_fotos = ListProperty()
    foto_actual=0
       

    def build(self):
        #self.v=Ventana() #no se si hace falta, para arreglar el size de las fotos
        #algunas, por ahora, constantes
        self.nombre_foto='test'
        self.ultima_foto = 0
        self.timeout=5000 #son 5 segundos
        self.mostrar_foto_nueva = False
        self.s = MyScatter(size=(100, 100), size_hint=(None, None))
        #s.top = 500
        #self.v.add_widget(self.s)
        self.crear_lista()
        self.foto1=Picture(source='./fotos_booth/' + self.lista_fotos[self.foto_actual],nocache=True)
        self.foto_actual=+1
        self.widget_actual=1
        self.foto2=Picture(source='./fotos_booth/' + self.lista_fotos[self.foto_actual],nocache=True)
        self.s.add_widget(self.foto1)
        self.anim=Animation(pos=(100,100),scale=1.5,t='in_out_quart',d=4)
        self.anim.start(self.s)
        Clock.schedule_once(self.proxima_foto, 5)
        #aca le meto la funcion que va a llamar cuando aprete el boton de foto
    gpio_init()
        GPIO.add_event_detect(17, GPIO.FALLING, callback=self.sacar_foto, bouncetime=300)
        return self.s

    def crear_lista(self):
        self.lista_fotos = os.listdir('./fotos_booth')
        #print self.lista_fotos
        for archivo in self.lista_fotos:
            if not archivo.endswith('.jpg'):
                self.lista_fotos.remove(archivo)
        self.lista_fotos.sort()
        print 'la ultima foto es %s' %self.lista_fotos[ len(self.lista_fotos) -1 ]
        self.ultima_foto = re.findall(r'\d+', self.lista_fotos[len(self.lista_fotos)-1])
        self.ultima_foto = int(self.ultima_foto[ len(self.ultima_foto) -1 ])
        print 'el ultimo numero es %d' % self.ultima_foto

    def proxima_foto(self,*largs):
        if self.mostrar_foto_nueva:
            self.s.remove_widget(self.foto_nueva)
            self.mostrar_foto_nueva = False
        self.foto_actual=self.foto_actual+1
        if self.foto_actual >= len(self.lista_fotos): self.foto_actual = 0
        if self.widget_actual == 1:
            #self.s.pos=(random.randint(0,300),random.randint(0,300))
            try:
            self.s.remove_widget(self.foto1)
        except:
        print 'no pudo remover el widget 1'
            self.s.scale=1
            self.s.add_widget(self.foto2)
            self.s.pos=(random.randint(0,300),random.randint(0,300))
            self.widget_actual=2
            self.foto1=Picture(source='./fotos_booth/' + self.lista_fotos[self.foto_actual],nocache=True)
           
        elif self.widget_actual == 2:
            try:
        self.s.remove_widget(self.foto2)
        except:
        print 'no pudo remover el widget 2'
            #self.s.pos=(random.randint(0,300),random.randint(0,300))
            self.s.scale=1
            self.s.add_widget(self.foto1)
            self.s.pos=(random.randint(0,self.s.width),random.randint(0,self.s.height))
            self.widget_actual=1
            self.foto2=Picture(source='./fotos_booth/' + self.lista_fotos[self.foto_actual],nocache=True)

           
        self.anim.start(self.s)
        print self.s.pos
        Clock.schedule_once(self.proxima_foto, 5)

    def ruidito():
        os.system('aplay camera1.wav')
       
    def sacar_foto(self,channel,*largs):
        #detengo la presentacion
        Clock.unschedule(self.proxima_foto)
        #envio el comando para sacar la foto
    print self.ultima_foto
        self.ultima_foto +=1
    print self.ultima_foto
    print 'raspistill -t %d -o ./fotos_booth/%s%03d.jpg -p 800,100,800,450' % ( self.timeout, self.nombre_foto, self.ultima_foto)
        os.system('raspistill -t %d -w 800 -h 450 -o ./fotos_booth/%s%03d.jpg -p 800,100,800,450' % ( self.timeout, self.nombre_foto, self.ultima_foto) )
        self.lista_fotos.append( '%s%d.jpg' % (self.nombre_foto,self.ultima_foto) )
        Clock.schedule_once( self.ruidito , self.timeout )
        self.foto_nueva=Picture( source='./fotos_booth/%s%03d.jpg'%(self.nombre_foto,self.ultima_foto), nocache=True)
        self.s.add_widget( self.foto_nueva )
        Clock.schedule_once(self.proxima_foto,7)
        self.mostrar_foto_nueva=True
       
       

BoothApp().run()



-----

Federico daniele

unread,
Feb 6, 2014, 5:17:19 PM2/6/14
to kivy-...@googlegroups.com
the actual meat of the program is in proxima_foto(). some identation problems in the past response
I'm new at this


def proxima_foto(self,*largs):
        if self.mostrar_foto_nueva:
            self.s.remove_widget(self.foto_nueva)
            self.mostrar_foto_nueva = False
        self.foto_actual=self.foto_actual+1
        if self.foto_actual >= len(self.lista_fotos): self.foto_actual = 0
       
        #here is where I load the images

Mathieu Virbel

unread,
Feb 9, 2014, 7:30:26 AM2/9/14
to kivy-...@googlegroups.com
Hey Federico,

Memory management with GPU is not something we take care of. Mostly
because it's the job of the application and/or the drivers to do so.
Let me explain few points:

1/ the command we sent to the GPU are almost in one way: we send them
without checking the result. That's the fastest way to maximize the
rendering. When you call a GL functions, it doesn't execute it right
away, but it generate a command on the queue that will be executed as
soon as possible. That's it, if we ask something in return after each
command, even after each image loading in GPU, the application would be
_much_ slower, because it has to flush the pipeline to be able to get
what you want.

Imagine, if you load a full-hd image, it takes times to upload it into
the GPU. The glImage2d execution is not blocking. If you ask "is the
image correctly uploaded ?" with a glGetError, this command will block
until the image is uploaded. So you loose precious time for something else.

2/ the GPU memory is _shared_ between all the applications that use it.
There is no generic API for letting us know about the memory currently
available. There is proprietary API for NVIDIA and ATI for that: it
gives you the total/current usage/free memory of the whole GPU. Again,
even with thoses indicator, you're not the one that can manage the
memory: if the api tell you that you have 10MB left, you might take the
decision to load only one image. But: maybe one other app with quit and
free memory, or the inverse, between the time you ask for the free
memory and load, maybe there is no memory. Maybe the driver will take
the decision to free some resources too to let you the place for it. We
have _no_ way to tell what will going on.

3/ you can safely assume that a Raspberry Pi have -low- memory. So act
for it, do your math: a full hd image is 1920x1080 pixels. When you
unpack it into memory, most of the time, internal GPU format is BGRA. So
one full-hd image took: 1920 * 1080 * 4 = 8294400 bytes = ~8MB

With 128MB GPU Memory, you can only load:
>>> (128 * 1024 * 1024) / (1920 * 1080 * 4)
16

-> 16 full hd images.

Of course, if the memory is shared, and if anything else is loaded (all
teh vertex/indices/shader/etc, it takes memory too), it will be less.

Right now, i've no clue on how to check it without us providing an error
code when it fail. You might be able to check the platform and have
different limits about the number of simultaneous images. You can also
clear the Cache (check the documentation / mailing list for that).
Also, when you clear the cache / gpu instructions, they are _scheduled_
to be cleared on the next frame, not when you call the clear method. So
if you want to clear and load images, call the clear method, and
schedule the loading 1 to 2 frames after.

Good luck,

Mathieu

PS: few years before, i wrote a blog post about managing texture and do
some texture compression. You might be interested about it if you
control the images (ie, not an image explorer, but an app with a lot of
content). Dunno if the Rpi support any kind of texture compression.

http://txzone.net/2011/09/texture-compression-why-does-it-matter/
> --
> You received this message because you are subscribed to the Google
> Groups "Kivy users support" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to kivy-users+...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.

Federico daniele

unread,
Feb 17, 2014, 5:34:02 PM2/17/14
to kivy-...@googlegroups.com
Hi Mathieu:
Thanks for the reply, it was quite helpfull. It was indeed a memory issue, and I fixed using
    cache.remove('image')
    cache.remove('texture')
every time I unloaded a picture

I did realized that I couldn't load more than 12~14 pictures in the GPU RAM, the thing was that I did't need that anyway, becouse the program must show just 1 picture at the time. I was loading 2 images trying to hide the time spended in the loading process.
the link that you provided me was interesting, but i do not need to use compression, at least not for now.
thanks again, i was really blocked with this.
Reply all
Reply to author
Forward
0 new messages