import sys
import sqlite3
import cv2
import numpy as np
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QLabel, QLineEdit, QPushButton,
QGroupBox, QFormLayout, QMessageBox, QFileDialog,
QAction, QMenu, QTabWidget)
from PyQt5.QtGui import QPixmap, QFont, QImage
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QSize
import os
import glob
# 车牌识别相关函数
def getPlate(image):
rawImage = image.copy()
# 去噪处理
image = cv2.GaussianBlur(image, (3, 3), 0)
# 色彩空间转换(RGB-->GRAY)
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Sobel算子(X方向边缘梯度)
Sobel_x = cv2.Sobel(image, cv2.CV_16S, 1, 0)
absX = cv2.convertScaleAbs(Sobel_x) # 映射到[0.255]内
image = absX
# 阈值处理
ret, image = cv2.threshold(image, 0, 255, cv2.THRESH_OTSU)
# 闭运算:先膨胀后腐蚀,车牌各个字符是分散的,让车牌构成一体
kernelX = cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5))
image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernelX)
# 开运算:先腐蚀后膨胀,去除噪声
kernelY = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 19))
image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernelY)
# 中值滤波:去除噪声
image = cv2.medianBlur(image, 15)
# 查找轮廓
contours, w1 = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 逐个遍历,将宽度>3倍高度的轮廓确定为车牌
plate = None
for item in contours:
rect = cv2.boundingRect(item)
x = rect[0]
y = rect[1]
weight = rect[2]
height = rect[3]
if weight > (height * 3):
plate = rawImage[y:y + height, x:x + weight]
return plate
# ======================预处理函数,图像去噪等处理=================
def preprocess(image):
# 图像去噪灰度处理
image = cv2.GaussianBlur(image, (3, 3), 0)
# 色彩空间转换
gray_image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
# 阈值处理(二值化)
ret, image = cv2.threshold(gray_image, 0, 255, cv2.THRESH_OTSU)
# 膨胀处理,让一个字构成一个整体(大多数字不是一体的,是分散的)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
image = cv2.dilate(image, kernel)
return image
# ===========拆分车牌函数,将车牌内各个字符分离==================
def splitPlate(image):
# 查找轮廓,各个字符的轮廓
contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
words = []
# 遍历所有轮廓
for item in contours:
rect = cv2.boundingRect(item)
words.append(rect)
# 按照x轴坐标值排序(自左向右排序)
words = sorted(words, key=lambda s: s[0], reverse=False)
# 用word存放左上角起始点及长宽值
plateChars = []
for word in words:
# 筛选字符的轮廓(高宽比在1.5-8之间,宽度大于3)
if (word[3] > (word[2] * 1.5)) and (word[3] < (word[2] * 8)) and (word[2] > 3):
plateChar = image[word[1]:word[1] + word[3], word[0]:word[0] + word[2]]
plateChars.append(plateChar)
return plateChars
# =================模板,部分省份,使用字典表示==============================
templateDict = {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9',
10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F', 16: 'G', 17: 'H',
18: 'J', 19: 'K', 20: 'L', 21: 'M', 22: 'N', 23: 'P', 24: 'Q', 25: 'R',
26: 'S', 27: 'T', 28: 'U', 29: 'V', 30: 'W', 31: 'X', 32: 'Y', 33: 'Z',
34: '京', 35: '津', 36: '冀', 37: '晋', 38: '蒙', 39: '辽', 40: '吉', 41: '黑',
42: '沪', 43: '苏', 44: '浙', 45: '皖', 46: '闽', 47: '赣', 48: '鲁', 49: '豫',
50: '鄂', 51: '湘', 52: '粤', 53: '桂', 54: '琼', 55: '渝', 56: '川', 57: '贵',
58: '云', 59: '藏', 60: '陕', 61: '甘', 62: '青', 63: '宁', 64: '新',
65: '港', 66: '澳', 67: '台'}
# ==================获取所有字符的路径信息===================
def getcharacters():
c = []
for i in range(0, 67):
words = []
words.extend(glob.glob('template/' + templateDict.get(i) + '/*.*'))
c.append(words)
return c
# =============计算匹配值函数=====================
def getMatchValue(template, image):
try:
# 读取模板图像
templateImage = cv2.imdecode(np.fromfile(template, dtype=np.uint8), 1)
# 模板图像色彩空间转换,BGR-->灰度
templateImage = cv2.cvtColor(templateImage, cv2.COLOR_BGR2GRAY)
# 模板图像阈值处理, 灰度-->二值
ret, templateImage = cv2.threshold(templateImage, 0, 255, cv2.THRESH_OTSU)
# 获取待识别图像的尺寸
height, width = image.shape
# 将模板图像调整为与待识别图像尺寸一致
templateImage = cv2.resize(templateImage, (width, height))
# 计算模板图像、待识别图像的模板匹配值
result = cv2.matchTemplate(image, templateImage, cv2.TM_CCOEFF)
# 将计算结果返回
return result[0][0]
except Exception as e:
print(f"读取模板图像失败: {e}")
return 0
# ===========对车牌内字符进行识别====================
# plates,要识别的字符集,
# 也就是从车牌图像"GUA211"中分离出来的每一个字符的图像"G","U","A","2","1","1"
# chars,所有字符的模板集合,也就是0-9,A-Z,京-台,每一个字符模板
def matchChars(plates, chars):
results = [] # 存储所有的识别结果
# 最外层循环:逐个遍历要识别的字符。
# 例如,逐个遍历从车牌图像"GUA211"中分离出来的每一个字符的图像
# 如"G","U","A","2","1","1"
# plateChar分别存储,"G","U","A","2","1","1"
for plateChar in plates: # 逐个遍历要识别的字符
# bestMatch,存储的是待识别字符与每个特征字符的所有模板中最匹配的模板
# 例如,待识别图像"G",与所有的字符0-9,A-Z,京-台,每一个字符最匹配的模板
bestMatch = [] # 最佳匹配
# 中间层循环:针对模板内的字符,进行逐个遍历(每次循环针对一个特定的字符),
# words 对应的是每一个字符(例如字符A)的所有模板
for words in chars: # 遍历字符。chars:所有模板,words:某个字符的所有模板
# match,存储的是每个特征字符的所有匹配值
# 例如:待识别图像"G",与字符7的所有模板的匹配值
match = [] # 每个字符的匹配值
# 最内层循环:针对的是单个字符的所有模板,找到最佳的模板
# word对应的是单个模板
for word in words: # 遍历模板。words:某个字符所有模板,word单个模板
result = getMatchValue(word, plateChar)
match.append(result)
bestMatch.append(max(match)) # 将每个字符模板的最佳匹配加入bestMatch
i = bestMatch.index(max(bestMatch)) # i是最佳匹配的字符模板的索引值
r = templateDict[i] # r是单个待识别字符的识别结果
results.append(r) # 将每一个分割字符的识别结果加入到results内
return results # 返回所有的识别结果
class VehicleInfoApp(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
self.initDatabase()
self.camera = None
self.camera_thread = None
self.is_camera_running = False
self.current_video_path = None
self.video_timer = None
def initUI(self):
# 设置窗口标题和大小
self.setWindowTitle('车辆信息查询系统')
self.setGeometry(100, 100, 1200, 800)
# 创建菜单栏
menubar = self.menuBar()
file_menu = menubar.addMenu('')
# 添加退出动作
exit_action = QAction('退出', self)
exit_action.setShortcut('Ctrl+Q')
exit_action.setStatusTip('退出应用程序')
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# 创建中心部件和主布局
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QHBoxLayout(central_widget)
main_layout.setSpacing(20)
# 左侧:查询信息区域
left_widget = QWidget()
left_layout = QVBoxLayout(left_widget)
left_layout.setSpacing(20)
# 创建标题
title_label = QLabel('车辆信息查询')
title_label.setFont(QFont('SimHei', 24, QFont.Bold))
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet('color: #333; margin: 15px 0;')
left_layout.addWidget(title_label)
# 创建查询区域
query_group = QGroupBox('车牌查询')
query_group.setFont(QFont('SimHei', 16))
query_group.setStyleSheet('QGroupBox { border: 1px solid #aaa; border-radius: 5px; margin-top: 10px; }'
'QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; }')
query_layout = QVBoxLayout()
input_layout = QHBoxLayout()
query_label = QLabel('车牌号码:')
query_label.setFont(QFont('SimHei', 14))
self.query_input = QLineEdit()
self.query_input.setFont(QFont('SimHei', 14))
self.query_input.setPlaceholderText('请输入车牌号码')
self.query_input.setMinimumHeight(40)
self.query_button = QPushButton('查询')
self.query_button.setFont(QFont('SimHei', 14))
self.query_button.setFixedSize(120, 40)
self.query_button.setStyleSheet('''
QPushButton {
background-color: #4CAF50;
color: white;
border-radius: 5px;
}
QPushButton:hover {
background-color: #45a049;
}
''')
self.query_button.clicked.connect(self.queryInfo)
input_layout.addWidget(query_label)
input_layout.addWidget(self.query_input)
input_layout.addWidget(self.query_button)
input_layout.setSpacing(15)
query_layout.addLayout(input_layout)
# 图片显示区域
self.image_group = QGroupBox('车牌图片')
self.image_group.setFont(QFont('SimHei', 16))
self.image_group.setStyleSheet('QGroupBox { border: 1px solid #aaa; border-radius: 5px; margin-top: 10px; }'
'QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; }')
image_layout = QVBoxLayout()
self.plate_image = QLabel()
self.plate_image.setAlignment(Qt.AlignCenter)
self.plate_image.setMinimumSize(500, 400)
self.plate_image.setText('暂无图片')
self.plate_image.setFont(QFont('SimHei', 14))
self.plate_image.setStyleSheet('border: 1px solid #ddd; background-color: #f5f5f5;')
image_layout.addWidget(self.plate_image)
self.image_group.setLayout(image_layout)
query_layout.addWidget(self.image_group)
query_group.setLayout(query_layout)
left_layout.addWidget(query_group)
# 车辆信息区域
self.info_group = QGroupBox('车辆信息')
self.info_group.setFont(QFont('SimHei', 16))
self.info_group.setStyleSheet('QGroupBox { border: 1px solid #aaa; border-radius: 5px; margin-top: 10px; }'
'QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; }')
info_layout = QFormLayout()
info_layout.setSpacing(15)
# 创建信息标签
self.name_label = QLabel('')
self.age_label = QLabel('')
self.vehicle_model_label = QLabel('')
self.plate_number_label = QLabel('')
self.registration_date_label = QLabel('')
self.vehicle_color_label = QLabel('')
# 设置字体和样式
for label in [self.name_label, self.age_label, self.vehicle_model_label,
self.plate_number_label, self.registration_date_label,
self.vehicle_color_label]:
label.setFont(QFont('SimHei', 14))
label.setMinimumHeight(30)
# 添加到表单布局
info_layout.addRow(QLabel('车主姓名:', font=QFont('SimHei', 14, QFont.Bold)), self.name_label)
info_layout.addRow(QLabel('车主年龄:', font=QFont('SimHei', 14, QFont.Bold)), self.age_label)
info_layout.addRow(QLabel('车辆型号:', font=QFont('SimHei', 14, QFont.Bold)), self.vehicle_model_label)
info_layout.addRow(QLabel('车牌号码:', font=QFont('SimHei', 14, QFont.Bold)), self.plate_number_label)
info_layout.addRow(QLabel('注册日期:', font=QFont('SimHei', 14, QFont.Bold)), self.registration_date_label)
info_layout.addRow(QLabel('车辆颜色:', font=QFont('SimHei', 14, QFont.Bold)), self.vehicle_color_label)
self.info_group.setLayout(info_layout)
left_layout.addWidget(self.info_group)
# 添加到主布局
main_layout.addWidget(left_widget, 7)
# 右侧:视频/摄像头区域
right_widget = QWidget()
right_layout = QVBoxLayout(right_widget)
right_layout.setSpacing(20)
# 视频/摄像头显示区域
self.media_group = QGroupBox('视频/摄像头')
self.media_group.setFont(QFont('SimHei', 16))
self.media_group.setStyleSheet('QGroupBox { border: 1px solid #aaa; border-radius: 5px; margin-top: 10px; }'
'QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; }')
media_layout = QVBoxLayout()
self.media_display = QLabel()
self.media_display.setAlignment(Qt.AlignCenter)
self.media_display.setMinimumSize(350, 250)
self.media_display.setText('请选择媒体源')
self.media_display.setFont(QFont('SimHei', 14))
self.media_display.setStyleSheet('border: 1px solid #ddd; background-color: #f5f5f5;')
media_layout.addWidget(self.media_display)
# 媒体控制按钮
control_layout = QHBoxLayout()
self.import_image_button = QPushButton('导入图片')
self.import_image_button.setFont(QFont('SimHei', 12))
self.import_image_button.setFixedSize(100, 35)
self.import_image_button.setStyleSheet('''
QPushButton {
background-color: #2196F3;
color: white;
border-radius: 5px;
}
QPushButton:hover {
background-color: #0b7dda;
}
''')
self.import_image_button.clicked.connect(self.importImage)
self.import_video_button = QPushButton('导入视频')
self.import_video_button.setFont(QFont('SimHei', 12))
self.import_video_button.setFixedSize(100, 35)
self.import_video_button.setStyleSheet('''
QPushButton {
background-color: #9C27B0;
color: white;
border-radius: 5px;
}
QPushButton:hover {
background-color: #7B1FA2;
}
''')
self.import_video_button.clicked.connect(self.importVideo)
self.camera_button = QPushButton('打开摄像头')
self.camera_button.setFont(QFont('SimHei', 12))
self.camera_button.setFixedSize(100, 35)
self.camera_button.setStyleSheet('''
QPushButton {
background-color: #FF5722;
color: white;
border-radius: 5px;
}
QPushButton:hover {
background-color: #f4511e;
}
''')
self.camera_button.clicked.connect(self.toggleCamera)
control_layout.addWidget(self.import_image_button)
control_layout.addWidget(self.import_video_button)
control_layout.addWidget(self.camera_button)
control_layout.setAlignment(Qt.AlignCenter)
control_layout.setSpacing(15)
media_layout.addLayout(control_layout)
self.media_group.setLayout(media_layout)
right_layout.addWidget(self.media_group)
# 车牌识别结果区域
self.result_group = QGroupBox('识别结果')
self.result_group.setFont(QFont('SimHei', 16))
self.result_group.setStyleSheet('QGroupBox { border: 1px solid #aaa; border-radius: 5px; margin-top: 10px; }'
'QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; }')
result_layout = QVBoxLayout()
self.result_text = QLabel('未识别')
self.result_text.setAlignment(Qt.AlignCenter)
self.result_text.setFont(QFont('SimHei', 16, QFont.Bold))
self.result_text.setMinimumHeight(50)
self.result_text.setStyleSheet('border: 1px solid #ddd; background-color: #e8f5e9; padding: 10px;')
self.recognize_button = QPushButton('识别车牌')
self.recognize_button.setFont(QFont('SimHei', 14))
self.recognize_button.setFixedSize(150, 40)
self.recognize_button.setStyleSheet('''
QPushButton {
background-color: #FFC107;
color: #333;
border-radius: 5px;
}
QPushButton:hover {
background-color: #FFA000;
}
''')
self.recognize_button.clicked.connect(self.recognizePlate)
self.recognize_button.setEnabled(False)
result_layout.addWidget(self.result_text)
result_layout.addWidget(self.recognize_button, alignment=Qt.AlignCenter)
result_layout.setSpacing(15)
self.result_group.setLayout(result_layout)
right_layout.addWidget(self.result_group)
# 添加到主布局
main_layout.addWidget(right_widget, 3)
# 创建状态栏
self.statusBar().showMessage('就绪')
def initDatabase(self):
# 连接到SQLite数据库
try:
self.conn = sqlite3.connect('vehicle_info.db')
self.cursor = self.conn.cursor()
# 创建表(如果不存在)
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS vehicles (
id INTEGER PRIMARY KEY,
plate_number TEXT UNIQUE,
name TEXT,
age INTEGER,
vehicle_model TEXT,
registration_date TEXT,
vehicle_color TEXT,
image_path TEXT
)
''')
# 插入示例数据(如果表为空)
self.cursor.execute('SELECT COUNT(*) FROM vehicles')
count = self.cursor.fetchone()[0]
if count == 0:
sample_data = [
('豫U88888', '张三', 35, '大众迈腾', '2020-01-15', '黑色', 'car1.jpg'),
('沪B67890', '李四', 28, '宝马3系', '2021-05-20', '白色', 'car2.jpg'),
('粤A54321', '王五', 42, '奔驰C级', '2019-10-10', '银色', 'car3.jpg'),
('津GUA211','雄安',33,'福克斯','2019-03-27','银灰色'),
]
self.cursor.executemany(
'INSERT INTO vehicles (plate_number, name, age, vehicle_model, registration_date, vehicle_color, image_path) VALUES (?, ?, ?, ?, ?, ?, ?)',
sample_data
)
self.conn.commit()
except sqlite3.Error as e:
QMessageBox.critical(self, '数据库错误', f'连接数据库失败: {str(e)}')
sys.exit(1)
def queryInfo(self):
plate_number = self.query_input.text().strip()
if not plate_number:
QMessageBox.warning(self, '输入错误', '请输入车牌号码')
return
try:
# 查询数据库
self.cursor.execute(
'SELECT name, age, vehicle_model, plate_number, registration_date, vehicle_color, image_path '
'FROM vehicles WHERE plate_number = ?',
(plate_number,)
)
result = self.cursor.fetchone()
if result:
# 更新信息标签
self.name_label.setText(result[0])
self.age_label.setText(str(result[1]))
self.vehicle_model_label.setText(result[2])
self.plate_number_label.setText(result[3])
self.registration_date_label.setText(result[4])
self.vehicle_color_label.setText(result[5])
# 更新图片
if result[6] and os.path.exists(result[6]):
pixmap = QPixmap(result[6])
self.plate_image.setPixmap(pixmap.scaled(
self.plate_image.width(),
self.plate_image.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
else:
self.plate_image.setText('图片不存在')
self.statusBar().showMessage(f'查询成功: {plate_number}')
else:
# 清空信息标签
for label in [self.name_label, self.age_label, self.vehicle_model_label,
self.plate_number_label, self.registration_date_label,
self.vehicle_color_label]:
label.setText('')
self.plate_image.setText('暂无图片')
QMessageBox.information(self, '查询结果', '未找到该车牌信息')
self.statusBar().showMessage(f'查询失败: {plate_number}')
except sqlite3.Error as e:
QMessageBox.critical(self, '数据库错误', f'查询数据失败: {str(e)}')
self.statusBar().showMessage('查询失败')
def importImage(self):
# 打开文件对话框选择图片
file_path, _ = QFileDialog.getOpenFileName(
self, '选择图片', '', '图片文件 (*.png *.jpg *.jpeg *.bmp)'
)
if file_path:
# 停止摄像头和视频
self.stopCameraAndVideo()
# 显示选择的图片
pixmap = QPixmap(file_path)
self.media_display.setPixmap(pixmap.scaled(
self.media_display.width(),
self.media_display.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
self.current_media_path = file_path
self.recognize_button.setEnabled(True)
self.result_text.setText('未识别')
def importVideo(self):
# 打开文件对话框选择视频
file_path, _ = QFileDialog.getOpenFileName(
self, '选择视频', '', '视频文件 (*.mp4 *.avi *.mov *.mkv)'
)
if file_path:
# 停止摄像头
self.stopCamera()
self.current_video_path = file_path
self.video = cv2.VideoCapture(file_path)
if not self.video.isOpened():
QMessageBox.critical(self, '视频错误', '无法打开视频文件')
return
# 获取视频帧率
fps = self.video.get(cv2.CAP_PROP_FPS)
self.video_timer = QTimer(self)
self.video_timer.timeout.connect(self.updateVideoFrame)
self.video_timer.start(int(1000 / fps)) # 每帧的时间间隔
self.media_display.setText('正在加载视频...')
self.import_video_button.setText('暂停视频')
self.import_video_button.clicked.disconnect()
self.import_video_button.clicked.connect(self.pauseVideo)
self.recognize_button.setEnabled(False)
self.result_text.setText('视频模式')
def pauseVideo(self):
if self.video_timer and self.video_timer.isActive():
self.video_timer.stop()
self.import_video_button.setText('继续播放')
self.import_video_button.clicked.disconnect()
self.import_video_button.clicked.connect(self.resumeVideo)
else:
self.resumeVideo()
def resumeVideo(self):
if self.video_timer and not self.video_timer.isActive() and self.current_video_path:
self.video = cv2.VideoCapture(self.current_video_path)
fps = self.video.get(cv2.CAP_PROP_FPS)
self.video_timer.start(int(1000 / fps))
self.import_video_button.setText('暂停视频')
self.import_video_button.clicked.disconnect()
self.import_video_button.clicked.connect(self.pauseVideo)
def updateVideoFrame(self):
ret, frame = self.video.read()
if ret:
# 调整图像大小以提高性能
frame = cv2.resize(frame, (640, 480))
height, width, channel = frame.shape
bytes_per_line = 3 * width
q_img = QImage(frame.data, width, height, bytes_per_line, QImage.Format_RGB888).rgbSwapped()
self.media_display.setPixmap(QPixmap.fromImage(q_img).scaled(
self.media_display.width(),
self.media_display.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
else:
# 视频结束,重新开始
self.video.set(cv2.CAP_PROP_POS_FRAMES, 0)
def toggleCamera(self):
if not self.is_camera_running:
# 停止视频
self.stopVideo()
# 打开摄像头
self.camera = cv2.VideoCapture(0)
if not self.camera.isOpened():
QMessageBox.critical(self, '摄像头错误', '无法打开摄像头')
return
self.camera_thread = CameraThread(self.camera)
self.camera_thread.frame_ready.connect(self.updateCameraFrame)
self.camera_thread.start()
self.is_camera_running = True
self.camera_button.setText('关闭摄像头')
self.recognize_button.setEnabled(False)
self.result_text.setText('摄像头模式')
else:
# 关闭摄像头
self.stopCamera()
def updateCameraFrame(self, frame):
# 将OpenCV帧转换为Qt图像并显示
height, width, channel = frame.shape
bytes_per_line = 3 * width
q_img = QImage(frame.data, width, height, bytes_per_line, QImage.Format_RGB888).rgbSwapped()
self.media_display.setPixmap(QPixmap.fromImage(q_img).scaled(
self.media_display.width(),
self.media_display.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
def recognizePlate(self):
if hasattr(self, 'current_media_path') and self.current_media_path:
image = cv2.imread(self.current_media_path)
if image is not None:
plate = getPlate(image)
if plate is not None:
processed_plate = preprocess(plate)
plate_chars = splitPlate(processed_plate)
chars = getcharacters()
results = matchChars(plate_chars, chars)
plate_number = ''.join(results)
self.result_text.setText(plate_number)
# 将识别结果填充到查询输入框中
self.query_input.setText(plate_number)
# 自动触发查询操作
self.queryInfo()
# 将识别到的车牌图片显示在车牌查询区域
height, width, channel = plate.shape
bytes_per_line = 3 * width
# 创建一个连续的数组副本解决memoryview类型问题
plate_copy = np.ascontiguousarray(plate)
q_img = QImage(plate_copy.data, width, height, bytes_per_line, QImage.Format_RGB888).rgbSwapped()
pixmap = QPixmap.fromImage(q_img)
self.plate_image.setPixmap(pixmap.scaled(
self.plate_image.width(),
self.plate_image.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
else:
self.result_text.setText('未识别到车牌')
self.plate_image.setText('暂无图片')
else:
self.result_text.setText('无法读取图像')
self.plate_image.setText('暂无图片')
else:
self.result_text.setText('请先导入图片')
self.plate_image.setText('暂无图片')
def stopCamera(self):
if self.camera_thread:
self.camera_thread.stop()
self.camera_thread.wait()
if self.camera:
self.camera.release()
self.is_camera_running = False
self.camera_button.setText('打开摄像头')
self.media_display.setText('请选择媒体源')
def stopVideo(self):
if self.video_timer:
self.video_timer.stop()
if hasattr(self, 'video') and self.video:
self.video.release()
if hasattr(self, 'current_video_path'):
self.current_video_path = None
self.import_video_button.setText('导入视频')
self.import_video_button.clicked.disconnect()
self.import_video_button.clicked.connect(self.importVideo)
def stopCameraAndVideo(self):
self.stopCamera()
self.stopVideo()
def resizeEvent(self, event):
# 窗口大小改变时调整图片大小
if self.media_display.pixmap() and not self.media_display.pixmap().isNull():
self.media_display.setPixmap(self.media_display.pixmap().scaled(
self.media_display.width(),
self.media_display.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
if self.plate_image.pixmap() and not self.plate_image.pixmap().isNull():
self.plate_image.setPixmap(self.plate_image.pixmap().scaled(
self.plate_image.width(),
self.plate_image.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
super().resizeEvent(event)
def closeEvent(self, event):
# 关闭摄像头和数据库连接
self.stopCameraAndVideo()
try:
if self.conn:
self.conn.close()
except Exception as e:
print(f'关闭数据库连接失败: {str(e)}')
event.accept()
class CameraThread(QThread):
frame_ready = pyqtSignal(np.ndarray)
def __init__(self, camera):
super().__init__()
self.camera = camera
self.running = False
def run(self):
self.running = True
while self.running:
ret, frame = self.camera.read()
if ret:
# 调整图像大小以提高性能
frame = cv2.resize(frame, (640, 480))
self.frame_ready.emit(frame)
self.msleep(30) # 30ms的延迟,约30fps
def stop(self):
self.running = False
if __name__ == '__main__':
# 确保中文显示正常
font = QFont('SimHei')
app = QApplication(sys.argv)
app.setFont(font)
# 设置全局样式
app.setStyleSheet('''
QMainWindow, QWidget {
background-color: #f9f9f9;
}
QLabel {
color: #333;
}
QLineEdit {
padding: 5px;
border: 1px solid #ccc;
border-radius: 5px;
}
''')
window = VehicleInfoApp()
window.show()
sys.exit(app.exec_())
车辆信息查询系统功能分析
这个Python程序实现了一个完整的车辆信息查询系统,主要功能包括:
1. 车牌识别功能
- 使用OpenCV进行车牌检测和字符分割
- 通过模板匹配算法识别车牌字符
- 支持识别中文省份简称、英文字母和数字
2. 数据库管理功能
- 使用SQLite数据库存储车辆信息
- 包含车牌号、车主姓名、年龄、车型、注册日期、颜色等信息
- 自动初始化示例数据
3. 图形用户界面功能
- 基于PyQt5的现代化UI界面
- 主要功能区域:
- 左侧:车辆信息查询和显示区域
- 右侧:媒体处理和车牌识别区域
4. 具体功能模块
查询功能
- 通过车牌号码查询车辆详细信息
- 显示车主信息、车辆信息和关联图片
媒体处理功能
- 支持导入图片进行车牌识别
- 支持导入视频文件并播放
- 支持打开/关闭摄像头实时捕获
车牌识别功能
- 对导入的图片进行车牌检测和识别
- 显示识别结果并自动填充到查询框
- 自动查询识别到的车牌信息
5. 技术特点
- 使用多线程处理摄像头视频流
- 自适应窗口大小调整
- 完善的错误处理和状态提示
- 简洁美观的界面设计
这个系统适用于停车场管理、交通执法等需要快速查询车辆信息的场景,实现了从车牌识别到信息查询的完整工作流程。