Skip to content

Latest commit

 

History

History
396 lines (298 loc) · 11.3 KB

File metadata and controls

396 lines (298 loc) · 11.3 KB

🎥 Video

Human friendly interface to the Video for Linux 2 (V4L2) subsystem.

Without further ado:

python from linuxpy.video.device import Device with Device.from_id(0) as cam: for i, frame in enumerate(cam): print(f"frame #{i}: {len(frame)} bytes") frame #0: 54630 bytes frame #1: 50184 bytes frame #2: 44054 bytes frame #3: 42822 bytes frame #4: 42116 bytes frame #5: 41868 bytes frame #6: 41322 bytes frame #7: 40896 bytes frame #8: 40844 bytes frame #9: 40714 bytes frame #10: 40662 bytes ...

Device creation

Create a device object from an ID:

from linuxpy.video.device import Device
camera = Device.from_id(10)

from a filename:

from linuxpy.video.device import Device
camera = Device("/dev/video10")

or from an existing file object:

from linuxpy.video.device import Device
with open("/dev/video10", "rb+", buffering=0) as fd:
    camera = Device(fd)

Before using video Device object you need to open it (except in the example directly above when creating a device from a file object). You can either use the device object as a context manager (prefered):

with Device.from_id(10) as camera:
    ...

The Device object is a reusable, reentrant but not thread safe context manager. This means that Device object can not only be used in multiple with statements, but may also be used inside a with statement that is already using the same context manager.

So the following examples will work just fine:

with Device.from_id(10) as camera:
    ...
    with camera:
        ...

with camera:
    ...

Alternatively, you can manage calls Device.open()/Device.close() manually:

camera = Device.from_id(10)
camera.open()
try:
    ...
finally:
    camera.close()

Finding devices

Get a random video device:

>>> from linuxpy.video.device import find

>>> dev = find()
<Device name=/dev/video10, closed=True>

All video devices:

>>>
>>> devs = list(find(find_all=True))
[<Device name=/dev/video10, closed=True>,
 <Device name=/dev/video3, closed=True>,
 <Device name=/dev/video2, closed=True>,
 <Device name=/dev/video1, closed=True>,
 <Device name=/dev/video0, closed=True>]

List of all capture devices:

>>> from linuxpy.video.device import iter_video_capture_devices
>>> list(iter_video_capture_devices())
[<Device name=/dev/video10, closed=True>,
 <Device name=/dev/video2, closed=True>,
 <Device name=/dev/video0, closed=True>]

Custom filter: find a uvc device:

>>> find(custom_match=lambda d: "uvc" in d.info.driver)
<Device name=/dev/video3, closed=True>

Custom filter: find all uvc devices:

>>> list(find(find_all=True, custom_match=lambda d: "uvc" in d.info.driver))
[<Device name=/dev/video3, closed=True>,
 <Device name=/dev/video2, closed=True>,
 <Device name=/dev/video1, closed=True>,
 <Device name=/dev/video0, closed=True>]

Capture

Simple capture without any configuration is possible using the Device object as an infinite iterator:

from linuxpy.video.device import Device, VideoCapture

with Device.from_id(0) as camera:
    for frame in camera:
        ...

The resulting Frame objects can safely and efficiently be converted to bytes.

To be able to configure the acquisition, you will need to use the VideoCapture helper. Here is an example with image size and format configuration:

from linuxpy.video.device import Device, VideoCapture

with Device.from_id(0) as camera:
    capture = VideoCapture(camera)
    capture.set_format(640, 480, "MJPG")
    capture.set_fps(10)
    with capture:
        for frame in capture:
            ...

which is roughly equivalent to:

with Device.from_id(0) as camera:
    capture = VideoCapture(camera)
    capture.set_format(640, 480, "MJPG")
    capture.set_fps(10)
    capture.arm()
    try:
        capture.start()
        try:
            for frame in capture:
                print(frame)
        finally:
            capture.stop()
    finally:
        capture.disarm()

Note that VideoCapture configuration must be done before the camera is armed (ie, before them arm() call or entering the with capture: statement.)

By default, VideoCapture will use memory map if the device has STREAMING capability and falls back to standard read if not. It is also possible to force a specific reader:

from linuxpy.video.device import Capability, Device, VideoCapture, ReadSource

with Device.from_id(0) as cam:
    with VideoCapture(cam, buffer_type=ReadSource):
        for frame in capture:
            ...

The previous example will grab frame data using the linux read system call.

Frame

The frame object contains metadata concerning the current (width, height, pixel format, size, timestamp, frame_nb) as well as the frame data. Unless the VideoCapture uses a custom buffer type for the acquisition, the frame.data represents the immutable data for the specific frame and it is safe to use without any additional copies.

>>> # Acquire one frame
>>> with Device.from_id(0) as cam:
        stream = iter(cam)
        frame = next(frame)

