Kivy segmentation fault on blitting opencv picture - python

I am trying to cast video from my external cam, that is captured through some aliexpress EasyCap, to my kivy app. One issue I've faced is that crashes with segmentation fault on trying to
texture = Texture.create(size=(frame.shape[0], frame.shape[1]))
I've found that the problem is on kivy's side. It sometimes can't create NPOT textures. So, I've changed it to POT shape and copied what is possible to another numpy array.
flipped = cv2.flip(frame, 0)
buf = np.zeros((512, 512, 3), dtype=np.uint8)
for i in range(min(frame.shape[0], 512)):
for j in range(min(frame.shape[1], 512)):
buf[i, j] = flipped[i, j]
buf = buf.tostring()
texture = Texture.create(size=(512, 512))
texture.blit_buffer(buf, colorfmt="bgr", bufferfmt="ubyte")
self.texture = texture
But it still crashes with the same old segmentation fault on the following line:
texture.blit_buffer(buf, colorfmt="bgr", bufferfmt="ubyte")
If it is relevant, cv2.imshow("image", buf) before buf.tostring() show the image correctly.
Here's the original code:
from kivy.app import App
from kivy.uix.image import Image
from kivy.clock import Clock
from kivy.graphics.texture import Texture
import cv2
import threading
from time import sleep
import numpy as np
class KivyCamera(Image):
def __init__(self, **kwargs):
super(KivyCamera, self).__init__(**kwargs)
self.fps = 30
self.capture = cv2.VideoCature(0)
threading.Thread(target=self.update).start()
def update(self):
while True:
ret, frame = self.capture.read()
if ret:
buf = cv2.flip(frame, 0).tostring()
texture = Texture.create(size=(frame.shape[0], frame.shape[1])
texture.blit_buffer(buf, colorfmt="bgr", bufferfmt="ubyte")
self.texture = texture
sleep(1.0 / self.fps)
class CamApp(App):
def build(self):
return KivyCamera()
if __name__ == "__main__":
CamApp().run()

Few issues with your code:
The GUI updates should always be done in the mainthread as indicated by John Anderson. Hence instead of separate thread, the update() function should be called through Clock schedule.
The size tuple in the Texture.create statement should be reversed and the colorfmt="bgr" should be added. It should be: texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt="bgr").
There is no layout defined. Layouts like BoxLayout should be added as a placeholder of Image widget.
Below is the modified working version of your code:
from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.graphics.texture import Texture
import cv2
class KivyCamera(BoxLayout):
def __init__(self, **kwargs):
super(KivyCamera, self).__init__(**kwargs)
self.img1=Image()
self.add_widget(self.img1)
self.capture = cv2.VideoCapture(0)
Clock.schedule_interval(self.update, 1.0/33.0)
def update(self, *args):
ret, frame = self.capture.read()
if ret:
buf = cv2.flip(frame, 0).tostring()
texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt="bgr")
texture.blit_buffer(buf, colorfmt="bgr", bufferfmt="ubyte")
self.img1.texture = texture
class CamApp(App):
def build(self):
return KivyCamera()
if __name__ == "__main__":
CamApp().run()
The while loop is removed and Clock.schedule_interval is used with 1 / 33. steps.
Hope this helps.

Not well documented, but I believe the Texture manipulation (specifically the blit_buffer()) must be done on the main thread. Your update() method is being run in another thread, so use Clock.schedule_once() to call a method that does the Texture manipulation (and the self.texture = texture) back in the main thread.

Related

AttributeError: 'NoneType' object has no attribute 'tostring' in application, but works fine in PC

