65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Python 和 DirectShow 从摄像头捕获图像

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.20/5 (4投票s)

2019 年 1 月 14 日

CPOL

6分钟阅读

viewsIcon

85600

downloadIcon

1701

在本文中,我将介绍一个完全用 Python 编写的简单应用程序,该应用程序允许您使用 DirectShow 从摄像头捕获图像,并使用 OpenCV 对其进行简单的处理。

引言

在 Python 中使用 OpenCV 是原型化视觉应用的绝佳解决方案,它允许您快速草拟和测试算法。处理从文件读取的图像非常容易,但处理从摄像头捕获的图像则不那么容易。OpenCV 提供了一些基本方法来访问连接到 PC 的摄像头(通过 VideoCapture 对象),但大多数时候,即使是简单的原型,它们也不够用。例如,无法列出所有连接到 PC 的摄像头,也没有快速调整摄像头参数的方法。或者,您可以使用 PyGame 或摄像头制造商提供的 SDK(如果可用)。

在 Windows 中,与摄像头交互通常使用 DirectShow。它的主要优点是:

  • 几乎所有摄像头都提供允许从 DirectShow 使用的驱动程序。
  • 它是一项成熟且被广泛使用的技术。
  • 它基于 COM 框架,因此可以从不同的编程语言中使用。

相反,它是一项相当古老的技术,正在被 Windows Media Foundation 取代,并且微软已不再开发它。但这没什么大不了的,因为它具备所有需要的功能,并且在许多应用程序中使用,(在我看来),微软将长期保持其可用性。

在这里,我将介绍一个完全用 Python 编写的简单应用程序,该应用程序允许您使用 DirectShow 从摄像头捕获图像,在屏幕上显示它们,并使用 OpenCV 对它们进行简单的处理。该应用程序基于一个类,该类公开了 DirectShow 的一些功能,并可以在其他应用程序中重用。代码设计易于更改和扩展。

您也可以在 https://github.com/andreaschiavinato/python_grabber 上找到该应用程序的代码。

背景

要理解本文,您需要具备 Python 和 Windows 应用程序开发的基本知识。

使用应用程序

  1. 确保安装了以下 Python 包:numpymatplotlibopencv-pythoncomtypes
  2. 运行 main.py
  3. 启动应用程序后,将显示 选择视频设备 对话框。选择一个摄像头,然后按 确定

  1. 将显示一个允许您选择摄像头分辨率的屏幕。选择所需分辨率或保留默认值,然后按 确定
  2. 摄像头的实时流显示在屏幕的左侧。
  3. 抓取 按钮可捕获照片,照片将显示在屏幕的右侧。您可以使用屏幕右侧的按钮对照片应用一些滤镜,或通过按 保存 来保存图片。

在 Windows 10 上,如果文本大小设置为非 100% 的值(在 Windows 的 显示设置 屏幕上),摄像头的实时图像可能会在包含框中显示不正确。这似乎是 DirectShow 的一个问题,因为我在其他应用程序中也有相同的行为。

DirectShow 简介

DirectShow 是编写多媒体应用程序的框架。DirectShow 应用程序的构建块是 filter(过滤器),这是一个执行基本操作的对象,例如:

  • 从摄像头或文件读取音频/视频
  • 将音频/视频从一种格式转换为另一种格式
  • 将音频/视频渲染到屏幕或文件

过滤器可以具有输入和输出引脚,并且可以将它们连接在一起以执行所需的工作。

DirectShow 提供了一个名为 Filter Graph(过滤器图)的对象,该对象负责收集、链接和运行过滤器。

对于我们的应用程序,我们需要以下过滤器:

  • 一个摄像头源过滤器,用于从摄像头读取图像
  • 一个 SampleGrabber 过滤器,允许我们对摄像头提供的每一帧进行一些操作
  • 一个视频渲染过滤器,用于在 GUI 上显示摄像头的实时流

根据摄像头,我们可能需要其他过滤器来将摄像头提供的图像转换为 SampleGrabber 可以使用的图像。这些附加过滤器由 directshow 自动添加。

如果您想了解更多关于 DirectShow 的信息,值得阅读 微软提供的文档

使用 FilterGraph 类

您可以将 FilterGraph 类(包含在文件 dshow_graph.py 中)作为独立组件使用。该类代表一个 DirectShow 过滤器图对象。

示例 1

此代码列出了连接到您的 PC 的摄像头。

from pygrabber.dshow_graph import FilterGraph

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

示例 2

此代码显示了一个屏幕,其中包含您 PC 上第一个摄像头的实时图像。

我们将两个过滤器添加到图中:一个过滤器是对应于您 PC 上连接的第一个摄像头的源过滤器,第二个是默认的渲染过滤器,它将在屏幕上的窗口中显示来自摄像头的图像。然后我们调用 prepare,它将两个过滤器连接在一起,然后调用 run 来执行图。

最后,我们需要一种方法来暂停程序,同时观看摄像头的视频。我使用了 Tkinter 的 mainloop 函数,该函数会获取和处理 Windows 事件,因此应用程序看起来不会冻结。

from pygrabber.dshow_graph import FilterGraph
from tkinter import Tk

