多屏幕上的 Kivy 相机

2022-01-15 00:00:00 python opencv kivy kivy-language

问题描述

我正在尝试在 kivy 中创建一个具有两个屏幕的 simples 应用程序,我需要它在每个屏幕中加载自定义相机,不需要同时进行.

我尝试在 gui.kv 中加载显微镜小部件和主小部件中的 cam,但我报错了

self._buffer = frame.reshape(-1)AttributeError:NoneType"对象没有重塑"属性

当我移除其中一个摄像头时,它可以工作,但我需要在两个屏幕上都有摄像头

关注我的代码

ma​​in.py

进口kivy从 kivy.app 导入应用程序从 kivy.uix.screenmanager 导入 ScreenManager,Screen从 kivy.lang 导入生成器从相机导入 CameraCv#--加载CV相机类凸轮(CameraCv):经过类显微镜(屏幕):def 药膏(自我):print('救了')#-- 主要小部件主类(屏幕):# -  拍照def 捕获(自我):self.ids.cam.capture()#--场景管理器类屏幕管理器(屏幕管理器):经过#--加载我的.kv guiGUI = Builder.load_file('gui.kv')#--主应用我的应用程序(应用程序)类:定义构建(自我):返回界面如果 __name__ == "__main__":MyApp().run()

gui.kv

#:kivy 1.11.1网格布局:列数:1屏幕管理器:id:screen_manager主要的:名称:主要"编号:主要显微镜:名称:显微镜"编号:显微镜<主要>:盒子布局:方向:'垂直'标签:文字:'BLA BLA BLA BLA'粗体:真颜色:[1,1,1,1]size_hint:(1,无)身高:100网格布局:列数:2大小:root.width,root.height填充:10盒子布局:方向:垂直"标签:文字:'相机'粗体:真颜色:[1,1,0,1]# size_hint: (1, 无)#身高:160切换按钮:编号:凸轮文本:'播放/暂停'on_press: cam.play = 不是 cam.playsize_hint:(1,无)身高:60凸轮:编号:凸轮玩:真size_hint:(1,无)身高:350盒子布局:方向:'水平'按钮:文字:显微镜"on_press:cam.play = 假app.root.ids['screen_manager'].current = '显微镜'size_hint:(1,无)身高:70<显微镜>:盒子布局:方向:'水平'# 如果我移除这个凸轮,它会在主屏幕上工作凸轮:身份证:凸轮分辨率:(640、480)打法:假按钮:文字:'萨尔瓦图像'#on_press: root.save()大小提示:(1,无)身高:70<凸轮>:分辨率:(640,480)打法:假保持比率:真允许拉伸:真画布之前:推矩阵旋转:角度:root.angle轴:0、0、1来源:root.center画布.之后:流行矩阵

camera.py

从 kivy.uix.camera 导入相机从 kivy.properties 导入 BooleanProperty、NumericProperty从 kivy.uix.button 导入按钮从 kivy.uix.boxlayout 导入 BoxLayout进口猕猴桃进口时间将 numpy 导入为 np导入简历2#导入控制类 CameraCv(相机):# - 确定相机的角度角度 = NumericProperty(0)def __init__(self, **kwargs):super(CameraCv, self).__init__(**kwargs)self.isAndroid = kivy.platform == "android"如果 self.isAndroid:self.angle = -90def change_index(self, *args):new_index = 1 如果 self.index == 0 否则 0self._camera._set_index(new_index)self.index = new_indexself.angle = -90 如果 self.index == 0 否则 90#-- 在 kv 纹理中转换 cvdef on_tex(self, *l):图像 = np.frombuffer(self.texture.pixels,dtype='uint8')图像 = image.reshape(self.texture.height,self.texture.width,-1)#image = controle.cropCircle(image,50,210)numpy_data = image.tostring()self.texture.blit_buffer(numpy_data, bufferfmt="ubyte", colorfmt='rgba')super(CameraCv, self).on_tex(self.texture)def get_cameras_count(self):相机 = 1如果 self.isAndroid:相机 = self._camera.get_camera_count()返回相机def 捕获(自我,* args):#timestr = time.strftime("%Y%m%d_%H%M%S")#self.export_to_png("temp/IMG_{}.png".format(timestr))self.export_to_png('temp/temp.png')

首先,我感谢

I'm trying to create a simples app in kivy that have two screen and i need it load the a custom camera in each screen dont need do in same time.

I tryed load in gui.kv the cam in microscope widget and main widget, but's retorn me a erro

self._buffer = frame.reshape(-1)
AttributeError: 'NoneType' object has no attribute 'reshape'

When i remove one of the cameras it work, but's i need the camera in both screen

follow my code

