5

I'm having some trouble enumerating over cameras in Python over multiple OS's.

Here's some of the approaches I've tried:

import cv2 as cv
num = 0
while 1:
    cap = cv.VideoCapture(num)
    if cap.isOpened():
        # working capture
        num += 1
    else:
        break

The downsides of using Opencv is that Opencv doesn't provide any friendly display name. Additionally, enumerating over cameras is slow, as you need to actually open and close the camera to check if it's a valid camera.

I've also tried using libraries like PyPylon and pyuvc. They work, but only for specific brands.

I've done some researching on stack overflow, and some people have suggested python's gstreamer bindings as a possible OS independent solution. This is what I have so far.

import pgi
pgi.require_version("Gtk", "3.0")
pgi.require_version("Gst", "1.0")
pgi.require_version("GstVideo", "1.0")
from pgi.repository import Gtk, GObject, Gst, GstVideo

Gst.init("")
dm = Gst.DeviceMonitor()
dm.set_show_all_devices(True)

dm.start()

print("Displaying devices.")
for device in dm.get_devices():
    print(device.get_display_name())
print("Displaying providers.")
for provider in dm.get_providers():
    print(provider)

dm.stop()

This is the output I'm getting:

Displaying devices.
papalook Microphone
DisplayPort
HDMI
Built-in Output
Built-in Microph
Displaying providers.
osxaudiodeviceprovider

For some reason, I'm not getting any webcams, but only audio devices. Any ideas on what I'm doing wrong? Any different approaches I should be taking? Thanks.

aczh
  • 61
  • 1
  • 3

2 Answers2

2

I recently encountered this problem and didn't even realize how hard it is! Sharing here my solution, hopefully it helps someone.

As already pointed out, there is no easy way of doing this in a cross platform manner and we still need to write platform specific code. My solution is actually combination of some of the approaches offered here, so let's break it down.

1. Get the index of a camera

First thing we want to tackle is how many camera devices is connected to the computer. We can use OpenCV for this and the approach that was already mentioned above.

2. Linux

Linux stores information about video devices at /sys/class/video4linux Since we know the index of each camera, we can do something like this to get extra info.

cat /sys/class/video4linux/video1/name

3. Windows

Windows provides a bunch of useful APIs through the Windows Runtime also known as WinRT. Microsoft provides a Python library for this and to get the camera information we need to use DevicesEnumeration API.

4. MacOS

For MacOs, we can use a similar approach as for Linux. It seems that the ioreg and system_profiler commands can provide camera name information. Unfortunately, I don't have a MacOS system to test this out so I left a TODO. It would be great if someone can try and share it.

Here is my code.

import asyncio
import platform
import subprocess

import cv2

if platform.system() == 'Windows':
    import winrt.windows.devices.enumeration as windows_devices

VIDEO_DEVICES = 4


class Camera:

    def __init__(self):
        self.cameras = []

    def get_camera_info(self) -> list:
        self.cameras = []

        camera_indexes = self.get_camera_indexes()

        if len(camera_indexes) == 0:
            return self.cameras

        self.cameras = self.add_camera_information(camera_indexes)

        return self.cameras

    def get_camera_indexes(self):
        index = 0
        camera_indexes = []
        max_numbers_of_cameras_to_check = 10
        while max_numbers_of_cameras_to_check > 0:
            capture = cv2.VideoCapture(index)
            if capture.read()[0]:
                camera_indexes.append(index)
                capture.release()
            index += 1
            max_numbers_of_cameras_to_check -= 1
        return camera_indexes

    # TODO add MacOS specific implementations
    def add_camera_information(self, camera_indexes: list) -> list:
        platform_name = platform.system()
        cameras = []

        if platform_name == 'Windows':
            cameras_info_windows = asyncio.run(self.get_camera_information_for_windows())

            for camera_index in camera_indexes:
                camera_name = cameras_info_windows.get_at(camera_index).name.replace('\n', '')
                cameras.append({'camera_index': camera_index, 'camera_name': camera_name})

            return cameras

        if platform_name == 'Linux':
            for camera_index in camera_indexes:
                camera_name = subprocess.run(['cat', '/sys/class/video4linux/video{}/name'.format(camera_index)],
                                             stdout=subprocess.PIPE).stdout.decode('utf-8')
                camera_name = camera_name.replace('\n', '')
                cameras.append({'camera_index': camera_index, 'camera_name': camera_name})

            return cameras

    async def get_camera_information_for_windows(self):
        return await windows_devices.DeviceInformation.find_all_async(VIDEO_DEVICES)


camera = Camera()
Wizard
  • 292
  • 4
  • 11
  • It appears that on Windows 11, not all camera indices found in `get_camera_indexes` will safely survive through `add_camera_information` from the code above, e.g. in the case of USB webcams which expose multiple camera numbers through/to the OS. (I guess that a try-catch approach would be appropriate, unless of course if there's an api approach to filter down the numbers to exclude inoperative camera numbers that show up in the list to begin with). – matanster Jun 17 '22 at 15:32
1

For Windows you can use the library pygrabber which is a pure python tool to capture photos from cameras and for doing simple image processing using DirectShow and OpenCV. It also enumerates the connected webcams like so:

from __future__ import print_function
from pygrabber.dshow_graph import FilterGraph

graph = FilterGraph()
print(graph.get_input_devices())

I have modified the library to work under Python 2 and 3. Download at: https://github.com/bunkahle/pygrabber

Original source only for Python 3: https://github.com/andreaschiavinato/python_grabber

bunkus
  • 975
  • 11
  • 19