Skip to content
This repository has been archived by the owner on Nov 14, 2023. It is now read-only.

Commit

Permalink
0.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertFlatt committed Feb 10, 2023
1 parent c5cfaa2 commit a087f8b
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 242 deletions.
6 changes: 3 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = camera4kivy
version = 0.1.0
version = 0.2.0
author = Robert Flatt
description = Yet Another Camera for Kivy
long_description = file: README.md
Expand All @@ -17,9 +17,9 @@ classifiers =
package_dir =
= src
packages = find:
python_requires = >=3.6
python_requires = >=3.7
install_requires =
gestures4kivy >= 0.1.0
gestures4kivy >= 0.1.1

[options.packages.find]
where = src
2 changes: 1 addition & 1 deletion src/camera4kivy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .preview import Preview

from .preview import CameraProviderInfo
50 changes: 16 additions & 34 deletions src/camera4kivy/based_on_kivy_core/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,48 +20,27 @@
from kivy.utils import platform
from kivy.event import EventDispatcher
from kivy.logger import Logger
from kivy.core import core_select_lib
from .. import core_select_lib


class CameraBase(EventDispatcher):
'''Abstract Camera Widget class.
Concrete camera classes must implement initialization and
frame capturing to a buffer that can be uploaded to the gpu.
:Parameters:
`index`: int
Source index of the camera.
`resolution`: tuple (int, int)
Resolution to try to request from the camera.
Used in the gstreamer pipeline by forcing the appsink caps
to this resolution. If the camera doesn't support the resolution,
a negotiation error might be thrown.
:Events:
`on_load`
Fired when the camera is loaded and the texture has become
available.
`on_texture`
Fired each time the camera texture is updated.
'''

__events__ = ('on_load', 'on_texture')

def __init__(self, **kwargs):
kwargs.setdefault('stopped', False)
kwargs.setdefault('index', 0)
kwargs.setdefault('context', None)
self.stopped = kwargs.get('stopped')
self._resolution = kwargs.get('resolution')
self._index = kwargs.get('index')
self._context = kwargs.get('context')
self._buffer = None
self._format = 'rgb'
self._texture = None
self.capture_device = None
super(CameraBase, self).__init__()
super().__init__()
self.init_camera()
if not self.stopped:
self.start()
#if not self.stopped and not self._context:
# self.start()

def _get_texture(self):
return self._texture
Expand Down Expand Up @@ -90,16 +69,18 @@ def _copy_to_gpu(self):
if self._texture is None:
Logger.debug('Camera: copy_to_gpu() failed, _texture is None !')
return
self._texture.blit_buffer(self._buffer, colorfmt=self._format)
self._texture.blit_buffer(self._buffer, colorfmt=self._format)
self._buffer = None
self.dispatch('on_texture')
if self._context:
self._context.on_texture()
else:
self.dispatch('on_texture')

def on_texture(self):
pass

def on_load(self):
pass
#def on_texture(self):
# pass

#def on_load(self):
# pass

# Load the appropriate providers
providers = ()
Expand All @@ -115,6 +96,7 @@ def on_load(self):
elif platform == 'android':
pass
else:
#providers += (('picamera2', 'camera_picamera2', 'CameraPiCamera2'), )
providers += (('picamera', 'camera_picamera', 'CameraPiCamera'), )
providers += (('gi', 'camera_gi', 'CameraGi'), )
providers += (('opencv', 'camera_opencv', 'CameraOpenCV'), )
Expand Down
186 changes: 55 additions & 131 deletions src/camera4kivy/based_on_kivy_core/camera/camera_opencv.py
Original file line number Diff line number Diff line change
@@ -1,170 +1,94 @@
'''
OpenCV Camera: Implement CameraBase with OpenCV
'''

#
# TODO: make usage of thread or multiprocess
#

from __future__ import division

__all__ = ('CameraOpenCV')


from kivy.logger import Logger
from kivy.clock import Clock
from kivy.graphics.texture import Texture
from kivy.core.camera import CameraBase
from kivy.utils import platform

try:
# opencv 1 case
import opencv as cv

try:
import opencv.highgui as hg
except ImportError:
class Hg(object):
'''
On OSX, not only are the import names different,
but the API also differs.
There is no module called 'highgui' but the names are
directly available in the 'cv' module.
Some of them even have a different names.
Therefore we use this proxy object.
'''

def __getattr__(self, attr):
if attr.startswith('cv'):
attr = attr[2:]
got = getattr(cv, attr)
return got

hg = Hg()

except ImportError:
# opencv 2 case (and also opencv 3, because it still uses cv2 module name)
try:
import cv2
# here missing this OSX specific highgui thing.
# I'm not on OSX so don't know if it is still valid in opencv >= 2
except ImportError:
raise

from kivy.graphics import Color, Rectangle, Rotate, Fbo
import cv2
from . import CameraBase

class CameraOpenCV(CameraBase):
'''
Implementation of CameraBase using OpenCV
'''
_update_ev = None

def __init__(self, **kwargs):
# we will need it, because constants have
# different access paths between ver. 2 and 3
try:
self.opencvMajorVersion = int(cv.__version__[0])
except NameError:
self.opencvMajorVersion = int(cv2.__version__[0])

self._device = None
self._update_ev = None
super(CameraOpenCV, self).__init__(**kwargs)

