Auto Byte

专注未来出行及智能汽车科技

微信扫一扫获取更多资讯

Science AI

关注人工智能与其他前沿技术、基础学科的交叉研究与融合发展

微信扫一扫获取更多资讯

无需复杂深度学习算法,基于计算机视觉使用Python和OpenCV计算道路交通

本文介绍了不使用复杂的深度学习算法计算道路交通的方法。该方法基于计算机视觉,仅使用 Python 和 OpenCV,在背景提取算法的帮助下,使用简单的移动侦测来完成任务。


今天我们将学习如何在没有复杂深度学习算法的前提下基于计算机视觉计算道路交通。

该教程中,我们仅使用 Python 和 OpenCV,在背景提取算法的帮助下,使用简单的移动侦测来完成任务。

代码地址:https://github.com/creotiv/object_detection_projects/tree/master/opencv_traffic_counting

这里是我们的计划:

  1. 了解用于前景检测的背景提取算法的主要思想。
  2. OpenCV 图像过滤器。
  3. 基于轮廓的目标检测。
  4. 构建处理管道,用于进一步的数据处理。

背景提取算法

背景提取有很多不同算法,但是它们的主要思想非常简单。

我们来假设你有一个自己房间的视频,该视频的很多帧都没有人/宠物,因此基本上是静态的,我们称之为 background_layer。那么,要想获取视频中移动的物体,我们只需:

foreground_objects = current_frame - background_layer

但是有时候,我们无法获取静态帧,因为光线的变化、某些物体被移动或一直移动等。在这些情况下,我们保存某些帧,尝试找出它们中相同的像素,这些像素就是 background_layer 的一部分。区别通常在于我们获取 background_layer 和用于使选择更加准确的额外过滤的方式。

本教程中,我们将使用 MOG 算法进行背景提取。视频经算法处理后,如下图所示:

左侧是原始帧,右侧是使用 MOG(带有阴影检测)算法提取的背景

如图所示,前景模板仍存在一些噪声,我们将尝试使用标准过滤技术移除噪声。

代码如下:

  1. import os

  2. import logging

  3. import logging.handlers

  4. import random

  5. import numpy as np

  6. import skvideo.io

  7. import cv2

  8. import matplotlib.pyplot as plt

  9. import utils

  10. # without this some strange errors happen

  11. cv2.ocl.setUseOpenCL(False)

  12. random.seed(123)

  13. # ============================================================================

  14. IMAGE_DIR = "./out"

  15. VIDEO_SOURCE = "input.mp4"

  16. SHAPE = (720, 1280)  # HxW

  17. # ============================================================================

  18. def train_bg_subtractor(inst, cap, num=500):

  19.    '''

  20.        BG substractor need process some amount of frames to start giving result

  21.    '''

  22.    print ('Training BG Subtractor...')

  23.    i = 0

  24.    for frame in cap:

  25.        inst.apply(frame, None, 0.001)

  26.        i += 1

  27.        if i >= num:

  28.            return cap

  29. def main():

  30.    log = logging.getLogger("main")

  31.    # creting MOG bg subtractor with 500 frames in cache

  32.    # and shadow detction

  33.    bg_subtractor = cv2.createBackgroundSubtractorMOG2(

  34.        history=500, detectShadows=True)

  35.    # Set up image source

  36.    # You can use also CV2, for some reason it not working for me

  37.    cap = skvideo.io.vreader(VIDEO_SOURCE)

  38.    # skipping 500 frames to train bg subtractor

  39.    train_bg_subtractor(bg_subtractor, cap, num=500)

  40.    frame_number = -1

  41.    for frame in cap:

  42.        if not frame.any():

  43.            log.error("Frame capture failed, stopping...")

  44.            break

  45.        frame_number += 1

  46.        utils.save_frame(frame, "./out/frame_%04d.png" % frame_number)

  47.        fg_mask = bg_subtractor.apply(frame, None, 0.001)

  48.        utils.save_frame(frame, "./out/fg_mask_%04d.png" % frame_number)

  49. # ============================================================================

  50. if __name__ == "__main__":

  51.    log = utils.init_logging()

  52.    if not os.path.exists(IMAGE_DIR):

  53.        log.debug("Creating image directory `%s`...", IMAGE_DIR)

  54.        os.makedirs(IMAGE_DIR)

  55.    main()

过滤

