在本教程中,您将学习如何将 OpenCV 的“dnn”模块与 NVIDIA GPU 结合使用,以将对象检测(YOLO 和 SSD)和实例分割(Mask R-CNN)的速度提高 1,549%。
上周,我们发现了如何配置和安装 OpenCV 及其“深度神经网络”(dnn)模块以使用 NVIDIA GPU 进行推理。
使用 OpenCV 的 GPU 优化 dnn 模块,我们只需三行代码即可将给定网络的计算从 CPU 推送到 GPU:
# load the model from disk and set the backend target to a
# CUDA-enabled GPU
net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
今天我们将更详细地讨论完整的代码示例——在本教程结束时,您将能够应用:
65.90 FPS 的单次检测器 (SSD)
YOLO 目标检测速度为 11.87 FPS
Mask R-CNN 实例分割速度为 11.05 FPS
要了解如何使用 OpenCV 的 dnn 模块和 NVIDIA GPU 进行更快的对象检测和实例分割,请继续阅读!
带有 NVIDIA GPU 的 OpenCV ‘dnn’:YOLO、SSD 和 Mask R-CNN 的速度提高了 1,549%
在本教程中,您将学习如何使用 OpenCV 的“深度神经网络”(dnn) 模块和支持 NVIDIA/CUDA 的 GPU 来实现 Single Shot Detectors、YOLO 和 Mask R-CNN。
使用 NVIDIA GPU 支持编译 OpenCV 的“dnn”模块
项目结构如下:
$ tree --dirsfirst
.
├── example_videos
│ ├── dog_park.mp4
│ ├── guitar.mp4
│ └── janie.mp4
├── opencv-ssd-cuda
│ ├── MobileNetSSD_deploy.caffemodel
│ ├── MobileNetSSD_deploy.prototxt
│ └── ssd_object_detection.py
├── opencv-yolo-cuda
│ ├── yolo-coco
│ │ ├── coco.names
│ │ ├── yolov3.cfg
│ │ └── yolov3.weights
│ └── yolo_object_detection.py
├── opencv-mask-rcnn-cuda
│ ├── mask-rcnn-coco
│ │ ├── colors.txt
│ │ ├── frozen_inference_graph.pb
│ │ ├── mask_rcnn_inception_v2_coco_2018_01_28.pbtxt
│ │ └── object_detection_classes_coco.txt
│ └── mask_rcnn_segmentation.py
└── output_videos
7 directories, 15 files
在今天的教程中,我们将回顾三个 Python 脚本:
ssd_object_detection.py:使用 CUDA 对 20 个 COCO 类执行基于 Caffe 的 MobileNet SSD 对象检测。
yolo_object_detection.py:使用 CUDA 对 80 个 COCO 类执行 YOLO V3 对象检测。
mask_rcnn_segmentation.py:使用 CUDA 对 90 个 COCO 类执行基于 TensorFlow 的 Inception V2 分割。
每个模型文件和类名文件都包含在各自的文件夹中,除了我们的 MobileNet SSD(类名直接在脚本中硬编码在 Python 列表中)。 让我们按照今天使用的顺序查看文件夹名称:
opencv-ssd-cuda/
opencv-yolo-cuda/
opencv-mask-rcnn-cuda/
正如所有三个目录名称所表明的那样,我们将使用 OpenCV 的 DNN 模块,该模块由 CUDA 支持编译。 如果您的 OpenCV 没有为您的 NVIDIA GPU 编译并支持 CUDA,那么您需要使用上周教程中的说明配置您的系统。
使用 OpenCV 的支持 NVIDIA GPU 的“dnn”模块实现单次检测器 (SSD)
我们将要研究的第一个物体检测器是单次检测器 (SSD),我们最初在 2017 年就介绍过它:
使用深度学习和 OpenCV 进行目标检测使用深度学习和 OpenCV 进行实时目标检测 那时我们只能在 CPU 上运行这些 SSD; 但是,今天我将向您展示如何使用 NVIDIA GPU 将推理速度提高多达 211%。
打开项目目录结构中的 ssd_object_detection.py 文件,并插入以下代码:
# import the necessary packages
from imutils.video import FPS
import numpy as np
import argparse
import imutils
import cv2
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--prototxt", required=True,
help="path to Caffe 'deploy' prototxt file")
ap.add_argument("-m", "--model", required=True,
help="path to Caffe pre-trained model")
ap.add_argument("-i", "--input", type=str, default="",
help="path to (optional) input video file")
ap.add_argument("-o", "--output", type=str, default="",
help="path to (optional) output video file")
ap.add_argument("-d", "--display", type=int, default=1,
help="whether or not output frame should be displayed")
ap.add_argument("-c", "--confidence", type=float, default=0.2,
help="minimum probability to filter weak detections")
ap.add_argument("-u", "--use-gpu", type=bool, default=False,
help="boolean indicating if CUDA GPU should be used")
args = vars(ap.parse_args())
在这里,我们已经导入了我们的包。请注意,我们不需要任何特殊的 CUDA 导入。 CUDA 功能内置(通过我们上周的编译)到第 6 行的 cv2 导入中。
接下来让我们解析命令行参数:
--prototxt:我们预训练的 Caffe MobileNet SSD “部署”prototxt 文件路径。
--model:我们预训练的 Caffe MobileNet SSD 模型的路径。
--input:我们输入视频文件的可选路径。如果未提供,则默认使用您的第一台相机。
--output:我们输出视频文件的可选路径。
--display:可选的布尔标志,指示我们是否将输出帧显示到 OpenCV GUI 窗口。显示帧会消耗 CPU 周期,因此对于真正的基准测试,您可能希望关闭显示(默认情况下它是打开的)。
--confidence:过滤弱检测的最小概率阈值。默认情况下,该值设置为 20%;但是,如果您愿意,您可以覆盖它。
--use-gpu:指示是否应使用 CUDA GPU 的布尔值。默认情况下,此值为 False(即关闭)。如果您希望将支持 NVIDIA CUDA 的 GPU 用于 OpenCV 的对象检测,则需要将 1 值传递给此参数。
接下来我们将指定我们的类和相关的随机颜色:
# initialize the list of class labels MobileNet SSD was trained to
# detect, then generate a set of bounding box colors for each class
CLASSES = ["background", "aeroplane", "bicycle", "bird", "boat",
"bottle", "bus", "car", "cat", "chair", "cow", "diningtable",
"dog", "horse", "motorbike", "person", "pottedplant", "sheep",
"sofa", "train", "tvmonitor"]
COLORS = np.random.uniform(0, 255, size=(len(CLASSES), 3))
然后我们将加载基于 Caffe 的模型:
# load our serialized model from disk
net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])
# check if we are going to use GPU
if args["use_gpu"]:
# set CUDA as the preferable backend and target
print("[INFO] setting preferable backend and target to CUDA...")
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
正如第 35 行所示,我们使用 OpenCV 的 dnn 模块加载我们的 Caffe 对象检测模型。
检查是否应使用支持 NVIDIA CUDA 的 GPU。 从那里,我们相应地设置后端和目标(第 38-42 行)。
让我们继续使用我们的 GPU 开始处理帧和执行对象检测(当然,前提是 --use-gpu 命令行参数已打开):
# initialize the video stream and pointer to output video file, then
# start the FPS timer
print("[INFO] accessing video stream...")
vs = cv2.VideoCapture(args["input"] if args["input"] else 0)
writer = None
fps = FPS().start()
# loop over the frames from the video stream
while True:
# read the next frame from the file
(grabbed, frame) = vs.read()
# if the frame was not grabbed, then we have reached the end
# of the stream
if not grabbed:
break
# resize the frame, grab the frame dimensions, and convert it to
# a blob
frame = imutils.resize(frame, width=400)
(h, w) = frame.shape[:2]
blob = cv2.dnn.blobFromImage(frame, 0.007843, (300, 300), 127.5)
# pass the blob through the network and obtain the detections and
# predictions
net.setInput(blob)
detections = net.forward()
# loop over the detections
for i in np.arange(0, detections.shape[2]):
# extract the confidence (i.e., probability) associated with
# the prediction
confidence = detections[0, 0, i, 2]
# filter out weak detections by ensuring the `confidence` is
# greater than the minimum confidence
if confidence > args["confidence"]:
# extract the index of the class label from the
# `detections`, then compute the (x, y)-coordinates of
# the bounding box for the object
idx = int(detections[0, 0, i, 1])
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
(startX, startY, endX, endY) = box.astype("int")
# draw the prediction on the frame
label = "{}: {:.2f}%".format(CLASSES[idx],
confidence * 100)
cv2.rectangle(frame, (startX, startY), (endX, endY),
COLORS[idx], 2)
y = startY - 15 if startY - 15 > 15 else startY + 15
cv2.putText(frame, label, (startX, y),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, COLORS[idx], 2)
在这里我们访问我们的视频流。 请注意,该代码旨在与视频文件和实时视频流兼容,这就是我选择不使用线程化 VideoStream 类的原因。
循环帧,我们依次进行下述处理:
读取和预处理传入的帧。
从框架构建一个 blob。
使用 Single Shot Detector 和我们的 GPU 检测对象(如果设置了 --use-gpu 标志)。
过滤对象只允许高置信度对象通过。
注释边界框、类标签和概率。
最后,我们将总结:
# check to see if the output frame should be displayed to our
# screen
if args["display"] > 0:
# show the output frame
cv2.imshow("Frame", frame)
key = cv2.waitKey(1) & 0xFF
# if the `q` key was pressed, break from the loop
if key == ord("q"):
break
# if an output video file path has been supplied and the video
# writer has not been initialized, do so now
if args["output"] != "" and writer is None:
# initialize our video writer
fourcc = cv2.VideoWriter_fourcc(*"MJPG")
writer = cv2.VideoWriter(args["output"], fourcc, 30,
(frame.shape[1], frame.shape[0]), True)
# if the video writer is not None, write the frame to the output
# video file
if writer is not None:
writer.write(frame)
# update the FPS counter
fps.update()
# stop the timer and display FPS information
fps.stop()
print("[INFO] elasped time: {:.2f}".format(fps.elapsed()))
print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))
在剩下的几行中,我们:
如果需要,显示带注释的视频帧。
如果我们正在显示,则捕获按键。
将带注释的输出帧写入磁盘上的视频文件。
更新、计算和打印 FPS 统计数据。
开发您的 SSD + OpenCV + CUDA 脚本做得很好。 在接下来的部分中,我们将使用 GPU 和 CPU 分析结果。
Single Shot Detectors:使用 OpenCV 的“dnn”模块和 NVIDIA GPU 将对象检测速度提高 211%
要查看我们的 Single Shot Detector 的运行情况,请确保使用本教程的“下载”部分下载 (1) 源代码和 (2) 与 OpenCV 的 dnn 模块兼容的预训练模型。
从那里,执行以下命令,通过在我们的 CPU 上运行它来获取我们的 SSD 的基线:
$ python ssd_object_detection.py \
--prototxt MobileNetSSD_deploy.prototxt \
--model MobileNetSSD_deploy.caffemodel \
--input ../example_videos/guitar.mp4 \
--output ../output_videos/ssd_guitar.avi \
--display 0
[INFO] accessing video stream...
[INFO] elasped time: 11.69
[INFO] approx. FPS: 21.13
在这里,我们在 CPU 上获得了约 21 FPS,这对于物体检测器来说非常好!
要真正看到检测器,让我们提供 --use-gpu 1 命令行参数,指示 OpenCV 将 dnn 计算推送到我们的 NVIDIA Tesla V100 GPU:
$ python ssd_object_detection.py \
--prototxt MobileNetSSD_deploy.prototxt \
--model MobileNetSSD_deploy.caffemodel \
--input ../example_videos/guitar.mp4 \
--output ../output_videos/ssd_guitar.avi \
--display 0 \
--use-gpu 1
[INFO] setting preferable backend and target to CUDA...
[INFO] accessing video stream...
[INFO] elasped time: 3.75
[INFO] approx. FPS: 65.90
使用我们的 NVIDIA GPU,我们现在达到了约 66 FPS,这将我们的每秒帧数吞吐量提高了 211% 以上! 正如视频演示所示,我们的 SSD 非常准确。
注意:正如 Yashas 的这条评论所讨论的,MobileNet SSD 的性能可能很差,因为 cuDNN 没有针对所有 NVIDA GPU 上的深度卷积优化内核。 如果您看到 GPU 结果与 CPU 结果相似,这可能是问题所在。
为 OpenCV 的支持 NVIDIA GPU/CUDA 的“dnn”模块实现 YOLO 对象检测
虽然 YOLO 无疑是最快的基于深度学习的对象检测器之一,但 OpenCV 中包含的 YOLO 模型却是——在 CPU 上,YOLO 努力打破 3 FPS。
因此,如果您打算将 YOLO 与 OpenCV 的 dnn 模块一起使用,则最好使用 GPU。
我们来看看如何使用 YOLO 物体检测器(yolo_object_detection.py)和 OpenCV 的 CUDA-enabled dnn 模块:
# import the necessary packages
from imutils.video import FPS
import numpy as np
import argparse
import cv2
import os
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-y", "--yolo", required=True,
help="base path to YOLO directory")
ap.add_argument("-i", "--input", type=str, default="",
help="path to (optional) input video file")
ap.add_argument("-o", "--output", type=str, default="",
help="path to (optional) output video file")
ap.add_argument("-d", "--display", type=int, default=1,
help="whether or not output frame should be displayed")
ap.add_argument("-c", "--confidence", type=float, default=0.5,
help="minimum probability to filter weak detections")
ap.add_argument("-t", "--threshold", type=float, default=0.3,
help="threshold when applyong non-maxima suppression")
ap.add_argument("-u", "--use-gpu", type=bool, default=0,
help="boolean indicating if CUDA GPU should be used")
args = vars(ap.parse_args())
我们的导入几乎与我们之前的脚本相同,只是一次交换。在这个脚本中,我们不需要 imutils,但我们需要 Python 的 os 模块来进行文件 I/O。同样,CUDA 功能已融入我们自定义编译的 OpenCV 安装中。
让我们回顾一下我们的命令行参数:
--yolo:预训练 YOLO 模型目录的基本路径。
--input:我们输入视频文件的可选路径。如果未提供,则默认使用您的第一台相机。
--output:我们输出视频文件的可选路径。
--display:可选的布尔标志,指示我们是否将输出帧用于 OpenCV GUI 窗口。显示帧会消耗 CPU 周期,因此对于真正的基准测试,您可能希望关闭显示(默认情况下它是打开的)。
--confidence:过滤弱检测的最小概率阈值。默认情况下,该值设置为 50%;但是,如果您愿意,您可以覆盖它。
--threshold:默认情况下,非极大值抑制 (NMS) 阈值设置为 30%。
--use-gpu:指示是否应使用 CUDA GPU 的布尔值。默认情况下,此值为 False(即关闭)。如果您希望将支持 NVIDIA CUDA 的 GPU 用于 OpenCV 的对象检测,则需要将 1 值传递给此参数。
接下来我们将加载我们的类标签并分配随机颜色:
# load the COCO class labels our YOLO model was trained on
labelsPath = os.path.sep.join([args["yolo"], "coco.names"])
LABELS = open(labelsPath).read().strip().split("\n")
# initialize a list of colors to represent each possible class label
np.random.seed(42)
COLORS = np.random.randint(0, 255, size=(len(LABELS), 3),
dtype="uint8")
我们从 coco.names 文件加载类标签并分配随机颜色。
现在我们准备好从磁盘加载我们的 YOLO 模型,包括在需要时设置 GPU 后端/目标:
# derive the paths to the YOLO weights and model configuration
weightsPath = os.path.sep.join([args["yolo"], "yolov3.weights"])
configPath = os.path.sep.join([args["yolo"], "yolov3.cfg"])
# load our YOLO object detector trained on COCO dataset (80 classes)
print("[INFO] loading YOLO from disk...")
net = cv2.dnn.readNetFromDarknet(configPath, weightsPath)
# check if we are going to use GPU
if args["use_gpu"]:
# set CUDA as the preferable backend and target
print("[INFO] setting preferable backend and target to CUDA...")
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
第 36 行和第 37 行获取我们预训练的 YOLO 检测器模型和权重路径。
如果设置了 --use-gpu 命令行标志,则第 41-48 行加载模型并将 GPU 设置为后端。
继续,我们将开始使用 YOLO 执行对象检测:
# determine only the *output* layer names that we need from YOLO
ln = net.getLayerNames()
ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]
# initialize the width and height of the frames in the video file
W = None
H = None
# initialize the video stream and pointer to output video file, then
# start the FPS timer
print("[INFO] accessing video stream...")
vs = cv2.VideoCapture(args["input"] if args["input"] else 0)
writer = None
fps = FPS().start()
# loop over frames from the video file stream
while True:
# read the next frame from the file
(grabbed, frame) = vs.read()
# if the frame was not grabbed, then we have reached the end
# of the stream
if not grabbed:
break
# if the frame dimensions are empty, grab them
if W is None or H is None:
(H, W) = frame.shape[:2]
# construct a blob from the input frame and then perform a forward
# pass of the YOLO object detector, giving us our bounding boxes
# and associated probabilities
blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416),
swapRB=True, crop=False)
net.setInput(blob)
layerOutputs = net.forward(ln)
第 51 行和第 52 行仅从 YOLO 模型中获取输出层名称。 我们需要这些来使用 OpenCV 对 YOLO 进行推理。
然后我们抓取帧尺寸并初始化我们的视频流 + FPS 计数器。从那里,我们将遍历帧并开始 YOLO 对象检测。 在循环内部,我们:
抓图。
从框架构建一个 blob。
计算预测(即对 blob 执行 YOLO 推理)。
继续,我们将处理结果:
# initialize our lists of detected bounding boxes, confidences,
# and class IDs, respectively
boxes = []
confidences = []
classIDs = []
# loop over each of the layer outputs
for output in layerOutputs:
# loop over each of the detections
for detection in output:
# extract the class ID and confidence (i.e., probability)
# of the current object detection
scores = detection[5:]
classID = np.argmax(scores)
confidence = scores[classID]
# filter out weak predictions by ensuring the detected
# probability is greater than the minimum probability
if confidence > args["confidence"]:
# scale the bounding box coordinates back relative to
# the size of the image, keeping in mind that YOLO
# actually returns the center (x, y)-coordinates of
# the bounding box followed by the boxes' width and
# height
box = detection[0:4] * np.array([W, H, W, H])
(centerX, centerY, width, height) = box.astype("int")
# use the center (x, y)-coordinates to derive the top
# and and left corner of the bounding box
x = int(centerX - (width / 2))
y = int(centerY - (height / 2))
# update our list of bounding box coordinates,
# confidences, and class IDs
boxes.append([x, y, int(width), int(height)])
confidences.append(float(confidence))
classIDs.append(classID)
# apply non-maxima suppression to suppress weak, overlapping
# bounding boxes
idxs = cv2.dnn.NMSBoxes(boxes, confidences, args["confidence"],
args["threshold"])
# ensure at least one detection exists
if len(idxs) > 0:
# loop over the indexes we are keeping
for i in idxs.flatten():
# extract the bounding box coordinates
(x, y) = (boxes[i][0], boxes[i][1])
(w, h) = (boxes[i][2], boxes[i][3])
# draw a bounding box rectangle and label on the frame
color = [int(c) for c in COLORS[classIDs[i]]]
cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)
text = "{}: {:.4f}".format(LABELS[classIDs[i]],
confidences[i])
cv2.putText(frame, text, (x, y - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
仍然在我们的循环中,现在我们将:
初始化结果列表。
循环检测并累积输出,同时过滤低置信度检测。
应用非极大值抑制 (NMS)。
使用对象的边界框、类标签和置信度值注释输出帧。
我们将结束我们的帧处理循环并接下来执行清理:
# check to see if the output frame should be displayed to our
# screen
if args["display"] > 0:
# show the output frame
cv2.imshow("Frame", frame)
key = cv2.waitKey(1) & 0xFF
# if the `q` key was pressed, break from the loop
if key == ord("q"):
break
# if an output video file path has been supplied and the video
# writer has not been initialized, do so now
if args["output"] != "" and writer is None:
# initialize our video writer
fourcc = cv2.VideoWriter_fourcc(*"MJPG")
writer = cv2.VideoWriter(args["output"], fourcc, 30,
(frame.shape[1], frame.shape[0]), True)
# if the video writer is not None, write the frame to the output
# video file
if writer is not None:
writer.write(frame)
# update the FPS counter
fps.update()
# stop the timer and display FPS information
fps.stop()
print("[INFO] elasped time: {:.2f}".format(fps.elapsed()))
print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))
其余行处理显示、按键、打印 FPS 统计信息和清理。虽然我们的 YOLO + OpenCV + CUDA 脚本比 SSD 脚本更难实现,但你在那里做得很好。 在下一节中,我们将分析结果。
YOLO:使用 OpenCV 的支持 NVIDIA GPU 的“dnn”模块将对象检测速度提高 380%
我们现在准备测试我们的 YOLO 目标检测器。确保您已使用本教程的“下载”部分下载与 OpenCV 的 dnn 模块兼容的源代码和预训练模型。从那里,执行以下命令以获取我们 CPU 上 YOLO 的基线:
$ python yolo_object_detection.py --yolo yolo-coco \
--input ../example_videos/janie.mp4 \
--output ../output_videos/yolo_janie.avi \
--display 0
[INFO] loading YOLO from disk...
[INFO] accessing video stream...
[INFO] elasped time: 51.11
[INFO] approx. FPS: 2.47
在我们的 CPU 上,YOLO 获得了相当可怜的 2.47 FPS。
但是通过将计算推送到我们的 NVIDIA V100 GPU,我们现在达到了 11.87 FPS,提高了 380%:
$ python yolo_object_detection.py --yolo yolo-coco \
--input ../example_videos/janie.mp4 \
--output ../output_videos/yolo_janie.avi \
--display 0 \
--use-gpu 1
[INFO] loading YOLO from disk...
[INFO] setting preferable backend and target to CUDA...
[INFO] accessing video stream...
[INFO] elasped time: 10.61
[INFO] approx. FPS: 11.87
正如我在最初的 YOLO + OpenCV 博文中讨论的那样,我不太确定为什么 YOLO 获得如此低的每秒帧数吞吐率。 YOLO 一直被认为是最快的物体检测器之一。
也就是说,转换后的模型或 OpenCV 处理推理的方式似乎有问题——不幸的是,我不知道确切的问题是什么,但我欢迎在评论部分提供反馈。
为 OpenCV 的启用 CUDA 的“dnn”模块实现 Mask R-CNN 实例分割
在这一点上,我们已经研究了 SSD 和 YOLO,这两种不同类型的基于深度学习的对象检测器——但是像 Mask R-CNN 这样的实例分割网络呢? 我们能否将我们的 NVIDIA GPU 与 OpenCV 的支持 CUDA 的 dnn 模块一起使用来提高 Mask R-CNN 的每秒帧数处理速度?
在您的目录结构中打开 mask_rcnn_segmentation.py 以了解如何:
# import the necessary packages
from imutils.video import FPS
import numpy as np
import argparse
import cv2
import os
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-m", "--mask-rcnn", required=True,
help="base path to mask-rcnn directory")
ap.add_argument("-i", "--input", type=str, default="",
help="path to (optional) input video file")
ap.add_argument("-o", "--output", type=str, default="",
help="path to (optional) output video file")
ap.add_argument("-d", "--display", type=int, default=1,
help="whether or not output frame should be displayed")
ap.add_argument("-c", "--confidence", type=float, default=0.5,
help="minimum probability to filter weak detections")
ap.add_argument("-t", "--threshold", type=float, default=0.3,
help="minimum threshold for pixel-wise mask segmentation")
ap.add_argument("-u", "--use-gpu", type=bool, default=0,
help="boolean indicating if CUDA GPU should be used")
args = vars(ap.parse_args())
首先我们处理我们的进口。它们与我们之前的 YOLO 脚本相同。
从那里我们将解析命令行参数:
--mask-rcnn:预训练的 Mask R-CNN 模型目录的基本路径。
--input:我们输入视频文件的可选路径。如果未提供,则默认使用您的第一台相机。
--output:我们输出视频文件的可选路径。
--display:可选的布尔标志,指示我们是否将输出帧显示到 OpenCV GUI 窗口。显示帧会消耗 CPU 周期,因此对于真正的基准测试,您可能希望关闭显示(默认情况下它是打开的)。
--confidence:过滤弱检测的最小概率阈值。默认情况下,该值设置为 50%;但是,如果您愿意,您可以覆盖它。
--threshold:像素分割的最小阈值。默认情况下,此值设置为 30%。
--use-gpu:指示是否应使用 CUDA GPU 的布尔值。默认情况下,此值为 False(即;关闭)。如果您希望将支持 NVIDIA CUDA 的 GPU 用于 OpenCV 的实例分割,则需要将 1 值传递给该参数。
有了我们的导入和命令行参数,现在我们将加载我们的类标签并分配随机颜色:
# load the COCO class labels our Mask R-CNN was trained on
labelsPath = os.path.sep.join([args["mask_rcnn"],
"object_detection_classes_coco.txt"])
LABELS = open(labelsPath).read().strip().split("\n")
# initialize a list of colors to represent each possible class label
np.random.seed(42)
COLORS = np.random.randint(0, 255, size=(len(LABELS), 3),
dtype="uint8")
加载模型
# derive the paths to the Mask R-CNN weights and model configuration
weightsPath = os.path.sep.join([args["mask_rcnn"],
"frozen_inference_graph.pb"])
configPath = os.path.sep.join([args["mask_rcnn"],
"mask_rcnn_inception_v2_coco_2018_01_28.pbtxt"])
# load our Mask R-CNN trained on the COCO dataset (90 classes)
# from disk
print("[INFO] loading Mask R-CNN from disk...")
net = cv2.dnn.readNetFromTensorflow(weightsPath, configPath)
# check if we are going to use GPU
if args["use_gpu"]:
# set CUDA as the preferable backend and target
print("[INFO] setting preferable backend and target to CUDA...")
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
在这里,我们获取预训练的 Mask R-CNN 权重和模型的路径。
如果设置了 --use-gpu 命令行标志,我们然后从磁盘加载模型并将目标后端设置为 GPU。 当只使用你的 CPU 时,分割会像糖蜜一样缓慢。 如果您设置了 --use-gpu 标志,您将以扭曲速度处理您的输入视频或相机流。
让我们开始处理帧:
# initialize the video stream and pointer to output video file, then
# start the FPS timer
print("[INFO] accessing video stream...")
vs = cv2.VideoCapture(args["input"] if args["input"] else 0)
writer = None
fps = FPS().start()
# loop over frames from the video file stream
while True:
# read the next frame from the file
(grabbed, frame) = vs.read()
# if the frame was not grabbed, then we have reached the end
# of the stream
if not grabbed:
break
# construct a blob from the input frame and then perform a
# forward pass of the Mask R-CNN, giving us (1) the bounding box
# coordinates of the objects in the image along with (2) the
# pixel-wise segmentation for each specific object
blob = cv2.dnn.blobFromImage(frame, swapRB=True, crop=False)
net.setInput(blob)
(boxes, masks) = net.forward(["detection_out_final",
"detection_masks"])
抓取一帧后,我们将其转换为 blob 并通过我们的网络执行前向传递以预测对象框和掩码。现在我们准备好处理我们的结果:
# loop over the number of detected objects
for i in range(0, boxes.shape[2]):
# extract the class ID of the detection along with the
# confidence (i.e., probability) associated with the
# prediction
classID = int(boxes[0, 0, i, 1])
confidence = boxes[0, 0, i, 2]
# filter out weak predictions by ensuring the detected
# probability is greater than the minimum probability
if confidence > args["confidence"]:
# scale the bounding box coordinates back relative to the
# size of the frame and then compute the width and the
# height of the bounding box
(H, W) = frame.shape[:2]
box = boxes[0, 0, i, 3:7] * np.array([W, H, W, H])
(startX, startY, endX, endY) = box.astype("int")
boxW = endX - startX
boxH = endY - startY
# extract the pixel-wise segmentation for the object,
# resize the mask such that it's the same dimensions of
# the bounding box, and then finally threshold to create
# a *binary* mask
mask = masks[i, classID]
mask = cv2.resize(mask, (boxW, boxH),
interpolation=cv2.INTER_CUBIC)
mask = (mask > args["threshold"])
# extract the ROI of the image but *only* extracted the
# masked region of the ROI
roi = frame[startY:endY, startX:endX][mask]
# grab the color used to visualize this particular class,
# then create a transparent overlay by blending the color
# with the ROI
color = COLORS[classID]
blended = ((0.4 * color) + (0.6 * roi)).astype("uint8")
# store the blended ROI in the original frame
frame[startY:endY, startX:endX][mask] = blended
# draw the bounding box of the instance on the frame
color = [int(c) for c in color]
cv2.rectangle(frame, (startX, startY), (endX, endY),
color, 2)
# draw the predicted label and associated probability of
# the instance segmentation on the frame
text = "{}: {:.4f}".format(LABELS[classID], confidence)
cv2.putText(frame, text, (startX, startY - 5),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
接下来循环处理:
根据置信度过滤它们。
调整大小和绘制/注释对象透明彩色蒙版。
在输出帧上注释边界框、标签和概率。
从那里我们将继续并结束我们的循环,计算 FPS 统计数据,并清理:
# check to see if the output frame should be displayed to our
# screen
if args["display"] > 0:
# show the output frame
cv2.imshow("Frame", frame)
key = cv2.waitKey(1) & 0xFF
# if the `q` key was pressed, break from the loop
if key == ord("q"):
break
# if an output video file path has been supplied and the video
# writer has not been initialized, do so now
if args["output"] != "" and writer is None:
# initialize our video writer
fourcc = cv2.VideoWriter_fourcc(*"MJPG")
writer = cv2.VideoWriter(args["output"], fourcc, 30,
(frame.shape[1], frame.shape[0]), True)
# if the video writer is not None, write the frame to the output
# video file
if writer is not None:
writer.write(frame)
# update the FPS counter
fps.update()
# stop the timer and display FPS information
fps.stop()
print("[INFO] elasped time: {:.2f}".format(fps.elapsed()))
print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))
开发你的 Mask R-CNN + OpenCV + CUDA 脚本,干得好! 在下一节中,我们将比较 CPU 与 GPU 的结果。有关实现的更多详细信息,请参阅此关于使用 OpenCV 的 Mask R-CNN 的博客文章。
Mask R-CNN:使用 OpenCV 的“dnn”NVIDIA GPU 模块将实例分割速度提高 1,549%
我们的最终测试将是使用 CPU 和 NVIDIA GPU 来比较 Mask R-CNN 的性能确保您已使用本教程的“下载”部分下载源代码和预训练的 OpenCV 模型文件。然后,您可以打开命令行并在 CPU 上对 Mask R-CNN 模型进行基准测试:
$ python mask_rcnn_segmentation.py \
--mask-rcnn mask-rcnn-coco \
--input ../example_videos/dog_park.mp4 \
--output ../output_videos/mask_rcnn_dog_park.avi \
--display 0
[INFO] loading Mask R-CNN from disk...
[INFO] accessing video stream...
[INFO] elasped time: 830.65
[INFO] approx. FPS: 0.67
Mask R-CNN 架构在计算上非常昂贵,因此在 CPU 上看到 0.67 FPS 的结果是可以预料的。
但是 GPU 呢?GPU 能否将我们的 Mask R-CNN 推向接近实时的性能?
要回答这个问题,只需向 mask_rcnn_segmentation.pyscript 提供 --use-gpu 1 命令行参数:
$ python mask_rcnn_segmentation.py \
--mask-rcnn mask-rcnn-coco \
--input ../example_videos/dog_park.mp4 \
--output ../output_videos/mask_rcnn_dog_park.avi \
--display 0 \
--use-gpu 1
[INFO] loading Mask R-CNN from disk...
[INFO] setting preferable backend and target to CUDA...
[INFO] accessing video stream...
[INFO] elasped time: 50.21
[INFO] approx. FPS: 11.05
在我的 NVIDIA Telsa V100 上,我们的 Mask R-CNN 模型现在达到了 11.05 FPS,大幅提高了 1,549%!使几乎所有与 OpenCV 的“dnn”模块兼容的模型在 NVIDIA GPU 上运行
如果您一直在关注今天帖子中的每个源代码示例,您会注意到每个示例都遵循特定模式将计算推送到支持 NVIDIA CUDA 的 GPU:
从磁盘加载训练好的模型。
将 OpenCV 后端设置为 CUDA。
将计算推送到支持 CUDA 的设备。
这三点巧妙地转化为三行代码:
net = cv2.dnn.readNetFromCaffe(args["prototxt"], args["model"])
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
通常,您可以在使用 OpenCV 的 dnn 模块时遵循相同的方法——如果您有一个与 OpenCV 和 dnn 兼容的模型,那么只需将 CUDA 设置为后端和目标,它就可以用于 GPU 推理。你真正需要做的就是用你用来从磁盘加载网络的任何方法替换 cv2.dnn.readNetFromCaffe 函数,包括:
cv2.dnn.readNet
cv2.dnn.readNetFromDarknet
cv2.dnn.readNetFromModelOptimizer
cv2.dnn.readNetFromONNX
cv2.dnn.readNetFromTensorflow
cv2.dnn.readNetFromTorch
cv2.dnn.readTensorFromONNX
你需要参考你的模型训练所用的确切框架,以确认它是否与 OpenCV 的 dnn 库兼容——我希望将来也能介绍这样的教程。
概括
在本教程中,您学习了如何应用 OpenCV 的“深度神经网络”(dnn) 模块进行 GPU 优化推理。在 OpenCV 4.2 发布之前,OpenCV 的 dnn 模块的计算能力极其有限——大多数读者只能在他们的 CPU 上运行推理,这肯定不太理想。然而,多亏了 dlib 的 Davis King、Yashas Samaga(他实现了 OpenCV 的“dnn”NVIDIA GPU 支持)和 Google Summer of Code 2019 计划,OpenCV 现在可以享受 NVIDIA GPU 和 CUDA 支持,从而比以往更容易应用 state- 最先进的网络到您自己的项目。
翻译自:《opencv-dnn-with-nvidia-gpus-1549-faster-yolo-ssd-and-mask-r-cnn》感兴趣可以阅读原文。