main-66 #5

Closed
spdis wants to merge 5 commits from main-66 into main
12 changed files with 642 additions and 87 deletions

View File

@ -5,8 +5,4 @@
<orderEntry type="jdk" jdkName="cnm" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

BIN
LPRNET_part/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
LPRNET_part/2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -1,3 +1,4 @@
# 导入必要的库
import torch
import torch.nn as nn
import cv2
@ -11,6 +12,7 @@ from PIL import Image
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# LPRNet字符集定义与训练时保持一致
# 包含中国省份简称、数字、字母和特殊字符
CHARS = ['', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '',
@ -19,84 +21,115 @@ CHARS = ['京', '沪', '津', '渝', '冀', '晋', '蒙', '辽', '吉', '黑',
'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'I', 'O', '-']
# 创建字符到索引的映射字典
CHARS_DICT = {char: i for i, char in enumerate(CHARS)}
# 简化的LPRNet模型定义
# 简化的LPRNet模型定义 - 基础卷积块
class small_basic_block(nn.Module):
def __init__(self, ch_in, ch_out):
super(small_basic_block, self).__init__()
# 定义一个小的基本卷积块,包含四个卷积层
self.block = nn.Sequential(
# 1x1卷积降低通道数
nn.Conv2d(ch_in, ch_out // 4, kernel_size=1),
nn.ReLU(),
# 3x1卷积处理水平特征
nn.Conv2d(ch_out // 4, ch_out // 4, kernel_size=(3, 1), padding=(1, 0)),
nn.ReLU(),
# 1x3卷积处理垂直特征
nn.Conv2d(ch_out // 4, ch_out // 4, kernel_size=(1, 3), padding=(0, 1)),
nn.ReLU(),
# 1x1卷积恢复通道数
nn.Conv2d(ch_out // 4, ch_out, kernel_size=1),
)
def forward(self, x):
return self.block(x)
# LPRNet模型定义 - 车牌识别网络
class LPRNet(nn.Module):
def __init__(self, lpr_max_len, phase, class_num, dropout_rate):
super(LPRNet, self).__init__()
self.phase = phase
self.lpr_max_len = lpr_max_len
self.class_num = class_num
# 定义主干网络
self.backbone = nn.Sequential(
# 初始卷积层
nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1), # 0
nn.BatchNorm2d(num_features=64),
nn.ReLU(), # 2
# 最大池化层
nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 1, 1)),
# 第一个基本块
small_basic_block(ch_in=64, ch_out=128), # *** 4 ***
nn.BatchNorm2d(num_features=128),
nn.ReLU(), # 6
# 第二个池化层
nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(2, 1, 2)),
# 第二个基本块
small_basic_block(ch_in=64, ch_out=256), # 8
nn.BatchNorm2d(num_features=256),
nn.ReLU(), # 10
# 第三个基本块
small_basic_block(ch_in=256, ch_out=256), # *** 11 ***
nn.BatchNorm2d(num_features=256),
nn.ReLU(), # 13
# 第三个池化层
nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(4, 1, 2)), # 14
# Dropout层防止过拟合
nn.Dropout(dropout_rate),
# 特征提取卷积层
nn.Conv2d(in_channels=64, out_channels=256, kernel_size=(1, 4), stride=1), # 16
nn.BatchNorm2d(num_features=256),
nn.ReLU(), # 18
# 第二个Dropout层
nn.Dropout(dropout_rate),
# 分类卷积层
nn.Conv2d(in_channels=256, out_channels=class_num, kernel_size=(13, 1), stride=1), # 20
nn.BatchNorm2d(num_features=class_num),
nn.ReLU(), # 22
)
# 定义容器层,用于融合全局上下文信息
self.container = nn.Sequential(
nn.Conv2d(in_channels=448+self.class_num, out_channels=self.class_num, kernel_size=(1,1), stride=(1,1)),
)
def forward(self, x):
# 保存中间特征
keep_features = list()
for i, layer in enumerate(self.backbone.children()):
x = layer(x)
# 保存特定层的输出特征
if i in [2, 6, 13, 22]: # [2, 4, 8, 11, 22]
keep_features.append(x)
# 处理全局上下文信息
global_context = list()
for i, f in enumerate(keep_features):
# 对不同层的特征进行不同尺度的平均池化
if i in [0, 1]:
f = nn.AvgPool2d(kernel_size=5, stride=5)(f)
if i in [2]:
f = nn.AvgPool2d(kernel_size=(4, 10), stride=(4, 2))(f)
# 对特征进行归一化处理
f_pow = torch.pow(f, 2)
f_mean = torch.mean(f_pow)
f = torch.div(f, f_mean)
global_context.append(f)
# 拼接全局上下文特征
x = torch.cat(global_context, 1)
# 通过容器层处理
x = self.container(x)
# 对序列维度进行平均,得到最终输出
logits = torch.mean(x, dim=2)
return logits
# LPRNet推理类
class LPRNetInference:
def __init__(self, model_path=None, img_size=[94, 24], lpr_max_len=8, dropout_rate=0.5):
"""
@ -109,6 +142,7 @@ class LPRNetInference:
"""
self.img_size = img_size
self.lpr_max_len = lpr_max_len
# 检测是否有可用的CUDA设备
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 设置默认模型路径
@ -130,6 +164,7 @@ class LPRNetInference:
else:
print(f"Warning: 模型文件不存在或未指定: {model_path}. 使用随机权重.")
# 将模型移动到指定设备并设置为评估模式
self.model.to(self.device)
self.model.eval()
@ -164,9 +199,11 @@ class LPRNetInference:
image_array = cv2.resize(image_array, tuple(self.img_size))
# 使用与训练时相同的预处理方式
# 归一化处理减去127.5并乘以0.0078125,将像素值从[0,255]映射到[-1,1]
image_array = image_array.astype('float32')
image_array -= 127.5
image_array *= 0.0078125
# 调整维度顺序从HWC到CHW
image_array = np.transpose(image_array, (2, 0, 1)) # HWC -> CHW
# 转换为tensor并添加batch维度
@ -186,7 +223,7 @@ class LPRNetInference:
prebs = logits.cpu().detach().numpy()
preb = prebs[0, :, :] # 取第一个batch [num_classes, sequence_length]
# 贪婪解码:对每个时间步选择最大概率的字符
# 贪婪解码: 对每个时间步选择最大概率的字符
preb_label = []
for j in range(preb.shape[1]): # 遍历每个时间步
preb_label.append(np.argmax(preb[:, j], axis=0))
@ -248,7 +285,7 @@ class LPRNetInference:
print(f"预测图像失败: {e}")
return None, 0.0
# 全局变量
# 全局变量,用于存储模型实例
lpr_model = None
def LPRNinitialize_model():
@ -295,6 +332,13 @@ def LPRNmodel_predict(image_array):
return ['', '', '', '0', '0', '0', '0', '0']
try:
# 使用OpenCV调整图像大小到模型要求的尺寸
image_array = cv2.resize(image_array, (94, 24))
print(f"666999图片尺寸: {image_array.shape}")
# 显示修正后的图像
cv2.imshow('Resized License Plate Image (94x24)', image_array)
cv2.waitKey(1) # 非阻塞显示,允许程序继续执行
# 预测车牌号
predicted_text, confidence = lpr_model.predict(image_array)

View File

@ -5,6 +5,18 @@ import cv2
class OCRProcessor:
def __init__(self):
self.model = TextRecognition(model_name="PP-OCRv5_server_rec")
# 定义允许的字符集合(不包含空白字符)
self.allowed_chars = [
# 中文省份简称
'', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '',
# 字母 A-Z
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
# 数字 0-9
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
]
print("OCR模型初始化完成占位")
def predict(self, image_array):
@ -14,6 +26,14 @@ class OCRProcessor:
results = output[0]["rec_text"]
placeholder_result = results.split(',')
return placeholder_result
def filter_allowed_chars(self, text):
"""只保留允许的字符"""
filtered_text = ""
for char in text:
if char in self.allowed_chars:
filtered_text += char
return filtered_text
# 保留原有函数接口
_processor = OCRProcessor()
@ -42,8 +62,12 @@ def LPRNmodel_predict(image_array):
else:
result_str = str(raw_result)
# 过滤掉'·'字符
# 过滤掉'·'和'-'字符
filtered_str = result_str.replace('·', '')
filtered_str = filtered_str.replace('-', '')
# 只保留允许的字符
filtered_str = _processor.filter_allowed_chars(filtered_str)
# 转换为字符列表
char_list = list(filtered_str)

Binary file not shown.

549
main.py
View File

@ -1,25 +1,22 @@
import sys
import os
import cv2
import numpy as np
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QScrollArea, QFrame, QSizePolicy
)
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, \
QFileDialog, QFrame, QScrollArea, QComboBox
from PyQt5.QtCore import QTimer, Qt, pyqtSignal, QThread
from PyQt5.QtGui import QImage, QPixmap, QFont, QPainter, QPen, QColor
import os
from yolopart.detector import LicensePlateYOLO
#选择使用哪个模块
from LPRNET_part.lpr_interface import LPRNmodel_predict
from LPRNET_part.lpr_interface import LPRNinitialize_model
# from LPRNET_part.lpr_interface import LPRNmodel_predict
# from LPRNET_part.lpr_interface import LPRNinitialize_model
#使用OCR
#from OCR_part.ocr_interface import LPRNmodel_predict
#from OCR_part.ocr_interface import LPRNinitialize_model
# from OCR_part.ocr_interface import LPRNmodel_predict
# from OCR_part.ocr_interface import LPRNinitialize_model
# 使用CRNN
#from CRNN_part.crnn_interface import LPRNmodel_predict
#from CRNN_part.crnn_interface import LPRNinitialize_model
# from CRNN_part.crnn_interface import LPRNmodel_predict
# from CRNN_part.crnn_interface import LPRNinitialize_model
class CameraThread(QThread):
"""摄像头线程类"""
@ -56,6 +53,60 @@ class CameraThread(QThread):
self.frame_ready.emit(frame)
self.msleep(30) # 约30fps
class VideoThread(QThread):
"""视频处理线程类"""
frame_ready = pyqtSignal(np.ndarray)
video_finished = pyqtSignal()
def __init__(self):
super().__init__()
self.video_path = None
self.cap = None
self.running = False
self.paused = False
def load_video(self, video_path):
"""加载视频文件"""
self.video_path = video_path
self.cap = cv2.VideoCapture(video_path)
return self.cap.isOpened()
def start_video(self):
"""开始播放视频"""
if self.cap and self.cap.isOpened():
self.running = True
self.paused = False
self.start()
return True
return False
def pause_video(self):
"""暂停/继续视频"""
self.paused = not self.paused
return self.paused
def stop_video(self):
"""停止视频"""
self.running = False
if self.cap:
self.cap.release()
self.quit()
self.wait()
def run(self):
"""线程运行函数"""
while self.running:
if not self.paused and self.cap and self.cap.isOpened():
ret, frame = self.cap.read()
if ret:
self.frame_ready.emit(frame)
else:
# 视频播放结束
self.video_finished.emit()
self.running = False
break
self.msleep(30) # 约30fps
class LicensePlateWidget(QWidget):
"""单个车牌结果显示组件"""
@ -162,15 +213,21 @@ class MainWindow(QMainWindow):
super().__init__()
self.detector = None
self.camera_thread = None
self.video_thread = None
self.current_frame = None
self.detections = []
self.current_mode = "camera" # 当前模式camera, video, image
self.is_processing = False # 标志位,表示是否正在处理识别任务
self.last_plate_results = [] # 存储上一次的车牌识别结果
self.current_recognition_method = "CRNN" # 当前识别方法
self.init_ui()
self.init_detector()
self.init_camera()
self.init_video()
# 初始化OCR/CRNN模型函数名改成一样的了所以不要修改这里了想用哪个模块直接导入
LPRNinitialize_model()
# 初始化默认识别方法CRNN的模型
self.change_recognition_method(self.current_recognition_method)
def init_ui(self):
@ -197,7 +254,7 @@ class MainWindow(QMainWindow):
self.camera_label.setStyleSheet("QLabel { background-color: black; border: 1px solid #ccc; }")
self.camera_label.setAlignment(Qt.AlignCenter)
self.camera_label.setText("摄像头未启动")
self.camera_label.setScaledContents(True)
self.camera_label.setScaledContents(False)
# 控制按钮
button_layout = QHBoxLayout()
@ -207,8 +264,26 @@ class MainWindow(QMainWindow):
self.stop_button.clicked.connect(self.stop_camera)
self.stop_button.setEnabled(False)
# 视频控制按钮
self.open_video_button = QPushButton("打开视频")
self.stop_video_button = QPushButton("停止视频")
self.pause_video_button = QPushButton("暂停视频")
self.open_video_button.clicked.connect(self.open_video_file)
self.stop_video_button.clicked.connect(self.stop_video)
self.pause_video_button.clicked.connect(self.pause_video)
self.stop_video_button.setEnabled(False)
self.pause_video_button.setEnabled(False)
# 图片控制按钮
self.open_image_button = QPushButton("打开图片")
self.open_image_button.clicked.connect(self.open_image_file)
button_layout.addWidget(self.start_button)
button_layout.addWidget(self.stop_button)
button_layout.addWidget(self.open_video_button)
button_layout.addWidget(self.stop_video_button)
button_layout.addWidget(self.pause_video_button)
button_layout.addWidget(self.open_image_button)
button_layout.addStretch()
left_layout.addWidget(self.camera_label)
@ -227,6 +302,20 @@ class MainWindow(QMainWindow):
title_label.setFont(QFont("Arial", 16, QFont.Bold))
title_label.setStyleSheet("QLabel { color: #333; padding: 10px; }")
# 识别方法选择
method_layout = QHBoxLayout()
method_label = QLabel("识别方法:")
method_label.setFont(QFont("Arial", 10))
self.method_combo = QComboBox()
self.method_combo.addItems(["CRNN", "LPRNET", "OCR"])
self.method_combo.setCurrentText("CRNN") # 默认选择CRNN
self.method_combo.currentTextChanged.connect(self.change_recognition_method)
method_layout.addWidget(method_label)
method_layout.addWidget(self.method_combo)
method_layout.addStretch()
# 车牌数量显示
self.count_label = QLabel("识别到的车牌数量: 0")
self.count_label.setAlignment(Qt.AlignCenter)
@ -253,9 +342,17 @@ class MainWindow(QMainWindow):
scroll_area.setWidget(self.results_widget)
# 当前识别任务显示
self.current_method_label = QLabel("当前识别方法: CRNN")
self.current_method_label.setAlignment(Qt.AlignRight)
self.current_method_label.setFont(QFont("Arial", 9))
self.current_method_label.setStyleSheet("QLabel { color: #666; padding: 5px; }")
right_layout.addWidget(title_label)
right_layout.addLayout(method_layout)
right_layout.addWidget(self.count_label)
right_layout.addWidget(scroll_area)
right_layout.addWidget(self.current_method_label)
# 添加到主布局
main_layout.addWidget(left_frame, 2)
@ -296,6 +393,12 @@ class MainWindow(QMainWindow):
self.camera_thread = CameraThread()
self.camera_thread.frame_ready.connect(self.process_frame)
def init_video(self):
"""初始化视频线程"""
self.video_thread = VideoThread()
self.video_thread.frame_ready.connect(self.process_frame)
self.video_thread.video_finished.connect(self.on_video_finished)
def start_camera(self):
"""启动摄像头"""
if self.camera_thread.start_camera():
@ -311,23 +414,167 @@ class MainWindow(QMainWindow):
self.start_button.setEnabled(True)
self.stop_button.setEnabled(False)
self.camera_label.setText("摄像头已停止")
self.camera_label.clear()
# 只在摄像头模式下清除标签内容
if self.current_mode == "camera":
self.camera_label.clear()
def on_video_finished(self):
"""视频播放结束时的处理"""
self.video_thread.stop_video()
self.open_video_button.setEnabled(True)
self.stop_video_button.setEnabled(False)
self.pause_video_button.setEnabled(False)
self.camera_label.setText("视频播放结束")
self.current_mode = "camera"
def open_video_file(self):
"""打开视频文件"""
# 停止当前模式
if self.current_mode == "camera" and self.camera_thread and self.camera_thread.running:
self.stop_camera()
elif self.current_mode == "video" and self.video_thread and self.video_thread.running:
self.stop_video()
# 选择视频文件
video_path, _ = QFileDialog.getOpenFileName(self, "选择视频文件", "", "视频文件 (*.mp4 *.avi *.mov *.mkv)")
if video_path:
if self.video_thread.load_video(video_path):
self.current_mode = "video"
self.start_video()
self.camera_label.setText(f"正在播放视频: {os.path.basename(video_path)}")
else:
self.camera_label.setText("视频加载失败")
def start_video(self):
"""开始播放视频"""
if self.video_thread.start_video():
self.open_video_button.setEnabled(False)
self.stop_video_button.setEnabled(True)
self.pause_video_button.setEnabled(True)
self.pause_video_button.setText("暂停")
else:
self.camera_label.setText("视频播放失败")
def pause_video(self):
"""暂停/继续视频"""
if self.video_thread.pause_video():
self.pause_video_button.setText("继续")
else:
self.pause_video_button.setText("暂停")
def stop_video(self):
"""停止视频"""
self.video_thread.stop_video()
self.open_video_button.setEnabled(True)
self.stop_video_button.setEnabled(False)
self.pause_video_button.setEnabled(False)
self.camera_label.setText("视频已停止")
# 只在视频模式下清除标签内容
if self.current_mode == "video":
self.camera_label.clear()
self.current_mode = "camera"
def open_image_file(self):
"""打开图片文件"""
# 停止当前模式
if self.current_mode == "camera" and self.camera_thread and self.camera_thread.running:
self.stop_camera()
elif self.current_mode == "video" and self.video_thread and self.video_thread.running:
self.stop_video()
# 选择图片文件
image_path, _ = QFileDialog.getOpenFileName(self, "选择图片文件", "", "图片文件 (*.jpg *.jpeg *.png *.bmp)")
if image_path:
self.current_mode = "image"
try:
# 读取图片 - 方法1: 使用cv2.imdecode处理中文路径
image = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), cv2.IMREAD_COLOR)
# 如果方法1失败尝试方法2: 直接使用cv2.imread
if image is None:
image = cv2.imread(image_path)
if image is not None:
print(f"成功加载图片: {image_path}, 尺寸: {image.shape}")
self.process_image(image)
# 不在这里设置文本,避免覆盖图片
# self.camera_label.setText(f"正在显示图片: {os.path.basename(image_path)}")
else:
print(f"图片加载失败: {image_path}")
self.camera_label.setText("图片加载失败")
except Exception as e:
print(f"图片处理异常: {str(e)}")
self.camera_label.setText(f"图片处理错误: {str(e)}")
def process_image(self, image):
"""处理图片"""
try:
print(f"开始处理图片,图片尺寸: {image.shape}")
self.current_frame = image.copy()
# 进行车牌检测
print("正在进行车牌检测...")
self.detections = self.detector.detect_license_plates(image)
print(f"检测到 {len(self.detections)} 个车牌")
# 在图像上绘制检测结果
print("正在绘制检测结果...")
display_frame = self.draw_detections(image.copy())
# 转换为Qt格式并显示
print("正在显示图片...")
self.display_frame(display_frame)
# 更新右侧结果显示
print("正在更新结果显示...")
self.update_results_display()
print("图片处理完成")
except Exception as e:
print(f"图片处理过程中出错: {str(e)}")
import traceback
traceback.print_exc()
def process_frame(self, frame):
"""处理摄像头帧"""
self.current_frame = frame.copy()
# 进行车牌检测
self.detections = self.detector.detect_license_plates(frame)
# 先显示原始帧,保证视频流畅播放
self.display_frame(frame)
# 在图像上绘制检测结果
display_frame = self.draw_detections(frame.copy())
# 转换为Qt格式并显示
self.display_frame(display_frame)
# 更新右侧结果显示
self.update_results_display()
# 如果当前没有在处理识别任务,则开始新的识别任务
if not self.is_processing:
self.is_processing = True
# 异步进行车牌检测和识别
QTimer.singleShot(0, self.async_detect_and_update)
def async_detect_and_update(self):
"""异步进行车牌检测和识别"""
if self.current_frame is None:
self.is_processing = False # 重置标志位
return
try:
# 进行车牌检测
self.detections = self.detector.detect_license_plates(self.current_frame)
# 在图像上绘制检测结果
display_frame = self.draw_detections(self.current_frame.copy())
# 更新显示帧(显示带检测结果的帧)
# 无论是摄像头模式还是视频模式,都显示检测框
self.display_frame(display_frame)
# 更新右侧结果显示
self.update_results_display()
except Exception as e:
print(f"异步检测和更新失败: {str(e)}")
import traceback
traceback.print_exc()
finally:
# 无论成功或失败,都要重置标志位
self.is_processing = False
def draw_detections(self, frame):
"""在图像上绘制检测结果"""
@ -335,14 +582,96 @@ class MainWindow(QMainWindow):
def display_frame(self, frame):
"""显示帧到界面"""
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_frame.shape
bytes_per_line = ch * w
qt_image = QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
pixmap = QPixmap.fromImage(qt_image)
scaled_pixmap = pixmap.scaled(self.camera_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.camera_label.setPixmap(scaled_pixmap)
try:
print(f"开始显示帧,帧尺寸: {frame.shape}")
# 方法1: 标准方法
try:
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_frame.shape
bytes_per_line = ch * w
qt_image = QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
print(f"方法1: 创建QImage尺寸: {qt_image.width()}x{qt_image.height()}")
if qt_image.isNull():
print("方法1: QImage为空尝试方法2")
raise Exception("QImage为空")
pixmap = QPixmap.fromImage(qt_image)
if pixmap.isNull():
print("方法1: QPixmap为空尝试方法2")
raise Exception("QPixmap为空")
# 手动缩放图片以适应标签大小,保持宽高比
scaled_pixmap = pixmap.scaled(self.camera_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.camera_label.setPixmap(scaled_pixmap)
print("方法1: 帧显示完成")
return
except Exception as e1:
print(f"方法1失败: {str(e1)}")
# 方法2: 使用imencode和imdecode
try:
print("尝试方法2: 使用imencode和imdecode")
_, buffer = cv2.imencode('.jpg', frame)
rgb_frame = cv2.imdecode(buffer, cv2.IMREAD_COLOR)
rgb_frame = cv2.cvtColor(rgb_frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_frame.shape
bytes_per_line = ch * w
qt_image = QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
print(f"方法2: 创建QImage尺寸: {qt_image.width()}x{qt_image.height()}")
if qt_image.isNull():
print("方法2: QImage为空")
raise Exception("QImage为空")
pixmap = QPixmap.fromImage(qt_image)
if pixmap.isNull():
print("方法2: QPixmap为空")
raise Exception("QPixmap为空")
# 手动缩放图片以适应标签大小,保持宽高比
scaled_pixmap = pixmap.scaled(self.camera_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.camera_label.setPixmap(scaled_pixmap)
print("方法2: 帧显示完成")
return
except Exception as e2:
print(f"方法2失败: {str(e2)}")
# 方法3: 直接使用QImage的构造函数
try:
print("尝试方法3: 直接使用QImage的构造函数")
height, width, channel = frame.shape
bytes_per_line = 3 * width
q_image = QImage(frame.data, width, height, bytes_per_line, QImage.Format_BGR888)
print(f"方法3: 创建QImage尺寸: {q_image.width()}x{q_image.height()}")
if q_image.isNull():
print("方法3: QImage为空")
raise Exception("QImage为空")
pixmap = QPixmap.fromImage(q_image)
if pixmap.isNull():
print("方法3: QPixmap为空")
raise Exception("QPixmap为空")
# 手动缩放图片以适应标签大小,保持宽高比
scaled_pixmap = pixmap.scaled(self.camera_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.camera_label.setPixmap(scaled_pixmap)
print("方法3: 帧显示完成")
return
except Exception as e3:
print(f"方法3失败: {str(e3)}")
# 所有方法都失败
print("所有显示方法都失败")
self.camera_label.setText("图片显示失败")
except Exception as e:
print(f"显示帧过程中出错: {str(e)}")
import traceback
traceback.print_exc()
self.camera_label.setText(f"显示错误: {str(e)}")
def update_results_display(self):
"""更新右侧结果显示"""
@ -350,13 +679,8 @@ class MainWindow(QMainWindow):
count = len(self.detections)
self.count_label.setText(f"识别到的车牌数量: {count}")
# 清除之前的结果
for i in reversed(range(self.results_layout.count())):
child = self.results_layout.itemAt(i).widget()
if child:
child.setParent(None)
# 添加新的结果
# 准备新的车牌结果列表
new_plate_results = []
for i, detection in enumerate(self.detections):
# 矫正车牌图像
corrected_image = self.correct_license_plate(detection)
@ -364,15 +688,53 @@ class MainWindow(QMainWindow):
# 获取车牌号,传入车牌类型信息
plate_number = self.recognize_plate_number(corrected_image, detection['class_name'])
# 创建车牌显示组件
plate_widget = LicensePlateWidget(
i + 1,
detection['class_name'],
corrected_image,
plate_number
)
# 添加到新结果列表
new_plate_results.append({
'id': i + 1,
'class_name': detection['class_name'],
'corrected_image': corrected_image,
'plate_number': plate_number
})
# 比较新旧结果是否相同
results_changed = False
if len(self.last_plate_results) != len(new_plate_results):
results_changed = True
else:
for i in range(len(new_plate_results)):
if i >= len(self.last_plate_results):
results_changed = True
break
last_result = self.last_plate_results[i]
new_result = new_plate_results[i]
# 比较车牌类型和车牌号
if (last_result['class_name'] != new_result['class_name'] or
last_result['plate_number'] != new_result['plate_number']):
results_changed = True
break
# 只有当结果发生变化时才更新显示
if results_changed:
# 清除之前的结果
for i in reversed(range(self.results_layout.count())):
child = self.results_layout.itemAt(i).widget()
if child:
child.setParent(None)
self.results_layout.addWidget(plate_widget)
# 添加新的结果
for result in new_plate_results:
plate_widget = LicensePlateWidget(
result['id'],
result['class_name'],
result['corrected_image'],
result['plate_number']
)
self.results_layout.addWidget(plate_widget)
# 更新存储的上一次结果
self.last_plate_results = new_plate_results
def correct_license_plate(self, detection):
"""矫正车牌图像"""
@ -390,40 +752,69 @@ class MainWindow(QMainWindow):
)
def recognize_plate_number(self, corrected_image, class_name):
"""识别车牌号"""
if corrected_image is None:
return "识别失败"
try:
# 预测函数(来自模块)
# 函数名改成一样的了,所以不要修改这里了,想用哪个模块直接导入
result = LPRNmodel_predict(corrected_image)
# 将字符列表转换为字符串支持8位车牌号
if isinstance(result, list) and len(result) >= 7:
# 根据车牌类型决定显示位数
if class_name == '绿牌' and len(result) >= 8:
# 绿牌显示8位过滤掉空字符占位符
plate_chars = [char for char in result[:8] if char != '']
# 如果过滤后确实有8位显示8位否则显示7位
if len(plate_chars) == 8:
return ''.join(plate_chars)
else:
return ''.join(plate_chars[:7])
else:
# 蓝牌或其他类型显示前7位过滤掉空字符
plate_chars = [char for char in result[:7] if char != '']
return ''.join(plate_chars)
else:
return "识别失败"
except Exception as e:
print(f"车牌号识别失败: {e}")
return "识别失败"
"""识别车牌号"""
if corrected_image is None:
return "识别失败"
try:
# 根据当前选择的识别方法调用相应的函数
if self.current_recognition_method == "CRNN":
from CRNN_part.crnn_interface import LPRNmodel_predict
elif self.current_recognition_method == "LPRNET":
from lightCRNN_part.lightcrnn_interface import LPRNmodel_predict
elif self.current_recognition_method == "OCR":
from OCR_part.ocr_interface import LPRNmodel_predict
# 预测函数(来自模块)
result = LPRNmodel_predict(corrected_image)
# 将字符列表转换为字符串支持8位车牌号
if isinstance(result, list) and len(result) >= 7:
# 根据车牌类型决定显示位数
if class_name == '绿牌' and len(result) >= 8:
# 绿牌显示8位过滤掉空字符占位符
plate_chars = [char for char in result[:8] if char != '']
# 如果过滤后确实有8位显示8位否则显示7位
if len(plate_chars) == 8:
return ''.join(plate_chars)
else:
return ''.join(plate_chars[:7])
else:
# 蓝牌或其他类型显示前7位过滤掉空字符
plate_chars = [char for char in result[:7] if char != '']
return ''.join(plate_chars)
else:
return "识别失败"
except Exception as e:
print(f"车牌号识别失败: {e}")
return "识别失败"
def change_recognition_method(self, method):
"""切换识别方法"""
self.current_recognition_method = method
self.current_method_label.setText(f"当前识别方法: {method}")
# 初始化对应的模型
if method == "CRNN":
from CRNN_part.crnn_interface import LPRNinitialize_model
LPRNinitialize_model()
elif method == "LPRNET":
from lightCRNN_part.lightcrnn_interface import LPRNinitialize_model
LPRNinitialize_model()
elif method == "OCR":
from OCR_part.ocr_interface import LPRNinitialize_model
LPRNinitialize_model()
# 如果当前有显示的帧,重新处理以更新识别结果
if self.current_frame is not None:
self.process_frame(self.current_frame)
def closeEvent(self, event):
"""窗口关闭事件"""
if self.camera_thread:
if self.camera_thread and self.camera_thread.running:
self.camera_thread.stop_camera()
if self.video_thread and self.video_thread.running:
self.video_thread.stop_video()
event.accept()
def main():

100
test_lpr_real_images.py Normal file
View File

@ -0,0 +1,100 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
LPRNet接口真实图片测试脚本
测试LPRNET_part目录下的真实车牌图片
"""
import cv2
import numpy as np
import os
from LPRNET_part.lpr_interface import LPRNinitialize_model, LPRNmodel_predict
def test_real_images():
"""
测试LPRNET_part目录下的真实车牌图片
"""
print("=== LPRNet真实图片测试 ===")
# 初始化模型
print("1. 初始化LPRNet模型...")
success = LPRNinitialize_model()
if not success:
print("模型初始化失败!")
return
# 获取LPRNET_part目录下的图片文件
lprnet_dir = "LPRNET_part"
image_files = []
if os.path.exists(lprnet_dir):
for file in os.listdir(lprnet_dir):
if file.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp')):
image_files.append(os.path.join(lprnet_dir, file))
if not image_files:
print("未找到图片文件!")
return
print(f"2. 找到 {len(image_files)} 个图片文件")
# 测试每个图片
for i, image_path in enumerate(image_files, 1):
print(f"\n--- 测试图片 {i}: {os.path.basename(image_path)} ---")
try:
# 使用支持中文路径的方式读取图片
image = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), cv2.IMREAD_COLOR)
if image is None:
print(f"无法读取图片: {image_path}")
continue
print(f"图片尺寸: {image.shape}")
# 进行预测
result = LPRNmodel_predict(image)
print(f"识别结果: {result}")
print(f"识别车牌号: {''.join(result)}")
except Exception as e:
print(f"处理图片 {image_path} 时出错: {e}")
import traceback
traceback.print_exc()
print("\n=== 测试完成 ===")
def test_image_loading():
"""
测试图片加载方式
"""
print("\n=== 图片加载测试 ===")
lprnet_dir = "LPRNET_part"
if os.path.exists(lprnet_dir):
for file in os.listdir(lprnet_dir):
if file.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp')):
image_path = os.path.join(lprnet_dir, file)
print(f"\n测试文件: {file}")
# 方法1: 普通cv2.imread
img1 = cv2.imread(image_path)
print(f"cv2.imread结果: {img1 is not None}")
# 方法2: 支持中文路径的方式
try:
img2 = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), cv2.IMREAD_COLOR)
# img2 = cv2.resize(img2,(128,48))
print(f"cv2.imdecode结果: {img2 is not None}")
if img2 is not None:
print(f"图片尺寸: {img2.shape}")
except Exception as e:
print(f"cv2.imdecode失败: {e}")
if __name__ == "__main__":
# 首先测试图片加载
test_image_loading()
# 然后测试完整的识别流程
test_real_images()