@


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

请添加图片描述

[video(video-mnga3hTa-1749038658493)(type-bilibili)(url-https://player.bilibili.com/player.html?aid=114613433664878)(image-https://i-blog.csdnimg.cn/img_convert/2b5ed52c1f551a0e34b016cda2743b9e.jpeg#pic_center)(title-PyQt5车牌识别可视化系统)]

一.前言

这是我使用PyQt5开发的车牌识别系统,主要采用了深度学习对车牌以及车牌颜色进行识别
系统采用分析RTMP视频流的方式对视频内画面进行分析,提取出车牌以及车牌颜色
模型采用通用数据集进行的训练,模型文件拓展名为.pth
识别车牌类型包括:黄牌、蓝牌、绿牌、黑牌、白牌,支持双层车牌、警牌、民航、学牌...
采用多种方案对数据进行可视化展示:折线图、饼图、条形图、热力图、表格
支持ROI区域设置:矩形、圆形、多边形,另外支持清除ROI
支持拉流控制:开始识别、停止识别、设置视频流
支持识别结果详细数据导出、支持CSV、Excel、TXT

分类 功能模块 功能说明
系统架构与核心技术 开发框架 使用 PyQt5 开发
视频输入 分析 RTMP 视频流,对视频画面进行实时处理
识别模型 使用深度学习训练的模型(.pth 格式),基于通用数据集
识别能力 支持车牌类型 黄牌、蓝牌、绿牌、黑牌、白牌
支持双层车牌、警牌、民航、学牌等
车牌颜色识别 同时识别车牌颜色
可视化展示 图表类型 支持折线图、饼图、条形图、热力图、表格
交互功能 ROI 区域设置 支持矩形、圆形、多边形 ROI 设置,支持清除 ROI
拉流控制 包括开始识别、停止识别、设置视频流地址
数据导出与管理 导出功能 支持识别结果导出为 CSV、Excel、TXT 格式

二.效果预览

软件启动后进入主界面主界面包括三个区域分别是:
左侧信息区域:展示了CPU内存利用率折线图、车牌颜色分布饼图、我们的系统支持黄牌、蓝牌、绿牌、黑牌、白牌,最底部是置信度分布条形图,对于颜色、识别置信度使用条形图展示各个区间的数据
中间区域:上面是实时画面显示,画面是数据处理过的,已经标注车牌以及颜色,使用红色四角框标注车牌区域,使用红色白底文字展示车牌号以及颜色,底部是当前识别到目标的详细数据,具体包括:颜色、识别置信度、车牌号、车牌颜色、车牌图片、车牌类别、车牌所在区域矩形、以及区域高度,
右侧区域:顶部是识别能力热力图,此热力图展示了识别置信度与区域高度的热力图,用于评估当前的识别能力,中间是车牌首字符分布条形图,我们采用不同颜色的条形展示具体车牌的数量分布,底部为实时日志,实时日志展示了日志的输出时间以及识别到的车牌数量还有具体车牌、颜色数据。

区域 功能模块 展示内容/功能说明
左侧区域 CPU/内存利用率折线图 实时展示系统资源使用情况
车牌颜色分布饼图 支持颜色:黄牌、蓝牌、绿牌、黑牌、白牌
识别置信度分布条形图 使用条形图展示不同置信度区间的数据
车牌颜色分布条形图 使用条形图展示各颜色车牌的数量分布
中间区域 实时画面显示 已处理数据,标注车牌区域(红色四角框),文字标注车牌号+颜色(红字白底)
当前识别目标数据 包括以下详细信息:
- 颜色
- 识别置信度
- 车牌号
- 车牌颜色
- 车牌图片
- 车牌类别
- 车牌所在区域矩形
- 区域高度
右侧区域 识别能力热力图 展示识别置信度与区域高度的热力图,用于评估识别能力
车牌首字符分布条形图 不同颜色条形表示不同首字符的车牌数量
实时日志 展示输出时间、识别到的车牌数量及其详细数据(车牌、颜色)

1.实时识别

软件启动后会自动拉取目标视频画面,对画面中的内容进行实时逐帧分析,最后将分析后的结果展示到主屏中间区域,我们采用了多进程的方式提升了识别+处理数据效率,采用队列的方式处理不同进程中的数据。
请添加图片描述

2.ROI

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

3.数据导出

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

请添加图片描述

在这里插入图片描述
数据导出流程
在这里插入图片描述

三.相关技术与实现

1.目标识别与检测

本系统使用YOLOv8对目标进行识别与检测

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

在这里插入图片描述
本次使用YOLOv8版本对车辆和行人进行识别,车辆的类型包括:小汽车、摩托车、自行车、卡车,行人就是马路上行走的人,系统采用队列对读取到的视频流帧进行处理,标注分析好之后使用信号的方式发射给PyQt5的前端,前端设置槽函数接收、处理、展示数据。

2.可视化展示

本系统使用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)

3.如何设置推流环境

环境问题老生常谈了,直接参考我的这篇博客:
日常--OBS+mediamtx实现本地RTMP推流环境搭建(详细图文)

4.如何实现的车牌和颜色识别

集成了一个车牌检测、字符识别与图像可视化的完整车牌识别系统,基于 PyTorch 和 OpenCV 构建,支持使用 YOLO 系列模型进行车牌定位,并结合自定义字符识别模型完成车牌号码和颜色的精准识别。系统内置了图像预处理、非极大值抑制(NMS)、双层车牌分割与拼接、合法性校验等功能,能够有效识别各种类型的单行或双层车牌,并输出包括车牌号、颜色、类型、置信度、定位框坐标及 base64 编码图像在内的结构化结果。支持通过设置 ROI 区域提升识别效率,并提供可视化接口在原图上绘制检测结果,包含直角框、文字标签与中文字体渲染。

5.项目结构

粉丝私聊我,担心项目跑不起来,这里统一回答:一定能跑起来,代码没问题的,大家要配置好环境
我们的项目结构清晰,可拓展性强,使用包、类名做了区分,所有核心代码在src/目录下。
大家有任何疑问可以加我好友,博客最底部有二维码的~

在这里插入图片描述

四.总结

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

posted on 2025-07-07 19:47  懷淰メ  阅读(26)  评论(0)    收藏  举报