main.py

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

from camera import CameraCv

#--load cv camera
class Cam(CameraCv):
    pass


class Microscope(Screen):

    def salve(self):
        print('salved')


#-- main widget 
class Main(Screen):


     #-- take a pic       
    def capture(self):
       self.ids.cam.capture()
#--scene manager
class ScreenManager(ScreenManager):
    pass

#--load my.kv gui
GUI = Builder.load_file('gui.kv')

#--main app
class MyApp(App):

    def build(self):

        return GUI

if __name__ == "__main__":

    MyApp().run()

gui.kv

#:kivy 1.11.1
GridLayout:
    cols: 1
    ScreenManager:
        id: screen_manager

        Main:
            name: "main"
            id: main

        Microscope:
            name: "microscope"
            id: microscope

<Main>:
    BoxLayout:

        orientation:'vertical'


        Label:
            text: 'BLA BLA BLA BLA '
            bold: True
            color: [1,1,1,1]
            size_hint: (1, None)
            height: 100

        GridLayout:
            cols:2
            size: root.width, root.height
            padding: 10

            BoxLayout:
                orientation: 'vertical'
                Label:
                    text: 'CAMERA'
                    bold: True
                    color: [1,1,0,1]
                    # size_hint: (1, None)
                    # height: 160
                ToggleButton:
                    id: Cam
                    text:'Play/Pause'
                    on_press: cam.play = not cam.play
                    size_hint: (1, None)
                    height: 60


            Cam:
                id: cam
                play: True
                size_hint: (1, None)
                height: 350     



        BoxLayout:
            orientation:'horizontal'      

            Button:
                text:"MICROSCOPIO"            
                on_press:
                    cam.play = False
                    app.root.ids['screen_manager'].current = 'microscope'



                size_hint: (1, None)
                height: 70

<Microscope>:
    BoxLayout:
        orientation:'horizontal'
        # if i remove this cam it work in main screen
        Cam:
            id:cam
            resolution: (640, 480)
            play: False

        Button:
            text:'SALVAR IMAGEM'
            #on_press: root.save()
            size_hint:(1,None)
            height: 70


<Cam>:
    resolution: (640,480)
    play: False
    keep_ratio: True
    allow_stretch: True

    canvas.before:
        PushMatrix
        Rotate:
            angle: root.angle
            axis: 0, 0, 1
            origin: root.center
    canvas.after:
        PopMatrix   

camera.py

from kivy.uix.camera import Camera
from kivy.properties import BooleanProperty, NumericProperty
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import kivy
import time
import numpy as np
import cv2
#import controle

class CameraCv(Camera):
    # - determine angle of camera
    angle = NumericProperty(0)

    def __init__(self, **kwargs):
        super(CameraCv, self).__init__(**kwargs)

        self.isAndroid = kivy.platform == "android"
        if self.isAndroid:
            self.angle = -90

    def change_index(self, *args):
        new_index = 1 if self.index == 0 else 0
        self._camera._set_index(new_index)
        self.index = new_index
        self.angle = -90 if self.index == 0 else 90

    #-- convert cv in kv texture
    def on_tex(self, *l):

        image = np.frombuffer(self.texture.pixels, dtype='uint8')
        image = image.reshape(self.texture.height, self.texture.width, -1)
        #image = controle.cropCircle(image,50,210)


        numpy_data = image.tostring()

        self.texture.blit_buffer(numpy_data, bufferfmt="ubyte", colorfmt='rgba')

        super(CameraCv, self).on_tex(self.texture)

    def get_cameras_count(self):
        cameras = 1

        if self.isAndroid:
            cameras = self._camera.get_camera_count()
        return cameras

    def capture(self,*args):

        #timestr = time.strftime("%Y%m%d_%H%M%S")
        #self.export_to_png("temp/IMG_{}.png".format(timestr))
        self.export_to_png('temp/temp.png')

[EDIT]

first, i thank Mr Furas for the answer and the example, it worked perfectly so I just had to adapt the his code.

So the code now is:

camera.py

import numpy as np
import cv2
#import controle #--custom opncv methods

from kivy.uix.image import Image
from kivy.core.camera import Camera as CoreCamera
from kivy.properties import NumericProperty, ListProperty, BooleanProperty

# access to camera
core_camera = CoreCamera(index=0, resolution=(640, 480), stopped=True)