我们这种情况需要这些过滤器:

Threshold(http://docs.opencv.org/3.1.0/d7/d4d/tutorial_py_thresholding.html)、Erode、Dilate、Opening 和 Closing(http://docs.opencv.org/3.1.0/d9/d61/tutorial_py_morphological_ops.html)。请打开链接并阅读,查看这些过滤器的工作方式(而不是简单的复制/粘贴)。

那么,现在我们将使用过滤器移除前景模板上的噪声。

首先,我们将使用 Closing 过滤器移除区域中的缝隙,然后使用 Opening 移除 1–2 个像素点,之后使用 Dilate 使物体更加清晰。

  1. def filter_mask(img):

  2.    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2, 2))

  3.    # Fill any small holes

  4.    closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

  5.    # Remove noise

  6.    opening = cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel)

  7.    # Dilate to merge adjacent blobs

  8.    dilation = cv2.dilate(opening, kernel, iterations=2)

  9.    # threshold

  10.    th = dilation[dilation < 240] = 0

  11.    return th

处理后的前景如下图所示:

基于轮廓的目标检测

为达到目的,我们使用带有下列参数的标准 cv2.findContours 方法:

cv2.CV_RETR_EXTERNAL—get only outer contours.

cv2.CV_CHAIN_APPROX_TC89_L1 - use Teh-Chin chain approximation algorithm (faster)

  1. def get_centroid(x, y, w, h):

  2.    x1 = int(w / 2)

  3.    y1 = int(h / 2)

  4.    cx = x + x1

  5.    cy = y + y1

  6.    return (cx, cy)

  7. def detect_vehicles(fg_mask, min_contour_width=35, min_contour_height=35):

  8.    matches = []

  9.    # finding external contours

  10.    im, contours, hierarchy = cv2.findContours(

  11.        fg_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_L1)

  12.    # filtering by with, height

  13.    for (i, contour) in enumerate(contours):

  14.        (x, y, w, h) = cv2.boundingRect(contour)

  15.        contour_valid = (w >= min_contour_width) and (

  16.            h >= min_contour_height)

  17.        if not contour_valid:

  18.            continue

  19.        # getting center of the bounding box

  20.        centroid = get_centroid(x, y, w, h)

  21.        matches.append(((x, y, w, h), centroid))

  22.    return matches

在出口区,我们通过高度、宽度添加过滤,并添加质心。

很简单,对吧?

构建处理管道

你必须理解,在机器学习和计算机视觉领域中,没有一种魔术般的算法能够搞定一切,即使我们想象存在这样一种算法,我们仍然无法使用它,因为它在大规模应用时会无效。比如,几年前,Netflix 创办了一个比赛,最佳电影推荐算法奖励 300 万美元。有一支队伍创建了一个最佳算法,但问题是该算法无法大规模应用,因此对该公司没有用处。但是,Netflix 仍然奖励了他们 100 万。:)

那么,现在我们将构建简单的处理管道,该管道不是为了大规模使用,而是为了方便,但原理是一样的。

  1. class PipelineRunner(object):

  2.    '''

  3.        Very simple pipline.

  4.        Just run passed processors in order with passing context from one to

  5.        another.

  6.        You can also set log level for processors.

  7.    '''

  8.    def __init__(self, pipeline=None, log_level=logging.DEBUG):

  9.        self.pipeline = pipeline or []

  10.        self.context = {}

  11.        self.log = logging.getLogger(self.__class__.__name__)

  12.        self.log.setLevel(log_level)

  13.        self.log_level = log_level

  14.        self.set_log_level()

  15.    def set_context(self, data):

  16.        self.context = data

  17.    def add(self, processor):

  18.        if not isinstance(processor, PipelineProcessor):

  19.            raise Exception(

  20.                'Processor should be an isinstance of PipelineProcessor.')

  21.        processor.log.setLevel(self.log_level)

  22.        self.pipeline.append(processor)

  23.    def remove(self, name):

  24.        for i, p in enumerate(self.pipeline):

  25.            if p.__class__.__name__ == name:

  26.                del self.pipeline[i]

  27.                return True

  28.        return False

  29.    def set_log_level(self):

  30.        for p in self.pipeline:

  31.            p.log.setLevel(self.log_level)

  32.    def run(self):

  33.        for p in self.pipeline:

  34.            self.context = p(self.context)

  35.        self.log.debug("Frame #%d processed.", self.context['frame_number'])

  36.        return self.context

  37. class PipelineProcessor(object):

  38.    '''

  39.        Base class for processors.

  40.    '''

  41.    def __init__(self):

  42.        self.log = logging.getLogger(self.__class__.__name__)

