文章目錄
- 一.前言
- 二.效果預覽
- 1.實時識別
- 2.ROI
- 3.數據導出
- 三.相關技術與實現
- 1.目標識別與檢測
- 2.可視化展示
- 3.如何設置推流環境
- 4.如何實現的車牌和顏色識別
- 5.項目結構
- 四.總結
本系統支持黃牌、藍牌、綠牌、黑牌、白牌,支持雙層車牌,歡迎了解!
PyQt5車牌識別可視化系統
一.前言
這是我使用PyQt5開發的車牌識別系統,主要采用了深度學習對車牌以及車牌顏色進行識別
系統采用分析RTMP視頻流的方式對視頻內畫面進行分析,提取出車牌以及車牌顏色
模型采用通用數據集進行的訓練,模型文件拓展名為.pth
識別車牌類型包括:黃牌、藍牌、綠牌、黑牌、白牌,支持雙層車牌、警牌、民航、學牌…
采用多種方案對數據進行可視化展示:折線圖、餅圖、條形圖、熱力圖、表格
支持ROI區域設置:矩形、圓形、多邊形,另外支持清除ROI
支持拉流控制:開始識別、停止識別、設置視頻流
支持識別結果詳細數據導出、支持CSV、Excel、TXT
二.效果預覽
軟件啟動后進入主界面主界面包括三個區域分別是:
左側信息區域:展示了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] += 1heatmap_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_labelsself.y_labels = conf_labelsself.z_values = [v[2] for v in heatmap_data]return heatmap_datadef 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) + 1self.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_datadef 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 optiondef 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_dataself.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區域讓用戶操作更加個性化!