0

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

kivy.uix.button回调成功捕获图像,但gpiozero回调说,未能保存图像,然后Exception: Shader didnt link, check info log.使相机输入变黑(尽管稍后仍然可以使用成功选项捕获图像)。为什么一个回调有效,而另一个无效?

这是相关代码,以及相应的终端输出。我已经用# ALL CAPS COMMENTS. (我的代码受到kivy docs camera example的启发,该示例也成功捕获)。

主文件

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()

升降机.kv

# #: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()

终端输出(带注释)

[INFO   ] [Logger      ] Record log in /home/pi/.kivy/logs/kivy_20-12-30_27.txt
[INFO   ] [Kivy        ] v1.11.1
[INFO   ] [Kivy        ] Installed at "/usr/local/lib/python3.7/dist-packages/kivy/__init__.py"
[INFO   ] [Python      ] v3.7.3 (default, Dec 20 2019, 18:57:59) 
[GCC 8.3.0]
[INFO   ] [Python      ] Interpreter at "/usr/bin/python3"
[INFO   ] [Factory     ] 184 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_pil, img_gif (img_ffpyplayer ignored)
[INFO   ] [Text        ] Provider: sdl2(['text_pango'] ignored)
[INFO   ] [Camera      ] Provider: picamera
[INFO   ] [Window      ] Provider: sdl2(['window_egl_rpi'] ignored)
[INFO   ] [GL          ] Using the "OpenGL" graphics system
[INFO   ] [GL          ] Backend used <sdl2>
[INFO   ] [GL          ] OpenGL version <b'3.1 Mesa 19.3.2'>
[INFO   ] [GL          ] OpenGL vendor <b'VMware, Inc.'>
[INFO   ] [GL          ] OpenGL renderer <b'llvmpipe (LLVM 9.0.1, 128 bits)'>
[INFO   ] [GL          ] OpenGL parsed version: 3, 1
[INFO   ] [GL          ] Shading version <b'1.40'>
[INFO   ] [GL          ] Texture max size <8192>
[INFO   ] [GL          ] Texture max units <32>
[INFO   ] [Window      ] auto add sdl2 input provider
[INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
[INFO   ] [ProbeSysfs  ] device match: /dev/input/event0
[INFO   ] [MTD         ] Read event from </dev/input/event0>
[INFO   ] [ProbeSysfs  ] device match: /dev/input/event0
[INFO   ] [HIDInput    ] Read event from </dev/input/event0>
[INFO   ] [Base        ] Start application main loop
[INFO   ] [MTD         ] </dev/input/event0> range position X is 0 - 800
[INFO   ] [MTD         ] </dev/input/event0> range position Y is 0 - 480
[INFO   ] [HIDMotionEvent] using <WaveShare WS170120>
[INFO   ] [MTD         ] </dev/input/event0> range touch major is 0 - 0
[INFO   ] [HIDMotionEvent] <WaveShare WS170120> range ABS X position is 0 - 800
[INFO   ] [MTD         ] </dev/input/event0> range touch minor is 0 - 0
[INFO   ] [HIDMotionEvent] <WaveShare WS170120> range ABS Y position is 0 - 480
[INFO   ] [MTD         ] </dev/input/event0> range pressure is 0 - 255
[INFO   ] [HIDMotionEvent] <WaveShare WS170120> range ABS pressure is 0 - 255
[INFO   ] [MTD         ] </dev/input/event0> axes invertion: X is 0, Y is 0
[INFO   ] [HIDMotionEvent] <WaveShare WS170120> range position X is 0 - 800
[INFO   ] [MTD         ] </dev/input/event0> rotation set to 0
[INFO   ] [HIDMotionEvent] <WaveShare WS170120> range position Y is 0 - 480
[INFO   ] [HIDMotionEvent] <WaveShare WS170120> range pressure is 0 - 255
[INFO   ] [GL          ] NPOT texture support is available
Capture step 1 # TRIGGERED BY KIVY.UIX.BUTTON ON_PRESS CALLBACK
Capture step 2
Capture step 3
Captured
Capture step 1 # TRIGGERED BY GPIOZERO WHEN_PRESSED CALLBACK
Capture step 2
Capture step 3
 Traceback (most recent call last):
   File "/usr/lib/python3/dist-packages/gpiozero/pins/rpigpio.py", line 244, in _call_when_changed
     super(RPiGPIOPin, self)._call_when_changed()
   File "/usr/lib/python3/dist-packages/gpiozero/pins/local.py", line 143, in _call_when_changed
     self.state if state is None else state)
   File "/usr/lib/python3/dist-packages/gpiozero/pins/pi.py", line 293, in _call_when_changed
     method(ticks, state)
   File "/usr/lib/python3/dist-packages/gpiozero/input_devices.py", line 197, in _pin_changed
     self._fire_events(ticks, bool(self._state_to_value(state)))
   File "/usr/lib/python3/dist-packages/gpiozero/mixins.py", line 368, in _fire_events
     self._fire_activated()
   File "/usr/lib/python3/dist-packages/gpiozero/mixins.py", line 397, in _fire_activated
     super(HoldMixin, self)._fire_activated()
   File "/usr/lib/python3/dist-packages/gpiozero/mixins.py", line 344, in _fire_activated
     self.when_activated()
   File "main.py", line 35, in capture
     camera.export_to_png("IMG_{}.png".format(timestr))
   File "/usr/local/lib/python3.7/dist-packages/kivy/uix/widget.py", line 727, in export_to_png
     self.export_as_image().save(filename, flipped=False)
   File "/usr/local/lib/python3.7/dist-packages/kivy/uix/widget.py", line 744, in export_as_image
     with_stencilbuffer=True)
   File "kivy/graphics/fbo.pyx", line 152, in kivy.graphics.fbo.Fbo.__init__
   File "kivy/graphics/instructions.pyx", line 777, in kivy.graphics.instructions.RenderContext.__init__
   File "kivy/graphics/shader.pyx", line 184, in kivy.graphics.shader.Shader.__init__
   File "kivy/graphics/shader.pyx", line 701, in kivy.graphics.shader.Shader.vs.__set__
   File "kivy/graphics/shader.pyx", line 557, in kivy.graphics.shader.Shader.build_vertex
   File "kivy/graphics/shader.pyx", line 587, in kivy.graphics.shader.Shader.link_program
 Exception: Shader didnt link, check info log.
