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

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

Related

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.

KivyMD App Error : build self.image = Image() TypeError: 'module' object is not callable

I'm trying to build a text scanner app using pytesseract, opencv, and KivyMD as this tutorial: https://www.youtube.com/watch?v=xIYbgCvFfdQ&list=PL0lYY7rL__yLMEI7k9hA8L1EOVlH79FyY&index=2
But my result is empty window and errors. Please help
This is my code main.py
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.button import MDRaisedButton
from kivy.uix.image import Image
from kivy.graphics.texture import Texture
from kivy.clock import Clock
import cv2
import pytesseract
from kivymd.uix.label import MDLabel
from PIL import Image
class MainApp(MDApp):
def build(self):
layout = MDBoxLayout(orientation='vertical')
self.image = Image()
self.label = MDLabel()
layout.add_widget(self.image)
layout.add_widget(self.label)
self.save_img_button = MDRaisedButton(
text="CLICK HERE",
pos_hint={'center_x': .5, 'center_y': .5},
size_hint=(None, None))
self.save_img_button.bind(on_press=self.take_picture)
layout.add_widget(self.save_img_button)
self.capture = cv2.VideoCapture(1)
Clock.schedule_interval(self.load_video, 1.0/30.0)
return layout
def load_video(self, *args):
ret, frame = self.capture.read()
# Frame initialize
self.image_frame = frame
buffer = cv2.flip(frame, 0).tostring()
texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt='bgr')
texture.blit_buffer(buffer, colorfmt='bgr', bufferfmt='ubyte')
self.image.texture = texture
def take_picture(self, *args):
image_name = "picture_at_2_02.png"
img = cv2.cvtColor(self.image_frame, cv2.COLOR_BGR2GRAY)
img = cv2.GaussianBlur(img, (3, 3), 0)
img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]
text_data = pytesseract.image_to_string(img, lang='eng', config="--oem 3 --psm 6")
print(text_data)
self.label.text = text_data
cv2.imshow("cv2 final image", img)
cv2.imwrite(image_name, self.image_frame)
if __name__=="__main__":
MainApp().run()
Here are errors:
Traceback (most recent call last):
File "c:/Users/aroon/prac python/MyApp/main.py", line 53, in <module>
MainApp().run()
File "C:\Users\aroon\Anaconda3\lib\site-packages\kivy\app.py", line 949, in run
self._run_prepare()
File "C:\Users\aroon\Anaconda3\lib\site-packages\kivy\app.py", line 919, in _run_prepare
root = self.build()
File "c:/Users/aroon/prac python/MyApp/main.py", line 16, in build
self.image = Image()
TypeError: 'module' object is not callable
This is due to the fact that the image classes in the PIL and kivy libraries are called the same - Image, you just need to change the class name this way from PIL import Image as PILImage

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.

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

Kivy and OpenCv mask

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.

Categories