kivy load camera (zbarscan) on click button

Viewed 109

I've just started my first kivy app. The app is intended to start with button "Start Scan" and then show up the QR scanner built with ZBarCam.

I'm using Screens with the ScreenManager to change from the button view to the camera view (with zbarcam), the problem is that I realized that the camera is initialized from the beginning , so before clicking on the button the camera is already on (I know it because the led from the camera is on).

I don't know if Screen should not be used in this case, or if is there a way to tell the app to do not initialize all screens.

The code I'm using is the following:

QrApp.py:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen


class QrReader(Screen):
    pass

class ScanButton(Screen):
    pass

class QrApp(App):
    pass

if __name__ == '__main__':
    QrApp().run()

qrapp.kv:

ScreenManager:
    id: screen_manager
    ScanButton:
        id: scan_btn
        name: 'scan_btn'
        manager: 'screen_manager'
    QrReader:
        id: qr_reader
        name: 'qr_reader'
        manager: 'screen_manager'


<ScanButton>:
    BoxLayout:
        orientation: 'vertical'
        Button:
            text:'Start Scan'
            font_size:"50sp"
            color: [0, 255, 255, .67]
            on_press: app.root.current = 'qr_reader'

<QrReader>:
    #:import ZBarCam kivy_garden.zbarcam.ZBarCam
    BoxLayout:
        orientation: 'vertical'
        ZBarCam:
            id:qrcodecam
        Label:
            size_hint: None, None
            size: self.texture_size[0], 50
            text: ' '.join([str(symbol.data) for symbol in qrcodecam.symbols])

Thanks!

==== ALTERNATIVE BASED IN A COMMENT (still fails) ====

Based on the comment from n4321d I tried to add the ZBarCam as widget in the QrReader Screen. While I can now initiate the camera when the widget is added, I don't see how I can get the symbols that is, the text read from the QR.

This alternative code is the following:

QrApp.py:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen


class QrReader(Screen):
    def on_enter(self):
        from kivy_garden.zbarcam import ZBarCam
        zbarcam = ZBarCam()
        self.add_widget(zbarcam)
        self.add_widget(Label(
            text='PRINT SYMBOLS', #' '.join([str(symbol.data) for symbol in zbarcam.symbols] does not work
            size_hint=(None,None),
            size=(Window.width*0.1, Window.height*0.1),
            center=(Window.width*0.3, Window.height*0.5)))

class ScanButton(Screen):
    pass

class QrApp(App):
    pass

if __name__ == '__main__':
    QrApp().run()

qrapp.kv

ScreenManager:
    id: screen_manager
    ScanButton:
        id: scan_btn
        name: 'scan_btn'
        manager: 'screen_manager'
    QrReader:
        id: qr_reader
        name: 'qr_reader'
        manager: 'screen_manager'

<ScanButton>:
    BoxLayout:
        orientation: 'vertical'
        Button:
            text:'Start Scan'
            font_size:"50sp"
            color: [0, 255, 255, .67]
            on_press:
                app.root.current = 'qr_reader'


<QrReader>:
    BoxLayout:
        orientation: 'vertical'

====== SOLUTION ========

My workaround solution is posted as an answer to this question here

3 Answers

First: I would also add an on_leave function where you remove your cam widget otherwise you will keep adding new widgets every time you load it.

I dont have a working cam now , so i cannot test your code. Looking at your code I think you have to bind the text in your Label to the text in zbarcam.symbols with a function: self.label = Label(....); zbarcam.bind(symbols=lambda *x: setattr(self.label, "text", str(x[1]))) or somethign like that.

Here is an example using a random text generator instead of ZBarCam (since i cannot run that).

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ListProperty
import random
from kivy.clock import Clock


kv_str = """
ScreenManager:
    id: screen_manager
    ScanButton:
        id: scan_btn
        name: 'scan_btn'
        manager: 'screen_manager'
    QrReader:
        id: qr_reader
        name: 'qr_reader'
        manager: 'screen_manager'

<ScanButton>:
    BoxLayout:
        orientation: 'vertical'
        Button:
            text:'Start Scan'
            font_size:"50sp"
            color: [0, 255, 255, .67]
            on_press:
                app.root.current = 'qr_reader'


<QrReader>:
    BoxLayout:
        orientation: 'vertical'
"""

class ZBarCam(Label):
    symbols = ListProperty([])
    
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        Clock.schedule_interval(self.gen_rand_text, 1)

    def gen_rand_text(self, *args):
        self.text = random.choice(['aaaaa', 'bbbbb', 'ccccc'])
        self.symbols.append(self.text)
        if len(self.symbols) > 3:
            del self.symbols[0]


class QrReader(Screen):
    def on_enter(self):
        self.zbarcam = ZBarCam()
        self.add_widget(self.zbarcam)
        self.label = Label(
            text='PRINT SYMBOLS', #' '.join([str(symbol.data) for symbol in zbarcam.symbols] does not work
            size_hint=(None,None),
            size=(Window.width*0.1, Window.height*0.1),
            center=(Window.width*0.3, Window.height*0.5))
        self.add_widget(self.label)
        self.zbarcam.bind(symbols = lambda *x: setattr(self.label, "text", str(x[1])))
    
    def on_leave(self, *args):
        self.remove_widget(self.zbarcam)
        self.remove_widget(self.label)

class ScanButton(Screen):
    pass

class QrApp(App):
    def build(self):
        return Builder.load_string(kv_str)

if __name__ == '__main__':
    QrApp().run()

if it still does not work you might have to call self.zbarcam.start() too in the on_enter method after adding it

hope this helps

Thanks to answer from n4321d I finally found a solution.

I also struggled releasing the camera, as zbarcam.stop() stops zbarcam but camera was apparently still open (led was on). After digging a lot, I found this workaround which seems to work well to release the camera.

I also merged the button and qr reader in the same Class and I no longer use kv file, in this example. The final code is the following:

QrApp.py:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy_garden.zbarcam import ZBarCam

class QrScanner(BoxLayout):
    def __init__(self, **kwargs):
        super(QrScanner, self).__init__(**kwargs)
        btn1 = Button(text='Scan Me',  font_size="50sp")
        btn1.bind(on_press=self.callback)
        self.add_widget(btn1)

    def callback(self, instance):
        """On click button, initiate zbarcam and schedule text reader"""
        self.remove_widget(instance) # remove button
        self.zbarcam = ZBarCam()
        self.add_widget(self.zbarcam)
        Clock.schedule_interval(self.read_qr_text, 1)

    def read_qr_text(self, *args):
        """Check if zbarcam.symbols is filled and stop scanning in such case"""
        if(len(self.zbarcam.symbols) > 0): # when something is detected
            self.qr_text = self.zbarcam.symbols[0].data # text from QR
            Clock.unschedule(self.read_qr_text, 1)
            self.zbarcam.stop() # stop zbarcam
            self.zbarcam.ids['xcamera']._camera._device.release() # release camera



class QrApp(App):
    def build(self):
        return QrScanner()

if __name__ == '__main__':
    QrApp().run()

There is no way to lazy-load kivy Screens. Try this as a workaround: Do not let ScreenManager know about all Screens. Use switch_to to remove the current screen and load the desired one instead. Check Kivy docs to learn how to use it.

Related