# Widget to display camera
class CameraCv(Image):
    '''Camera class. See module documentation for more information.
    '''

    play = BooleanProperty(True)
    '''Boolean indicating whether the camera is playing or not.
    You can start/stop the camera by setting this property::
        # start the camera playing at creation (default)
        cam = Camera(play=True)
        # create the camera, and start later
        cam = Camera(play=False)
        # and later
        cam.play = True
    :attr:`play` is a :class:`~kivy.properties.BooleanProperty` and defaults to
    True.
    '''

    index = NumericProperty(-1)
    '''Index of the used camera, starting from 0.
    :attr:`index` is a :class:`~kivy.properties.NumericProperty` and defaults
    to -1 to allow auto selection.
    '''

    resolution = ListProperty([-1, -1])
    '''Preferred resolution to use when invoking the camera. If you are using
    [-1, -1], the resolution will be the default one::
        # create a camera object with the best image available
        cam = Camera()
        # create a camera object with an image of 320x240 if possible
        cam = Camera(resolution=(320, 240))
    .. warning::
        Depending on the implementation, the camera may not respect this
        property.
    :attr:`resolution` is a :class:`~kivy.properties.ListProperty` and defaults
    to [-1, -1].
    '''

    def __init__(self, **kwargs):
        self._camera = None
        super(CameraCv, self).__init__(**kwargs)  # `CameraCv` instead of `Camera`
        if self.index == -1:
            self.index = 0
        on_index = self._on_index
        fbind = self.fbind
        fbind('index', on_index)
        fbind('resolution', on_index)
        on_index()

    def on_tex(self, *l):

        image = np.frombuffer(self.texture.pixels, dtype='uint8')
        image = image.reshape(self.texture.height, self.texture.width, -1)
        #image = controle.cropCircle(image,50,210) #custom opencv method
        numpy_data = image.tostring()

        self.texture.blit_buffer(numpy_data, bufferfmt="ubyte", colorfmt='rgba')
        self.canvas.ask_update()

    def _on_index(self, *largs):
        self._camera = None
        if self.index < 0:
            return
        if self.resolution[0] < 0 or self.resolution[1] < 0:
            return

        self._camera = core_camera # `core_camera` instead of `CoreCamera(index=self.index, resolution=self.resolution, stopped=True)`

        self._camera.bind(on_load=self._camera_loaded)
        if self.play:
            self._camera.start()
            self._camera.bind(on_texture=self.on_tex)

    def _camera_loaded(self, *largs):
        self.texture = self._camera.texture
        self.texture_size = list(self.texture.size)

    def on_play(self, instance, value):
        if self._camera:
            return
        if not value:
            self._camera.start()
        else:
            self._camera.stop()

gui.kv

#:kivy 1.11.1
GridLayout:
    cols: 1
    ScreenManager:
        id: screen_manager

        Main:
            name: "main"
            id: main

        Microscope:
            name: "microscope"
            id: microscope

<Main>:
    BoxLayout:

        orientation:'vertical'


        Label:
            text: 'BLA BLA BLA BLA '
            bold: True
            color: [1,1,1,1]
            size_hint: (1, None)
            height: 100

        GridLayout:
            cols:2
            size: root.width, root.height
            padding: 10
            id:box1
            BoxLayout:

                orientation: 'vertical'
                Label:
                    text: 'CAMERA'
                    bold: True
                    color: [1,1,0,1]
                    # size_hint: (1, None)
                    # height: 160
                ToggleButton:

                    text:'Play/Pause'
                    on_press: camera1.play = not camera1.play
                    size_hint: (1, None)
                    height: 60


            CameraCv:
                id: camera1
                resolution: (640,480)
                size_hint: (1, None)
                height: 350

        BoxLayout:
            orientation:'horizontal'          
            Button:
                text:"MICROSCOPIO"            
                on_press:        
                    app.root.ids['screen_manager'].current = 'microscope'                         
                size_hint: (1, None)
                height: 70

<Microscope>:
    BoxLayout:
        id: box2
        orientation:'vertical'
        # if i remove this cam it work in main screen
        CameraCv:
            id: camera2
            resolution: (640,480)
            size_hint: (1, None)
            height: 350
            keep_ratio: True
            allow_stretch: True

        Button:
            text:'image salve'
            on_press: app.root.ids['screen_manager'].current = 'main'
            size_hint:(1,None)
            height: 70


<CameraCv>:

    keep_ratio: True
    allow_stretch: True

    canvas.before:
        PushMatrix
        Rotate:
            axis: 0, 0, 1
            origin: root.center
    canvas.after:
        PopMatrix 

main.py

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

from camera import CameraCv

class Microscope(Screen):

    pass

#-- main widget 
class Main(Screen):
    pass

#--scene manager
class ScreenManager(ScreenManager):
    pass

#--load my.kv gui
GUI = Builder.load_file('gui.kv')

#--main app
class MyApp(App):

    def build(self):

        return GUI

if __name__ == "__main__":

    MyApp().run()