由于输入构造函数(input constructor)将使用一串处理器,它们将按顺序运行,每个处理器处理一部分工作。那么,现在我们就来创建一个轮廓检测处理器。

  1. class ContourDetection(PipelineProcessor):

  2.    '''

  3.        Detecting moving objects.

  4.        Purpose of this processor is to subtrac background, get moving objects

  5.        and detect them with a cv2.findContours method, and then filter off-by

  6.        width and height.

  7.        bg_subtractor - background subtractor isinstance.

  8.        min_contour_width - min bounding rectangle width.

  9.        min_contour_height - min bounding rectangle height.

  10.        save_image - if True will save detected objects mask to file.

  11.        image_dir - where to save images(must exist).        

  12.    '''

  13.    def __init__(self, bg_subtractor, min_contour_width=35, min_contour_height=35, save_image=False, image_dir='images'):

  14.        super(ContourDetection, self).__init__()

  15.        self.bg_subtractor = bg_subtractor

  16.        self.min_contour_width = min_contour_width

  17.        self.min_contour_height = min_contour_height

  18.        self.save_image = save_image

  19.        self.image_dir = image_dir

  20.    def filter_mask(self, img, a=None):

  21.        '''

  22.            This filters are hand-picked just based on visual tests

  23.        '''

  24.        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2, 2))

  25.        # Fill any small holes

  26.        closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)

  27.        # Remove noise

  28.        opening = cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel)

  29.        # Dilate to merge adjacent blobs

  30.        dilation = cv2.dilate(opening, kernel, iterations=2)

  31.        return dilation

  32.    def detect_vehicles(self, fg_mask, context):

  33.        matches = []

  34.        # finding external contours

  35.        im2, contours, hierarchy = cv2.findContours(

  36.            fg_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_L1)

  37.        for (i, contour) in enumerate(contours):

  38.            (x, y, w, h) = cv2.boundingRect(contour)

  39.            contour_valid = (w >= self.min_contour_width) and (

  40.                h >= self.min_contour_height)

  41.            if not contour_valid:

  42.                continue

  43.            centroid = utils.get_centroid(x, y, w, h)

  44.            matches.append(((x, y, w, h), centroid))

  45.        return matches

  46.    def __call__(self, context):

  47.        frame = context['frame'].copy()

  48.        frame_number = context['frame_number']

  49.        fg_mask = self.bg_subtractor.apply(frame, None, 0.001)

  50.        # just thresholding values

  51.        fg_mask[fg_mask < 240] = 0

  52.        fg_mask = self.filter_mask(fg_mask, frame_number)

  53.        if self.save_image:

  54.            utils.save_frame(fg_mask, self.image_dir +

  55.                             "/mask_%04d.png" % frame_number, flip=False)

  56.        context['objects'] = self.detect_vehicles(fg_mask, context)

  57.        context['fg_mask'] = fg_mask

  58.        return contex

其实就是把背景提取、过滤和检测部分合并起来。

