news 2026/9/15 16:51:52

PyTorch+OpenCV实现车牌识别:从两阶段检测到字符分类完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyTorch+OpenCV实现车牌识别:从两阶段检测到字符分类完整实战

简介:面向高校计算机相关专业学生的初级车牌识别完整项目,基于PyTorch与OpenCV实现,可作为期末大作业、课程设计和毕业设计的参考,也适合初次接触深度学习视觉任务的开发者动手练手。整个zip包共7个文件、25.38MB,其中3个Python脚本分别覆盖字符神经网络、车牌定位与整体识别流程,2个pth文件为已训练模型,下载后可直接加载使用,另有images素材和README文档,省去自己准备数据和训练模型的麻烦。项目源码经过本地编译调试,运行稳定,评审分为98,能够清晰演示图像预处理、车牌区域定位、字符分割和基于PyTorch的字符分类全过程,帮助读者从零理解一套完整车牌识别管线。目前已有64人学习下载,适合需要高完成度、可运行入门项目的学习者直接使用。

1. 从 char.pth 到 plate.pth:一套能跑通的车牌识别最小闭环

做车牌识别有很多现成的商用 SDK,但如果你只是想搞懂“从一张图到一串字符”中间到底发生了什么,又需要一份能过毕设答辩或者课程评审的完整代码,那手写一个 PyTorch + OpenCV 的初级项目是最划算的路径。这套项目源码里包含两个独立训练好的模型文件 char.pth(字符识别)和 plate.pth(车牌区域分类),外加三个核心 Python 脚本:charNeuralNet.py 负责字符级 CNN 的定义与推理、plateNeuralNet.py 负责车牌定位网络的构建、carPlateIdentity.py 把两者串成完整流程。值得注意的是,作者没有用 YOLO 这类大而全的检测框架,而是选择了两阶段串联方案——先粗定位车牌区域,再逐字符分类,这在计算资源有限、项目周期短的应用场景里非常实用。适合正在做期末大作业、本科毕设或者想快速上手 OCR 类项目的开发者参考。

项目的推理主线如下: 输入图像 → plateNeuralNet 定位候选区 → 按坐标裁剪车牌 → 字符分割 → charNeuralNet 逐字符识别 → 输出车牌字符串

整个链路清晰,没有黑盒,模型权重和源码都是本地编译调试过的,拿到的压缩包解压后即可运行,属于典型的“低成本高效率”的课程设计结构。接下来我会从网络设计、训练数据组织、推理管道到调参陷阱,逐个环节拆开讲。

2. 网络结构与模型文件:为什么是两段式而不是端到端

先看文件清单里最关键的三个东西:plateNeuralNet.pycharNeuralNet.py以及两个.pth权重文件。要理解这套项目的设计思路,必须先搞清楚它为什么要分成“车牌检测”和“字符识别”两个独立网络,而不是用一两个端到端模型把活全干了。

2.1 两段式架构的选型逻辑

端到端的车牌识别通常采用检测 + 识别的联合模型,比如 LPRNet,它用一个 CNN + RNN + CTC 的结构直接从车牌图像输出字符串。这种方式训练复杂、需要大量标注数据,对初学者不友好。而这套项目用的是“先定位、再分类”的传统两段式思路:

  • 第一段:plateNeuralNet对整张输入图做二分类或区域回归,输出车牌的大致位置(通常用滑动窗口或简单的区域建议)。
  • 第二段:charNeuralNet对裁剪出来的车牌图做字符分类,识别每个位置的汉字、字母和数字。

这种拆分的好处有三个:一是每个网络的任务简单,收敛快;二是可以分别调试,定位不准时不用动识别模型;三是对 CPU 推理友好,两个小网络串联的耗时远低于一个大型端到端模型。

