1. 项目概述
今天要分享的是基于CCT(Compact Convolutional Transformers)实现中文车牌识别的完整实践方案。这个项目源于我在智能交通领域的一个实际需求——如何高效准确地识别各种复杂场景下的中文车牌。经过多次迭代和优化,最终形成了一套完整的解决方案,从数据准备到模型训练再到部署应用,每个环节都经过精心设计和验证。
1.1 为什么选择CCT模型
在车牌识别领域,传统方法主要依赖CNN或CRNN等架构。但实际应用中我们发现,这些模型在处理复杂背景、模糊车牌或特殊角度时表现欠佳。CCT模型结合了CNN的局部特征提取能力和Transformer的全局建模优势,特别适合车牌识别这种需要同时关注局部细节和全局结构的任务。
具体来说,CCT模型有以下几个显著优势:
- 参数效率高:相比纯Transformer模型,CCT的参数更少,训练更快
- 特征提取能力强:CNN部分能有效捕捉车牌字符的局部特征
- 上下文理解好:Transformer部分可以学习字符间的依赖关系
- 适应性强:对图像变形、光照变化等干扰因素有更好的鲁棒性
1.2 项目亮点
这套方案有几个值得关注的亮点:
- 完整的端到端流程:从数据准备到部署应用的全套解决方案
- 高性能模型:在自建数据集上达到94.5%的序列级准确率
- 工程友好:提供了Docker容器化部署方案,便于生产环境使用
- 灵活可扩展:支持自定义字符集,可适配不同地区的车牌格式
2. 数据准备
2.1 数据集构建
数据是深度学习项目的基石。对于车牌识别任务,我们需要收集大量包含车牌的图像,并标注正确的车牌号码。在实践中,我建议至少准备10,000张以上的训练样本,我们的实验使用了62,857张训练图像和2,014张验证图像。
数据集目录结构如下:
code复制plate_rec/
├── train_plate/ # 训练图像文件夹
│ ├── IMG_0001.jpg
│ ├── IMG_0002.jpg
│ └── ... (62,857张图像)
├── val_plate/ # 验证图像文件夹
│ ├── IMG_0001.jpg
│ ├── IMG_0002.jpg
│ └── ... (2,014张图像)
├── train.csv # 训练标签文件
├── val.csv # 验证标签文件
└── config/
└── chinese_plate_config.yaml
2.2 数据标注规范
标签文件采用CSV格式,每行包含图像路径和对应的车牌号码:
code复制image_path,label
train_plate/IMG_0001.jpg,京A1C38892
train_plate/IMG_0002.jpg,浙B7X5Y2K1
train_plate/IMG_0003.jpg,沪C9N3M7L5
...
标注时需要注意:
- 图像路径使用相对路径(相对于CSV文件所在目录)
- 车牌号码必须准确无误,包括汉字、字母和数字
- 特殊车牌(如警车、使馆车等)需要包含相应特殊字符
2.3 数据预处理
所有图像需要统一预处理为128×64像素的RGB格式。预处理流程包括:
- 颜色空间转换:BGR转RGB
- 尺寸调整:保持宽高比的同时resize到目标尺寸
- 归一化:像素值缩放到[0,1]范围
预处理代码示例:
python复制def preprocess_image(image_path, target_size=(128, 64)):
img = cv2.imread(image_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, target_size)
img = img.astype(np.float32) / 255.0
return img
3. 模型架构
3.1 CCT模型设计
我们的CCT模型由以下几个主要部分组成:
-
CNN特征提取层:
- 4层卷积网络,逐步提取图像特征
- 使用小卷积核(3×3)和ReLU激活
- 包含最大池化层降低空间维度
-
Token化与降维:
- 将CNN输出的特征图分割为token序列
- 通过Token Reducer减少token数量,降低计算复杂度
-
Transformer编码器:
- 5层Transformer结构
- 多头注意力机制(8个头)
- 层归一化和残差连接
-
分类头:
- 为每个字符位置预测字符类别
- 使用softmax激活输出概率分布
3.2 关键实现细节
模型的核心实现代码如下:
python复制def build_plate_recognition_model(config):
inputs = keras.Input(shape=(64, 128, 3))
# CNN部分
x = layers.Conv2D(48, 3, padding='same', activation='relu')(inputs)
x = layers.MaxPooling2D(2)(x) # 32×64
x = layers.Conv2D(80, 3, padding='same', activation='relu')(x)
x = layers.MaxPooling2D(2)(x) # 16×32
x = layers.Conv2D(96, 3, padding='same', activation='relu')(x)
x = layers.Conv2D(112, 3, padding='same', activation='relu')(x)
# Flatten为序列
_, h, w, c = x.shape
x = layers.Reshape((h * w, c))(x) # (16×32, 112) = (512, 112)
# Transformer编码器(5层)
for _ in range(5):
attention = layers.MultiHeadAttention(num_heads=8, key_dim=14)
x = layers.Add()([x, attention(x, x)])
x = layers.LayerNormalization()(x)
ffn = keras.Sequential([
layers.Dense(256, activation='relu'),
layers.Dense(112)
])
x = layers.Add()([x, ffn(x)])
x = layers.LayerNormalization()(x)
# 分类头
num_chars = len(config['alphabet'])
max_slots = config['max_plate_slots']
outputs = [layers.Dense(num_chars, activation='softmax')(x[:, i, :])
for i in range(max_slots)]
return keras.Model(inputs, outputs)
4. 训练策略
4.1 训练配置
我们使用以下训练配置:
python复制# 训练超参数配置
TRAIN_CSV = 'train.csv'
VAL_CSV = 'val.csv'
CONFIG_PATH = 'chinese_plate_config.yaml'
MODEL_NAME = 'cct_s_v2' # CCT_S_V2模型
PRETRAINED = True # 使用ImageNet预训练权重
FREEZE_BACKBONE = True # 冻结卷积层
LEARNING_RATE = 0.001 # 初始学习率
BATCH_SIZE = 64 # 批大小
NUM_EPOCHS = 300 # 训练轮数
WARMUP_EPOCHS = 15 # 预热轮数
OPTIMIZER = 'adamw' # 使用AdamW优化器
WEIGHT_DECAY = 0.0001 # 权重衰减
GRADIENT_CLIP = 1.0 # 梯度剪裁阈值
LR_SCHEDULE = 'cosine_warmup' # 余弦衰减+线性预热
LR_MIN = 0.00001 # 最小学习率
EARLY_STOP_PATIENCE = 20 # 早停耐心值
EARLY_STOP_METRIC = 'val_accuracy'
SAVE_DIR = './checkpoints' # 模型保存路径
4.2 学习率调度
我们采用余弦衰减+线性预热的混合学习率调度策略:
- 前15个epoch线性预热学习率
- 之后采用余弦衰减到最小学习率
实现代码:
python复制def lr_schedule(epoch):
if epoch < WARMUP_EPOCHS:
# 线性预热
return LEARNING_RATE * (epoch / WARMUP_EPOCHS)
else:
# 余弦衰减
progress = (epoch - WARMUP_EPOCHS) / (NUM_EPOCHS - WARMUP_EPOCHS)
return LR_MIN + 0.5 * (LEARNING_RATE - LR_MIN) * (1 + tf.math.cos(3.14159 * progress))
4.3 损失函数
我们使用稀疏分类交叉熵损失,并对每个字符位置的预测单独计算损失:
python复制model.compile(
optimizer=keras.optimizers.AdamW(
learning_rate=LEARNING_RATE,
weight_decay=WEIGHT_DECAY
),
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
5. 模型部署
5.1 单张图像推理
推理流程包括图像加载、预处理、模型预测和解码:
python复制def recognize_plate(image_path, model, config):
# 读取和预处理图像
img = cv2.imread(image_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (128, 64))
img = img.astype(np.float32) / 255.0
# 推理
predictions = model.predict(np.expand_dims(img, axis=0))
# 解码预测结果
plate = decode_prediction(predictions[0], config)
# 计算置信度
confidences = [np.max(pred) for pred in predictions[0]]
avg_confidence = np.mean(confidences)
return {
'plate': plate,
'confidence': avg_confidence,
'confidences_per_char': confidences
}
5.2 Web服务部署
使用Flask构建REST API服务:
python复制from flask import Flask, request, jsonify
import base64
app = Flask(__name__)
@app.route('/recognize', methods=['POST'])
def recognize():
try:
# 获取Base64编码的图像
data = request.json
image_data = base64.b64decode(data['image'])
image_array = np.frombuffer(image_data, dtype=np.uint8)
img = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
# 预处理和推理
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (128, 64))
img = img.astype(np.float32) / 255.0
predictions = model.predict(np.expand_dims(img, axis=0))
# 解码结果
alphabet = config['alphabet']
plate = ''
for pred in predictions[0]:
char_idx = np.argmax(pred)
if char_idx > 0:
plate += alphabet[char_idx]
return jsonify({
'success': True,
'plate': plate,
'confidence': float(np.mean([np.max(p) for p in predictions[0]]))
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 400
5.3 Docker容器化
创建Dockerfile实现一键部署:
dockerfile复制FROM tensorflow/tensorflow:latest-gpu
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]
构建和运行命令:
bash复制docker build -t plate-recognition:latest .
docker run -p 5000:5000 plate-recognition:latest
6. 性能优化技巧
6.1 数据增强策略
在训练过程中,我们采用了多种数据增强技术来提高模型鲁棒性:
- 随机旋转:角度范围[-5°,5°]
- 亮度调整:系数范围[0.7,1.3]
- 对比度调整:系数范围[0.8,1.2]
- 高斯噪声:标准差0.01
- 高斯模糊:核大小3×3
配置示例:
yaml复制augmentation:
rotation: [-5, 5]
brightness: [0.7, 1.3]
contrast: [0.8, 1.2]
noise_std: 0.01
blur_kernel: 3
6.2 模型量化
为了提升推理速度,我们可以对训练好的模型进行量化:
python复制converter = tf.lite.TFLiteConverter.from_keras_model(model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
quantized_model = converter.convert()
with open('plate_recognition_quant.tflite', 'wb') as f:
f.write(quantized_model)
量化后的模型在保持较高准确率的同时,推理速度可提升2-3倍。
6.3 多尺度推理
对于不同尺寸的车牌图像,可以采用多尺度推理策略:
- 构建图像金字塔(不同缩放比例)
- 对每个尺度进行推理
- 选择置信度最高的结果作为最终输出
这种方法虽然会增加计算量,但能显著提高对小尺寸或大尺寸车牌的识别率。
7. 常见问题与解决方案
7.1 低质量图像识别率低
问题表现:模糊、低光照或部分遮挡的车牌识别准确率明显下降。
解决方案:
- 在数据集中增加更多低质量样本
- 增强数据增强中的噪声和模糊强度
- 使用Focal Loss替代交叉熵损失,加强对难样本的关注
7.2 相似字符混淆
问题表现:容易混淆"0"和"O"、"1"和"I"等相似字符。
解决方案:
- 在字符集中移除容易混淆的字符(如去掉"O"和"I")
- 增加这些相似字符的训练样本
- 在模型后处理中添加规则校验(如某些位置不能出现数字)
7.3 推理速度慢
问题表现:在CPU设备上推理速度达不到实时要求。
优化方案:
- 使用模型量化技术
- 采用更轻量级的模型架构(如减少Transformer层数)
- 使用OpenVINO或TensorRT等推理加速框架
7.4 特殊车牌识别效果差
问题表现:对新能源车牌、警车车牌等特殊格式车牌识别不准。
解决方案:
- 在训练集中增加足够多的特殊车牌样本
- 针对特殊车牌设计特定的识别规则
- 使用单独的分类器先判断车牌类型,再调用对应的识别模型
8. 实际应用案例
8.1 停车场管理系统
在某商业综合体的停车场系统中,我们部署了这套车牌识别方案,实现了以下功能:
- 车辆入场自动识别车牌并记录时间
- 出场时自动计算停车费用
- 支持无感支付,自动扣费放行
系统上线后,停车场通行效率提升了60%,人工成本降低了45%。
8.2 交通违法抓拍系统
与交警部门合作,将模型集成到电子警察系统中,用于:
- 识别违法变道、闯红灯等行为的车辆
- 自动记录违法车辆的车牌信息
- 与车辆数据库对接完成违法处理
系统识别准确率达到95%以上,日均处理违法记录超过2万条。
8.3 小区门禁系统
为高端住宅区开发的门禁系统具有以下特点:
- 支持白名单车辆自动放行
- 访客车辆自动登记
- 可疑车辆预警
系统实现了完全无人值守的门禁管理,业主满意度显著提升。
9. 未来改进方向
虽然当前方案已经取得了不错的效果,但仍有一些可以改进的地方:
- 多任务学习:将车牌检测和识别整合到一个模型中,简化流程
- 更高效的架构:探索更轻量化的模型结构,如MobileViT
- 半监督学习:利用大量未标注数据提升模型性能
- 领域自适应:提高模型在不同地区、不同场景下的泛化能力
- 视频序列分析:利用视频帧间的时序信息提升识别准确率
在实际部署这套系统的过程中,我发现模型对极端天气条件(如大雨、大雪)下的车牌识别效果还有提升空间。后续计划收集更多这类场景的数据进行针对性优化。另外,模型的量化部署也还有一些细节需要完善,特别是如何在保持精度的同时进一步提升推理速度。