现在,我们来创建一个处理器,其将在不同帧上检测到的物体连接起来并创建路径,还能计算出口区的车辆数量。

  1. '''

  2.        Counting vehicles that entered in exit zone.

  3.        Purpose of this class based on detected object and local cache create

  4.        objects pathes and count that entered in exit zone defined by exit masks.

  5.        exit_masks - list of the exit masks.

  6.        path_size - max number of points in a path.

  7.        max_dst - max distance between two points.

  8.    '''

  9.    def __init__(self, exit_masks=[], path_size=10, max_dst=30, x_weight=1.0, y_weight=1.0):

  10.        super(VehicleCounter, self).__init__()

  11.        self.exit_masks = exit_masks

  12.        self.vehicle_count = 0

  13.        self.path_size = path_size

  14.        self.pathes = []

  15.        self.max_dst = max_dst

  16.        self.x_weight = x_weight

  17.        self.y_weight = y_weight

  18.    def check_exit(self, point):

  19.        for exit_mask in self.exit_masks:

  20.            try:

  21.                if exit_mask[point[1]][point[0]] == 255:

  22.                    return True

  23.            except:

  24.                return True

  25.        return False

  26.    def __call__(self, context):

  27.        objects = context['objects']

  28.        context['exit_masks'] = self.exit_masks

  29.        context['pathes'] = self.pathes

  30.        context['vehicle_count'] = self.vehicle_count

  31.        if not objects:

  32.            return context

  33.        points = np.array(objects)[:, 0:2]

  34.        points = points.tolist()

  35.        # add new points if pathes is empty

  36.        if not self.pathes:

  37.            for match in points:

  38.                self.pathes.append([match])

  39.        else:

  40.            # link new points with old pathes based on minimum distance between

  41.            # points

  42.            new_pathes = []

  43.            for path in self.pathes:

  44.                _min = 999999

  45.                _match = None

  46.                for p in points:

  47.                    if len(path) == 1:

  48.                        # distance from last point to current

  49.                        d = utils.distance(p[0], path[-1][0])

  50.                    else:

  51.                        # based on 2 prev points predict next point and calculate

  52.                        # distance from predicted next point to current

  53.                        xn = 2 * path[-1][0][0] - path[-2][0][0]

  54.                        yn = 2 * path[-1][0][1] - path[-2][0][1]

  55.                        d = utils.distance(

  56.                            p[0], (xn, yn),

  57.                            x_weight=self.x_weight,

  58.                            y_weight=self.y_weight

  59.                        )

  60.                    if d < _min:

  61.                        _min = d

  62.                        _match = p

  63.                if _match and _min <= self.max_dst:

  64.                    points.remove(_match)

  65.                    path.append(_match)

  66.                    new_pathes.append(path)

  67.                # do not drop path if current frame has no matches

  68.                if _match is None:

  69.                    new_pathes.append(path)

  70.            self.pathes = new_pathes

  71.            # add new pathes

  72.            if len(points):

  73.                for p in points:

  74.                    # do not add points that already should be counted

  75.                    if self.check_exit(p[1]):

  76.                        continue

  77.                    self.pathes.append([p])

  78.        # save only last N points in path

  79.        for i, _ in enumerate(self.pathes):

  80.            self.pathes[i] = self.pathes[i][self.path_size * -1:]

  81.        # count vehicles and drop counted pathes:

  82.        new_pathes = []

  83.        for i, path in enumerate(self.pathes):

  84.            d = path[-2:]

  85.            if (

  86.                # need at list two points to count

  87.                len(d) >= 2 and

  88.                # prev point not in exit zone

  89.                not self.check_exit(d[0][1]) and

  90.                # current point in exit zone

  91.                self.check_exit(d[1][1]) and

  92.                # path len is bigger then min

  93.                self.path_size <= len(path)

  94.            ):

  95.                self.vehicle_count += 1

  96.            else:

  97.                # prevent linking with path that already in exit zone

  98.                add = True

  99.                for p in path:

  100.                    if self.check_exit(p[1]):

  101.                        add = False

  102.                        break

  103.                if add:

  104.                    new_pathes.append(path)

  105.        self.pathes = new_pathes

  106.        context['pathes'] = self.pathes

  107.        context['objects'] = objects

  108.        context['vehicle_count'] = self.vehicle_count

  109.        self.log.debug('#VEHICLES FOUND: %s' % self.vehicle_count)

  110.        return context

该教程有一点复杂,我们一部分一部分地过一遍。

下图中绿色的掩膜是出口区,我们在该区域计算车辆的数量。比如,我们将计算长度大于 3 个点(以移除噪声)的路径,其中第 4 个点就在绿色区域。

我们使用掩膜,因为它对很多操作都有效且比使用向量算法更简单。只需要使用二元和(binary and)运算检查该区域的点就可以了。下图显示了我们的设置方式:

  1. EXIT_PTS = np.array([

  2.    [[732, 720], [732, 590], [1280, 500], [1280, 720]],

  3.    [[0, 400], [645, 400], [645, 0], [0, 0]]

  4. ])

  5. base = np.zeros(SHAPE + (3,), dtype='uint8')

  6. exit_mask = cv2.fillPoly(base, EXIT_PTS, (255, 255, 255))[:, :, 0]

