Python + OpenCv实现视频中的车辆检测计数(车流量统计) (2020.7.15已更新源码)

1.序言

这里主要说一下遇到的问题以及想法,如有问题欢迎大家指正。

2.设计思路以及遇到的问题

  1. 第一步是完成物体(也就是车辆)的检测,这里有两种解决办法,第一种办法是使用opencv的形态学处理,比如背景消除、做帧差、膨胀腐蚀等等,这个办法比较基础,但是要处理好需要调整诸多细节,我的细节调整的不够好,因此实现时会遇到许多小问题。第二个办法是直接实现物体的识别,需要用到一定的训练模型,我测试过googlenet和SSD,最终选择了SSD,如果大家有能力也能自己训练,可能效果更好。
  2. 第二步是追踪,这里我看到很多博文都提到了opencv自带的追踪器,比如kcf等,但是我对于追踪器没有成功使用,放弃了这个选择,并选择了最简单粗暴的方法:逐帧读取视频并寻找车辆,一帧一帧画出来。
  3. 第三步是计数,这里我卡了一个星期,遇到了各种问题,为此我几乎翻遍了CSDN关于这块的文章,有些文章确实给到了一些思路,但大部分都没给出具体过程。第一个问题是怎么判断是否计数的问题,我最开始的想法是:当车辆轮廓的y坐标碰到检测线(这里假定为700)时加1,但接下里的测试却非常失败,很多车辆在爱检测线附近会掉帧或者检测不到或者其他原因,总之仅仅靠一个y轴直接判断肯定是不够的,后来我改成往下大于检测线1(700),往上大于检测线2(400),发现问题更大了,你会一直检测(这里我划分ROI区域没有解决,可能是我方法不对)。之后在CSDN 中找到一篇博文,提到的做法是建立一个类,每次出现新的目标就创建一个类,旧的目标就替换坐标,这个做法后来证实的确有效。但问题是如何判断这一帧的车辆是否新的目标,我给出的解决办法是创建一个列表,将第一帧的所有目标(也就是每一个对象)存入,第二帧的时候遍历每一个目标与列表的每一辆车,判断x,y的距离,如果变化不超过车的长度和宽度,就认为这是同一辆车(也有学长告诉为特征值匹配的办法,也许更可行,但我能力不够没有学到这里,所以没有采用)。第三个问题 是来去方向问题,最开始的想法是读取质心,将每一辆车变成点,然后读取帧,当这一帧的这一辆车的y坐标相较于上一辆车增加时,就认为这辆车方向向下,否者认为方向向上,后来的测试证明我大错特错,有些车的轮廓会来回浮动,质点也会来回浮动,因此有时候下一帧中方向向下的车辆的y坐标甚至低于上一帧,所以我没有采取这种方式。最终只能考虑最简单粗暴的办法:根据x坐标给定方向,视频中x轴1000往左为下,1000往右为上,我承认这种办法确实很笨,但我确实没有想出好的办法来,如有同学有办法解决,恳请您大胆指出。第四个问题回到了计数判断环节,这里由于上面问题的解决,所以我给出的判断条件是y坐标+方向+是否已经计数。最后有个非常重要的步骤是车辆跨越一定限度后删除对象,节约计算时间。

3.实现过程

1.物体检测识别和追中请看我的上几篇博文,链接1 opencv检测 链接2SSD车辆识别
结果如下:
在这里插入图片描述

2.定义类:(其实id没啥用,方便测试)

class Car:
    def __init__(self,c_id,c_x,c_y,direction,c_count):	#初始化
        self.c_id = c_id	#车辆编号
        self.c_x=c_x	#车辆坐标(实时更新)
        self.c_y=c_y
        self.direction = direction	#车辆方向
        self.c_count = c_count	#车辆是否已经计数


    def updateCoords(self,x,y):	#更新坐标
        self.c_x= x
        self.c_y=y

3.计数

                for i in cars:
                    if abs(cx - i.c_x) < width and abs(cy - i.c_y) < height: # 找到这辆车与上一帧中最近的车
                        new = False
                        #最开始的想法,Y轴坐标比上一帧大方向向下,比上一帧小方向向上,但失败了
                        # if cy > i.c_y:
                        #     i.direction = 'down'
                        # if cy < i.c_y:
                        #     i.direction = 'up'

                        i.updateCoords(cx, cy)
                        if i.c_y >= 700 and i.direction == 'down' and i.c_count == False:
                            count_down += 1
                            i.c_count = True
                        if i.c_y < 400 and i.direction == 'up' and i.c_count == False:
                            count_up += 1
                            i.c_count = True
                            #删除对象
                    if i.c_y >= 790 or i.c_y <= 290:
                        cars.remove(i)

                if new == True:
                    p = Car(pid, cx, cy, 'unknow', False)
                    #判断方向,这里我实在没找到好的办法,只能从简,大家测试的时候需要修改
                    #如果有好的办法请您指点
                    if p.c_x > 1000:
                        p.direction = 'up'
                    else:
                        p.direction = 'down'
                    cars.append(p)
                    pid += 1
                print(len(cars))
                cv.circle(image, (cx, cy), 5, (0, 0, 255), -1)
                cv.rectangle(image, (int(left), int(top)), (int(right), int(bottom)), (255, 0, 0), thickness=2)