def init_camera(self):
# The only change in this file
if platform == 'win' and self.opencvMajorVersion in (2, 3, 4):
self._format = 'bgr'
if platform == 'win':
self._index = self._index + cv2.CAP_DSHOW
# consts have changed locations between versions 2 and 3
if self.opencvMajorVersion in (3, 4):
PROPERTY_WIDTH = cv2.CAP_PROP_FRAME_WIDTH
PROPERTY_HEIGHT = cv2.CAP_PROP_FRAME_HEIGHT
PROPERTY_FPS = cv2.CAP_PROP_FPS
elif self.opencvMajorVersion == 2:
PROPERTY_WIDTH = cv2.cv.CV_CAP_PROP_FRAME_WIDTH
PROPERTY_HEIGHT = cv2.cv.CV_CAP_PROP_FRAME_HEIGHT
PROPERTY_FPS = cv2.cv.CV_CAP_PROP_FPS
elif self.opencvMajorVersion == 1:
PROPERTY_WIDTH = cv.CV_CAP_PROP_FRAME_WIDTH
PROPERTY_HEIGHT = cv.CV_CAP_PROP_FRAME_HEIGHT
PROPERTY_FPS = cv.CV_CAP_PROP_FPS

Logger.debug('Using opencv ver.' + str(self.opencvMajorVersion))

if self.opencvMajorVersion == 1:
# create the device
self._device = hg.cvCreateCameraCapture(self._index)
# Set preferred resolution
cv.SetCaptureProperty(self._device, cv.CV_CAP_PROP_FRAME_WIDTH,
self.resolution[0])
cv.SetCaptureProperty(self._device, cv.CV_CAP_PROP_FRAME_HEIGHT,
self.resolution[1])
# and get frame to check if it's ok
frame = hg.cvQueryFrame(self._device)
# Just set the resolution to the frame we just got, but don't use
# self.resolution for that as that would cause an infinite
# recursion with self.init_camera (but slowly as we'd have to
# always get a frame).
self._resolution = (int(frame.width), int(frame.height))
# get fps
self.fps = cv.GetCaptureProperty(self._device, cv.CV_CAP_PROP_FPS)

elif self.opencvMajorVersion in (2, 3, 4):
# create the device
self._device = cv2.VideoCapture(self._index)
# Set preferred resolution
self._device.set(PROPERTY_WIDTH,
self.resolution[0])
self._device.set(PROPERTY_HEIGHT,
self.resolution[1])
# and get frame to check if it's ok
ret, frame = self._device.read()

# source:
# http://stackoverflow.com/questions/32468371/video-capture-propid-parameters-in-opencv # noqa
self._resolution = (int(frame.shape[1]), int(frame.shape[0]))
# get fps
self.fps = self._device.get(PROPERTY_FPS)

self._device = cv2.VideoCapture(self._index)
self._device.set(cv2.CAP_PROP_FRAME_WIDTH, self._resolution[0])
self._device.set(cv2.CAP_PROP_FRAME_HEIGHT, self._resolution[1])
ret, frame = self._device.read()
self._resolution = (int(frame.shape[1]), int(frame.shape[0]))
self.fps = self._device.get(cv2.CAP_PROP_FPS)
if self.fps == 0 or self.fps == 1:
self.fps = 1.0 / 30
elif self.fps > 1:
self.fps = 1.0 / self.fps
self.crop = self._context.crop_for_aspect_orientation(*self._resolution)
self.stopped = True

if not self.stopped:
self.start()

def _update(self, dt):
def update(self, dt):
if self.stopped:
return
if self._texture is None:
# Create the texture
self._texture = Texture.create(self._resolution)
self._texture.flip_vertical()
self.dispatch('on_load')
self._context.on_load()
try:
ret, frame = self._device.read()
self._format = 'bgr'
try:
self._buffer = frame.imageData
self._copy_to_gpu()
except AttributeError:
# frame is already of type ndarray
# which can be reshaped to 1-d.
if ret:
self._buffer = frame.reshape(-1)
self._copy_to_gpu()
except:
pass
except:
if self.photo_capture:
self.photo_capture = False
cropped = frame[self.crop[1]: self.crop[1]+self.crop[3],
self.crop[0]: self.crop[0]+self.crop[2], :]
cv2.imwrite(self.photo_path, cropped)
if self.photo_callback:
self.photo_callback(self.photo_path)
if self.video_capture:
cropped = frame[self.crop[1]: self.crop[1]+self.crop[3],
self.crop[0]: self.crop[0]+self.crop[2], :]
self.video_stream.write(cropped)

except Exception as e:
Logger.exception('OpenCV: Couldn\'t get image from Camera')

def start(self):
super(CameraOpenCV, self).start()
self.stopped = False
self.photo_capture = False
self.video_capture = False
if self._update_ev is not None:
self._update_ev.cancel()
self._update_ev = Clock.schedule_interval(self._update, self.fps)
self._update_ev = Clock.schedule_interval(self.update, 1/30)

def stop(self):
super(CameraOpenCV, self).stop()
self.stopped = True
self._device = None
if self._update_ev is not None:
self._update_ev.cancel()
self._update_ev = None

def photo(self, path, callback):
self.photo_capture = True
self.photo_path = path
self.photo_callback = callback

def video_start(self, path, callback):
self.video_capture = True
self.video_path = path
self.video_callback = callback
size = (self.crop[2], self.crop[3])
rate = Clock.get_fps()
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
self.video_stream = cv2.VideoWriter(path, fourcc, rate, size)

def video_stop(self):
self.video_capture = False
self.video_stream.release()

Loading

0 comments on commit a087f8b

Please sign in to comment.