import cv2
import numpy as np
import pytesseract
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from PIL import Image, ImageTk
import imutils
# --- 配置区(关键优化:中文识别核心配置)---
TESSERACT_PATH = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
pytesseract.pytesseract.tesseract_cmd = TESSERACT_PATH
# 语言包路径必须用原始字符串,避免转义错误(双重保障)
pytesseract.pytesseract_config = r'--tessdata-dir "C:\Program Files\Tesseract-OCR\tessdata"'
# 车牌专用配置(确保汉字包含所有省份简称)
PROVINCE_LIST = "京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼"
CHAR_WHITELIST = PROVINCE_LIST + 'ABCDEFGHJKLMNPQRSTUVWXYZ0123456789'
class LicensePlateRecognitionApp:
def __init__(self, root):
self.root = root
self.root.title("智能车牌识别系统(支持汉字识别)")
self.root.geometry("1600x1000")
self.root.resizable(True, True)
# 初始化变量
self.image_path = None
self.original_image = None
self.gray_image = None # 灰度图
self.blur_image = None # 高斯滤波图
self.edged_image = None # 边缘检测图
self.contour_image = None # 轮廓标记图
self.plate_image = None # 定位车牌图
self.recognition_result = ""
# 创建界面
self._create_widgets()
def _create_widgets(self):
# 主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# 顶部控制区
control_frame = ttk.Frame(main_frame)
control_frame.pack(fill=tk.X, pady=5)
self.select_btn = ttk.Button(control_frame, text="选择图片", command=self.select_image)
self.select_btn.pack(side=tk.LEFT, padx=5)
self.recognize_btn = ttk.Button(control_frame, text="开始识别", command=self.start_recognition,
state=tk.DISABLED)
self.recognize_btn.pack(side=tk.LEFT, padx=5)
self.clear_btn = ttk.Button(control_frame, text="清空", command=self.clear_all)
self.clear_btn.pack(side=tk.LEFT, padx=5)
# 2×3网格图像显示区
image_frame = ttk.Frame(main_frame)
image_frame.pack(fill=tk.BOTH, expand=True, pady=10)
self.step_titles = [
"原图", "灰度图", "高斯滤波",
"边缘检测", "轮廓标记", "定位车牌"
]
self.step_labels = []
for row in range(2):
for col in range(3):
step_frame = ttk.LabelFrame(image_frame, text=self.step_titles[row * 3 + col], padding="5")
step_frame.grid(row=row, column=col, padx=5, pady=5, sticky=tk.NSEW)
image_frame.grid_rowconfigure(row, weight=1)
image_frame.grid_columnconfigure(col, weight=1)
img_label = ttk.Label(step_frame)
img_label.pack(fill=tk.BOTH, expand=True)
self.step_labels.append(img_label)
# 识别结果区
result_frame = ttk.LabelFrame(main_frame, text="识别结果(支持汉字)", padding="10")
result_frame.pack(fill=tk.X, pady=10)
self.result_var = tk.StringVar()
self.result_entry = ttk.Entry(result_frame, textvariable=self.result_var,
font=("微软雅黑", 22, "bold"), state='readonly') # 用微软雅黑支持汉字显示
self.result_entry.pack(fill=tk.X, expand=True, padx=20, pady=5)
# 状态栏
self.status_var = tk.StringVar(value="请选择图片开始识别(确保Tesseract已安装中文语言包)")
status_bar = ttk.Label(self.root, textvariable=self.status_var, anchor=tk.W)
status_bar.pack(fill=tk.X)
def select_image(self):
"""选择图片并显示原图"""
path = filedialog.askopenfilename(
filetypes=[("图片文件", "*.jpg;*.jpeg;*.png;*.bmp")],
title="请选择车牌图片"
)
if not path:
return
self.image_path = path
self.original_image = cv2.imread(path)
if self.original_image is None:
messagebox.showerror("错误", "无法加载图片,请选择有效图片!")
return
self._display_image(self.original_image, self.step_labels[0])
for label in self.step_labels[1:]:
label.config(image="")
label.image = None
self.status_var.set(f"已加载图片: {path.split('/')[-1]}")
self.recognize_btn.config(state=tk.NORMAL)
self.clear_all(keep_image=True)
def start_recognition(self):
"""开始识别(核心优化:汉字识别相关)"""
if self.original_image is None:
messagebox.showerror("错误", "请先选择图片!")
return
self.status_var.set("正在处理图像...")
self.root.update_idletasks()
try:
# 1. 图像预处理(强化汉字轮廓,关键优化!)
img = self.original_image.copy()
self.gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 优化1:自适应阈值二值化(比固定阈值更适合汉字识别,保留细节)
self.blur_image = cv2.GaussianBlur(self.gray_image, (5, 5), 0)
# 反相二值化:使汉字(前景)为白色,背景为黑色,符合OCR识别习惯
self.edged_image = cv2.adaptiveThreshold(self.blur_image, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV,
blockSize=15, C=2)
# 优化2:形态学操作(膨胀+腐蚀),强化汉字笔画连接,避免断裂
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
self.edged_image = cv2.morphologyEx(self.edged_image, cv2.MORPH_DILATE, kernel, iterations=1)
self.edged_image = cv2.morphologyEx(self.edged_image, cv2.MORPH_ERODE, kernel, iterations=1)
# 2. 轮廓查找与筛选
contours = cv2.findContours(self.edged_image.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = imutils.grab_contours(contours)
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:10]
# 3. 寻找车牌轮廓(四边形+宽高比)
plate_contour = None
for c in contours:
perimeter = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * perimeter, True)
if len(approx) == 4:
x, y, w, h = cv2.boundingRect(approx)
ar = w / float(h)
if 2.5 < ar < 5.0: # 车牌标准宽高比
plate_contour = approx
break
if plate_contour is None:
raise ValueError("未找到符合条件的车牌轮廓,请选择清晰的车牌图片!")
# 4. 生成轮廓标记图
self.contour_image = img.copy()
cv2.drawContours(self.contour_image, [plate_contour], -1, (0, 255, 0), 3)
# 5. 提取车牌区域(扩大边界,避免汉字被截断)
mask = np.zeros(self.gray_image.shape, dtype="uint8")
cv2.drawContours(mask, [plate_contour], -1, 255, -1)
mask = cv2.bitwise_and(self.edged_image, self.edged_image, mask=mask)
(x, y) = np.where(mask == 255)
if len(x) == 0 or len(y) == 0:
raise ValueError("车牌区域无有效特征,请重新选择图片!")
(top_x, top_y) = (np.max([0, np.min(x) - 5]), np.max([0, np.min(y) - 5]))
(bottom_x, bottom_y) = (
np.min([img.shape[0] - 1, np.max(x) + 5]), np.min([img.shape[1] - 1, np.max(y) + 5]))
self.plate_image = img[top_x:bottom_x + 1, top_y:bottom_y + 1]
# 6. OCR识别(汉字识别关键配置!)
self.status_var.set("正在识别字符(含汉字)...")
self.root.update_idletasks()
# 优化3:车牌图像预处理(专门针对汉字)
plate_gray = cv2.cvtColor(self.plate_image, cv2.COLOR_BGR2GRAY)
# 中值滤波去噪(保留汉字边缘,比高斯滤波更适合文字)
plate_gray = cv2.medianBlur(plate_gray, 3)
# 再次二值化,确保汉字黑白对比强烈
_, plate_binary = cv2.threshold(plate_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 优化4:OCR参数(专为汉字优化)
# --oem 1:LSTM引擎(对汉字识别效果远好于默认引擎)
# --psm 8:单个词模式(车牌整体是一个词,适合汉字+字母+数字组合)
# -l chi_sim:强制使用中文语言包
# tessedit_char_whitelist:只保留车牌可能的字符,减少干扰
custom_config = r'--oem 1 --psm 8 -l chi_sim -c tessedit_char_whitelist=' + CHAR_WHITELIST
self.recognition_result = pytesseract.image_to_string(plate_binary, config=custom_config).strip()
# 7. 结果后处理(过滤无效字符,确保汉字在前)
self.recognition_result = self._optimize_chinese_result(self.recognition_result)
# 8. 显示所有步骤和结果
self._display_all_steps()
final_result = self.recognition_result if self.recognition_result else "识别失败"
self.result_var.set(final_result)
self.status_var.set("识别完成!" if self.recognition_result else "识别失败:未匹配到有效车牌")
except Exception as e:
messagebox.showerror("识别错误", f"错误详情:{str(e)}\n请检查:1.中文语言包是否安装 2.图片是否清晰")
self.status_var.set("识别失败!")
def _optimize_chinese_result(self, result):
"""优化汉字识别结果:过滤无效字符,确保省份汉字在前"""
if not result:
return ""
# 仅保留白名单中的字符
filtered = [c for c in result if c in CHAR_WHITELIST]
filtered_result = "".join(filtered)
# 车牌规则:第一个字符必须是省份汉字,校验并调整
province_chars = "京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领"
if filtered_result and filtered_result[0] not in province_chars:
# 查找第一个省份汉字的位置并前移
for i, c in enumerate(filtered_result):
if c in province_chars:
filtered_result = filtered_result[i:] + filtered_result[:i]
break
return filtered_result
def _display_all_steps(self):
"""显示图像处理的所有步骤(原始图/灰度图/二值化/轮廓图/车牌区域)"""
# 示例:将各步骤图像转换为Tkinter可显示的格式(需结合UI组件实现)
# 假设self有label组件用于显示各步骤:self.original_label, self.gray_label等
def cv2_to_tk(img):
"""OpenCV图像转Tkinter PhotoImage"""
if len(img.shape) == 2: # 灰度图
img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)
else: # BGR转RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = Image.fromarray(img)
# 缩放图像适配UI
img = img.resize((200, 100), Image.Resampling.LANCZOS)
return ImageTk.PhotoImage(img)
# 显示各步骤图像
if hasattr(self, "original_label"):
self.original_label.config(image=cv2_to_tk(self.original_image))
self.original_label.image = cv2_to_tk(self.original_image)
if hasattr(self, "gray_label"):
self.gray_label.config(image=cv2_to_tk(self.gray_image))
self.gray_label.image = cv2_to_tk(self.gray_image)
if hasattr(self, "edged_label"):
self.edged_label.config(image=cv2_to_tk(self.edged_image))
self.edged_label.image = cv2_to_tk(self.edged_image)
if hasattr(self, "contour_label"):
self.contour_label.config(image=cv2_to_tk(self.contour_image))
self.contour_label.image = cv2_to_tk(self.contour_image)
if hasattr(self, "plate_label"):
self.plate_label.config(image=cv2_to_tk(self.plate_image))
self.plate_label.image = cv2_to_tk(self.plate_image)
def _display_all_steps(self):
"""显示所有步骤图像"""
self._display_image(self.original_image, self.step_labels[0])
self._display_image(self.gray_image, self.step_labels[1], is_gray=True)
self._display_image(self.blur_image, self.step_labels[2], is_gray=True)
self._display_image(self.edged_image, self.step_labels[3], is_gray=True)
self._display_image(self.contour_image, self.step_labels[4])
self._display_image(self.plate_image, self.step_labels[5])
def _display_image(self, img, label, is_gray=False):
"""图像显示函数(适配汉字显示)"""
label_w = label.winfo_width() or 300
label_h = label.winfo_height() or 200
h, w = img.shape[:2]
scale = min(label_w / w, label_h / h)
new_w, new_h = int(w * scale), int(h * scale)
img_resized = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)
if is_gray or len(img_resized.shape) == 2:
img_rgb = cv2.cvtColor(img_resized, cv2.COLOR_GRAY2RGB)
else:
img_rgb = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)
pil_img = Image.fromarray(img_rgb)
tk_img = ImageTk.PhotoImage(pil_img)
label.config(image=tk_img)
label.image = tk_img
def clear_all(self, keep_image=False):
"""清空所有结果"""
self.result_var.set("")
self.recognition_result = ""
self.gray_image = None
self.blur_image = None
self.edged_image = None
self.contour_image = None
start_idx = 1 if keep_image else 0
for label in self.step_labels[start_idx:]:
label.config(image="")
label.image = None
if not keep_image:
self.image_path = None
self.original_image = None
self.plate_image = None
self.status_var.set("请选择图片开始识别(确保Tesseract已安装中文语言包)")
self.recognize_btn.config(state=tk.DISABLED)
def main():
# 适配Windows高清屏
try:
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)
except:
pass
root = tk.Tk()
app = LicensePlateRecognitionApp(root)
root.mainloop()
if __name__ == "__main__":
main()