Now it work perfecty

解决方案

I took source code of class Camera

https://github.com/kivy/kivy/blob/master/kivy/uix/camera.py

and created own class MyCamera

I create instance of CoreCamera outside class MyCamera and all instances of MyCamera use the same one instance of CoreCamera

core_camera = CoreCamera(index=0, resolution=(640, 480), stopped=True)

class MyCamera(Image):

    def _on_index(self, *largs):
        # ... 

        self._camera = core_camera

instead of

class MyCamera(Image):

    def _on_index(self, *largs):
        # ... 

        self._camera = CoreCamera(index=self.index, resolution=self.resolution, stopped=True)

It has still some problems

  • in CoreCamera is set resolution and all MyClass use the same resolution - but they still need resolution: (640, 480) in gui.kv (but they don't use this value)
  • play starts/stops animation for all MyClass because CoreCamera control it.

I used example from

https://kivy.org/doc/stable/examples/gen__camera__main__py.html

to create example with two widgets displaying the same camera

# https://kivy.org/doc/stable/examples/gen__camera__main__py.html
# https://github.com/kivy/kivy/blob/master/kivy/uix/camera.py

from kivy.uix.image import Image
from kivy.core.camera import Camera as CoreCamera
from kivy.properties import NumericProperty, ListProperty, BooleanProperty

# access to camera
core_camera = CoreCamera(index=0, resolution=(640, 480), stopped=True)

# Widget to display camera
class MyCamera(Image):
    '''Camera class. See module documentation for more information.
    '''

    play = BooleanProperty(True)
    '''Boolean indicating whether the camera is playing or not.
    You can start/stop the camera by setting this property::
        # start the camera playing at creation (default)
        cam = Camera(play=True)
        # create the camera, and start later
        cam = Camera(play=False)
        # and later
        cam.play = True
    :attr:`play` is a :class:`~kivy.properties.BooleanProperty` and defaults to
    True.
    '''

    index = NumericProperty(-1)
    '''Index of the used camera, starting from 0.
    :attr:`index` is a :class:`~kivy.properties.NumericProperty` and defaults
    to -1 to allow auto selection.
    '''

    resolution = ListProperty([-1, -1])
    '''Preferred resolution to use when invoking the camera. If you are using
    [-1, -1], the resolution will be the default one::
        # create a camera object with the best image available
        cam = Camera()
        # create a camera object with an image of 320x240 if possible
        cam = Camera(resolution=(320, 240))
    .. warning::
        Depending on the implementation, the camera may not respect this
        property.
    :attr:`resolution` is a :class:`~kivy.properties.ListProperty` and defaults
    to [-1, -1].
    '''

    def __init__(self, **kwargs):
        self._camera = None
        super(MyCamera, self).__init__(**kwargs)  # `MyCamera` instead of `Camera`
        if self.index == -1:
            self.index = 0
        on_index = self._on_index
        fbind = self.fbind
        fbind('index', on_index)
        fbind('resolution', on_index)
        on_index()

    def on_tex(self, *l):
        self.canvas.ask_update()

    def _on_index(self, *largs):
        self._camera = None
        if self.index < 0:
            return
        if self.resolution[0] < 0 or self.resolution[1] < 0:
            return

        self._camera = core_camera # `core_camera` instead of `CoreCamera(index=self.index, resolution=self.resolution, stopped=True)`

        self._camera.bind(on_load=self._camera_loaded)
        if self.play:
            self._camera.start()
            self._camera.bind(on_texture=self.on_tex)

    def _camera_loaded(self, *largs):
        self.texture = self._camera.texture
        self.texture_size = list(self.texture.size)

    def on_play(self, instance, value):
        if not self._camera:
            return
        if value:
            self._camera.start()
        else:
            self._camera.stop()


from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
import time

Builder.load_string('''
<CameraClick>:
    orientation: 'vertical'
    MyCamera:
        id: camera1
        resolution: (640, 480)
    MyCamera:
        id: camera2
        resolution: (640, 480)
    ToggleButton:
        text: 'Play'
        on_press: camera1.play = not camera1.play
        size_hint_y: None
        height: '48dp'
    Button:
        text: 'Capture'
        size_hint_y: None
        height: '48dp'
        on_press: root.capture()
''')


class CameraClick(BoxLayout):
    def capture(self):
        '''
        Function to capture the images and give them the names
        according to their captured time and date.
        '''
        camera = self.ids['camera1']
        timestr = time.strftime("%Y%m%d_%H%M%S")
        camera.export_to_png("IMG_{}.png".format(timestr))
        print("Captured")

class TestCamera(App):

    def build(self):
        return CameraClick()

TestCamera().run()

相关文章