多屏幕上的 Kivy 相机
问题描述
我正在尝试在 kivy 中创建一个具有两个屏幕的 simples 应用程序,我需要它在每个屏幕中加载自定义相机,不需要同时进行.
我尝试在 gui.kv
中加载显微镜小部件和主小部件中的 cam,但我报错了
self._buffer = frame.reshape(-1)AttributeError:NoneType"对象没有重塑"属性
当我移除其中一个摄像头时,它可以工作,但我需要在两个屏幕上都有摄像头
关注我的代码
main.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 allMyClass
use the same resolution - but they still needresolution: (640, 480)
ingui.kv
(but they don't use this value) play
starts/stops animation for allMyClass
becauseCoreCamera
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()
相关文章