Kivy and OpenCv mask - python

I have made this simple code which show two images in kivy one image is the normal image and the other image is the mask
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.image import Image
from kivy.graphics.texture import Texture
from kivy.clock import Clock
import cv2
import numpy as np
class NormalImage(Image):
def __init__(self, **kwargs):
super(NormalImage, self).__init__(**kwargs)
self.size = (600, 600)
self.pos = (0, 0)
self.img = cv2.imread('img.jpg')
cv2.imshow("normal image", self.img)
buf1 = cv2.flip(self.img, 0)
buf = buf1.tostring()
image_texture = Texture.create(size=(self.img.shape[1], self.img.shape[0]), colorfmt='bgr')
image_texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte')
self.texture = image_texture
class BinaryImage(Image):
def __init__(self, **kwargs):
super(BinaryImage, self).__init__(**kwargs)
self.size = (600, 600)
self.pos = (300, 0)
img = NormalImage().img
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
hsvMin = np.array([19,150,100])
hsvMax = np.array([30,255,255])
mask = cv2.inRange(hsv,hsvMin, hsvMax)
cv2.imshow("mask", mask)
buf1 = cv2.flip(mask, 0)
buf = buf1.tostring()
image_texture = Texture.create(size=(mask.shape[1], mask.shape[0]), colorfmt='bgr')
image_texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte')
self.texture = image_texture
class RootWidget(Widget):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
self.add_widget(NormalImage())
self.add_widget(BinaryImage())
class ImageApp(App):
def build(self):
return RootWidget()
def on_stop(self):
cv2.destroyAllWindows()
if __name__ == '__main__':
ImageApp().run()
The normal image is shown properly, but the problem is that the mask isn't shown properly. It's supposed to show exactly the same as the image in the opencv window.
There is also some colored pixels on the mask, and it's supposed to be only black and white pixels.
If you know how to show the mask properly in kivy let me know.
If you want to try with image that I used.

Related

How can I access a separate camera class dynamically in python with kivy (without pre-initialising camera)

I have written a camera access class using python+kivy (kivycamera.py) and it is working.
kivycamera.py
# from kivymd.app import MDApp
from kivy.uix.image import Image
from kivy.graphics.texture import Texture
from kivy.clock import Clock
import cv2
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFillRoundFlatButton
import time
# class KivyCamera(MDApp):
class KivyCamera(Image):
def build(self):
layout = MDBoxLayout(orientation='vertical', spacing=10)
self.image = Image()
layout.add_widget(self.image)
self.start_camera_button = MDFillRoundFlatButton(
text="START CAMERA",
pos_hint={'center_x': 0.5, 'center_y': 0.5},
size_hint=(0.4, None),
# size=("100dp", "100dp")
)
self.start_camera_button.bind(on_press=self.start_camera)
layout.add_widget(self.start_camera_button)
self.save_img_button = MDFillRoundFlatButton(
text="TAKE PICTURE",
pos_hint={'center_x': 0.5, 'center_y': 0.5},
size_hint=(0.4, None),
# size=("100dp", "100dp")
)
self.save_img_button.bind(on_press=self.take_picture)
layout.add_widget(self.save_img_button)
# self.video = cv2.VideoCapture(0)
# Clock.schedule_interval(self.load_video, 1.0 / 30.0)
# return layout
# return self.image
def start_camera(self, *args):
self.video = cv2.VideoCapture(0)
Clock.schedule_interval(self.load_video, 1.0 / 30.0)
pass
def load_video(self, *args):
check, frame = self.video.read()
if check:
x, y, w, h = 200, 200, 250, 250
p, q, r, s = 220, 220, 210, 210
frame = cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 3)
frame = cv2.rectangle(frame, (p, q), (p + r, q + s), (255, 0, 0), 3)
self.image_frame = frame
buffer = cv2.flip(frame, 0).tobytes()
image_texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt="bgr")
image_texture.blit_buffer(buffer, colorfmt="bgr", bufferfmt="ubyte")
self.image.texture = image_texture
def take_picture(self, *args):
timestr = time.strftime("%Y%m%d_%H%M%S")
cv2.imwrite("IMG_{}.png".format(timestr), self.image_frame)
cv2.imshow("Hi", self.image_frame)
# KivyCamera().run()
How can I add this class to another MDApp or Screen only when it is required. I tried below approaches and those were not successful. Do not pre-initialise camera. Only activate camera using a button action.
Inside a separate MDApp
from kivymd.app import MDApp
from kivycamera import KivyCamera
class DemoApp(MDApp):
pass
# def build(self):
# self.camera = KivyCamera()
# self.camera.build()
# print(self.camera)
# return self.camera
if __name__ == '__main__':
DemoApp().run()
Kivy File
BoxLayout:
orientation: 'vertical'
MDLabel:
text: 'Hello'
KivyCamera:
or inside a separate screen using screen manager
class UploadScreen(Screen):
pass
# def build(self):
# self.my_camera = KivyCamera()
# self.my_camera.build()
# self.ids.my_camera = self.my_camera
# return self.my_camera
Kivy File
<UploadScreen>:
name: 'upload'
KivyCamera:
Your KivyCamera extends Image, but you are using the KivyCamera as a widget container. Also, in your build() method, you are creating a layout, but not doing anything with it. I suggest modifying KivyCamera definition to change its base class to some Layout (I chose RelativeLayout) and to add the layout as a child. Here is a modified version of your code that does this:
from kivymd.app import MDApp
from kivycamera import KivyCamera
class DemoApp(MDApp):
def build(self):
self.camera = KivyCamera()
self.camera.build()
print(self.camera)
return self.camera
if __name__ == '__main__':
DemoApp().run()
and kivycamera.py:
# from kivymd.app import MDApp
from kivy.uix.image import Image
from kivy.graphics.texture import Texture
from kivy.clock import Clock
import cv2
from kivy.uix.relativelayout import RelativeLayout
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDFillRoundFlatButton
import time
# class KivyCamera(MDApp):
class KivyCamera(RelativeLayout): # make this a container
def build(self):
layout = MDBoxLayout(orientation='vertical', spacing=10)
self.image = Image()
layout.add_widget(self.image)
self.start_camera_button = MDFillRoundFlatButton(
text="START CAMERA",
pos_hint={'center_x': 0.5, 'center_y': 0.5},
size_hint=(0.4, None),
# size=("100dp", "100dp")
)
self.start_camera_button.bind(on_press=self.start_camera)
layout.add_widget(self.start_camera_button)
self.save_img_button = MDFillRoundFlatButton(
text="TAKE PICTURE",
pos_hint={'center_x': 0.5, 'center_y': 0.5},
size_hint=(0.4, None),
# size=("100dp", "100dp")
)
self.save_img_button.bind(on_press=self.take_picture)
layout.add_widget(self.save_img_button)
self.add_widget(layout) # add the layout to the GUI
# self.video = cv2.VideoCapture(0)
# Clock.schedule_interval(self.load_video, 1.0 / 30.0)
# return layout
# return self.image
def start_camera(self, *args):
self.video = cv2.VideoCapture(0)
Clock.schedule_interval(self.load_video, 1.0 / 30.0)
pass
def load_video(self, *args):
check, frame = self.video.read()
if check:
x, y, w, h = 200, 200, 250, 250
p, q, r, s = 220, 220, 210, 210
frame = cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 3)
frame = cv2.rectangle(frame, (p, q), (p + r, q + s), (255, 0, 0), 3)
self.image_frame = frame
buffer = cv2.flip(frame, 0).tobytes()
image_texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt="bgr")
image_texture.blit_buffer(buffer, colorfmt="bgr", bufferfmt="ubyte")
self.image.texture = image_texture
def take_picture(self, *args):
timestr = time.strftime("%Y%m%d_%H%M%S")
cv2.imwrite("IMG_{}.png".format(timestr), self.image_frame)
cv2.imshow("Hi", self.image_frame)
# KivyCamera().run()
There are several other simplifications that you could make. For example, you could eliminate the build() method by changing it to an __init__() method (must add a super(KivyCamera, self).__init__() call), and eliminate the layout variable by making KivyCamera extend MDBoxLayout.

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

