1、智能视频监控系统项目背景介绍
2、系统的需求分析
在多摄像机视频监控系统中,通过前端结构化信息提取设备获取单个摄像机的结构化信息以后,需要将数据传输到云端服务器当中进行统一的存储和管理,从而为再识别等服务提供可靠的数据来源,满足更加丰富的用户需求。
本章将基于云平台的多摄像机监控系统的总体需求分成三个部分:存储功能、业务处理功能以及前端查询和显示功能。在存储功能方面,主要是对摄像机中提取出的结构化信息以及各个摄像机的信息进行存储;在业务处理功能方面,主要是实现行人图片的再识别算法,以及结合摄像机的拓扑关系的多摄像机的行人再识别算法,能够给用户提供接口,实时的调用并处理用户的需求;最后是前端的显示和查询功能,对用户进行的单摄像机图像信息查询能够及时的显示到前端,主要包括多用户管理功能。
3、系统的整体设计
3.1 系统的整体架构
整个系统架构按照物理结构可分成四个大的部分:分布式边缘计算节点、Web服务器、流媒体服务器以及网页客户端。
- 边缘计算节点:每个节点处理1~4路视频数据,实现对监控视频的结构化信息提取。
- Web服务器:负责整个系统的资源调度,用于保存和处理计算节点上传过来的数据,提供各种API接口供外部设备访问;
- 流媒体服务器:负责对监控视频内容进行采集、缓存调度和传输,以流媒体协议(RTMP/RTSP)将多路摄像机网络中的视频数据传输到客户端以及Web服务器端进行显示
- 客户端:前端UI界面展示,实现一些人际交互操作,用户直接通过浏览器访问指定网址即可。
3.2 前端计算节点
在前端对视频进行目标结构化处理,提取出行人或车辆矩形框位置以及图片,然后将该目标信息以JSON格式传送到云端服务器的数据库当中;每个节点可分为五个模块:视频读取模块、目标检测模块、目标跟踪与结构化信息提取模块、数据上传模块与嫌疑目标检测模块,其具体的如下图所示:
- 视频读取模块:读取实时视频数据并缓存在一个队列当中,队列缓存满了就删除最早进来的视频帧,目标检测模块每次从队列顶部抽取最新的一帧进行处理;
- 目标检测模块:这里采用yolov5实现对视频的实时检测,输入图片帧,输出目标检测框以及对应的类别信息,这里主要检测行人部分;
- 目标跟踪与结构化信息提取模块:主要对目标检测模块所检测到的目标进行跟踪,并将跟踪的结果提取其结构化信息,生成json文件、jpg文件以及avi文件;
- 结构化信息上传模块:主要通过http的POST请求,将结构化信息文件通过API传送到服务器的数据库中保存起来
- 嫌疑人检测模块:获取服务器发布的嫌疑人图片,对每次进入摄像机中的新目标进行查询,如果是嫌疑人则将其跟踪结果重点标注,传输到服务其当中。
3.3 后端服务器
后端服务器主要分成三个部分:计算节点信息处理模块、任务处理模块、任务发布模块。
- 计算节点信息处理模块: 主要是保存各个计算节点发送过来的结构化视频数据;
- 任务处理模块: 主要处理客户端发送过来的请求,包括行人再识别检索、行人结构化信息查询、行人跟踪等功能;
- 任务发布模块: 主要实现对各个计算节点任务的发布处理,包括目标检测模块、跟踪模块的训练权重,嫌疑人跟踪信息的发布等;
本系统中,后台的web服务器主要采用Ubuntu16.04+Nginx+uWSGI+Django2.2进行搭建和部署。
3.4 网页客户端
网页客户端采用HTML5+CSS+Javascript来编写,总共有5个界面:登录界面、主界面、行人检索界面、以及系统配置界面。
- 登录界面:主要负责用户管理,对注册的用户才有权限进入不同的界面;
- 主界面:主要负责监控信息的展示,结构化后行人与车辆的统计信息,等基础信息,这些并不需要算法进行结构化信息的加工处理;
- 行人检索界面:主要负责行人的查询、行人的离线跟踪,以及嫌疑人信息分发布;
- 系统配置界面:主要负责设置一些系统参数,包括摄像机IP信息、摄像机编号、前端计算节点参数权重等信息的发布。
4、系统算法原理介绍
4.1 目标检测算法
本文使用的是yolov5,yolov5能够部署到cpu计算节点上实现视频中行人目标信息的实时提取
4.2 目标跟踪关联算法
采用卡尔曼滤波方式进行行人的跟踪,这里将卡尔曼滤波和行人的表观特征相结合进行行人目标的关联
4.3 行人再识别算法
主要包括行人特征的提取以及特征的距离度量。在终端提取行人的特征,上传到服务器当中实现行人的检索。
在这里主要使用两种行人再识别算法:
- 用传统的方式LOMO+XQDA的方法提取行人特征,这种方法主要用于服务器端,其特点是使用CPU也能够快速的获得再识别结果;
LOMO+XQDA官方地址: http://www.cbsr.ia.ac.cn/users/scliao/projects/lomo_xqda/
LOMO+XQDA python实现:
https://github.com/muggledy/lomo-xqda - 采用深度学习的方法,如Resnet50、Mobilev3等网络,其特点是需要对网络进行训练,并且需要采用GPU进行特征提取,其可以放在前端计算节点上进行,获取更深层次的行人特征。
4.4 摄像机网络拓扑生成
发布跟踪任务,在各个摄像机之间实现嫌疑人的持续跟踪
4.5 跨摄像机行人跟踪算法
附录1:服务器代码
5.1 数据库的设计models.py
5.1.1用户信息表
字段 | 字段名称 | 字段类型 | 备注 |
---|---|---|---|
Username | 用户名 | char(128) | |
Password | 密码 | char(128) | |
邮件 | EmailField | ||
PhoneNumber | 电话 | Int |
5.1.2 摄像机信息表
字段 | 字段名称 | 字段类型 | 备注 |
---|---|---|---|
ip | 摄像机IP | GenericIPAddressField() | |
camera_name | char(64) | ||
Position | 摄像机位置 | char(40) | |
RelateCam | 相邻摄像机 | char(10) | |
width | 摄像机分辨率宽 | Int | |
height | 摄像机分辨率高 | Int | |
fps | 摄像机帧数 | Int |
5.1.3 行人结构化信息表
字段 | 字段类型 | 是否为空 | 是否唯一 | 备注 |
---|---|---|---|---|
cameraID | char(20) | 行人所在的摄像机ID | ||
cameraIP | char(20) | 行人所在的摄像机IP | ||
person_id | char(20) | 在摄像机cameraID中行人的编号 | ||
classes | CharField | 行人的类别 | ||
image | ImageField | 行人的关键帧图片 | ||
created | DateTimeField | 行人信息上传的时间 | ||
traceX | char(4096) | 行人X轴的轨迹信息 | ||
traceY | char(4096) | 行人Y轴的轨迹信息 | ||
EnterTime | char(100)) | 行人进入摄像机的时间 | ||
duration | Int | 行人在摄像机中持续的时间 | ||
video | FileField | 行人的视频序列 |
from django.db import models
class User(models.Model):
Username = models.CharField(max_length=128, unique=True)
password = models.CharField(max_length=128)
email = models.EmailField(unique=True)
PhoneNumber = models.IntegerField(blank=None)
class Meta:
ordering = ('Username',)
def __str__(self):
return self.Username
class Camera(models.Model):
ip = models.GenericIPAddressField(protocol='both', unpack_ipv4=True)
position = models.CharField(max_length=40)
relateCam = models.CharField(max_length=10, blank=True, null=True)
def __str__(self):
return self.ip
class Person(models.Model):
person_id = models.CharField(max_length=20, blank=True, null=True)
cameraID = models.ForeignKey('Camera', blank=True, null=True)
cameraIP = models.CharField(max_length=20, blank=True, null=True)
image = models.ImageField(upload_to="personImage/%Y/%m/%d/%H", blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
traceX = models.CharField(unique=False, blank=True, null=True, max_length=4096)
traceY = models.CharField(unique=False, blank=True, null=True, max_length=4096)
duration = models.IntegerField(unique=False, default=0, blank=True, null=False)
video = models.FileField(upload_to="personVideos/%Y/%m/%d/%H", blank=True, null=True)
classes = models.CharField(max_length=20, blank=True, null=True)
class Meta:
ordering = ('created',)
def __str__(self):
return self.person_id
5.2 序列化serializer.py
from rest_framework import serializers
from polls.models import *
class personSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
class cameraSerializer(serializers.ModelSerializer):
class Meta:
model = Camera
fields = '__all__'
class userSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
5.3 路由设计urls.py
from . import views
from django.conf.urls import url, include, static
urlpatterns = [
url(r'^person/$', views.person_list),
url(r'^person/(?P<pk>[0-9]+)$', views.person_detail),
url(r'^camera/$', views.camera_list),
url(r'^camera/(?P<pk>[0-9]+)$', views.camera_detail),
url(r'^findperson/$', views.find_person),
]
5.4 视图的设计views.py
@api_view(['GET','POST'])
def camera_list(req):
if req.method == 'GET':
camera = Camera.objects.all()
serializer = cameraSerializer(camera, many=True)
return Response(serializer.data)
elif req.method == 'POST':
serializer = cameraSerializer(data=req.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['GET','PUT','DELETE'])
def camera_detail(req, pk):
try:
camera = Camera.objects.get(pk=pk)
except Camera.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if req.method == 'GET':
serializer = cameraSerializer(camera)
return Response(serializer.data)
elif req.method == 'PUT':
serializer = cameraSerializer(camera, data=req.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif req.method == 'DELETE':
camera.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@api_view(['GET','POST'])
def person_list(req):
if req.method == 'GET':
persons = Person.objects.all()
person_serializers = personSerializer(persons, many=True)
return Response(person_serializers.data)
elif req.method == 'POST':
person_serializers = personSerializer(data=req.data)
if person_serializers.is_valid():
person_serializers.save()
Person.objects.filter(id=person_serializers.data["id"]).update(person_id=person_serializers.data["id"])
if "cameraIP" in req.data:
Person.objects.filter(id=person_serializers.da