
@toc
本系统支持黄牌、蓝牌、绿牌、黑牌、白牌,支持双层车牌,欢迎了解!

B站视频演示地址:
https://www.bilibili.com/video/BV1MW7DzDELo/?spm_id_from=333.1387.upload.video_card.click
这是我使用PyQt5开发的车牌识别系统,主要采用了深度学习对车牌以及车牌颜色进行识别
系统采用分析RTMP视频流的方式对视频内画面进行分析,提取出车牌以及车牌颜色
模型采用通用数据集进行的训练,模型文件拓展名为.pth
识别车牌类型包括:黄牌、蓝牌、绿牌、黑牌、白牌,支持双层车牌、警牌、民航、学牌...
采用多种方案对数据进行可视化展示:折线图、饼图、条形图、热力图、表格
支持ROI区域设置:矩形、圆形、多边形,另外支持清除ROI
支持拉流控制:开始识别、停止识别、设置视频流
支持识别结果详细数据导出、支持CSV、Excel、TXT
分类 | 功能模块 | 功能说明 |
|---|---|---|
系统架构与核心技术 | 开发框架 | 使用 PyQt5 开发 |
视频输入 | 分析 RTMP 视频流,对视频画面进行实时处理 | |
识别模型 | 使用深度学习训练的模型(.pth 格式),基于通用数据集 | |
识别能力 | 支持车牌类型 | 黄牌、蓝牌、绿牌、黑牌、白牌 支持双层车牌、警牌、民航、学牌等 |
车牌颜色识别 | 同时识别车牌颜色 | |
可视化展示 | 图表类型 | 支持折线图、饼图、条形图、热力图、表格 |
交互功能 | ROI 区域设置 | 支持矩形、圆形、多边形 ROI 设置,支持清除 ROI |
拉流控制 | 包括开始识别、停止识别、设置视频流地址 | |
数据导出与管理 | 导出功能 | 支持识别结果导出为 CSV、Excel、TXT 格式 |
软件启动后进入主界面主界面包括三个区域分别是:
左侧信息区域:展示了CPU内存利用率折线图、车牌颜色分布饼图、我们的系统支持黄牌、蓝牌、绿牌、黑牌、白牌,最底部是置信度分布条形图,对于颜色、识别置信度使用条形图展示各个区间的数据
中间区域:上面是实时画面显示,画面是数据处理过的,已经标注车牌以及颜色,使用红色四角框标注车牌区域,使用红色白底文字展示车牌号以及颜色,底部是当前识别到目标的详细数据,具体包括:颜色、识别置信度、车牌号、车牌颜色、车牌图片、车牌类别、车牌所在区域矩形、以及区域高度,
右侧区域:顶部是识别能力热力图,此热力图展示了识别置信度与区域高度的热力图,用于评估当前的识别能力,中间是车牌首字符分布条形图,我们采用不同颜色的条形展示具体车牌的数量分布,底部为实时日志,实时日志展示了日志的输出时间以及识别到的车牌数量还有具体车牌、颜色数据。
区域 | 功能模块 | 展示内容/功能说明 |
|---|---|---|
左侧区域 | CPU/内存利用率折线图 | 实时展示系统资源使用情况 |
车牌颜色分布饼图 | 支持颜色:黄牌、蓝牌、绿牌、黑牌、白牌 | |
识别置信度分布条形图 | 使用条形图展示不同置信度区间的数据 | |
车牌颜色分布条形图 | 使用条形图展示各颜色车牌的数量分布 | |
中间区域 | 实时画面显示 | 已处理数据,标注车牌区域(红色四角框),文字标注车牌号+颜色(红字白底) |
当前识别目标数据 | 包括以下详细信息: - 颜色 - 识别置信度 - 车牌号 - 车牌颜色 - 车牌图片 - 车牌类别 - 车牌所在区域矩形 - 区域高度 | |
右侧区域 | 识别能力热力图 | 展示识别置信度与区域高度的热力图,用于评估识别能力 |
车牌首字符分布条形图 | 不同颜色条形表示不同首字符的车牌数量 | |
实时日志 | 展示输出时间、识别到的车牌数量及其详细数据(车牌、颜色) |
软件启动后会自动拉取目标视频画面,对画面中的内容进行实时逐帧分析,最后将分析后的结果展示到主屏中间区域,我们采用了多进程的方式提升了识别+处理数据效率,采用队列的方式处理不同进程中的数据。

