【OpenCV学习】(九)目标识别之车辆检测与计数

【OpenCV学习】(九)目标识别之车辆检测及计数

背景

本篇将具体介绍一个实际应用项目——车辆检测及计数,在交通安全中是很重要的一项计数;当然,本次完全采用OpenCV进行实现,和目前落地的采用深度学习的算法并不相同,但原理是一致的;本篇将从基础开始介绍,一步步完成车辆检测计数的项目;

一、图像轮廓

本质:具有 相同颜色强度连续点的曲线;

作用:

1、可用于图形分析;

2、应用于物体的识别与检测;

注意点:

1、为了检测的准确性,需要先对图像进行二值化或Canny操作;

2、画轮廓的时候回修改输入的图像,需要先深拷贝原图;

轮廓查找的函数原型:

findContours(img,mode,ApproximationMode…)

  • mode RETR_EXTERNAL=0,表示只检测外轮廓; RETR_LIST=1,检测的轮廓不建立等级关系;(常用) RETR_CCOMP=2,每层最多两级; RETR_TREE=3,按树形结构存储轮廓,从右到左,从大到小;(常用)
  • ApproximationMode CHAIN_APPROX_BOBE:保存轮廓上所有的点; CHAIN_APPROX_SIMPLE:只保存轮廓的角点;

代码实战:

img = cv2.imread('./contours1.jpeg')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

contours输出结果:

(array([[[  0,   0]],

       [[  0, 435]],

       [[345, 435]],

       [[345,   0]]], dtype=int32),)

可以看出,我们找最外层轮廓,找出了一个矩形轮廓的四个点;

当然,我们不需要通过画形状来绘制轮廓,可以通过一个内置函数来绘制轮廓;

绘制轮廓函数原型

drawContours(img,contours,contoursIdx,color,thickness,…)

  • contours:表示保存轮廓的数组;
  • contoursIdx:表示绘制第几个轮廓,-1表示所有轮廓;

代码案例:

img = cv2.imread('./contours1.jpeg')
img2 = img.copy()

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

cv2.drawContours(img, contours, -1, (0, 0, 255), 1)
cv2.drawContours(img2, contours, -1, (0, 0, 255), -1)

cv2.imshow('org', img)
cv2.imshow('org2', img2)
cv2.waitKey(0)

【OpenCV学习】(九)目标识别之车辆检测与计数

如上图所示,左图是线宽设置为1,右图为线宽设置为-1,也就是填充的效果;

当然,OpenCV还提供了计算轮廓周长和面积的方法;

轮廓面积函数原型:

contourArea(contour)

轮廓周长函数原型:

arcLength(curve,closed)

  • curve:表示轮廓;
  • closed:是否是闭合的轮廓;

上述两个函数比较简单,在这就不做代码演示了;

二、多边形逼近与凸包

多边形逼近函数原型:

approxPolyDP(curve,epsilon,closed)

  • epslion:精度;

凸包的函数原型:

convexHull(points,clockwise,…)

  • points:轮廓;
  • clockwise:绘制方向,顺时针或逆时针;(不重要)

首先我们看一下基于轮廓查找输出的轮廓形状:

【OpenCV学习】(九)目标识别之车辆检测与计数

可以看出轮廓点十分密集,接下来看一下基于多变形逼近和凸包的效果:

代码案例:

img = cv2.imread('./hand.png')
img2 = img.copy()

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

e = 20
approx = cv2.approxPolyDP(contours[0], e, True)
approx = (approx, )
cv2.drawContours(img, approx, 0, (0, 0, 255), 3)

hull = cv2.convexHull(contours[0])
hull = (hull, )
cv2.drawContours(img2, hull, 0, (0, 0, 255), 3)

cv2.imshow('org', img)
cv2.imshow('org2', img2)
cv2.waitKey(0)

【OpenCV学习】(九)目标识别之车辆检测与计数

这里需要注意一点,绘制轮廓的函数对于轮廓的传入需要为元组,需要将得到的数组放到一个元组中!

当然,多边形逼近这里设置的精度为20,所以比较粗糙,设置小一些可以达到更好的效果;

三、外接矩形

外接矩阵分为最大外接矩阵和最小外接矩阵,如下图所示:

【OpenCV学习】(九)目标识别之车辆检测与计数

最小外接矩阵还有一个功能,就是计算旋转角度,从上图的绿框应该可以很明显看出;

最小外接矩阵函数原型:

minAreaRect(points)

返回值:起始点(x,y)、宽高(w,h)、角度(angle)

最大外接矩形函数原型:

boundingRect(array)

返回值:起始点(x,y)、宽高(w,h)

代码案例:

img = cv2.imread('./hello.jpeg')
img2 = img.copy()

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

r = cv2.minAreaRect(contours[1])
box = cv2.boxPoints(r)
box = np.int0(box)
cv2.drawContours(img, (box, ), 0, (0, 0, 255), 2)

x, y, w, h = cv2.boundingRect(contours[1])
cv2.rectangle(img2, (x, y), (x+w, y+h), (0, 0, 255), 2)

cv2.imshow('org', img)
cv2.imshow('org2', img2)
cv2.waitKey(0)

【OpenCV学习】(九)目标识别之车辆检测与计数

四、车辆统计实战