现在,我们连接路径中的点:

  1. new_pathes = []

  2. for path in self.pathes:

  3.    _min = 999999

  4.    _match = None

  5.    for p in points:

  6.        if len(path) == 1:

  7.            # distance from last point to current

  8.            d = utils.distance(p[0], path[-1][0])

  9.        else:

  10.            # based on 2 prev points predict next point and calculate

  11.            # distance from predicted next point to current

  12.            xn = 2 * path[-1][0][0] - path[-2][0][0]

  13.            yn = 2 * path[-1][0][1] - path[-2][0][1]

  14.            d = utils.distance(

  15.                p[0], (xn, yn),

  16.                x_weight=self.x_weight,

  17.                y_weight=self.y_weight

  18.            )

  19.        if d < _min:

  20.            _min = d

  21.            _match = p

  22.    if _match and _min <= self.max_dst:

  23.        points.remove(_match)

  24.        path.append(_match)

  25.        new_pathes.append(path)

  26.    # do not drop path if current frame has no matches

  27.    if _match is None:

  28.        new_pathes.append(path)

  29. self.pathes = new_pathes

  30. # add new pathes

  31. if len(points):

  32.    for p in points:

  33.        # do not add points that already should be counted

  34.        if self.check_exit(p[1]):

  35.            continue

  36.        self.pathes.append([p])

  37. # save only last N points in path

  38. for i, _ in enumerate(self.pathes):

  39.    self.pathes[i] = self.pathes[i][self.path_size * -1:]

在第一帧上,我们只需添加所有点作为新的路径。

接下来,如果 len(path) == 1,对于高速缓存中的每个路径,我们将尝试从新检测到的物体中找出点(质心),这些物体到路径最后一个点的欧几里得距离最短。

如果 len(path) > 1,我们将使用该路径中的最后两个点在同一条线上预测新的点,找出它和当前点之间的最小距离。

将最小距离的点添加至当前路径的末尾,然后将其从列表中移除。

如果还有剩下的点,我们将它们添加为新的路径。

我们还可以限制该路径中点的数量。

  1. # count vehicles and drop counted pathes:

  2. new_pathes = []

  3. for i, path in enumerate(self.pathes):

  4.    d = path[-2:]

  5.    if (

  6.        # need at list two points to count

  7.        len(d) >= 2 and

  8.        # prev point not in exit zone

  9.        not self.check_exit(d[0][1]) and

  10.        # current point in exit zone

  11.        self.check_exit(d[1][1]) and

  12.        # path len is bigger then min

  13.        self.path_size <= len(path)

  14.    ):

  15.        self.vehicle_count += 1

  16.    else:

  17.        # prevent linking with path that already in exit zone

  18.        add = True

  19.        for p in path:

  20.            if self.check_exit(p[1]):

  21.                add = False

  22.                break

  23.        if add:

  24.            new_pathes.append(path)

  25. self.pathes = new_pathes

  26. context['pathes'] = self.pathes

  27. context['objects'] = objects

  28. context['vehicle_count'] = self.vehicle_count

  29. self.log.debug('#VEHICLES FOUND: %s' % self.vehicle_count)

  30. return context

现在,我们尝试计算进入出口区的车辆的数量。我们需要观察路径中的最后两个点,并在出口区检查,是否其中靠后的一个在出口区,而靠前的不在,并确保 len(path) 比下限值要大。

之后的部分就是阻止新的点回联至出口区的点。

