找回密码
 立即注册
  • QQ空间
  • 回复
  • 收藏

使用Mask R-CNN和Python来找空闲停车位(附完整代码)

琴感| 2019-1-23 01:17 阅读 1142 评论 0

在大多数城市里,寻找停车位总是很难。本文的思路是将一个摄像头指向窗外,并使用深度学习让计算机在停车位空闲时能够进行实时检测。

这可能听起来相当复杂,但是通过深度学习构建这个版本的工作版本实际上非常快速和简单。所以让我们花几分钟时间,用Python和深度学习构建一个高精度的停车位通知系统吧!

问题分解

当我们想用机器学习来解决一个复杂的问题时,第一步是将问题分解成简单任务的序列。然后,使用我们的分解作为指导,我们可以从机器学习工具箱中取出不同的工具来解决每个较小的任务。通过将几个小的解决方案链接到一个管道中,我们将得到一个可以完成一些复杂任务的系统。

任务分解如下(最后一步可以根据自己使用不同的消息通知系统):

机器学习管道的输入是来自指向窗口的普通网络摄像头的视频流:

摄像头的视频示例

我们将通过管道传递每帧视频,一次一帧。

  • 管道的第一步是检测视频帧中所有可能的停车位。显然,我们需要知道图像的哪些部分是停车位才能检测到哪些停车位未被占用。
  • 第二步是检测每帧视频中的所有汽车。我们将跟踪每辆车从一帧到另一帧的移动。
  • 第三步是确定哪些停车位目前被汽车占用,哪些不是。这需要结合第一步和第二步的结果。
  • 最后一步是在停车位变得新的时候发送通知(根据自己的实际情况调整)。这将基于视频帧之间的汽车位置的变化。

我们可以使用各种技术以多种不同方式完成这些步骤。构建此管道有很多种方法,不同的方法将有不同的优点和缺点。

检测图像中的停车位

以下是我们的相机视图:

我们需要能够扫描出这张图片,然后找到可以停车的区域,就像这样

城市街道上的有效停车位

懒惰的方法是手工将每个停车位的位置硬编码到程序中,而不是试图自动检测停车位。但是,如果我们移动摄像机,或者想要探测到另一条街上的停车位,我们就必须手动对停车位位置进行硬编码。这很糟糕,所以让我们找到一种自动检测停车位的方法。

一个想法可能是寻找停车计时器并假设每个计量表旁边都有一个停车位:

检测图像中的停车计时器

但这种方法存在一些复杂性。首先,并非每个停车位都有停车计时器 - 事实上,我们最感兴趣的是找到我们无需付费的地点!其次,只知道停车计时器的位置并不能确切地告诉我们停车位的确切位置。

另一个想法是建立一个对象检测模型,寻找绘制在道路上的停车位标记,如下图所示:

注意黄色标记 - 这些是在道路上绘制每个停车位的边界的地方

但这种做法也很痛苦。首先,我所在城市的停车位线标记非常小,很难从远处看,所以用电脑也难以察觉。第二,街道上到处都是各种不相关的线条和标记。将哪些线路划分为停车位以及哪条线路是车道分隔线或人行横道是很困难的。

到底什么是停车位?停车位就是一个停车时间很长的地方。所以我们可能根本不需要检测停车位。为什么我们不能检测出长时间不动的车,然后假设它们停在停车位上呢?

换句话说,有效的停车位只是包含非移动车辆的地方:

因此,如果我们能检测到汽车,并找出哪些汽车没有在视频帧之间移动,我们就能推断出停车位的位置。很简单——让我们继续检测汽车!

检测图像中的汽车

我们可以使用许多机器学习方法来检测图像中的对象。以下是一些最常见的对象检测算法:

  • 训练一个HOG(方向梯度直方图)物体探测器并将其滑过我们的图像以找到所有的汽车。这种较旧的非深度学习方法运行起来相对较快,但它不能很好地处理在不同方向上旋转的汽车。
  • 训练CNN(卷积神经网络)物体探测器并将其滑过我们的图像,直到我们找到所有的汽车。这种方法是准确的,但效率不高,因为我们必须使用CNN多次扫描相同的图像才能找到整个图像中的所有汽车。虽然它可以很容易地找到以不同方向旋转的汽车,但它需要比基于HOG的物体探测器更多的训练数据。
  • 使用更新的深度学习方法,如Mask R-CNN、Faster R-CNN或YOLO,它结合了CNN的准确性、巧妙的设计和高效的技巧,大大加快了检测过程。只要我们有大量的训练数据来训练模型,这将运行得相对较快(在GPU上)。