I'm trying to make apk file from using python. This code is using cv2.VideoCapture(0) to make phone camera application. Here's my code
# Import kivy dependencies first
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
# Import kivy UX components
from kivy.uix.image import Image
# Import other kivy stuff
from kivy.clock import Clock
from kivy.graphics.texture import Texture
# Import other dependencies
import cv2
class CamApp(App):
def build(self):
self.vid = cv2.VideoCapture(0)
self.web_cam = Image()
layout = BoxLayout()
layout.add_widget(self.web_cam)
#self.capture = cv2.VideoCapture(0)
Clock.schedule_interval(self.update, 1.0 / 33.0)
self.vid.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
self.vid.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
#w = self.vid.get(cv2.CAP_PROP_FRAME_WIDTH)
#h = self.vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
#print("너비 {} 높이 {}".format(w, h))
return layout
def update(self, *args):
ret, frame = self.vid.read()
#frame = cv2.flip(frame, 0)
# Flip horizontal and convert image to texture
buf = cv2.flip(frame, 0).tostring()
img_texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt='bgr')
img_texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte')
self.web_cam.texture = img_texture
if __name__ == '__main__':
CamApp().run()
It works fine on PC and I used buildozer to make application. Here's my part of my buildozer.spec
# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy,opencv
Anyway, when I played the application I made, it shuts off after kivy loading page. So I used adb and got an error message
AttributeError: 'NoneType' object has no attribute 'tostring'
I can't understand why this error messaged showed up because it worked well in my PC.
Please help me...
Thank you!!
try to do this:
def update(self, *args):
ret, self.frame = self.vid.read()
#frame = cv2.flip(frame, 0)
# Flip horizontal and convert image to texture
buf = cv2.flip(self.frame, 0).tobytes()
img_texture = Texture.create(size=(self.frame.shape[1], self.frame.shape[0]), colorfmt='bgr')
img_texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte')
self.web_cam.texture = img_texture

Simultaneous placement of camera images and buttons in GUI applications using Kivy

