Search code examples
pythonpython-3.xcamerakivyraspberry-pi4

How can I use Kivy to zoom in or zoom out live camera?


I've searched about it and found "scatter", but scatter use for image. I want to zoom it with live camera.

Anyone know how can I do that?

This is code example I've written, but it doesn't work.

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import ScreenManager , Screen
from kivy.uix.image import Image
from kivy.core.window import Window
from kivy.base import runTouchApp
from kivymd.app import MDApp
from kivy.uix.boxlayout import BoxLayout
import time
from kivy.core.window import Window
from kivy.uix.camera import Camera
from kivy.uix.scatter import Scatter
from kivy.uix.relativelayout import RelativeLayout
from kivy.properties import NumericProperty


Window.size = (1600, 850)

class MyCamera(Camera):
    
    region_x = NumericProperty(0)
    region_y = NumericProperty(0)
    region_w = NumericProperty(1600)
    region_h = NumericProperty(850)
    
    def on_text(self,camera):
        self.texture = texture = camera.texture
        
        self.texture = self.texture.get_region(self.region_x, self.region_y, self.region_w, self.region_h)
        self.texture_size = list(texture.size)
        self.canvas.ask_update()



class MainPage(Screen):
    pass

class WindowManager(ScreenManager):
    pass


class CameraClick(Screen):
    scale = NumericProperty(1)


    def on_touch_down(self, touch):
        if touch.is_mouse_scrolling:
            if touch.button == 'scrolldown':
                print("down")
                if self.scale <10:
                    self.scale *= 1.1
                    self.ids['camera'].region_w /= 1.1
                    self.ids['camera'].region_h /= 1.1
                    self.ids['camera'].region_x = (1600-self.ids['camera'].region_w) // 2
                    self.ids['camera'].region_y = (850-self.ids['camera'].region_h) // 2 
    
    
            elif touch.button == 'scrollup':
                print("up")
                if self.scale >1:
                    self.scale *= 0.8
                    
                    self.ids['camera'].region_w /= 0.8
                    self.ids['camera'].region_h /= 0.8
                    if(self.ids['camera'].region_w > 1600) or (self.ids['camera'].region_h >850):
                        self.ids['camera'].region_w = 1600
                        self.ids['camera'].region_h = 850
                        
                        
                    self.ids['camera'].region_x = (1600-self.ids['camera'].region_w) //2
                    self.ids['camera'].region_y = (850-self.ids['camera'].region_h) //2
                
    
    def capture(self):
        camera = self.ids['camera']
        timestr = time.strftime("%Y%m%d_%H%M%S")
        camera.export_to_png("IMG_{}.png".format(timestr))
        print("Captured")

    
            

Builder.load_string("""

#:import utils kivy.utils
<WindowManager>:
    MainPage:
    CameraClick:
    
          

<MainPage>:
    name: "main page"
                 
    BoxLayout:
        cols:1
        orientation: "horizontal"
        size: root.width , root.height
        spacing: 25
        padding: 530, 900 , 900 , 260

        Button:
            text: "take a picture"
            color: (200,250,210)
            font_size: 40
            size_hint_x: 1
            height:60
            size_hint_y: None
            width:500
            on_release: app.root.current = "camera"

                              
<CameraClick>:

    name: "camera"
    orientation: 'vertical'    
    
    MyCamera:
        id: camera
        play: True
        allow_stretch: True
        resolusion: (640,480)

    BoxLayout:
        orientation: 'vertical'
        padding: 100 , 10 , 800 , 590

        Button:
            text: 'play'
            size_hint_y: None
            size_hint_x: None
            height: '48dp'
            pos:200,200
            font_size:40
            width: 100
            height: 50
            on_press: camera.play = not camera.play
                
                
    BoxLayout:
        orientation: 'vertical'
        padding: 100 , 10 , 800 , 380

        Button:
            text: 'capture'
            size_hint_y: None
            size_hint_x: None
            height: '48dp'
            pos:200,200
            font_size:40
            width: 100
            height: 50
            on_press: root.capture()
 

    BoxLayout:
        orientation: 'vertical'
        padding: 100 , 10 , 800 , 200
        
        Button:
            text: 'ZOOM'
            size_hint_y: None
            size_hint_x: None
            height: '48dp'
            pos:100,100
            font_size:30
            width: 100
            height: 50
            on_press: root.on_touch_down()
            
            
    BoxLayout:
        orientation: 'vertical'
        padding: 50 , 10 , 800 , 730
    

        Button:
            text: 'HOME'
            size_hint_y: None
            size_hint_x: None
            height: '48dp'
            pos:200,200
            font_size:40
            width: 100
            height: 50
            on_release: app.root.current = "main page"
        
""")

class Shenacell(MDApp):
    def build(self):
        self.theme_cls.primary_palette = "BlueGray"
        return WindowManager()

if __name__ == '__main__' :
    Shenacell().run()
    
    

    