本系统支持设置ROI(感兴趣区域),用户可以在画面上右击鼠标后展示菜单,菜单中选择ROI的类型,具体的类型支持:矩形、圆形、多边形,多边形灵活度高,另外可以对标注的ROI进行清除操作,ROI的操作大大增加了用户与本系统的交互。

用户可以在界面(非画面区域)右击鼠标,选择导出数据的类型:CSV、Excel、TXT都是支持的,具体数据我下面截图展示。
用户选了对应的文件类型之后,系统会让用户选择导出文件位置、设置导出文件名,软件会自动选择桌面为默认位置,缩短了用户操作路径,用户可以选择导出或者取消,如果选择了导出,系统会在指定位置创建数据文件,并且询问用户“是否打开文件”,用户选择打开后,会调用系统打开方式打开文件,展示文件内容。


数据导出流程

本系统使用YOLOv8对目标进行识别与检测
YOLOv8是Ultralytics公司推出的最新一代实时目标检测算法,基于YOLO(You Only Look Once)系列架构改进,具有更高的检测精度和更快的推理速度。它支持目标检测、实例分割和图像分类任务,采用灵活的Backbone和Neck设计,并优化了训练策略与损失函数,兼容多种部署环境(如ONNX、TensorRT等),适合工业级应用。YOLOv8提供多种预训练模型(从轻量级YOLOv8n到高性能YOLOv8x),平衡速度与精度,是计算机视觉领域的先进工具之一。

本次使用YOLOv8版本对车辆和行人进行识别,车辆的类型包括:小汽车、摩托车、自行车、卡车,行人就是马路上行走的人,系统采用队列对读取到的视频流帧进行处理,标注分析好之后使用信号的方式发射给PyQt5的前端,前端设置槽函数接收、处理、展示数据。
本系统使用pyecharts对上游数据进行可视化展示
Pyecharts 是基于 Python 的数据可视化库,依托强大的 ECharts(百度开源 JavaScript 图表库)构建,提供丰富的交互式图表类型(如折线图、柱状图、散点图、地图等)。它支持链式调用和简洁的 API 设计,可轻松生成动态、可缩放的可视化结果,并兼容 Jupyter Notebook、Web 页面及 Flask/Django 等框架。Pyecharts 支持多种数据格式(如 Pandas、NumPy),允许自定义样式和主题,适用于数据分析、商业报表和实时大屏展示,是 Python 生态中高效、美观的可视化工具之一。
图片取自网络,仅用于echarts图效果展示,不包含在本系统中。

我们采用了组件化的开发思想,每个图标组件都是能够单独调试运行的,下面是个热力图组件,