一般来说,我们希望选择最简单的解决方案,以最少的训练数据完成工作,而不是假设我们需要最新的算法。Mask R-CNN是一个合理的选择。

Mask R-CNN体系结构被设计成使得它在不使用滑动窗口方法的情况下以计算高效的方式检测跨越整个图像的对象。换句话说,它运行得相当快。使用GPU,我们应该能够以每秒几帧的速度检测高分辨率视频中的对象。对于这个项目来说应该没问题。

此外,Mask R-CNN为我们提供了有关每个检测到的对象的大量信息。大多数对象检测算法仅返回每个对象的边界框。但Mask R-CNN不仅会给我们每个对象的位置,还会给我们一个对象轮廓(或mask),如下所示:

为了训练Mask R-CNN,我们需要大量我们想要检测的对象图片。我们可以去外面拍摄汽车照片并追踪这些照片中的所有汽车,但这需要几天的工作。幸运的是,汽车是许多人想要检测的常见物体,因此汽车图像的几个公共数据集已经存在。

有一个名为COCO的流行机器学习数据集(http://cocodataset.org/),其中包含使用object masks注释的图像。在此数据集中,已经概述了超过12,000张汽车图像。这是COCO数据集中的一个图像:

COCO数据集中的图像,其中已经列出了对象

该数据非常适合训练Mask R-CNN模型。

我们可以从预先训练好的模型开始,而不是训练我们自己的机器学习模型,该模型可以开箱即用地检测汽车。对于这个项目,我们将使用来自Matterport的大型开源Mask R-CNN实现(https://github.com/matterport/Mask_RCNN),它带有预训练的模型。

如果我们在相机图像上运行预训练的模型,这就是开箱即可检测到的机器学习模型:

我们的图像(使用默认的COCO对象被识别) - 汽车,人,交通灯和树

我们不仅确定了汽车,而且我们也得到交通信号灯和人等信息。

对于图像中检测到的每个对象,我们从Mask R-CNN模型中得到4个东西:

  1. 检测到的对象类型(作为整数)。经过预训练的COCO模型知道如何检测80种不同的常见物体,如汽车和卡车。
  2. 物体检测的置信度得分。数字越大,模型就越能确定正确识别对象。
  3. 图像中对象的边界框,以X / Y像素位置给出。
  4. 位图“mask”,用于指示边界框内的哪些像素是对象的一部分,哪些不是。使用mask数据,我们还可以计算出对象的轮廓。

下面是使用Matterport的Mask R-CNN实现的预训练模型以及OpenCV来检测汽车边界框的Python代码:

当您运行该Python脚本时,您将在屏幕上看到一个图像,每个检测到的汽车周围都有一个框,如下所示:

每辆检测到的汽车周围都有一个绿色的边框

并且您还将获得在控制台中打印的每个检测到的汽车的像素坐标,如下所示:

我们已成功检测到图像中的汽车。

检测空闲停车位

我们知道图像中每辆车的像素位置。通过连续观看多个视频帧,我们可以轻松地确定哪些车辆没有移动,并假设这些区域是停车位。但是,我们如何检测汽车何时离开停车位呢?

问题是我们图像中汽车的边界框部分重叠:

即使对于不同停车位的汽车,每辆汽车的边界框也会重叠一点

因此,如果我们假设每个边界框都代表一个停车位,那么即使停车位是空的,这个框也有可能被汽车部分占用。我们需要一种方法来测量两个对象有多少重叠,这样我们就可以检查“大部分是空的”框。

我们将使用的措施称为Intersection Over Union或IoU。通过查找两个对象重叠的像素数量并将其除以两个对象所覆盖的像素数量来计算IoU,如下所示:

这将为我们提供汽车边界框与停车位边界框重叠的程度。有了这个,我们可以很容易地确定汽车是否在停车位。如果IoU测量值很低,如0.15,那意味着汽车并没有真正占用大部分停车位。但如果措施很高,如0.6,这意味着汽车占据了大部分停车位区域,因此我们可以确定该空间被占用。

由于IoU在计算机视觉中是一种常见的度量方法,所以您所使用的库通常已经实现了IoU。实际上,Matterport Mask R-CNN库将它作为一个名为mrcnn.utils.compute_overlaps()的函数包含进来,因此我们可以直接使用该函数。

假设我们在图像中有一个表示停车场的边界框列表,检查检测到的车辆是否在这些边界框中,只需添加一两行代码:

 # Filter the results to only grab the car / truck bounding boxes
car_boxes = get_car_boxes(r['rois'], r['class_ids'])
# See how much cars overlap with the known parking spaces
overlaps = mrcnn.utils.compute_overlaps(car_boxes, parking_areas)
print(overlaps)

结果如下:

在这个2D数组中,每一行表示一个停车位边界框。同样,每一列表示被检测到的车辆与停车位的重叠程度。得分为1.0意味着汽车完全占据了空间,得分为0.02这样的低分意味着汽车接触了空间,但并不占据大部分区域。

要找到未占用的停车位,只需检查数组中的每一行。如果所有的数字都是零或者很小,那就意味着没有任何东西占据这个空间。

但是请记住,在实时视频中,对象检测并不总是完美的。尽管Mask R-CNN是非常精确的,但它偶尔也会在一段视频中漏掉一两辆车。所以在标记一个停车位是空闲的之前,我们应该确保它在一段时间内是空闲的——也许是5到10个连续的视频帧。这将防止系统仅仅因为目标检测在一帧视频中出现了暂时的小故障就错误地检测到空闲停车位。一旦我们看到我们至少有一个停车位为几个连续的视频帧空闲,我们就准备发送一个通知消息了!

发送消息

我们的管道的最后一步是当我们发现几个视频帧的停车位已经空闲时发送消息提醒。

Python完整实现

让我们将管道的每一步组装成一个Python脚本。这是完整的代码:

import os
import numpy as np
import cv2
import mrcnn.config
import mrcnn.utils
from mrcnn.model import MaskRCNN
from pathlib import Path
#from twilio.rest import Client
# Configuration that will be used by the Mask-RCNN library
class MaskRCNNConfig(mrcnn.config.Config):
NAME = "coco_pretrained_model_config"
IMAGES_PER_GPU = 1
GPU_COUNT = 1
NUM_CLASSES = 1 + 80 # COCO dataset has 80 classes + one background class
DETECTION_MIN_CONFIDENCE = 0.6
# Filter a list of Mask R-CNN detection results to get only the detected cars / trucks
def get_car_boxes(boxes, class_ids):
car_boxes = []
for i, box in enumerate(boxes):
# If the detected object isn't a car / truck, skip it
if class_ids[i] in [3, 8, 6]:
car_boxes.append(box)
return np.array(car_boxes)
# Twilio config
twilio_account_sid = 'YOUR_TWILIO_SID'
twilio_auth_token = 'YOUR_TWILIO_AUTH_TOKEN'
twilio_phone_number = 'YOUR_TWILIO_SOURCE_PHONE_NUMBER'
destination_phone_number = 'THE_PHONE_NUMBER_TO_TEXT'
client = Client(twilio_account_sid, twilio_auth_token)
# Root directory of the project
ROOT_DIR = Path(".")
# Directory to save logs and trained model
MODEL_DIR = os.path.join(ROOT_DIR, "logs")
# Local path to trained weights file
COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5")
# Download COCO trained weights from Releases if needed
if not os.path.exists(COCO_MODEL_PATH):
mrcnn.utils.download_trained_weights(COCO_MODEL_PATH)
# Directory of images to run detection on
IMAGE_DIR = os.path.join(ROOT_DIR, "images")
# Video file or camera to process - set this to 0 to use your webcam instead of a video file
VIDEO_SOURCE = "test_images/parking.mp4"
# Create a Mask-RCNN model in inference mode
model = MaskRCNN(mode="inference", model_dir=MODEL_DIR, config=MaskRCNNConfig())
# Load pre-trained model
model.load_weights(COCO_MODEL_PATH, by_name=True)
# Location of parking spaces
parked_car_boxes = None
# Load the video file we want to run detection on
video_capture = cv2.VideoCapture(VIDEO_SOURCE)
# How many frames of video we've seen in a row with a parking space open
free_space_frames = 0
# Have we sent an SMS alert yet?
sms_sent = False
# Loop over each frame of video
while video_capture.isOpened():
success, frame = video_capture.read()
if not success:
break
# Convert the image from BGR color (which OpenCV uses) to RGB color
rgb_image = frame[:, :, ::-1]
# Run the image through the Mask R-CNN model to get results.
results = model.detect([rgb_image], verbose=0)
# Mask R-CNN assumes we are running detection on multiple images.
# We only passed in one image to detect, so only grab the first result.
r = results[0]
# The r variable will now have the results of detection:
# - r['rois'] are the bounding box of each detected object
# - r['class_ids'] are the class id (type) of each detected object
# - r['scores'] are the confidence scores for each detection
# - r['masks'] are the object masks for each detected object (which gives you the object outline)
if parked_car_boxes is None:
# This is the first frame of video - assume all the cars detected are in parking spaces.
# Save the location of each car as a parking space box and go to the next frame of video.
parked_car_boxes = get_car_boxes(r['rois'], r['class_ids'])
else:
# We already know where the parking spaces are. Check if any are currently unoccupied.
# Get where cars are currently located in the frame
car_boxes = get_car_boxes(r['rois'], r['class_ids'])
# See how much those cars overlap with the known parking spaces
overlaps = mrcnn.utils.compute_overlaps(parked_car_boxes, car_boxes)
# Assume no spaces are free until we find one that is free
free_space = False
# Loop through each known parking space box
for parking_area, overlap_areas in zip(parked_car_boxes, overlaps):
# For this parking space, find the max amount it was covered by any
# car that was detected in our image (doesn't really matter which car)
max_IoU_overlap = np.max(overlap_areas)
# Get the top-left and bottom-right coordinates of the parking area
y1, x1, y2, x2 = parking_area
# Check if the parking space is occupied by seeing if any car overlaps
# it by more than 0.15 using IoU
if max_IoU_overlap < 0.15:
# Parking space not occupied! Draw a green box around it
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 3)
# Flag that we have seen at least one open space
free_space = True
else:
# Parking space is still occupied - draw a red box around it
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 0, 255), 1)
# Write the IoU measurement inside the box
font = cv2.FONT_HERSHEY_DUPLEX
cv2.putText(frame, f"{max_IoU_overlap:0.2}", (x1 + 6, y2 - 6), font, 0.3, (255, 255, 255))
# If at least one space was free, start counting frames
# This is so we don't alert based on one frame of a spot being open.
# This helps prevent the script triggered on one bad detection.
if free_space:
free_space_frames += 1
else:
# If no spots are free, reset the count
free_space_frames = 0
# If a space has been free for several frames, we are pretty sure it is really free!
if free_space_frames > 10:
# Write SPACE AVAILABLE!! at the top of the screen
font = cv2.FONT_HERSHEY_DUPLEX
cv2.putText(frame, f"SPACE AVAILABLE!", (10, 150), font, 3.0, (0, 255, 0), 2, cv2.FILLED)
# If we haven't sent an SMS yet, sent it!
if not sms_sent:
"""
print("SENDING SMS!!!")
message = client.messages.create(
body="Parking space open - go go go!",
from_=twilio_phone_number,
to=destination_phone_number
)
"""
sms_sent = True
# Show the frame of video on the screen
cv2.imshow('Video', frame)
# Hit 'q' to quit
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# Clean up everything when finished
video_capture.release()
cv2.destroyAllWindows()

要运行此Python代码,您需要先安装Python 3.6 +,Matterport Mask R-CNN和OpenCV。

文章点评