kivy:“异常:着色器没有链接”,当被 gpiozero 回调调用时,而不是被 kivy.uix.button 回调调用 main.pylifter.kv终端输出带注释main.pylifter.kv

问题描述

我正在 kivy 中使用相机编写 GUI,但不确定为什么我的代码不起作用。我有一个相机提要,以及两种从中捕获图片方法:一种由 $1 gpiozero 回调触发,另一种由 when_pressed kivy.uix.button 回调触发。

on_press 回调成功捕获图像,但 kivy.uix.button 回调显示 gpiozero,无法保存图像,然后使相机源变黑(尽管图像稍后可以仍然会被成功选项捕获)。为什么一个回调有效,而另一个无效?

这里是相关代码,以及对应的终端输出。我用 Exception: Shader didnt link,check info log. 注释了终端输出。 (我的代码受到 kivy docs camera example 的启发,它也成功捕获)。

main.py

# ALL CAPS COMMENTS

lifter.kv

import kivy
#kivy.require('1.11.1')

# Uncomment these lines to see all the messages
#from kivy.logger import Logger
#import logging
#Logger.setLevel(logging.TRACE)

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.camera import Camera
from kivy.core.window import Window
from gpiozero import Button as gpiozeroButton   # renamed to avoid conflict w/ kivy.uix.button
import time

Window.fullscreen = 'auto'  # uses display's current resolution

capture_btn  = gpiozeroButton(pin=13,pull_up=False)    # set up GPIO

class RootWidget(FloatLayout):

    def __init__(self,**kwargs):
        super(RootWidget,self).__init__(**kwargs)
        capture_btn.when_pressed = self.capture    # initialize callback for GPIO button
        
    def capture(self):
        print('Capture step 1')
        camera = self.ids['camera']
        print('Capture step 2')
        timestr = time.strftime("%Y%m%d_%H%M%s")
        print('Capture step 3')
        camera.export_to_png("IMG_{}.png".format(timestr))
        print("Captured")


class Lifterapp(App):

    def build(self):
        return RootWidget()


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

终端输出(带注释)

# #:kivy 1.11.1

<RootWidget>:

    Camera:
        id: camera
        resolution: (640,480)
        play: True
    Button:
        text: "capture"
        pos_hint: {'x':0.0,'y':0.0}
        size_hint: (0.2,0.2)
        on_press: root.capture()

解决方法

您可以使用 kivy.clock.mainthread 装饰器来替换您的 __wrapper 函数。

from kivy.clock import mainthread

...

class RootWidget(FloatLayout):
   ...

   @mainthread
   def capture(self):
        ...

这也可以使用您不需要的 dt 参数,因为它保留了函数的签名。

最初的问题当然是管理 when_pressed 的代码在一个线程中运行,因此在任何 opengl 操作中都表现不佳(你得到一个“着色器没有链接”,但你也可能会崩溃,或在另一台计算机上什么也没有),因为 opengl 不应该在多个线程中使用,主线程装饰器通过使用时钟委托给主线程,避免了这个问题。

,

我创建了一个解决方法,但它很卡顿,所以我更喜欢更好的解决方案......

...或者至少解释一下为什么 gpiozero when_pressed 表现不佳。

我的解决方法是拥有一个私有的 __capture() 函数,它充当 Clock.schedule_once(self.capture) 的包装器。 capture_btn.when_pressed 将使用此私有包装回调来避免直接调用有问题的代码,而 capture() 的任何其他用途将正常使用公共回调,因为这在不与 gpiozero 交互时工作正常。

在下面的代码中,我希望 capture()__capture() 交换名称,因为这更符合私有函数的哲学,但不幸的是,当我尝试时,它会抛出 AttributeError: 'RootWidget' object has no attribute '__capture'我不知道如何解决这个问题(使用 _RootWidget__capture 没有帮助)。用 ## 注释的行说明了我首选但不起作用的方式。

main.py

import kivy
#kivy.require('1.11.1')

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.camera import Camera
from kivy.core.window import Window
from kivy.clock import Clock
from gpiozero import Button as gpiozeroButton   # renamed to avoid conflict w/ kivy.uix.button
import time

Window.fullscreen = 'auto'  # uses display's current resolution

capture_btn = gpiozeroButton(pin=13,pull_up=False)    # set up GPIO

class RootWidget(FloatLayout):

    def __init__(self,**kwargs):
        super(RootWidget,self).__init__(**kwargs)
##      capture_btn.when_pressed = self.capture
        capture_btn.when_pressed = self.__capture    # initialize callback for GPIO button
    
##  def __capture(self):
    def capture(self,dt=None):
        print('Capture step 1')
        camera = self.ids['camera']
        print('Capture step 2')
        timestr = time.strftime("%Y%m%d_%H%M%S")
        print('Capture step 3')
        camera.export_to_png("IMG_{}.png".format(timestr))
        print("Captured")

##  def capture(self):
    def __capture(self):    # FIXME this function shouldn't be needed (see function description)
        '''
        Since camera.export_to_png() throws "Exception: Shader didnt link" when called
        by a gpiozero when_pressed callback but not when scheduled using kivy.clock,I
        decided to create this wrapper function to circumvent the issue (I'm not sure 
        of the underlying problem,but this is a workaround in the meantime).
        '''
##      Clock.schedule_once(self.__capture)
        Clock.schedule_once(self.capture)


class LifterApp(App):

    def build(self):
        return RootWidget()


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

lifter.kv

lifter.kv 与问题中的文本保持一致。