涉及的知识点:

  • 窗口展示
  • 图像、视频的加载
  • 基本图形的绘制
  • 基本图像运算与处理
  • 形态学
  • 轮廓查找

实现流程:

加载视频 —— 通过形态学识别车辆 —— 对车辆进行统计 —— 显示统计信息

1、加载视频

这里就是一个简单加载视频的实现:

cap = cv2.VideoCapture('video.mp4')
while True:
    ret, frame = cap.read()

    if(ret == True):
        cv2.imshow('video', frame)

    key = cv2.waitKey(1)
    if(key == 27):
        break

cap.release()
cv2.destroyAllWindows()

2、去除背景

函数原型:

createBackgroundSubtractorMOG()

  • history:缓冲,表示多少毫秒,可不指定参数,用默认的即可;

具体实现原理比较复杂,用到了一些视频序列关联信息,把像素值不变的认为是背景;

注意:在opencv中已经不支持该函数,而是用createBackgroundSubtractorMOG2()替代;如果需要使用可以安装opencv_contrib模块,在其中的bgsegm中保留了该函数;

代码实现:

cap = cv2.VideoCapture('video.mp4')

bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()

while True:
    ret, frame = cap.read()

    if(ret == True):

        cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        blur = cv2.GaussianBlur(frame, (3, 3), 5)
        mask = bgsubmog.apply(blur)
        cv2.imshow('video', mask)

    key = cv2.waitKey(1)
    if(key == 27):
        break

cap.release()
cv2.destroyAllWindows()

【OpenCV学习】(九)目标识别之车辆检测与计数

这里尽量采用旧版的MOG函数,新版的MOG2函数比较精细,会将树叶等信息输出,去除效果没那么好;

3、形态处理

这里主要是为了处理一些小的噪声点以及目标中的黑色块;

代码实现:

cap = cv2.VideoCapture('video.mp4')

bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

while True:
    ret, frame = cap.read()

    if(ret == True):

        cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        blur = cv2.GaussianBlur(frame, (3, 3), 5)
        mask = bgsubmog.apply(blur)

        erode = cv2.erode(mask, kernel)

        dilate = cv2.dilate(erode, kernel, 3)

        close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)
        close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)

        contours, h = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE,)

        for (i, c) in enumerate(contours):
            (x, y, w, h) = cv2.boundingRect(c)
            cv2.rectangle(frame, (x, y), (x+w, y+h), (0,0,255), 2)

        cv2.imshow('video', frame)

    key = cv2.waitKey(1)
    if(key == 27):
        break

cap.release()
cv2.destroyAllWindows()

【OpenCV学习】(九)目标识别之车辆检测与计数

从图中效果来看,还是会有很多小的检测框,接下来就是处理重合检测框以及去掉一些多余的检测框,类似于NMS去重,当然原理还不太一样;

4、车辆统计

首先需要过滤一些小的矩形,已经检测框的长和宽,设定一些阈值即可;

代码实现:

cap = cv2.VideoCapture('video.mp4')

bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()

cars = []

car_n = 0

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))

while True:
    ret, frame = cap.read()

    if(ret == True):

        cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        blur = cv2.GaussianBlur(frame, (3, 3), 5)
        mask = bgsubmog.apply(blur)

        erode = cv2.erode(mask, kernel)

        dilate = cv2.dilate(erode, kernel, 3)

        close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)
        close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)

        contours, h = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE,)

        cv2.line(frame, (10, 550), (1200, 550), (0, 255, 255), 3)
        for (i, c) in enumerate(contours):
            (x, y, w, h) = cv2.boundingRect(c)

            isshow = (w >= 90) and (h >= 90)
            if(not isshow):
                continue

            cv2.rectangle(frame, (x, y), (x+w, y+h), (0,0,255), 2)
            centre_p = (x + int(w/2), y + int(h/2))
            cars.append(centre_p)
            cv2.circle(frame, (centre_p), 5, (0,0,255), -1)
            for (x, y) in cars:
                if(593 < y < 607):
                    car_n += 1
                    cars.remove((x, y))
        cv2.putText(frame, "Cars Count:" + str(car_n), (500, 60), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 5)
        cv2.imshow('video', frame)

    key = cv2.waitKey(1)
    if(key == 27):
        break

cap.release()
cv2.destroyAllWindows()

【OpenCV学习】(九)目标识别之车辆检测与计数

简单的效果已经出来了,对于大部分车辆都能够很好的检测并且计数了;

存在问题:

由于是用中心点与线的距离来判断,车速过慢可能会在两帧内重复计数,车速过快可能会计数不到;这就是传统算法存在的一个问题,基于深度学习的方法可以很好解决这些问题,可关注目标跟踪实战的那一篇文章!

总结

项目到这里就介绍了,通过该项目主要是将所学的知识点进行串联,重点在于形态学的运用!当然这个效果可能达不到实际应用的标准,这也是传统算法的一个弊端;有能力的可以采用深度学习的方法进行实现,也可以关注我后续的目标跟踪是实现车辆计数,效果会远比这个好。

Original: https://blog.csdn.net/weixin_40620310/article/details/122487454
Author: 一个热爱学习的深度渣渣
Title: 【OpenCV学习】(九)目标识别之车辆检测与计数

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/727698/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球