大家通过导入指定的包和库,就能执行起来此代码:
class HeatmapBridge(QObject):
updateSignal = pyqtSignal(str)
def __init__(self, get_option_callback):
super().__init__()
self._get_option = get_option_callback
@pyqtSlot(result=str)
def getOption(self):
return json.dumps(self._get_option())
class HeatmapWidget(QWidget):
def __init__(self, p=None, mode='height_conf'):
super().__init__(p)
self.mode = mode # 'height_conf' or 'province'
self.data = []
self.ui_init()
def ui_init(self):
self.heatmap_data = self.prepare_heatmap_data()
profile = QWebEngineProfile(f"profile_{id(self)}", self)
page = NoRightClickWebEnginePage(profile)
self.view = NoContextMenuWebEngineView()
self.view.setPage(page)
self.view.setAttribute(Qt.WA_TranslucentBackground, True)
self.view.setStyleSheet("background: transparent;")
self.view.page().setBackgroundColor(Qt.transparent)
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.view)
self.setLayout(layout)
self.bridge = HeatmapBridge(self.get_echarts_option)
self.channel = QWebChannel()
self.channel.registerObject("bridge", self.bridge)
self.view.page().setWebChannel(self.channel)
self.init_html()
def prepare_heatmap_data(self):
if self.mode == 'height_conf':
return self.prepare_by_height_conf()
elif self.mode == 'province':
return self.prepare_by_province()
else:
return []
def prepare_by_height_conf(self):
height_bins = [(0, 10), (10, 20), (20, 30), (30, 40), (40, 50), (50, 60)]
conf_bins = [(0.0, 0.2), (0.2, 0.4), (0.4, 0.6), (0.6, 0.8), (0.8, 1.0)]
height_labels = [f"{lo}–{hi}" for lo, hi in height_bins]
conf_labels = [f"{lo:.1f}–{hi:.1f}" for lo, hi in conf_bins]
count_matrix = [[0 for _ in height_bins] for _ in conf_bins]
for item in self.data:
height = item.get('roi_height')
conf = item.get('detect_conf')
h_idx = next((i for i, (lo, hi) in enumerate(height_bins) if lo <= height < hi), None)
c_idx = next((i for i, (lo, hi) in enumerate(conf_bins) if lo <= conf < hi), None)
if h_idx is not None and c_idx is not None:
count_matrix[c_idx][h_idx] += 1
heatmap_data = []
for i, conf_label in enumerate(conf_labels):
for j, height_label in enumerate(height_labels):
heatmap_data.append([height_label, conf_label, count_matrix[i][j]])
self.x_labels = height_labels
self.y_labels = conf_labels
self.z_values = [v[2] for v in heatmap_data]
return heatmap_data
def prepare_by_province(self):
province_count = {}
for item in self.data:
plate_no = item.get('plate_no', '')
if plate_no:
province = plate_no[0]
province_count[province] = province_count.get(province, 0) + 1
self.x_labels = list(province_count.keys())
self.y_labels = ["数量"]
self.z_values = list(province_count.values())
heatmap_data = [[prov, "数量", cnt] for prov, cnt in province_count.items()]
return heatmap_data
def get_echarts_option(self):
option = {
"tooltip": {"position": "top"},
"grid": {
"height": "60%",
"top": "18%",
"left": "16%",
"right": "10%"
},
"xAxis": {
"type": "category",
"data": self.x_labels,
"axisLabel": {"color": "#00FFE3"},
"axisLine": {"lineStyle": {"color": "#00FFE3"}},
},
"yAxis": {
"type": "category",
"data": self.y_labels,
"axisLabel": {"color": "#00FFE3"},
"axisLine": {"lineStyle": {"color": "#00FFE3"}},
},
"visualMap": {
"min": 0,
"max": max(self.z_values) if self.z_values else 1,
"calculable": True,
"orient": "horizontal",
"left": "center",
"bottom": "2%",
"inRange": {"color": ["#001f3f", "#0074D9", "#00FFFF"]}
},
"series": [{
"type": "heatmap",
"data": self.heatmap_data,
"label": {"show": True, "color": "#fff"},
"emphasis": {
"itemStyle": {
"shadowBlur": 10,
"shadowColor": "rgba(0,0,0,0.5)"
}
}
}]
}
return option
def init_html(self):
html = """
<html>
<head>
<meta charset="UTF-8">
<script src="https://cdn.jsdelivr.net/npm/echarts@5"></script>
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
<style>
html, body, #container {
margin: 0; padding: 0;
width: 100%; height: 100%;
background: transparent; overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var chart;
new QWebChannel(qt.webChannelTransport, function(channel) {
window.bridge = channel.objects.bridge;
chart = echarts.init(document.getElementById('container'), null, {
backgroundColor: 'transparent'
});
bridge.getOption().then(function(optionStr) {
let option = JSON.parse(optionStr);
chart.setOption(option);
});
bridge.updateSignal.connect(function(optionStr) {
let option = JSON.parse(optionStr);
chart.setOption(option, true);
});
window.addEventListener('resize', function () {
if (chart) chart.resize();
});
});
</script>
</body>
</html>
"""
self.view.setHtml(html)
def update_data(self, new_data):
self.data = new_data
self.heatmap_data = self.prepare_heatmap_data()
new_option_json = json.dumps(self.get_echarts_option())
self.bridge.updateSignal.emit(new_option_json)环境问题老生常谈了,直接参考我的这篇博客:
[日常--OBS+mediamtx实现本地RTMP推流环境搭建(详细图文)
](https://blog.csdn.net/a1397852386/article/details/148295591?spm=1001.2014.3001.5502)
集成了一个车牌检测、字符识别与图像可视化的完整车牌识别系统,基于 PyTorch 和 OpenCV 构建,支持使用 YOLO 系列模型进行车牌定位,并结合自定义字符识别模型完成车牌号码和颜色的精准识别。系统内置了图像预处理、非极大值抑制(NMS)、双层车牌分割与拼接、合法性校验等功能,能够有效识别各种类型的单行或双层车牌,并输出包括车牌号、颜色、类型、置信度、定位框坐标及 base64 编码图像在内的结构化结果。支持通过设置 ROI 区域提升识别效率,并提供可视化接口在原图上绘制检测结果,包含直角框、文字标签与中文字体渲染。
粉丝私聊我,担心项目跑不起来,这里统一回答:一定能跑起来,代码没问题的,大家要配置好环境
我们的项目结构清晰,可拓展性强,使用包、类名做了区分,所有核心代码在src/目录下。
大家有任何疑问可以加我好友,博客最底部有二维码的~

在这个模块我们使用堆叠折线图展示CPU利用率和内存利用率变化,其中折线图的x轴是具体的时间点精确到秒,y轴是具体数据的占用百分比,当我们把鼠标移入到折线图时,会显示具体的数值,折线图支持与鼠标的动态交互,我们的UI效果真的不错!

这里我们将系统能识别的车牌颜色类型占用比使用环形饼图来展示,用户能够一眼就看出各类车牌颜色的占比,我们的系统支持六中车牌颜色的识别:蓝、绿、黄、白、黑,这些数据都是实时更新的,我们为饼图外围添加了发光的阴影,用户可以点击饼图右侧的图例隐藏指定的类别,当数据变化时,会展示饼图占比变化动画。

在这个模块中,我们将颜色置信度与识别置信度进行汇总,采用条形图的方式将两者统计起来,用户能够直观地看到系统模型识别的准确程度,具体来说是:置信度越高识别准确率越高,我们用两种直观的颜色展示了两种指标的置信度,分别是科技蓝以及科技绿,我们的鼠标可以和条形图进行交互,在这个图表中,用户仍然可以像操作饼图一样点击图例隐藏、查看指定的置信度分布数值。

这里我们使用热力图展示了我们系统的识别能力,具体来说是将X轴的识别数量以及Y轴的识别置信度使用热力图的方式统计起来,如果热力图右上角即识别置信度大、置信度大的数量较多,代表我们的识别能力越强,我们的热力图下方有数据筛选的调节器,用户可以通过按下筛选器选择数值区间筛选数据,也可以将鼠标放到识别能力颜色块上方查看具体的热力数值,我们使用了科技渐变蓝色显示了每种数值的变化。

在这个条形图中我们按照车牌首字符进行了统计,动态展示车牌首字符以及识别数量的变化,采用不同颜色来标识不同的车牌,每种颜色都是渐变色,渐变色从浅到深,每个数值采用浅蓝色标注,x轴车牌首字符采用rgb(255,255,255)颜色来展示,从这个条形图中,我们能直观看到每省车牌的统计数量。

在实时日志模块中,我们将实时的识别结果展示出来,展示的内容包括:识别到的车牌数量以及具体车牌号以及车牌颜色,识别数量使用红色强调色标出,车牌以及颜色使用浅蓝色标出。
日志展示的速度取决于具体识别能力,我们在这个模块展示句很详细的文字信息,这是一种比较基础的日志展示方式。

在本系统的右上角我们使用文字的方式展示了CPU以及内存的变化,这些指标1秒刷新一次,数据和折线图的数据是一致的,都是取自系统的真实数据。
在这个模块中还有个系统状态指示灯,我们在首次启动时,要进行拉流操作,系统默认是NG红灯状态,当拉流成功后,系统变成绿灯,信号变成优秀,这些都是系统的真实数据。

本区域宽度占系统宽度的三分之二,是我们的核心内容展示区域,在这个区域中系统会将实时的识别结果展示在这里,使用红色四角边框将车牌区域进行标注,然后使用白底红字显示车牌号码以及颜色,值得一提的是,我们的系统能将尽可能多的车牌进行识别,从图片中也能看出,系统识别能力还是比较强的。

这里我们使用表格的方式将具体识别数据展示出来,展示的内容包括不限于:颜色、检测置信度、车牌颜色、车牌图像、车牌号、车牌类型(支持单行和双层车牌)、车牌所在当前帧坐标位置,车牌区域的像素高度,这里的表格是支持垂直方向滚动的,图像是具体的base64数据取识别目标帧,这个表格的内容和上图的内容是对应的。

本次和大家分享了我使用PyQt5开发的车牌、车牌颜色识别可视化系统,能够准确的识别目标视频中的车牌和颜色,采用多种可视化方案对数据使用图表进行展示,支持导出为不同格式的数据到文件,特有的ROI区域让用户操作更加个性化!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。