>>> frame
<Frame width=640, height=480, format=MJPEG, frame_nb=0, timestamp=48851.219369>
>>> frame.width, frame.height, frame.pixel_format
(640, 480, <PixelFormat.MJPEG: 1196444237>)

>>> frame.format
Format(width=640, height=480, pixel_format=<PixelFormat.MJPEG: 1196444237>, size=614989, bytes_per_line=0)

>>> len(frame.data)
61424

>>> frame.data is bytes(frame)
True

>>> frame.array
array([255, 216, 255, ...,   0,   0,   0], shape=(61424,), dtype=uint8)

Information

Getting information about the device:

>>> from linuxpy.video.device import Device, BufferType

>>> cam = Device.from_id(0)
>>> cam.open()
>>> cam.info.card
'Integrated_Webcam_HD: Integrate'

>>> cam.info.device_capabilities
<Capability.STREAMING|EXT_PIX_FORMAT|VIDEO_CAPTURE: 69206017>

>>> cam.info.formats
[ImageFormat(type=<BufferType.VIDEO_CAPTURE: 1>, description=b'Motion-JPEG',
             flags=<ImageFormatFlag.COMPRESSED: 1>, pixelformat=<PixelFormat.MJPEG: 1196444237>),
 ImageFormat(type=<BufferType.VIDEO_CAPTURE: 1>, description=b'YUYV 4:2:2',
             flags=<ImageFormatFlag.0: 0>, pixelformat=<PixelFormat.YUYV: 1448695129>)]

>>> cam.get_format(BufferType.VIDEO_CAPTURE)
Format(width=640, height=480, pixelformat=<PixelFormat.MJPEG: 1196444237>}

Controls

Device controls can be accessed on the controls member of the Device object. This object works like a dict. The keys can be control name or control id. The value is a control object containing the appropiate fields dependent on the type of control

Show list of available controls:

>>> for ctrl in cam.controls.values(): print(ctrl)
<IntegerControl brightness min=0 max=255 step=1 default=128 value=128>
<IntegerControl contrast min=0 max=255 step=1 default=32 value=32>
<IntegerControl saturation min=0 max=100 step=1 default=64 value=64>
<IntegerControl hue min=-180 max=180 step=1 default=0 value=0>
...
<BooleanControl exposure_dynamic_framerate default=False value=False>

Access a control with python dict syntax by control name:

>>> cam.controls["saturation"]
<IntegerControl saturation min=0 max=100 step=1 default=64 value=64>

Retrieve control information:

>>> saturation = cam.controls["saturation"]
>>> saturation.id
9963778

>>> cam.controls[9963778]
<IntegerControl saturation min=0 max=100 step=1 default=64 value=64>

>>> saturation.flags
<ControlFlag.SLIDER: 32>

Access controls using object attribute syntax:

>>> cam.controls.brightness.value = 64
>>> cam.controls.brightness
<IntegerControl brightness min=0 max=255 step=1 default=128 value=64>

(see also v4l2py-ctl example)

asyncio

linuxpy.video is asyncio friendly:

python -m asyncio from linuxpy.video.device import Device with Device.from_id(0) as cam: async for frame in cam: print(f"frame {len(frame)}") frame 10224 frame 10304 frame 10136 ...

(check basic async and web async examples)

gevent

linuxpy.video is also gevent friendly:

>>> from linuxpy.io import GeventIO
>>> from linuxpy.video.device import Device
>>> with Device.from_id(0, io=GeventIO) as camera:
...     for frame in camera:
...         print(f"frame {len(frame)}")
frame 10224
frame 10304
frame 10224
frame 10136
...

(check basic gevent and web gevent examples)

Video output

It is possible to write to a video output capable device (ex: v4l2loopback). The following example shows how to grab frames from device 0 and write them to device 10:

>>> from linuxpy.video.device import Device, VideoOutput, BufferType
>>> dev_source = Device.from_id(0)
>>> dev_sink = Device.from_id(10)
>>> with dev_source, dev_target:
>>>     source = VideoCapture(dev_source)
>>>     sink = VideoOutput(dev_sink)
>>>     source.set_format(640, 480, "MJPG")
>>>     sink.set_format(640, 480, "MJPG")
>>>     with source, sink:
>>>         for frame in source:
>>>             sink.write(frame.data)

By default, VideoOutput will use memory map if the device has STREAMING capability and falls back to standard write if not. It is also possible to force a specific writer with VideoOutput(cam, sink=Capability.READWRITE):

v4l2loopback

This is just an example on how to setup v4l2loopback.

Start from scratch:

# Remove kernel module and all devices (no client can be connected at this point)
sudo modprobe -r v4l2loopback

# Install some devices
sudo modprobe v4l2loopback video_nr=20,21 card_label="Loopback 0","Loopback 1"

References

See the linux/videodev2.h header file for details.