Solution

  • Here is source code for widget Camera.

    It has method on_tex() which gets texture from real camera

    def on_tex(self, camera):
        self.texture = texture = camera.texture
        self.texture_size = list(texture.size)
        self.canvas.ask_update()
    

    texture has method get_region() which can be used to get only some part of image -

    self.texture.get_region(x, y, width, height)
    

    and this way you can create zoom effect - when you have allow_stretch: True.

    Here is class which gets only some region.
    I assumed that camera gives image with size 640x480 but it would need to get value from variable resolution in Camera.

    class MyCamera(Camera):
        
        region_x = NumericProperty(0)
        region_y = NumericProperty(0)
        region_w = NumericProperty(640)
        region_h = NumericProperty(480)
        
        def on_tex(self, camera):
            
            self.texture = texture = camera.texture
    
            # get some region
            self.texture = self.texture.get_region(self.region_x, self.region_y, self.region_w, self.region_h)
    
            self.texture_size = list(texture.size)
            self.canvas.ask_update()
    

    And now in CameraClick I can change values region_x, region_y, region_w, region_h to create zoom effect.

    class CameraClick(Screen):
    
        scale = NumericProperty(1)
        
        def on_touch_down(self, touch):
            
            if touch.is_mouse_scrolling:
    
                if touch.button == 'scrolldown':
                    print("down")
                    if self.scale < 10:
                        self.scale *= 1.1
    
                        # scale region size
                        self.ids['camera'].region_w /= 1.1
                        self.ids['camera'].region_h /= 1.1
    
                        # center region
                        self.ids['camera'].region_x = (640-self.ids['camera'].region_w) // 2
                        self.ids['camera'].region_y = (480-self.ids['camera'].region_h) // 2
                        
                elif touch.button == 'scrollup':
                    print("up")
                    if self.scale > 1:
                        self.scale *= 0.8
    
                        # scale region size
                        self.ids['camera'].region_w /= 0.8
                        self.ids['camera'].region_h /= 0.8
                        if (self.ids['camera'].region_w > 640) or (self.ids['camera'].region_h > 480):
                            self.ids['camera'].region_w = 640
                            self.ids['camera'].region_h = 480
    
                        # center region
                        self.ids['camera'].region_x = (640-self.ids['camera'].region_w) // 2
                        self.ids['camera'].region_y = (480-self.ids['camera'].region_h) // 2
    

    It many need changes

    • use resolution to get real camera size instead hardcoded 640,480.
    • use buttons, keyboard or mouse to move image to see other regions.

    If you run code with allow_stretch: False then it gives smaller image instead of resizing it - so it would need different method. It would need to get texture, rescale it and crop to expected region.


    Full working code.

    EDIT:

    I added Capture. It needs super().on_touch_down(touch) in on_touch_down to execute on_press,on_release, etc.

    from kivymd.app import MDApp
    from kivy.uix.screenmanager import ScreenManager, Screen
    from kivy.uix.camera import Camera
    from kivy.lang import Builder
    from kivy.properties import NumericProperty
    import time
    
    
    class MyCamera(Camera):
    
        region_x = NumericProperty(0)
        region_y = NumericProperty(0)
        region_w = NumericProperty(640)
        region_h = NumericProperty(480)
    
        def on_tex(self, camera):
    
            self.texture = texture = camera.texture
    
            # get some region
            self.texture = self.texture.get_region(self.region_x, self.region_y, self.region_w, self.region_h)
    
            self.texture_size = list(texture.size)
            self.canvas.ask_update()
    
    
    class WindowManager(ScreenManager):
        pass
    
    
    class CameraClick(Screen):
    
        scale = NumericProperty(1)
    
        def on_touch_down(self, touch):
            #print('[DEBUG] on_touch_down')
    
            super().on_touch_down(touch)  # run original `Screen.on_touch_down` which runs `on_press`, `on_release`
    
            if touch.is_mouse_scrolling:
                if touch.button == 'scrolldown':
                    print("down: (zoom in) ", self.scale)
                    if self.scale < 10:
                        self.scale *= 1.1
                        # scale region size
                        self.ids['camera'].region_w /= 1.1
                        self.ids['camera'].region_h /= 1.1
                        # center region
                        self.ids['camera'].region_x = (640-self.ids['camera'].region_w) // 2
                        self.ids['camera'].region_y = (480-self.ids['camera'].region_h) // 2
                    
                elif touch.button == 'scrollup':
                    print("  up: (zoom out)", self.scale)
                    if self.scale > 1:
                        self.scale *= 0.8
                        # scale region size
                        self.ids['camera'].region_w /= 0.8
                        self.ids['camera'].region_h /= 0.8
                        if (self.ids['camera'].region_w > 640) or (self.ids['camera'].region_h > 480):
                            self.ids['camera'].region_w = 640
                            self.ids['camera'].region_h = 480
                        # center region
                        self.ids['camera'].region_x = (640-self.ids['camera'].region_w) // 2
                        self.ids['camera'].region_y = (480-self.ids['camera'].region_h) // 2
    
        def capture(self):
            camera = self.ids['camera']
            filename = time.strftime("IMG_%Y%m%d_%H%M%S.png")
            camera.export_to_png(filename)
            print("Captured:", filename)
    
    
    Builder.load_string("""
    <WindowManager>:
        CameraClick:
    
    <CameraClick>:
        name: "camera"
        orientation: 'vertical'
        MyCamera:
            id: camera
            play: True
            allow_stretch: True
        Button:
            text: 'Capture'
            size_hint_y: None
            size_hint_x: None
            on_press: root.capture()
    """)
    
    
    class Shenacell(MDApp):
        def build(self):
            return WindowManager()
    
    if __name__ == '__main__':
        Shenacell().run()
    

    Maybe it should be done as wrapper class which gets Camera as argument.


    BTW:

    If you would need to display together original image and zoomed image then you may need code from my answer for question Kivy Camera on multiple screen