I am currently trying to create an app using Kivy that will turn the video into grayscale when the button is pressed.
At first, I was able to display the camera image on the GUI, but I'm having trouble figuring out how to place buttons and other parts from here.
import sys
import numpy as np
import cv2
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle
from kivy.graphics.texture import Texture
from kivy.clock import Clock
from kivy.core.window import Window
import datetime
import random
WINDOW_WIDTH = 1500
WINDOW_HEIGHT = 1008
### Setting of the window
Window.size = (WINDOW_WIDTH, WINDOW_HEIGHT)
class MyApp(App, Widget):
title = "opencv on kivy"
def __init__(self, **kwargs):
super(MyApp, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1.0 / 30)
self.widget = Widget()
# self.cap = cv2.VideoCapture(0)
self.cap = cv2.VideoCapture(1)
# The method by the intarval
def update(self, dt):
ret, img = self.cap.read()
### The color sequence in openCV is BGR, so fix it to RGB.
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
### The origin of Kivy's coordinates is the lower left, so flip it upside down.
img = cv2.flip(img, 0)
# if img is not None:
texture = Texture.create(size=(img.shape[1], img.shape[0]))
texture.blit_buffer(img.tostring())
with self.widget.canvas:
Rectangle(texture=texture ,pos=(0 + int(WINDOW_WIDTH/2) - int(img.shape[1]/2), WINDOW_HEIGHT - img.shape[0]), size=(img.shape[1], img.shape[0]))
return self.widget
def build(self):
return self.widget
# return MyApp()
if __name__ == '__main__':
MyApp().run()
Also, the application that displays the button works fine.
However, I have no idea how to combine these since build(self) can only return a button or a single widget for the camera image.
# import kivy module
import kivy
# this restrict the kivy version i.e
# below this kivy version you cannot
# use the app or software
kivy.require("1.9.1")
# base Class of your App inherits from the App class.
# app:always refers to the instance of your application
from kivy.app import App
# creates the button in kivy
# if not imported shows the error
from kivy.uix.button import Button
# class in which we are creating the button
class ButtonApp(App):
def build(self):
btn = Button(text ="Push Me !")
return btn
# creating the object root for ButtonApp() class
root = ButtonApp()
# run function runs the whole program
# i.e run() method which calls the
# target function passed to the constructor.
root.run()
thank you in advance.
You can use self.add_widget(..) to add other widgets inside Widget, Button, etc. but it doesn't have function to automatically arange layout.
But build() can use any widget - it doesn't have to Button or Widget.
If you use BoxLayout then you can use self.add_widget(...) to add widgets in rows or columns.
Other layout widgets can be useful to organize widgets in different way.
class MyApp(App, BoxLayout): # class MyApp(App, Widget)
def __init__(self, **kwargs):
# ... code ...
self.orientation = 'vertical' # BoxLayout
self.widget = Widget()
self.add_widget(self.widget)
self.button = Button(text='Gray', on_press=self.change_color)
self.add_widget(self.button)
Full working code.
I use BoxLayout with orientation = 'vertical' to organize in rows I put Widget in top row, and Button in bottom row.
Button runs function which switch value self.convert_to_grey - True/False - and update() uses this value to convert image to gray (and back to RGB but with gray color)
import sys
import numpy as np
import cv2
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle
from kivy.graphics.texture import Texture
from kivy.clock import Clock
from kivy.core.window import Window
import datetime
import random
WINDOW_WIDTH = 1500
WINDOW_HEIGHT = 1008
### Setting of the window
Window.size = (WINDOW_WIDTH, WINDOW_HEIGHT)
class MyApp(App, BoxLayout):
title = "opencv on kivy"
def __init__(self, **kwargs):
super(MyApp, self).__init__(**kwargs)
self.orientation = 'vertical' # BoxLayout
self.convert_to_grey = False #
self.cap = cv2.VideoCapture(0)
#self.cap = cv2.VideoCapture(1)
self.widget = Widget()
self.add_widget(self.widget)
self.button = Button(text='Gray', on_press=self.change_color)
self.add_widget(self.button)
Clock.schedule_interval(self.update, 1.0 / 30)
def change_color(self, *args):
print('args:', args)
self.convert_to_grey = not self.convert_to_grey
def update(self, dt):
ret, img = self.cap.read()
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.flip(img, 0)
if self.convert_to_grey:
img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
texture = Texture.create(size=(img.shape[1], img.shape[0]))
texture.blit_buffer(img.tostring())
with self.widget.canvas:
Rectangle(texture=texture, pos=(0 + int(WINDOW_WIDTH/2) - int(img.shape[1]/2), WINDOW_HEIGHT - img.shape[0]), size=(img.shape[1], img.shape[0]))
return self.widget
def build(self):
return self
if __name__ == '__main__':
MyApp().run()
More layouts in Kivy doc:
Getting Started » Layouts
BTW: you can nested layouts - ie. inside BoxLayout you can use one row with GridLayout and other row with different layout or widget.

show opencv in kivymd screen

I am working on drowsiness detection so I need to use OpenCV but i am having a problem showing the webcam stream on kivymd GUI, and I am using multiple screens and I need it to show in a certain one.
here the code I am using currently
class KivyCamera(Image):
def __init__(self, capture, fps, **kwargs):
super(KivyCamera, self).__init__(**kwargs)
self.capture = capture
Clock.schedule_interval(self.update, 1.0 / fps)
def update(self, dt):
ret, frame = self.capture.read()
if ret:
# convert it to texture
buf1 = cv2.flip(frame, 0)
buf = buf1.tostring()
image_texture = Texture.create(
size=(frame.shape[1], frame.shape[0]), colorfmt='bgr')
image_texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte')
# display image from the texture
self.texture = image_texture
class CamApp(App):
def build(self):
self.capture = cv2.VideoCapture(0)
self.my_camera = KivyCamera(capture=self.capture, fps=30)
return self.my_camera
def on_stop(self):
#without this, app will not exit even if the window is closed
self.capture.release()
if __name__ == '__main__':
CamApp().run()
your code works for me
just add these imports:
from kivymd.app import App
from kivy.uix.image import Image
from kivy.clock import Clock
from kivy.graphics.texture import Texture
import cv2
I solve the problem by adding a button in one of the screen class inside the .kv file
And create a function which was binded to the button.
The func convert the image texture of kivy into opencv texture!
Hope you understand this