graph = FilterGraph()
graph.add_input_device(0)
graph.add_default_render()
graph.prepare()
graph.run()
root = Tk()
root.withdraw() # hide Tkinter main window
root.mainloop()

示例 3

以下代码使用 sample grabber 过滤器从摄像头捕获单个图像。要捕获图像,将调用 grab_frame 方法。图像将从作为参数传递给 add_sample_grabber 方法的 callback 函数中检索。在这种情况下,捕获的图像使用 opencvimshow 函数显示。

from pygrabber.dshow_graph import FilterGraph
import cv2

graph = FilterGraph()
cv2.namedWindow('Image', cv2.WINDOW_NORMAL)
graph.add_input_device(0)
graph.add_sample_grabber(lambda image: cv2.imshow("Image", image))
graph.add_null_render()
graph.prepare()
graph.run()
print("Press 'C' or 'c' to grab photo, another key to exit")
while cv2.waitKey(0) in [ord('c'), ord('C')]:
    graph.grab_frame()
graph.stop()
cv2.destroyAllWindows()
print("Done")

示例 4

以下代码以同步方式从摄像头捕获图像。使用 Event 对象来阻塞主线程,直到图像准备就绪。

图像使用 matplotlib 显示。请注意,使用 np.flip 函数来反转图像的最后一个维度,因为 sample grabber 过滤器返回的图像是 BGR 格式,但 mathplotlib 需要 RGB 格式。在前一个示例中我们没有这样做,因为 OpenCV 接受 BGR 格式。

import threading
import matplotlib.pyplot as plt
import numpy as np
from pygrabber.dshow_graph import FilterGraph

image_done = threading.Event()
image_grabbed = None

def img_cb(image):
    global image_done
    global image_grabbed
    image_grabbed = np.flip(image, 2)
    image_done.set()

graph = FilterGraph()
graph.add_input_device(0)
graph.add_sample_grabber(img_cb)
graph.add_null_render()
graph.prepare()
graph.run()
input("Press ENTER to grab photo")
graph.grab_frame()
image_done.wait(1000)
graph.stop()
plt.imshow(image_grabbed)
plt.show()

示例 5

以下代码是示例 4 的改进,它允许您同时从两个摄像头捕获图像。

import threading
import matplotlib.pyplot as plt
import numpy as np
from pygrabber.dshow_graph import FilterGraph

class Camera:
    def __init__(self, device_id):
        self.graph = FilterGraph()
        self.graph.add_input_device(device_id)
        self.graph.add_sample_grabber(self.img_cb)
        self.graph.add_null_render()
        self.graph.display_format_dialog()
        self.graph.prepare()
        self.graph.run()

        self.image_grabbed = None
        self.image_done = threading.Event()

    def img_cb(self, image):
        self.image_grabbed = np.flip(image, 2)
        self.image_done.set()

    def capture(self):
        self.graph.grab_frame()

    def wait_image(self):
        self.image_done.wait(1000)
        return self.image_grabbed        

print("Opening first camera")
camera1 = Camera(0)
print("Opening second camera")
camera2 = Camera(1)
input("Press ENTER to grab photos")
camera1.capture()
camera2.capture()
print("Waiting images")
image1 = camera1.wait_image()
image2 = camera2.wait_image()
print("Done")
ax1 = plt.subplot(2, 1, 1)
ax1.imshow(image1)
ax2 = plt.subplot(2, 1, 2)
ax2.imshow(image2)
plt.show()

示例 6

ScreenCaptureRecorder 是一款软件,其中包括一个 direct show 输入过滤器,该过滤器提供来自当前屏幕的图像。我们可以将其与 FilterGraph 类一起使用,以创建捕获屏幕截图的应用程序。

您可以从 https://github.com/rdp/screen-capture-recorder-to-video-windows-free/releases 安装 ScreenCaptureRecorder 。在我的例子中,安装程序没有注册捕获源过滤器,我必须手动执行以下命令来完成此操作。

Regsvr32 C:\Program Files (x86)\Screen Capturer Recorder\screen-capture-recorder.dll
Regsvr32 C:\Program Files (x86)\Screen Capturer Recorder\screen-capture-recorder-x64.dll

以下是示例 4 的一个变体,它确保 screen-capture-recorder 过滤器被用作输入设备,并且可以用于捕获屏幕截图。

import threading
import matplotlib.pyplot as plt
import numpy as np
from pygrabber.dshow_graph import FilterGraph

image_done = threading.Event()
image_grabbed = None

def img_cb(image):
    global image_done
    global image_grabbed
    image_grabbed = np.flip(image, 2)
    image_done.set()

graph = FilterGraph()
screen_recorder_id = next(device[0] for device in enumerate(graph.get_input_devices())
                          if device[1] == "screen-capture-recorder")
graph.add_input_device(screen_recorder_id)
graph.add_sample_grabber(img_cb)
graph.add_null_render()
graph.prepare()
graph.run()
input("Press ENTER to capture a screenshot")
graph.grab_frame()
image_done.wait(1000)
graph.stop()
plt.imshow(image_grabbed)
plt.show()

历史

  • 2019 年 1 月 14 日:初始版本
© . All rights reserved.