carPlateIdentity.py 里的核心调用顺序: # 1. 加载两个模型 plate_model = PlateNet() # 内部加载 plate.pth char_model = CharNet() # 内部加载 char.pth # 2. 车牌区域定位 candidates = plate_model.detect(frame) # 返回候选框坐标 # 3. 字符分割与识别 for box in candidates: plate_roi = crop_image(frame, box) chars = segment_chars(plate_roi) result = "".join([char_model.predict(c) for c in chars])

2.2 字符集的设计——为什么识别结果带省份简称

中国车牌的第一个字符是省份汉字简称(如“京”“沪”“苏”),第二个是发牌机关字母,后面是字母和数字的混合。charNeuralNet的类别数不是 36(26 字母 + 10 数字),而是 31 个省份汉字 + 24 个字母(排除 I 和 O,避免与 1 和 0 混淆)+ 10 个数字,总共 65 类。

2.2.1 模型输入尺寸与预处理

字符识别网络一般接收 20x20 或 24x24 的灰度图。这里的关键在于——OpenCV 读入的图像是 BGR 通道顺序,而 PyTorch 的预训练模型如果用了 RGB 顺序,会导致颜色通道错位,直接影响识别准确率。所以charNeuralNet.py中做了显式的通道转换:

import cv2 import torch import numpy as np def preprocess_char_image(img, size=(20, 20)): # 转灰度、缩放、归一化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) resized = cv2.resize(gray, size, interpolation=cv2.INTER_AREA) # 归一化到 [0, 1] 并转为 PyTorch 张量 tensor = torch.from_numpy(resized / 255.0).float() # 添加 batch 和 channel 维度:shape 变为 (1, 1, 20, 20) tensor = tensor.unsqueeze(0).unsqueeze(0) return tensor

这里把灰度图缩放成 20x20 是经验值,因为有研究对比过,字符识别用 20x20 或 24x24 在精度和速度上最均衡,再大收益很小,反而增加计算量。INTER_AREA插值适合缩小图像,能有效减少锯齿和噪声。

2.2.2 训练时怎么组织数据