How to align a rectangle using kivy?

Does anybody know the code to center align a rectangle using kivy but in a py file not kv. This is my code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle
from kivy.graphics import Color
class PongGame(Widget):
def __init__(self, **kwargs):
super(PongGame, self).__init__(**kwargs)
with self.canvas:
Color(255,255,255, mode='rgba')
self.rect = Rectangle(pos=(self.x + 50, self.y + 50), size=(50, 50))
# print(self.rect)
class PingyPong(App):
def build(self):
return PongGame()
# class MakeRectangle(Widget):
# def __init__(self, **kwargs):
# with self.canvas:
# # Color(255,255,255, mode='rgba')
# self.rect = Rectangle(pos=(200, 200), size=(50, 50))
# class PingPong(App):
# def build(self):
# return Rectangle()
if __name__ == '__main__':
PingyPong().run()
This is the output:
enter image description here
please help
You can do it by getting Window size and then create rectangle at the center
from kivy.core.window import Window
class PongGame(Widget):
def __init__(self, **kwargs):
super(PongGame, self).__init__(**kwargs)
with self.canvas:
Color(255,255,255, mode='rgba')
window_size = Window.size
size = 50
self.rect = Rectangle(pos=(window_size[0]/2-size/2, window_size[1]/2-size/2), size=(size, size))
print()

Kivy segmentation fault on blitting opencv picture

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.

Is there any way to specify numpy array to 'Image Widget'?

I'm looking for a way to specify numpy image arrays to source of Image widget without saving as file such as '.png' or '.jpg'.
I know a way to use canvas(Texture.create(), blit_buffer(), Rectangle()).....Is there any other way?
import kivy
from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.widget import Widget
import cv2
class Test(Widget):
img = cv2.imread('0.png', cv2.IMREAD_UNCHANGED)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.flip(img, 0)
# Is it possible to specify cv2(numpy) image to 'Image Widget' directly(without saving as file(jpg,png))?
w_img = Image(source=img, size=(100, 100)) # >> error
self.add_widget(w_img)
class DemoApp(App):
def build(self):
return Test()
if __name__ == '__main__':
DemoApp().run()
You have to pass the image through a Texture:
import kivy
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('0.png', cv2.IMREAD_UNCHANGED)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.flip(img, 0)
w, h, _ = img.shape
texture = Texture.create(size=(w, h))
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()

Categories