基于opencv的车辆统计

发布于:2024-05-09 ⋅ 阅读:(21) ⋅ 点赞:(0)

一、项目背景

检测并识别视频中来往车辆的数量
最终效果图:

二、整体流程

  1. 加载视频
  2. 图像预处理(去噪、背景减除、形态学)
  3. 对车辆进行统计
  4. 显示车辆统计信息(增加水印)

车辆识别中首先对灰度化的视频帧进行去噪。二值化的目的是简化图像,并突出图像中的主要特征,可以减少后续处理的复杂性。
接下来用高斯模糊滤波去除不均匀的细小的噪声。常用滤波器特点总结如下:

三、常用滤波器的特点

  1. 方盒滤波(cv2.boxFilter)与均值滤波(cv2.blur)
    均值滤波需要额外的归一化过程
  2. 高斯滤波(cv2.GaussianBlur)
    高斯滤波的核心思想是让临近的像素具有更高的重要度。对周围像素计算加权平均值,较近的像素具有较大的权重值。
    尤其高斯噪声效果明显,对椒盐噪声效果不好
  3. 中值滤波(medianBlur)
    每个像素的值被替换为其邻域中所有像素值的中值,对椒盐噪音效果明显
  4. 双边滤波(bilateralFilter)
    对图像的边缘信息能更好的保存,双边滤波和高斯滤波不同的就是:双边滤波既利用了位置信息又利用了像素信息来定义滤波窗口的权重,而高斯滤波只用了位置信息。
    双边滤波可以保留边缘,同时可以对边缘内的区域进行平滑处理。
    双边滤波的作用就相当于做了美颜

四、背景减除

背景减除(Background Subtraction)是许多基于计算机视觉的任务中的主要预处理步骤。如果我们有完整的静止的背景帧,那么我们可以通过帧差法来计算像素差从而获取到前景对象。但是在大多数情况下,我们可能没有这样的图像,所以我们需要从我们拥有的任何图像中提取背景。当运动物体有阴影时,由于阴影也在移动,情况会变的变得更加复杂。为此引入了背景减除算法,通过这一方法我们能够从视频中分离出运动的物体前景,从而达到目标检测的目的。

  • BackgroundSubtractorMOG
    • 这是一个以混合高斯模型为基础的前景/背景分割算法。它是 P.KadewTraKuPong 和 R.Bowden 在 2001 年提出的。
    • 它使用 K(K=3 或 5)个高斯分布混合对背景像素进行建模。使用这些颜色(在整个视频中)存在时间的长短作为混合的权重。背景的颜色一般持续的时间最长,而且更加静止。
    • 在编写代码时,我们需要使用函数:cv2.createBackgroundSubtractorMOG() 创建一个背景对象。这个函数有些可选参数,比如要进行建模场景的时间长度,高斯混合成分的数量,阈值等。将他们全部设置为默认值。然后在整个视频中我们是需要使用backgroundsubtractor.apply() 就可以得到前景的掩模了。
    • 移动的物体会被标记为白色,背景会被标记为黑色的

得到前景掩模后,对视频帧进行开运算,以进一步除图像中的噪声、填充小孔和连接物体。

五、形态学

开运算

原理:先进行腐蚀操作,然后进行膨胀操作。腐蚀操作可以去除图像中的小细节和噪声,而膨胀操作可以保持物体的整体形状。
特点:
可以有效去除图像中的小白点(前景噪声)和连接细小的物体。
能够平滑物体的边缘并保持物体的整体形状。
可以用于断开物体之间的连接以及分割物体。

闭运算

原理:先进行膨胀操作,然后进行腐蚀操作。膨胀操作可以填充物体中的小孔和裂缝,而腐蚀操作可以保持物体的整体形状。
特点:
可以填充图像中的小孔和裂缝,使得物体更加完整。
可以连接物体之间的断裂部分,使它们形成连续的整体。
有助于平滑物体的边缘并保持其整体形状。