数据集的组织方式是靠着文件夹命名来完成的。训练时每个字符的样本放在以该字符命名的文件夹下,比如dataset/char/京/*.jpgdataset/char/A/*.jpg,然后按类别编号映射到 one-hot 标签。这个映射关系保存在charNeuralNet.py的类内常量中,推理时靠torch.max(output, dim=1)取出最大概率对应的类别索引,再反向查字符表。

字符表索引示例: 0 -> "京", 1 -> "沪", 2 -> "苏", ..., 31 -> "A", 32 -> "B", ...

注意,车牌字符识别和一般 OCR 不同的地方在于:字符数量少、字体相对统一、背景干扰大(螺丝钉、铆钉、反光、污渍)。所以训练数据要尽量贴近真实拍摄场景,不然就算模型在测试集上准确率 99%,放到实拍图上可能掉到 60%。

2.3 模型文件与源码的对应关系

plate.pthchar.pth是两个已经训练好的权重文件,直接由对应的网络定义脚本加载。加载时要小心 PyTorch 版本差异导致的序列化格式不兼容。

如果本地运行报错KeyError: 'xxx'或者提示权重形状不匹配,大概率是网络定义和训练时的结构不一致。常见原因是修改了nn.Conv2d的通道数或全连接层的输入维度。每次改动网络结构后,必须重新训练,不能复用旧权重。

3. 车牌定位实战:从 OpenCV 形态学到 plateNeuralNet 推理

车牌定位是整个系统里最容易翻车的环节。很多初学者在这里会遇到两个棘手问题:一是把非车牌区域(比如空调外机、广告牌上的白底黑字)误检成车牌;二是车牌在画面里的尺度变化大,近处大、远处小,单一尺度的检测方法会漏检。所以plateNeuralNet.py的做法是先用 OpenCV 的传统图像处理手段做候选区域筛选,再送入训练好的 PyTorch 分类网络做确认。

3.1 传统图像处理的候选区生成

基于颜色的定位处理流程: 1. 将 BGR 图像转换到 HSV 空间 2. 设定蓝色车牌和黄色车牌的 HSV 阈值范围,生成二值掩码 3. 执行形态学闭运算,填补字符间隙 4. 用 cv2.findContours 找轮廓,滤除宽高比不符合车牌比例的轮廓 5. 对候选区域做透视校正,送到 plateNeuralNet 确认
import cv2 import numpy as np def get_plate_candidates(image): # 转换到 HSV 颜色空间,颜色分割对光照变化比 RGB 更鲁棒 hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 蓝色车牌阈值范围:H 在 100~124,S 和 V 根据光照情况调整 blue_mask = cv2.inRange(hsv, (100, 80, 80), (124, 255, 255)) # 黄色车牌阈值范围:H 在 15~35 yellow_mask = cv2.inRange(hsv, (15, 80, 80), (35, 255, 255)) mask = cv2.bitwise_or(blue_mask, yellow_mask) # 闭运算:先膨胀后腐蚀,把字符之间的缝隙填上 kernel = np.ones((5, 5), np.uint8) closed = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 找轮廓 contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidates = [] for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) aspect_ratio = w / h # 标准车牌宽高比约为 3.14(440mm / 140mm),允许 2.0~4.5 的浮动 if 2.0 < aspect_ratio < 4.5 and w > 60 and h > 20: candidates.append((x, y, w, h)) return candidates

参数选择的逻辑:HSV 阈值用的是窄范围。因为蓝色背景的车牌在 HSV 中 H 分量稳定在 110 左右,S 和 V 会随光照波动,所以把 S 和 V 的下限放宽到 80,上限取 255。形态学核大小选 5x5,这是经验值——核心了字符间隙就填不上,大了会把相邻的两个候选区粘连成一块。宽高比阈值参考中国车牌国标尺寸算出来的,一般轿车车牌 440mm x 140mm,比例约 3.14。用RETR_EXTERNAL只取最外层轮廓,避免提取到车牌内部的字符轮廓。

3.2 分类网络对候选区的二次确认

OpenCV 的规则只能筛掉比例明显不对的区域,但无法区分“蓝色的矩形区域”和“真正的车牌”。第二步把每个候选区域缩放到固定大小(比如 64x32),送入plateNeuralNet,输出该区域“是车牌”的概率。

import torch import torch.nn as nn class PlateNet(nn.Module): def __init__(self): super(PlateNet, self).__init__() # 输入是 64x32 的单通道灰度图 self.features = nn.Sequential( nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), # 32x16 nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), # 16x8 nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(2), # 8x4 ) self.classifier = nn.Sequential( nn.Flatten(), nn.Linear(64 * 8 * 4, 128), nn.ReLU(inplace=True), nn.Dropout(0.5), nn.Linear(128, 2), # 二分类:车牌 / 非车牌 ) def forward(self, x): return self.classifier(self.features(x))

Flatten之后的输入维度计算方式:最后一层池化输出特征图为 8x4,通道数为 64,展平后是 64x8x4=2048 维。改动输入图像尺寸时这些数字要跟着调整,否则全连接层直接报维度错误。

3.2.1 为什么在分类器里加 Dropout

这个二分类网络不复杂,但训练数据里“长得像车牌的负样本”非常多,比如蓝色的广告牌、反光的车身贴纸。如果不加Dropout(0.5),网络很容易过拟合到训练集中的特定背景纹理上,测试时误检率会明显偏高。0.5 是 PyTorch 里最常用的默认值,对中间层来说不会过度抑制特征表达。

3.3 透视校正:倾斜车牌怎么处理

自然场景下拍到的是车辆可能偏在画面一侧,车牌存在透视畸变。carPlateIdentity.py里用cv2.getPerspectiveTransform做四点校正,把倾斜的车牌“拉直”,这直接决定了后续字符分割的准确性。具体做法是取候选框四个顶点,映射到一个固定的 240x80 矩形。

def rectify_plate(image, pts): # pts 是车牌四个角点,顺序:左上、右上、右下、左下 src = np.array(pts, dtype=np.float32) dst = np.array([[0, 0], [240, 0], [240, 80], [0, 80]], dtype=np.float32) # 计算透视变换矩阵 M = cv2.getPerspectiveTransform(src, dst) warped = cv2.warpPerspective(image, M, (240, 80)) return warped

透视校正失败的典型表现是字符被拉伸或者压缩变形,常见原因是角点标注顺序混乱。cv2.getPerspectiveTransform要求输入的四点顺序是按顺时针或逆时针排列的,如果源点和目标点的对应关系错乱,矩阵计算不会报错,但输出图是一团乱麻。

4. 字符分割与识别:carPlateIdentity.py 的完整推理流程

车牌区域定位并校正之后,剩下的就是字符分割和逐字符识别。字符分割这一步的所有问题都出在二值化上,因为车牌字符有蓝底白字、黄底黑字、绿底黑字等不同配色组合,一个固定阈值根本搞不定所有情况。模块里采用 Otsu 自适应阈值作为主力方案,配合形态学轮廓分析,按“连通域外接矩形”的规则把字符挨个取出来。

4.1 Otsu 二值化与形态学清理

Otsu 的思想是自动找一个阈值,让前景和背景两类像素的类间方差最大。对于光照不均匀的车牌图,固定阈值 127 可能让字符和背景糊在一起,而 Otsu 能根据整张图的灰度分布算出当前最合理的分割点。字符分割前做的处理顺序很重要,几个步骤的顺序颠倒会直接导致字符粘连或者断裂。

import cv2 import numpy as np def segment_chars(plate_img): # 1. 转灰度并小幅高斯模糊,去除传感器噪声 gray = cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (3, 3), 0) # 2. Otsu 自适应二值化:大津法,自动计算阈值 _, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # 3. 反色,让字符为白色、背景为黑色,便于 findContours 提取 binary = cv2.bitwise_not(binary) # 4. 找轮廓并筛选每一个字符区域 contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) char_boxes = [] for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) # 字符宽高比约在 0.3~0.8 之间,且高至少占车牌高的 50% if h > plate_img.shape[0] * 0.5 and 0.2 < (w / h) < 0.9: char_boxes.append((x, y, w, h)) # 5. 按 x 坐标从左到右排好,还原车牌字符顺序 char_boxes.sort(key=lambda b: b[0]) return char_boxes

Otsu 的参数求的是灰度直方图的峰谷位置,但对上下两行结构的车牌(比如武警车牌)不适用,这类多行字符的场合要先把上下行拆开,再分别对每行做 Otsu。另外字符分割后如果发现某个字符被切成两半,大多是因为二值化之后字符内部出现空洞,可以先做一次cv2.morphologyEx(binary, cv2.MORPH_CLOSE, np.ones((2,2)))填洞。

4.2 完整推理主循环:从视频帧或图片到输出字符串

carPlateIdentity.py是整个项目的入口模块,它把前面说到的定位、校正、分割、识别串成一个完整管道。对外既能处理单张图片也能处理视频流。

import cv2 import torch from plateNeuralNet import PlateNet from charNeuralNet import CharNet class CarPlateRecognizer: def __init__(self, plate_weights="plate.pth", char_weights="char.pth"): # 初始化两个网络并加载权重 self.plate_net = PlateNet() self.char_net = CharNet() self.plate_net.load_state_dict(torch.load(plate_weights, map_location="cpu")) self.char_net.load_state_dict(torch.load(char_weights, map_location="cpu")) self.plate_net.eval() self.char_net.eval() def recognize(self, frame): # 1. 候选区生成与分类确认 candidates = get_plate_candidates(frame) plates = [] for (x, y, w, h) in candidates: roi = frame[y:y+h, x:x+w] roi_resized = cv2.resize(roi, (64, 32)) roi_gray = cv2.cvtColor(roi_resized, cv2.COLOR_BGR2GRAY) tensor = torch.from_numpy(roi_gray / 255.0).float().unsqueeze(0).unsqueeze(0) with torch.no_grad(): prob = torch.softmax(self.plate_net(tensor), dim=1) if prob[0][1] > 0.9: # 置信度大于 90% 才确认为车牌 plates.append((x, y, w, h)) # 2. 对每个确认的车牌区域做字符识别 results = [] for (x, y, w, h) in plates: roi = frame[y:y+h, x:x+w] warped = rectify_plate(roi, get_corners(roi)) char_boxes = segment_chars(warped) plate_str = "" for (cx, cy, cw, ch) in char_boxes: char_img = warped[cy:cy+ch, cx:cx+cw] # 字符图像前处理,统一到 20x20 char_tensor = preprocess_char_image(char_img) with torch.no_grad(): logits = self.char_net(char_tensor) pred_idx = torch.argmax(logits, dim=1).item() plate_str += self.char_net.idx_to_char[pred_idx] results.append((plate_str, (x, y, w, h))) return results

这段代码里有一个关键点:模型初始化后必须调用.eval()。PyTorch 的 dropout 和 batchnorm 在训练和推理时的行为不同,不切到 eval 模式的话,dropout 仍然会随机丢弃神经元输出,导致同样的输入每次跑出来的结果都不一样。这是新手排查识别结果“忽好忽坏”时最容易忽视的原因。

置信度阈值 0.9 不是拍脑袋定的。根据模型的泛化能力来调,如果验证集误检率偏高就往上调(比如 0.95),如果漏检多就往下放(比如 0.8)。阈值只影响定位阶段的取舍,不会影响字符识别阶段——这两个环节解耦之后,调参时就有了明确的抓手。

4.3 模型切换评估模式与推理加速点

with torch.no_grad():块可以关闭梯度记录,推理速度提升约 2 到 3 倍。同时map_location="cpu"很重要,如果模型是在 GPU 机器上训练的,直接torch.load到无 GPU 环境会报错。这个参数让权重即使在缺少 CUDA 的机器上也能加载。

5. 训练数据与模型重训:怎么把 accuracy 做到 98 分

这个项目自带训练好的权重,但对于毕设来说,光“能跑通”还不够,答辩时老师大概率会追问数据来源和训练过程。这一章讲清楚如何自己准备数据、复现训练,以及陷阱在哪。

5.1 数据增强与数据集划分

字符识别模型的训练数据不需要太多,但要保证多样性。常见做法是用开源车牌字符数据集(比如 CCPD 或 CRPD 做字符级裁剪),加上自己用程序合成的样本。合成数据的方法是把不同字体、不同大小、不同噪声的字符贴到蓝色或黄色背景上,模拟自然拍摄条件。训练前做数据增强能显著提升模型的鲁棒性:

import torchvision.transforms as transforms train_transform = transforms.Compose([ transforms.RandomAffine(degrees=(-15, 15), translate=(0.1, 0.1), scale=(0.8, 1.2)), transforms.ColorJitter(brightness=0.3, contrast=0.3), transforms.ToTensor(), ])

RandomAffine的随机旋转角度设为 ±15 度,这是模仿车辆在画面里轻微倾斜的情况。超过 15 度会让字符语义模糊,且你的归一化网络在见过更极端角度之后,反而会混淆“1”和“7”这种外形接近的字符。ColorJitter的亮度和对比度扰动幅度 0.3 是常用值,模拟白天和傍晚不同光照环境下图像的差异。

5.1.1 训练损失与优化器选择

车牌字符分类是一个标准的 65 类分类任务,损失函数用交叉熵。优化器在项目场景下优先选 Adam,因为它对学习率的敏感度低,骨架阶段不太容易出现训练发散。但 Adam 收敛后的精度一般比 SGD + momentum 略低,如果想冲更高的准确率,后期可以切到 SGD。

import torch.nn as nn from torch.optim import Adam criterion = nn.CrossEntropyLoss() optimizer = Adam(model.parameters(), lr=1e-3, weight_decay=1e-4) # 训练循环关键步骤 for epoch in range(30): running_loss = 0.0 for images, labels in train_loader: optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item()

学习率 1e-3 针对小规模数据集是安全起点,过大会导致 loss 震荡,过小收敛速度慢。weight_decay设置为 1e-4 做 L2 正则化,防止模型过度依赖训练数据中的噪声特征。训练 30 个 epoch 对字符识别这种任务来说足够了——再跑更多轮数,验证集准确率通常会进入平台期,甚至开始出现过拟合。

5.2 在低配置环境上重训时怎么做

如果你的机器没有 NVIDIA 显卡,训练效率会低一些,但字符识别网络很小,用 CPU 训练也完全扛得住。关键点是torch.load时的weights_only=False参数,新版 PyTorch 默认只加载张量,跳过非张量对象,可能导致部分模块加载失败。

# 用 CPU 训练的方式 python train_char.py --device cpu --batch_size 64 --epochs 30

批量大小选 64 是权衡了稳定性与速度的结果。更小的 batch 会引入更多梯度噪声,但有时能帮助模型跳出局部极小值;更大的 batch 虽然稳定,但显存或内存占用更大,且在小数据集上容易收敛到平坦的极小值附近,泛化能力变差。

6. 模型部署与换用场景的适配技巧

模型在开发机上跑通还只到一半,集成到真实业务或演示环境时,推理性能和兼容性才是最先暴露问题的地方。

6.1 用 OpenCV DNN 模块替代 PyTorch 推理

如果目标环境没有安装 PyTorch,或者板卡上的资源根本负担不起 PyTorch 运行时,可以考虑把训练好的网络导出成 OpenCV 的 ONNX 格式,然后用cv2.dnn做前向推理。.pth是 PyTorch 的原生权重格式,OpenCV 读不了这种格式,需要先导出为 ONNX。

import torch from charNeuralNet import CharNet model = CharNet() model.load_state_dict(torch.load("char.pth", map_location="cpu")) model.eval() # 构造一个固定尺寸的 dummy 输入 dummy_input = torch.randn(1, 1, 20, 20) torch.onnx.export( model, dummy_input, "char.onnx", opset_version=11, input_names=["input"], output_names=["output"], )

导出成功后,OpenCV 侧加载并推理的方法如下:

import cv2 import numpy as np net = cv2.dnn.readNetFromONNX("char.onnx") # 预处理方式要与训练时完全一致 img = cv2.imread("char_sample.jpg", cv2.IMREAD_GRAYSCALE) img = cv2.resize(img, (20, 20)) blob = cv2.dnn.blobFromImage(img / 255.0, size=(20, 20)) net.setInput(blob) output = net.forward() pred_idx = np.argmax(output, axis=1)[0]

.pth.onnx有一条要注意的路径:不同 PyTorch 版本导出的 ONNX 算子集兼容性不一致,装 OpenCV 的机器上如果readNetFromONNX报 “Unsupported operator” 之类的错误,通常是算子版本太新,导出时把opset_version调低到 9 或者 11 大概率能解决。

6.2 在不同分辨率摄像头上的适配

车牌识别部署中最隐蔽的性能杀手是输入图像分辨率。开发时用 1080p 的静态图调通流程,但实际接入摄像头后,画面分辨率和视场角不同,车牌在画面中所占像素差异很大。处理策略是:在送入plateNeuralNet前,先检查候选框的像素宽度,如果小于 60px 就做一次区域放大再识别。

def adaptive_recognize(frame, box): x, y, w, h = box # 如果车牌区域分辨率过低,先放大再识别 if w < 100: scale = 100 / w roi = frame[y:y+h, x:x+w] roi = cv2.resize(roi, (int(w * scale), int(h * scale))) else: roi = frame[y:y+h, x:x+w] # 其余流程不变 ...

这里 100 像素是经验阈值——绝大多数字符分割算法在字符宽度低于 10 像素时,Otsu 二值化出来会丢笔画,识别率急速下降。放大时用cv2.INTER_CUBIC会比INTER_AREA保留更多边缘细节,放大倍率较大时效果更好。

6.3 调试时最重要的验证命令

把整个管道拆开验证,不要一把梭跑完整流程。下面这条命令直接跳过定位和分割,只测 char.pth 的识别能力,用来自查到底是网络的锅还是前处理的锅。

# 单独测试一个字符图像 python -c " from charNeuralNet import CharNet import cv2, torch model = CharNet() model.load_state_dict(torch.load('char.pth', map_location='cpu')) model.eval() img = cv2.imread('test_char.jpg', cv2.IMREAD_GRAYSCALE) img = cv2.resize(img, (20, 20)) t = torch.from_numpy(img/255.).float().unsqueeze(0).unsqueeze(0) with torch.no_grad(): pred = model(t).argmax(dim=1).item() print(model.idx_to_char[pred]) "

如果单独字符识别正确但整牌识别错误,问题出在字符分割环节;如果整牌从定位就错了,问题出在plateNeuralNet或者形态学参数上。这个定位思路在调试时能省下大量时间。

6.4 绿色新能源车牌的兼容处理

目前路面上的绿色车牌格式是 8 位(省份简称 + 字母 + 6 位数字/字母),而传统蓝色车牌是 7 位。如果你要处理的场景里有新能源车,需要额外注意:定位阶段的 HSV 阈值要增加绿色范围(H 约 50~80),字符识别的类别数也要相应增加——否则绿牌后面的第 8 位字符会直接被丢弃,输出结果为 7 位字符串,和实际车牌不一致。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 16:51:13

用DAX Studio导出Power BI百万级数据:告别复制表,高效生成CSV

做 Power BI 的人应该都遇到过这种场景&#xff1a;表里明明有上百万行明细&#xff0c;业务方一句“把数据导出来发我”&#xff0c;你打开 Power BI 的“数据”视图&#xff0c;右键复制&#xff0c;粘到 Excel 里&#xff0c;结果要么只复制了当前屏幕显示的几千行&#xff…

作者头像 李华
网站建设 2026/9/15 16:50:39

Kubernetes 测试策略实战指南:从测试金字塔到 Prow CI 作业设计

Kubernetes 测试策略实战指南&#xff1a;从测试金字塔到 Prow CI 作业设计 【免费下载链接】community Kubernetes Community Documentation 项目地址: https://gitcode.com/GitHub_Trending/com/community 本文基于 Kubernetes Community 仓库中的 testing-strategy.m…

作者头像 李华
网站建设 2026/9/15 16:49:28

Python分析B站播放量:从数据采集到可视化实战

直接打开搜索引擎敲"python刷B站播放量"&#xff0c;能看到一堆脚本&#xff0c;有的号称"多线程换IP稳如老狗"&#xff0c;有的截图晒着后台播放量的涨幅曲线。作为一个写了多年Python、也在B站传过视频的人&#xff0c;我在开头先把话说死&#xff1a;这…

作者头像 李华
网站建设 2026/9/15 16:49:05

Pascal Context数据集处理全指南:MAT转PNG并接入PyTorch

做语义分割、场景解析这类任务&#xff0c;Pascal Context是个绕不开的数据集。我第一次接触它是在复现一篇上下文感知分割论文的时候&#xff0c;当时最头疼的不是模型结构本身&#xff0c;而是这个数据集的“数据准备”环节&#xff1a;官方给的标注是MATLAB的.mat文件&#…

作者头像 李华
网站建设 2026/9/15 16:48:07

VeraCrypt 加密卷怎么给 Docker 数据加密?原理与落地实操

VeraCrypt 加密卷怎么给 Docker 数据加密&#xff1f;原理与落地实操 【免费下载链接】VeraCrypt Disk encryption with strong security based on TrueCrypt 项目地址: https://gitcode.com/GitHub_Trending/ve/VeraCrypt 先说结论&#xff1a;把 VeraCrypt 加密卷挂在…

作者头像 李华