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')
Related
i there is there any way like that shown in the below,
from kivy.uix.image import Image
from kivy.uix.gridlayout import GridLayout
root=GridLayout(cols=2)
image="all image data as a string"
image=Image(source=image)
root.add_widget(image)
runTouchApp(root)
i tried this but it takes only image path
why i do like this because iam doing an app so that the image data as a string stored in database and i want to display that image without storing it in device storage
You can try wrapping the data in a BytesIO object and passing this as source, if it doesn't work (can't test right now) you should be able to do so with core.image.Image (https://kivy.org/doc/stable/api-kivy.core.image.html) and then use this image's texture attribute to assign to an kivy.uix.image.Image instance.
edit: here is an example of loading an image from memory, here using pillow to construct it and get it as a BytesIO object, but you could get that source data from your database all the same
from io import BytesIO
from pathlib import Path
from kivy.app import App
from kivy.core.image import Image as CoreImage
from kivy.uix.image import Image
# NOTE this import is important to ensure kivy is ready to load an image from memory
from kivy.core.window import Window
from PIL import Image as PillowImage, ImageDraw
WIDTH = 1000
class Application(App):
def build(self):
# create a pillow image
pillow_image = PillowImage.new(mode='RGBA', size=(WIDTH, WIDTH))
draw = ImageDraw.Draw(pillow_image)
for x in range(0, WIDTH, 5):
draw.line((0, x, x, WIDTH), fill=(x, x, x, 255))
draw.line((x, WIDTH, WIDTH, WIDTH - x), fill=(x, x, x, 255))
draw.line((WIDTH, WIDTH - x, WIDTH - x, 0), fill=(x, x, x, 255))
draw.line((WIDTH - x, 0, 0, x), fill=(x, x, x, 255))
# create bytes from the image data
image_bytes = BytesIO()
pillow_image.save(image_bytes, format='png')
image_bytes.seek(0)
# load image data in a kivy texture
core_image = CoreImage(image_bytes, ext='png')
texture = core_image.texture
img = Image(texture=texture)
return img
if __name__ == "__main__":
Application().run()
You can use the image data to create a Texture and then assign that Texture to the Image. See the documentation for Texture and Image.
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
At the moment the below code shows a camera layout using laptop webcam just fine - I want to show a rectangle frame within the camera window - user will hold a book aligned to the frame and I need to capture an image of the book i.e capture image of the part within the frame. I am struggling to
Show a transparent rectangle as a frame (this is inside a boxlayout inside a camera floatlayout)
Grab an image of only the part within the frame
There is a button below the camera layout at click of which the image will be saved to a folder on the machine
Please can somebody guide me how to proceed and whether this can be achieved in any other way using any other module
import kivy
from PIL import ImageGrab
from kivy.uix.boxlayout import BoxLayout
from numpy import shape
kivy.require('1.7.2')
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.camera import Camera
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.graphics import Color, Rectangle
class CamApp(App):
# Function to take a screenshot
def screengrab(self, *largs):
im2 = ImageGrab.grab(bbox=None)
im2.show()
#outname = self.fileprefix + '_%(counter)04d.png'
#Window.screenshot(name=outname)
def build(self):
# create a floating layout as base
camlayout = FloatLayout(size=(600, 600))
cam = Camera() # Get the camera
cam = Camera(resolution=(1024, 1024), size=(300, 300))
cam.play = True # Start the camera
camlayout.add_widget(cam)
boxlayout = BoxLayout(id='imageBox', size_hint=[0.5, 0.7], pos_hint={'center_x': .5, 'center_y': .5})
boxlayout.bind(size=self.update_rect, pos=self.update_rect)
with boxlayout.canvas.before:
#Color(0, 1, 0, 1) # green; colors range from 0-1 instead of 0-255
self.rect = Rectangle(size=boxlayout.size,
pos=boxlayout.pos, outline='black')
camlayout.add_widget(boxlayout)
button = Button(text='Take Picture', size_hint=(0.12, 0.12))
button.bind(on_press=self.screengrab)
camlayout.add_widget(button) # Add button to Camera Layout
self.fileprefix = 'snap'
return camlayout
def update_rect(self,instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
if __name__ == '__main__':
CamApp().run()
Click to see output
In kivy, the Color() function takes arguments representing RGBA. That means the last argument is alpha, and passing it a float will make it transparent.
So while Color(0, 1, 0, 1) is a solid color, Color(0, 1, 0, .5) will be semitransparent.
As for grabbing an image of only part of the frame, you might consider grabbing the whole frame, then using PIL to crop the image with
from PIL import Image
im = Image.open(r"C:\path\to\picture\my_screenshot.png")
im.crop((left, top, right, bottom))
Just replace left, top, right, and bottom with the dimensions you want to cut out.
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.
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()