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.
Related
I'm wondering why I am getting two different results from this code that I am using from github. If I run the compiled package on my cell phone, I get a barcode readout of 991245243. However if I use my computer, the readout is 0991245243. Can anyone explain why?
What I am trying to do is have the results from the barcode scanner check against a spreadsheet and then return the results from the adjacent cells. I want to look for exact matches because it could be catastrophic if multiple results are found and they conflict.
# Kivy OpenCV Barcode Scanner
# done by Vijeth P H
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.image import Image
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.graphics.texture import Texture
from pyzbar import pyzbar
import webbrowser
import cv2
# Create global variables, for storing and displaying barcodes
outputtext=''
weblink=''
leb=Label(text=outputtext,size_hint_y=None,height='48dp',font_size='45dp')
found = set() # this will not allow duplicate barcode scans to be stored
togglflag=True
class MainScreen(BoxLayout):
# first screen that is displayed when program is run
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.orientation='vertical' # vertical placing of widgets
self.cam=cv2.VideoCapture(0) # start OpenCV camera
self.cam.set(3,1280) # set resolution of camera
self.cam.set(4,720)
self.img=Image() # Image widget to display frames
# create Toggle Button for pause and play of video stream
self.togbut=ToggleButton(text='Pause',group='camstart',state='down',size_hint_y=None,height='48dp',on_press=self.change_state)
self.but=Button(text='Stop',size_hint_y=None,height='48dp',on_press=self.stop_stream)
self.add_widget(self.img)
self.add_widget(self.togbut)
self.add_widget(self.but)
Clock.schedule_interval(self.update,1.0/30) # update for 30fps
# update frame of OpenCV camera
def update(self,dt):
if togglflag:
ret, frame = self.cam.read() # retrieve frames from OpenCV camera
if ret:
buf1=cv2.flip(frame,0) # convert it into texture
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')
self.img.texture=image_texture # display image from the texture
barcodes = pyzbar.decode(frame) # detect barcode from image
for barcode in barcodes:
(x, y, w, h) = barcode.rect
cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
barcodeData = barcode.data.decode("utf-8")
barcodeType = barcode.type
weblink=barcodeData
text = "{} ({})".format(barcodeData, barcodeType)
cv2.putText(frame, text, (x, y - 10),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
if barcodeData not in found: # check if detected barcode is a duplicate
outputtext=text
leb.text=outputtext # display the barcode details
found.add(barcodeData)
self.change_screen()
key = cv2.waitKey(1) & 0xFF
if key == ord("q"):
cv2.destroyAllWindows()
exit(0)
# change state of toggle button
def change_state(self,*args):
global togglflag
if togglflag:
self.togbut.text='Play'
togglflag=False
else:
self.togbut.text='Pause'
togglflag=True
def stop_stream(self,*args):
self.cam.release() # stop camera
def change_screen(self,*args):
main_app.sm.current='second' # once barcode is detected, switch to second screen
class SecondScreen(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
self.lab1=Label(text='Output: ',size_hint_y=None,height='48dp',font_size='45dp')
self.but1=Button(text='Open in Web Browser',on_press=self.open_browser,size_hint_y=None,height='48dp')
self.add_widget(self.lab1)
self.add_widget(leb)
self.add_widget(self.but1)
def open_browser(self,*args):
webbrowser.open(weblink) # this opens link in browser
class TestApp(App):
def build(self):
self.sm=ScreenManager() # screenmanager is used to manage screens
self.mainsc=MainScreen()
scrn=Screen(name='main')
scrn.add_widget(self.mainsc)
self.sm.add_widget(scrn)
self.secondsc=SecondScreen()
scrn=Screen(name='second')
scrn.add_widget(self.secondsc)
self.sm.add_widget(scrn)
return self.sm
if __name__ == '__main__':
main_app=TestApp()
main_app.run()
cv2.destroyAllWindows()
Here is a picture as requested.
As the behaviors of the reader on the two platforms differ, and the decoding is wrong, one can suspect a bug in the reader that causes access to uninitialized memory. (If the problem was just a misread, not a bug, the texts would be strictly identical.)
Or can it be that the text display clips off the first character on the phone ?
Update:
The new information that on one platform the code is said to be an EAN13 and on the other a UPC-A explains the extra 0. (You should have said it upfront.) The two barcode standards require a slight difference in the way the number is reported.
But this does not explain why the code is completely wrong and why different symbologies are reported. (Maybe the default settings are different on the two patforms.)
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.
as the canvas instructions Stencil and Scissor (which seem to mo me have the same effect) are used to create masks and draw on top of it, im wondering have one could achieve this effect:
Drawing any shape on top of a background image (so far so easy). Then cut text out of the shape in order to look through on the background.
Is there any solution for that? Or any kivy/openGL instructions i have missed?
Here is something that comes close to what you want. It is a brute force method that makes the text of a kivy.core.text.Label transparent and then uses that texture in a canvas Rectangle:
from kivy.app import App
from kivy.clock import Clock
from kivy.graphics.context_instructions import Color
from kivy.graphics.texture import Texture
from kivy.graphics.vertex_instructions import Rectangle
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.core.text import Label as CoreLabel
class MyWidget(FloatLayout):
pass
Builder.load_string('''
<MyWidget>:
Image:
source: 'tester.png'
allow_stretch: True
keep_ratio: True
''')
class TestApp(App):
def build(self):
Clock.schedule_once(self.doit)
return MyWidget()
def doit(self, dt):
# build a Rectangle half the size and centered on the root (MyWidget)
size = (self.root.width/2, self.root.height/2)
pos = (self.root.x + (self.root.width - size[0])/2, self.root.y + (self.root.height - size[1])/2)
with self.root.canvas.after:
Color(1,1,1,1)
self.rect = Rectangle(pos=pos, size=size)
# create a Texture with desired text and desired size
label = CoreLabel(text='Hello', font_name='Roboto-Bold', font_size=300, valign='center', halign='center',
size=self.root.size, color=(1,1,1,1))
label.refresh()
# make the actual text transparent, and assign the new Texture to the Rectangle
self.rect.texture = self.make_text_transparent(label.texture, (255, 0, 0, 255))
def make_text_transparent(self, texture, bg_color):
# bg_color is a 4 tuple of byte values in the range 0-255
# The normal Label texture is transparent except for the text itself
# This code changes the texture to make the text transparent, and everything else
# gets set to bg_color
pixels = list(texture.pixels)
for index in range(3, len(pixels)-4, 4):
if pixels[index] == 0:
# change transparent pixels to the bg_color
pixels[index-3:index+1] = bg_color
else:
# make the text itself transparent
pixels[index] = 0
# create a new texture, blit the new pixels, and return the new texture
new_texture = Texture.create(size=texture.size, colorfmt='rgba')
new_texture.blit_buffer(bytes(pixels), colorfmt='rgba', bufferfmt='ubyte')
new_texture.flip_vertical()
return new_texture
TestApp().run()
You could generalize this by using another Texture, instead of bg_color and setting the transparent Label pixels to the corresponding pixel from the supplied Texture.
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()
Problem in hand is that i have a screen where i have created 4 widgets under gridlayout . Widget one is a custom widget which have a boxlayout and have 2 images and a button .
and other 3 widgets are simple buttons
Now i want to have a background image or coloured rectangle to the 1st widget but image is getting drawn at position 0,0 . I am trying to use canvas instructions to create a rectangle or Border Image but seems gridlayout shows the widget position as 0,0 and hence it created the rectangle at 0,0 . please see the code below :
Any ideas how to fix this and create rectangle as at the border of widget 1?
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen,FallOutTransition
from kivy.uix.image import Image
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.graphics import BorderImage
from kivy.graphics import Color, Rectangle ,Line
################# Images ######################
S = Image(source='images/Sti.png')
L = 'images/spinnin.gif'
################# Images ######################
class CButtonW(BoxLayout):
def __init__(self, **kwargs):
print "init --> CButton self.pos is ",self.pos
super(CButtonW, self).__init__(**kwargs)
self.orientation = 'vertical'
with self.canvas.before:
Color(1, 1, 0, 1, mode='rgba')
Rectangle(pos=self.pos,size=self.size)
BorderImage(
border=(10, 10, 10, 10),
source='images/tex.png')
self.add_widget(S)
self.add_widget(Button(text="Button 1"))
self.add_widget(Image(source=L))
class LevelScreen(Screen,GridLayout):
def __init__(self, **kwargs):
super(LevelScreen, self).__init__(**kwargs)
with self.canvas:
Line(points=(10, 10, 20, 30, 40, 50))
Color(1, 0, 1, 1, mode='rgba')
Rectangle(pos=self.pos, size=Window.size)
self.layout = GridLayout(cols=3,spacing=1,padding=10)
self.Button1 = CButtonW(id='1',text='Level 1')
self.Button2 = Button(id='2',text='Level 2')
self.Button3 = Button(id='3',text='Level 3')
self.Button4 = Button(id='4',text='Level 4')
self.layout.add_widget(self.Button1)
self.layout.add_widget(self.Button2)
self.layout.add_widget(self.Button3)
self.layout.add_widget(self.Button4)
LevelScreen.cols=3
LevelScreen.add_widget(self,self.layout)
print "position of 1st button is ",self.Button1.pos
print "position of 2 button is ",self.Button2.pos
# App Class
class MyJBApp(App):
def build(self):
sm = ScreenManager(transition= FallOutTransition())
sm.add_widget(LevelScreen(name='level'))
return sm
if __name__ == '__main__':
MyJBApp().run()
Your canvas instructions are drawn before the CButtonW is positioned by its layout, so at that point it's still in its default position of 0, 0.
You can fix it by adding code to reposition the instructions when the widget's position or size changes. The easiest way is to just use kv language in the first place, which will automatically create the relevant bindings, but you can also bind to pos or size in python, or use the on_propertyname events associated with their changes.