最后两个处理器是 CSV writer,可创建报告 CSV 文件和可视化文件,用于调试和输出更好的画面。

  1. class CsvWriter(PipelineProcessor):

  2.    def __init__(self, path, name, start_time=0, fps=15):

  3.        super(CsvWriter, self).__init__()

  4.        self.fp = open(os.path.join(path, name), 'w')

  5.        self.writer = csv.DictWriter(self.fp, fieldnames=['time', 'vehicles'])

  6.        self.writer.writeheader()

  7.        self.start_time = start_time

  8.        self.fps = fps

  9.        self.path = path

  10.        self.name = name

  11.        self.prev = None

  12.    def __call__(self, context):

  13.        frame_number = context['frame_number']

  14.        count = _count = context['vehicle_count']

  15.        if self.prev:

  16.            _count = count - self.prev

  17.        time = ((self.start_time + int(frame_number / self.fps)) * 100

  18.                + int(100.0 / self.fps) * (frame_number % self.fps))

  19.        self.writer.writerow({'time': time, 'vehicles': _count})

  20.        self.prev = count

  21.        return context

  22. class Visualizer(PipelineProcessor):

  23.    def __init__(self, save_image=True, image_dir='images'):

  24.        super(Visualizer, self).__init__()

  25.        self.save_image = save_image

  26.        self.image_dir = image_dir

  27.    def check_exit(self, point, exit_masks=[]):

  28.        for exit_mask in exit_masks:

  29.            if exit_mask[point[1]][point[0]] == 255:

  30.                return True

  31.        return False

  32.    def draw_pathes(self, img, pathes):

  33.        if not img.any():

  34.            return

  35.        for i, path in enumerate(pathes):

  36.            path = np.array(path)[:, 1].tolist()

  37.            for point in path:

  38.                cv2.circle(img, point, 2, CAR_COLOURS[0], -1)

  39.                cv2.polylines(img, [np.int32(path)], False, CAR_COLOURS[0], 1)

  40.        return img

  41.    def draw_boxes(self, img, pathes, exit_masks=[]):

  42.        for (i, match) in enumerate(pathes):

  43.            contour, centroid = match[-1][:2]

  44.            if self.check_exit(centroid, exit_masks):

  45.                continue

  46.            x, y, w, h = contour

  47.            cv2.rectangle(img, (x, y), (x + w - 1, y + h - 1),

  48.                          BOUNDING_BOX_COLOUR, 1)

  49.            cv2.circle(img, centroid, 2, CENTROID_COLOUR, -1)

  50.        return img

  51.    def draw_ui(self, img, vehicle_count, exit_masks=[]):

  52.        # this just add green mask with opacity to the image

  53.        for exit_mask in exit_masks:

  54.            _img = np.zeros(img.shape, img.dtype)

  55.            _img[:, :] = EXIT_COLOR

  56.            mask = cv2.bitwise_and(_img, _img, mask=exit_mask)

  57.            cv2.addWeighted(mask, 1, img, 1, 0, img)

  58.        # drawing top block with counts

  59.        cv2.rectangle(img, (0, 0), (img.shape[1], 50), (0, 0, 0), cv2.FILLED)

  60.        cv2.putText(img, ("Vehicles passed: {total} ".format(total=vehicle_count)), (30, 30),

  61.                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 1)

  62.        return img

  63.    def __call__(self, context):

  64.        frame = context['frame'].copy()

  65.        frame_number = context['frame_number']

  66.        pathes = context['pathes']

  67.        exit_masks = context['exit_masks']

  68.        vehicle_count = context['vehicle_count']

  69.        frame = self.draw_ui(frame, vehicle_count, exit_masks)

  70.        frame = self.draw_pathes(frame, pathes)

  71.        frame = self.draw_boxes(frame, pathes, exit_masks)

  72.        utils.save_frame(frame, self.image_dir +

  73.                         "/processed_%04d.png" % frame_number)

  74.        return context

CSV writer 按时间保存数据,因为我们需要用它做进一步的分析。因此我使用下列公式向 unix 时间戳添加额外的帧计时:

time = ((self.start_time + int(frame_number / self.fps)) * 100

+ int(100.0 / self.fps) * (frame_number % self.fps))

在 start time=1 000 000 000 和 fps=10 的情况下,结果如下:

frame 1 = 1 000 000 000 010

frame 1 = 1 000 000 000 020

获取完整的 csv 报告后,你可以随意聚合这些数据。

该项目的完整代码地址:

https://github.com/creotiv/object_detection_projects/tree/master/opencv_traffic_counting

结论

所以,这并不像人们想象的那么难。

但是,如果你运行该脚本,你会发现该解决方案并不完美,它存在一个问题——背景物体重叠,而且它还无法按类型进行车辆分类(实分析时你肯定需要)。但是,该方法拥有好的摄像头位置(道路上方),能够提供相当不错的准确率。这告诉我们即使简单的小算法用好了也能取得不错的结果。

那么我们要怎么做才能解决当前的问题呢?

一种方式是添加额外的过滤,使物体分离,以进行更好的检测。另一种方式是使用更复杂的算法,比如深度卷积网络。

原文地址:https://hackernoon.com/tutorial-making-road-traffic-counting-app-based-on-computer-vision-and-opencv-166937911660

工程PythonOpenCV计算机视觉工程交通
31
为什么不提供文章收藏功能呢?