^C[INFO   ] [Base        ] Leaving application in progress...
 Traceback (most recent call last):
   File "main.py", line 46, in <module>
     LifterApp().run()
   File "/usr/local/lib/python3.7/dist-packages/kivy/app.py", line 855, in run
     runTouchApp()
   File "/usr/local/lib/python3.7/dist-packages/kivy/base.py", line 504, in runTouchApp
     EventLoop.window.mainloop()
   File "/usr/local/lib/python3.7/dist-packages/kivy/core/window/window_sdl2.py", line 747, in mainloop
     self._mainloop()
   File "/usr/local/lib/python3.7/dist-packages/kivy/core/window/window_sdl2.py", line 479, in _mainloop
     EventLoop.idle()
   File "/usr/local/lib/python3.7/dist-packages/kivy/base.py", line 339, in idle
     Clock.tick()
   File "/usr/local/lib/python3.7/dist-packages/kivy/clock.py", line 591, in tick
     self._process_events()
   File "kivy/_clock.pyx", line 384, in kivy._clock.CyClockBase._process_events
   File "kivy/_clock.pyx", line 414, in kivy._clock.CyClockBase._process_events
   File "kivy/_clock.pyx", line 412, in kivy._clock.CyClockBase._process_events
   File "kivy/_clock.pyx", line 167, in kivy._clock.ClockEvent.tick
   File "/usr/local/lib/python3.7/dist-packages/kivy/core/camera/camera_picamera.py", line 71, in _update
     self._camera.capture(output, self._format, use_video_port=True)
   File "/usr/lib/python3/dist-packages/picamera/camera.py", line 1421, in capture
     if not encoder.wait(self.CAPTURE_TIMEOUT):
   File "/usr/lib/python3/dist-packages/picamera/encoders.py", line 393, in wait
     result = self.event.wait(timeout)
   File "/usr/lib/python3.7/threading.py", line 552, in wait
     signaled = self._cond.wait(timeout)
   File "/usr/lib/python3.7/threading.py", line 300, in wait
     gotit = waiter.acquire(True, timeout)
 KeyboardInterrupt
4

2 回答 2

1

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

from kivy.clock import mainthread

...

class RootWidget(FloatLayout):
   ...

   @mainthread
   def capture(self):
        ...

这也获得了您不需要的 dt 参数,因为它保留了函数的签名。

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

于 2021-01-01T16:47:18.430 回答
0

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

……或者至少解释一下为什么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没有帮助) . 带有注释的行##说明了我首选但不起作用的方式。

主文件

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()

升降机.kv

lifter.kv与问题中的文字没有变化。

于 2021-01-01T02:11:11.570 回答