4.结果(由于视频画质原因,遇到一些车辆比较模糊时会出现很多框,因此还需改进)
在这里插入图片描述

4.总结&吐槽

由于代码还不够完善,这里就不给大家一一贴出来了,主要是跟大家说一下我的思路,要是有小伙伴想参考的话,我就放一个下载链接吧,其实这个东西只要想明白了应该不是什么难事(虽然我现在也有点晕),作为计算机视觉还在入门的新人,欢迎大家指出我的错误以便改进,也欢迎同样是计算机视觉新手的小伙伴们一起学习,本博客将一直更新相关内容,我会把计算机视觉入门过程中所遇到的重要问题和自己的想法分享给大家,同时也希望能收到大家的点赞或者指点迷津。
我在学习过程同遇到很多有经验的同学,他们有的使用了yolo v3算法,有的使用tensorflow训练模型,深感自己能力的不足,现在我会继续学习这方面的内容,希望能有更多思路和内容分享给大家。
吐槽:算法确实挺难的,但更难的是找测试素材,这十字路口的监控录像上哪找去(各大视频网站愣是没找到几个能用的,给跪了orz)

5.更新源码

有许多小伙伴询问,现在将源码分享给大家,希望大家指正
2022.3.29更新:
这篇博客是20年写的,现在22年了,两年过去了,没想到这么多人评论留言和邮件联系我,在这里谢谢大家了,作为cv新人(可能一直都会是新人)受宠若惊。

需要说明的是,其实这个代码有很大的局限性,因为像素点什么的都写死了,换个视频就不行了。而且纯图像处理的方法确实有很大的问题,一点点调参数确实能把一些大的物体运动轨迹给抓住,但是一旦有的视频会有光线啊、遮挡物啊什么的,仅仅用图像处理就解决不掉了,因为这个代码是我刚学图像处理的时候做的,所以没想太多。主要是记录一下我遇到的问题吧,现在我现在已经很久没有用opencv了,有一些问题我也不知道在哪,这篇博客主要是提供一个思路,其实我回看一下思路确实没啥问题,只是水平有限当时没有做好,如果是现在来做的话,yolo模型确实是比较优秀的选择,但是你用了yolo后,可以把我过线检测以及定义类的思路用上。最后,无论是做毕设的、工作的还是在学习的兄弟,如果这篇博客帮到了大家,哪怕是一点点思路,那就是我开源的意义所在了,祝大家一帆风顺~

此外,有问题还是可以继续留言或者邮件,所有留言和邮件我都会看,并且会一一回复的!**

import numpy as np
import cv2 as cv
import  time
class Car:
    def __init__(self,c_id,c_x,c_y,direction,c_count,c_start_time,c_over_time,c_v):
        self.c_id = c_id
        self.c_x=c_x
        self.c_y=c_y
        self.direction = direction
        self.c_count = c_count
        self.c_start_time = c_start_time
        self.c_over_time = c_over_time
        self.c_v = c_v
    def updateCoords(self,x,y):
        self.c_x= x
        self.c_y=y

count_up = 0
count_down = 0
cars = []
pid = 1
max_v = 0
distance = 10
vc = cv.VideoCapture(r"C:\Users\zn\Desktop\监控录像_1.mp4")

fourcc = cv.VideoWriter_fourcc(*'XVID')
out = cv.VideoWriter('output.avi',fourcc,15, (1920,1080))

