Hello World
学习如何使用 DepthAI Python API 来显示彩色视频流
演示
依赖关系
首先把开发环境设置好。本教程使用:
Python 3.6 (Ubuntu) or Python 3.7 (Raspbian)。
安装或升级 DepthAI Python API安装详解
需要用到
cv2
和numpy
Python 模块。
代码概述
depthai
Python 模块可以让您访问主板上的4k 60Hz彩色相机。
我们将把这个相机模组的视频串流到你桌面上。
你可以在GitHub上找到 本教程的完整源代码 ,也可以到 gitee 上找完整源代码。
文件设置
在你的计算机上设置以下文件结构:
cd ~
mkdir -p depthai-tutorials-practice/1-hello-world
touch depthai-tutorials-practice/1-hello-world/hello-world.py
cd depthai-tutorials-practice/1-hello-world
父目录名称里面的 -practice
后缀是干什么的? 我们的教程可以在 depthai-tutorials 代码库中获得。
在我们加上 -practice
后,你就可以把你个人的代码和我们完整教程的代码区分开来(前提是你下载了这些教程)。
安装 pip 依赖
要显示 DepthAI 彩色视频流,我们需要导入少量的软件包。 下载并安装本教程所需的包:
python3 -m pip install numpy opencv-python depthai --user -i https://pypi.tuna.tsinghua.edu.cn/simple
测试你的环境
让我们验证一下是否能够加载所有的依赖项。 在你的代码编辑器中打开你之前 创建 的 hello-world.py
文件。
复制并粘贴以下内容到 hello-world.py
中:
import numpy as np # numpy - manipulate the packet data returned by depthai
import cv2 # opencv - display the video stream
import depthai # access the camera and its data packets
尝试运行脚本并确保其执行无误:
python3 hello-world.py
如果你看到了如下的错误:
ModuleNotFoundError: No module named 'depthai'
…请按照 我们的故障排除部分的这些步骤 来操作。
定义管道
DepthAI的任何动作,无论是神经推理还是彩色摄像机输出,都需要定义 管道 ,包括与我们需求相对应的节点和连接。
在这种情况下,我们要查看 彩色摄像机 的帧,以及要在它们之上运行的简单 神经网络。
让我们从一个空 Pipeline
对象开始
pipeline = depthai.Pipeline()
现在,我们要添加的第一个节点是 ColorCamera
。我们将使用 preview
调整为300x300的输出以适合 mobilenet-ssd 输入大小(我们将在以后定义)
cam_rgb = pipeline.createColorCamera()
cam_rgb.setPreviewSize(300, 300)
cam_rgb.setInterleaved(False)
接下来,让我们NeuralNetwork用mobilenet-ssd network定义一个节点。此示例的Blob文件可在 此处 找到。
detection_nn = pipeline.createNeuralNetwork()
detection_nn.setBlobPath("/path/to/mobilenet-ssd.blob")
现在,让我们将彩色摄像头 preview
输出连接到神经网络输入
cam_rgb.preview.link(detection_nn.input)
最后,我们希望同时接收彩色相机的帧和神经网络的推理结果-由于这些是在设备上生成的,因此需要将它们传输到我们的机器(主机)上。设备和主机之间的通信由来处理 XLink
,在本例中,由于我们要从设备到主机接收数据,因此我们将使用 XLinkOut
节点。
xout_rgb = pipeline.createXLinkOut()
xout_rgb.setStreamName("rgb")
cam_rgb.preview.link(xout_rgb.input)
xout_nn = pipeline.createXLinkOut()
xout_nn.setStreamName("nn")
detection_nn.out.link(xout_nn.input)
初始化DepthAI设备
定义管道后,我们现在可以初始化设备并启动它
启动 DepthAI 设备:
device = depthai.Device(pipeline)
device.startPipeline()
Note
默认情况下,DepthAI被作为USB3设备访问。这有 几个限制 。
如果您想通过USB2进行通信(不受这些限制但带宽有限),请使用以下代码初始化DepthAI
device = depthai.Device(pipeline, True)
从这一点开始,管道将在设备上运行,并产生我们要求的结果。抓住他们
添加助手
由于 XLinkOut
已经在管道中定义了节点,因此我们现在将定义主机端输出队列以访问产生的结果
q_rgb = device.getOutputQueue("rgb")
q_nn = device.getOutputQueue("nn")
这些将充满结果,因此下一步是消耗结果。我们将需要两个占位符-一个用于rgb框架,一个用于nn结果
frame = None
bboxes = []
同样,由于神经网络的实现细节,推理结果中的边界框坐标表示为<0..1>范围内的浮点数-因此相对于帧的宽度/高度(例如,如果图像的宽度为200px,并且nn返回的x_min坐标等于0.2 ,这意味着实际的(规范化的)x_min坐标为40px)。
这就是为什么我们需要定义一个辅助函数,frame_form
将这些<0..1>值转换为实际像素位置的原因
def frame_norm(frame, bbox):
return (np.array(bbox) * np.array([*frame.shape[:2], *frame.shape[:2]])[::-1]).astype(int)
消耗结果
准备好一切之后,我们就可以开始主程序循环了
while True:
# ...
现在,在此循环中,首先要做的是从nn节点和彩色摄像机获取最新结果
in_rgb = q_rgb.tryGet()
in_nn = q_nn.tryGet()
如果队列为空,则该 tryGet
方法将返回最新结果 None
。
来自rgb摄像机或神经网络的结果将以一维数组的形式提供,因此它们都需要进行转换才能用于显示(我们已经定义了所需的转换之一 frame_norm
函数)
首先,如果我们收到了来自RGB相机的帧,则需要将其从1D阵列转换为HWC形式(HWC代表“高度宽度通道”,因此是3D阵列,第一个尺寸是宽度,第二个高度,第三个是颜色通道)
if in_rgb is not None:
shape = (3, in_rgb.getHeight(), in_rgb.getWidth())
frame = in_rgb.getData().reshape(shape).transpose(1, 2, 0).astype(np.uint8)
frame = np.ascontiguousarray(frame)
其次,神经网络的结果也将需要转换。它们也以一维数组的形式返回,但是这次数组的大小是固定的(无论神经网络实际产生多少结果,它的大小都是恒定的)。数组中的实际结果后跟 -1
,然后用 0
填充以满足固定大小。一个结果有7个字段,每个字段分别为 image_id, label, confidence, x_min, y_min, x_max, y_max
。我们只需要最后四个值(即边界框),但我们还将过滤出 confidence
低于某个阈值的值-它可以在<0..1>之间,在此示例中,我们将使用临界点 0.8
if in_nn is not None:
bboxes = np.array(in_nn.getFirstLayerFp16())
bboxes = bboxes[:np.where(bboxes == -1)[0][0]]
bboxes = bboxes.reshape((bboxes.size // 7, 7))
bboxes = bboxes[bboxes[:, 2] > 0.8][:, 3:7]
为了更好地理解此流程,让我们举个例子。假设 np.array(in_nn.getFirstLayerFp16())
返回以下数组
[0, 15, 0.99023438, 0.45556641, 0.34399414 0.88037109, 0.9921875, 0, 15, 0.98828125, 0.03076172, 0.23388672, 0.60205078, 1.0078125, -1, 0, 0, 0, ...]
第一个操作,从数组中删除尾随零,所以现在bbox数组将如下所示 bboxes[:np.where(bboxes == -1)[0][0]]
[0, 15, 0.99023438, 0.45556641, 0.34399414 0.88037109, 0.9921875, 0, 15, 0.98828125, 0.03076172, 0.23388672, 0.60205078, 1.0078125]
第二个-将1D数组重塑为2D数组-其中每一行都是单独的结果 bboxes.reshape((bboxes.size // 7, 7))
[
[0, 15, 0.99023438, 0.45556641, 0.34399414 0.88037109, 0.9921875],
[0, 15, 0.98828125, 0.03076172, 0.23388672, 0.60205078, 1.0078125]
]
最后一个 bboxes = bboxes[bboxes[:, 2] > 0.8][:, 3:7]
-将根据置信度列(第三个,索引为 2
)高于定义的阈值( 0.8
)过滤结果-从这些结果中,仅将最后4列作为边界框。由于我们的两个结果都具有很高的置信度( 0.99023438
和 0.98828125
),因此不会对其进行过滤,最终数组将如下所示
[
[0.45556641, 0.34399414 0.88037109, 0.9921875],
[0.03076172, 0.23388672, 0.60205078, 1.0078125]
]
显示结果
到此为止,我们已经从DepthaI设备消耗了所有结果,剩下的就是实际显示它们。
if frame is not None:
for raw_bbox in bboxes:
bbox = frame_norm(frame, raw_bbox)
cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (255, 0, 0), 2)
cv2.imshow("preview", frame)
您可以在此处看到 frame_norm
我们先前定义的用于边界框坐标归一化的用法。通过使用 cv2.rectangle
我们在rgb框架上绘制一个矩形作为指示人脸位置的指示器,然后使用 cv2.imshow
最后,我们添加了一种终止程序的方法(因为它在无限循环内运行)。我们将使用 cv2.waitKey
的方法,即一键等待用户按下-在我们的例子中,我们要打破循环出来的时候用户按下 q
键
运行示例
放在一起,剩下要做的就是运行我们在本教程中准备的文件并查看结果
python3 hello_world.py
你已经上手了! 你可以在 GitHub 上找到 本教程的完整代码 。
有疑问?
我们很乐意为您提供代码或其他问题的帮助。
我们的联系方式
还可以通过我们发布的视频和文章了解OAK