Kivy-python: duplicate image on texture

I am developing app in python 3.6 with kivy.
I'd like to display an image saved as numpy array.
I wrote this code:
from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.widget import Widget
from kivy.graphics.texture import Texture
import cv2
class Test(Widget):
def __init__(self, **kwargs):
super(Test, self).__init__(**kwargs)
img = cv2.imread(r'./kulki.jpg', cv2.IMREAD_GRAYSCALE)
w, h = img.shape
texture = Texture.create(size=(h, w))
texture.blit_buffer(img.flatten(), colorfmt='rgb', bufferfmt='ubyte')
w_img = Image(size=(w, h), texture=texture)
self.add_widget(w_img)
class DemoApp(App):
def build(self):
return Test()
if __name__ == '__main__':
DemoApp().run()
and this is my output:
for this image:
Does anybody know why there are several of the same pictures instead of one? Anw why do I have to change dimensions in places (w,h) -> (h,w)?
Best regards!
I think the problem is that you are converting the image to grayscale when you read it, then your are using rgb for the Texture color format. If you make those two agree, then your code will work. For example, change:
texture.blit_buffer(img.flatten(), colorfmt='rgb', bufferfmt='ubyte')
to:
texture.blit_buffer(img.flatten(), colorfmt='luminance', bufferfmt='ubyte')

Trouble with slow kivy app to view webcam in real time

I am trying to make a kivy app to display images grabbed from a webcam in real time. It works, but is slow, and I am wondering in anyone knows how to fix this or knows of a different method to use. By slow, I mean that there is a large delay between successive images being displayed.
The code is simple, just consists of a rectangle on which the image grabbed from a camera is displayed via texture.blit_buffer . There is a 'Play' button to start and stop image display.
I have scheduled the texture on the rectangle to be updated every 0.02s, and using pythons time module I was able to time the execution of the function that updates the texture. The function is called and executes about every 0.02s, so the issue is not the function itself being slow. This is where I am hung up. What else is causing this to update the displayed image so slowly?
Anyway, here is the .py file
import numpy as np
import cv2
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.graphics.texture import Texture
from kivy.clock import Clock
from kivy.properties import NumericProperty
import time
class WebcamGUI(Widget):
IsPlaying = False
Event = None
Size_x = 640
Size_y = 480
texture = Texture.create(size=(Size_x,Size_y),colorfmt='bgr')
frame = np.ones(shape=[Size_x, Size_y, 3], dtype=np.uint8)
data = frame.tostring()
texture.blit_buffer(data, colorfmt='bgr', bufferfmt='ubyte')
cap = cv2.VideoCapture(0)
t = time.time()
def GetFrame(self,dt):
ret, frame = self.cap.read()
frame1 = cv2.flip(frame, 0)
data = frame1.tostring()
self.texture.blit_buffer(data, colorfmt='bgr', bufferfmt='ubyte')
print(time.time()-self.t)
self.t = time.time()
def Play(self):
self.IsPlaying = not self.IsPlaying
if self.IsPlaying:
self.Event = Clock.schedule_interval(self.GetFrame,0.02)
else:
self.Event.cancel()
class WebcamGUIApp(App):
def build(self):
GUI = WebcamGUI()
return GUI
def on_stop(self):
self.cap.release()
if __name__ == '__main__':
Window.fullscreen = False
WebcamGUIApp().run()
cv2.destroyAllWindows()
Here is the .kv file:
#:kivy 1.0.9
<WebcamGUI>:
canvas:
Rectangle:
pos: 100,100
size: self.Size_x,self.Size_y
texture: self.texture
ToggleButton:
pos: (10,10)
size: 100,25
text: "Play"
on_press: root.Play()

Categories