BS = cv.createBackgroundSubtractorMOG2(detectShadows=True)
while (vc.isOpened()):
    ret, frame = vc.read()
    # cv.imshow("frame01",frame)
    gray = cv.cvtColor(frame,cv.COLOR_BGR2GRAY)
    # ret,gray = cv.threshold(gray,127,255,cv.THRESH_BINARY)
    fgmask = BS.apply(gray)
    image = cv.medianBlur(fgmask,5)
    # cv.imshow("BS",fgmask)
    element = cv.getStructuringElement(cv.MORPH_RECT,(5, 5));#创建结构体
    image2 = cv.morphologyEx(image, cv.MORPH_CLOSE,element,iterations=5);#闭运算
    # image3 = cv.erode(image2, element)
    image3 = cv.morphologyEx(image2, cv.MORPH_OPEN, element, iterations=5)
    # cv.imshow('frame1', image3)
    contours, hierarchy = cv.findContours(image3, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)
    cv.line(frame, (0, 700), (1920, 700), (0, 255, 0), 3)
    cv.line(frame, (0, 400), (1920, 400), (0, 255, 0), 3)
    cv.line(frame, (0, 800), (1920, 800), (255, 255, 255), 2)
    cv.line(frame, (0, 300), (1920, 300), (255, 255, 255), 2)
    cv.putText(frame,"Up:"+str(count_up),(1100,100),cv.FONT_HERSHEY_COMPLEX,2,(255,0,0),3)
    cv.putText(frame, "Down:" + str(count_down),(700, 100), cv.FONT_HERSHEY_COMPLEX, 2, (255, 0, 0), 3)
    cv.putText(frame, "max_v:" + str('%.2f' %(max_v*3.6))+'km/h', (50, 100), cv.FONT_HERSHEY_COMPLEX, 1.5, (255, 0, 0), 2)
    null = True #如果这一帧没有检测到任何车,则将cars置空,减小误差,设置初始变量null设为True
    for cnt in contours:
        x, y, w, h = cv.boundingRect(cnt)
        cx = int(x + w / 2)
        cy = int(y + h / 2)
        if 300<int(y+(h/2))<800 and 400<int(x+(w/2))<1800:
            if cv.contourArea(cnt) < 13000 or w < 100 or h < 100:
                continue
            null = False #有车时不会清空
            new = True  #是否创建车辆
            for i in cars:
                if  abs(cx-i.c_x) <100 and abs(cy - i.c_y)<100:#找到这辆车与上一帧中最近的车
                    # cv.putText(frame, 'cid:' + str(i.c_id), (x, y-10), cv.FONT_HERSHEY_COMPLEX, 1, (255, 0, 0), 2)
                    # cv.putText(frame, "now_v:" + str('%.4f' % (i.c_v)) + 'm/s', (x, y-10),
                    #            cv.FONT_HERSHEY_COMPLEX, 1,
                    #            (255, 0, 0), 2)
                    new = False
                    i.updateCoords(cx,cy)#更新车辆位置信息
                    if 700<=i.c_y<=720 and i.direction =='down' and i.c_count ==False:
                        i.c_over_time = time.time()
                        i.c_v = distance/(i.c_over_time - i.c_start_time)
                        if i.c_v>max_v:
                            max_v = i.c_v
                        count_down+=1
                        i.c_count=True
                    if 380<=i.c_y<=400 and i.direction =='up' and (i.c_count == False):
                        i.c_over_time = time.time()
                        i.c_v = distance/ (i.c_over_time - i.c_start_time)
                        if i.c_v > max_v:
                            max_v = i.c_v
                        count_up+=1
                        i.c_count=True
                if i.c_y>720 or i.c_y<380:
                    cars.remove(i) #超过一定范围,删除对象

            if new == True and 340<cy<760: #符合一定条件,创建对象
                start_time = time.time()
                p = Car(pid, cx, cy, 'unknow', False, start_time ,0,0)
                if p.c_x <1000:
                    p.direction = 'down'
                else:
                    p.direction = 'up'
                cars.append(p)
                pid += 1
            cv.circle(frame, (cx, cy), 5, (0, 0, 255), -1)
            cv.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 3)
    if null == True:    #该帧没车,清空cars
        cars = []
    # cv.putText(frame, 'cars:' + str(len(cars)), (100, 100), cv.FONT_HERSHEY_COMPLEX, 2, (255, 0, 0), 3)
    cv.imshow("frame",frame)
    out.write(frame)
    #cv.imshow('frame2', image3)
    # print(max_v*3.6)
    k = cv.waitKey(30) & 0xff
    if k == 27:
        break
vc.release()
cv.destroyAllWindows()

6.联系方式

有问题欢迎联系我:
视频删掉了,好像是从优酷还是腾讯视频找的吧。
1759412770@qq.com
zn1759412770@163.com

  • 74
    点赞
  • 432
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 59
    评论
实现图片的车牌检测,可以使用PythonOpenCV库。 首先,需要加载图片并将其转换为灰度图像: ```python import cv2 # 加载图片 img = cv2.imread('car.jpg') # 将图片转换为灰度图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ``` 接下来,可以使用Haar级联分类器来检测车牌。Haar级联分类器是一种基于特征的对象检测方法,可以通过训练来检测特定的对象。OpenCV提供了一些预训练的Haar级联分类器,包括用于车牌检测的分类器。 ```python # 加载车牌分类器 plate_cascade = cv2.CascadeClassifier('haarcascade_license_plate.xml') # 检测车牌 plates = plate_cascade.detectMultiScale(gray, 1.3, 5) ``` 在这里,`detectMultiScale`方法将返回一个矩形列表,表示检测到的车牌位置和大小。可以使用`rectangle`方法将这些矩形绘制在原始图像上: ```python # 绘制检测到的车牌 for (x, y, w, h) in plates: cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2) # 显示结果 cv2.imshow('License Plate Detection', img) cv2.waitKey(0) ``` 最终的代码如下: ```python import cv2 # 加载图片 img = cv2.imread('car.jpg') # 将图片转换为灰度图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 加载车牌分类器 plate_cascade = cv2.CascadeClassifier('haarcascade_license_plate.xml') # 检测车牌 plates = plate_cascade.detectMultiScale(gray, 1.3, 5) # 绘制检测到的车牌 for (x, y, w, h) in plates: cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2) # 显示结果 cv2.imshow('License Plate Detection', img) cv2.waitKey(0) ``` 其,`haarcascade_license_plate.xml`是在OpenCV官网上下载的预训练的车牌分类器。
评论 59
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

锌a

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值