比较:
应用场景:开运算主要用于去除噪声和细小物体,闭运算主要用于填充小孔和连接物体。
效果:开运算更适合去除细小的噪声和连接细小的物体,闭运算更适合填充小孔和连接断裂的物体。
形状保持:两者都可以保持物体的整体形状,但开运算可能会略微削弱物体的尺寸,而闭运算则可能略微增加物体的尺寸。
在实际应用中,开运算和闭运算常常结合使用,以便达到更好的图像处理效果,例如先进行开运算去除噪声,然后进行闭运算填充孔洞,以得到更加干净和完整的物体。

图像预处理完成后,用外接矩形画出车辆轮廓,识别出的图像中较小的轮廓忽略掉。统计车辆数据时判断外接矩形的中心点是否通过检测线统计经过检测线的车辆数目。

六、项目完整代码

# 去背景
# 加载视频
import cv2
import numpy as np

cap = cv2.VideoCapture('./video.mp4')
bgsubmog = cv2.bgsegm.createBackgroundSubtractorMOG()
# 形态学kernel,相当于设置卷积核为5x5的矩形元素卷积核
kernel= cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
min_w = 90
min_h = 90
cars = []
# 检测线高, 和视频的宽高有关系
line_high = 620
# 线的偏移量
offset = 7
carno = 0

def center(x, y, w, h):
    x1 = int(w/2)
    y1 = int(h/2)
    cx = int(x) + x1
    cy = int(y) + y1
    
    return cx, cy

while True:
    ret, frame = cap.read()
    
    if ret == True:
        # 将原始帧进行灰度化, 然后去噪
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        # 用高斯滤波去噪
        blur = cv2.GaussianBlur(gray, (3, 3), 5)
        # 获取前景掩码
        mask = bgsubmog.apply(blur)
        # 去掉了一些干扰噪声
        erode = cv2.erode(mask, kernel)
        # 再把图像还原回来, 执行膨胀操作
        dilate = cv2.dilate(erode, kernel, iterations=2)
        
        # 闭操作, 把物体内部的小块
        close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel, iterations=2)
        
        # 查找轮廓
        result, contours, h = cv2.findContours(close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
        # 画出检测线
        cv2.line(frame, (10, line_high), (1200, line_high), (255, 255, 0), 3)
        # 画出轮廓
        for (i, c) in enumerate(contours):
            (x, y, w, h) = cv2.boundingRect(c)
            cv2.rectangle(frame, (int(x), int(y)), (int(x + w), int(y + h)), (0, 0, 255), 2)
            
            # 通过外接矩形的宽高大小来过滤掉小轮廓.
            is_valid = (w >= min_w) and (h >= min_h)
            if not is_valid:
                continue
         
            # 到这里都是有效的车
            # 有效才画矩形
            cv2.rectangle(frame, (int(x), int(y)), (int(x + w), int(y + h)), (0, 0, 255), 2)
            # 把车抽象为一点. 即矩形的中心点.
            cpoint = center(x, y, w, h)
            cars.append(cpoint)
            # 画出中心点
            cv2.circle(frame, (cpoint), 5, (0, 0, 255), -1)
            # 判断汽车是否过线. 
            for (x, y) in cars:
                if y > (line_high - offset) and y < (line_high + offset):
                    # 落入了有效区间. 
                    # 计数加1
                    carno += 1
                    cars.remove((x, y))
                    print(carno)
        # 打印计数信息
        cv2.putText(frame, 'Vehicle Count:' + str(carno), (500, 60), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 5)
        cv2.imshow('frame', frame)
        
    key = cv2.waitKey(10)
    if key == 'q':
        break
cap.release()
cv2.destroyAllWindows()

七、参考资料

https://blog.csdn.net/great_yzl/article/details/119645423#%E5%9F%BA%E7%A1%80%E7%90%86%E8%AE%BA
https://blog.csdn.net/qq_27261889/article/details/80822270
https://blog.csdn.net/Vermont_/article/details/108424547


网站公告

今日签到

点亮在社区的每一天
去签到