【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)

在这里插入图片描述

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

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

轮廓面积函数原型:

contourArea(contour)

轮廓周长函数原型:

arcLength(curve,closed)

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

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

二、多边形逼近与凸包

多边形逼近函数原型:

approxPolyDP(curve,epsilon,closed)

  • epslion:精度;

凸包的函数原型:

convexHull(points,clockwise,…)

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

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

在这里插入图片描述

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

代码案例:

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)

# cv2.drawContours(img, contours, -1, (0, 0, 255), 1)
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)

在这里插入图片描述

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

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

三、外接矩形

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

在这里插入图片描述

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

最小外接矩阵函数原型:

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)

在这里插入图片描述

四、车辆统计实战

涉及的知识点:

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

实现流程:

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

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):                  # Esc退出
        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):                  # Esc退出
        break

cap.release()
cv2.destroyAllWindows()

在这里插入图片描述

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

3、形态处理

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

代码实现:

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

bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()

# 形态学kernel
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):                  # Esc退出
        break

cap.release()
cv2.destroyAllWindows()

在这里插入图片描述

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

4、车辆统计

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

代码实现:

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

bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()

# 保存车辆中心点信息
cars = []
# 统计车的数量
car_n = 0

# 形态学kernel
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):                  # Esc退出
        break

cap.release()
cv2.destroyAllWindows()

在这里插入图片描述

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

存在问题:

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

总结

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

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2023年3月5日
下一篇 2023年3月5日

相关推荐