目录[-]
一、背景
公司采购了一批车牌识别摄像头,准备部署在多个停车场。但实际测试中发现,部分设备在夜间、绿牌、倾斜角度等场景下识别率很低,导致车辆无法自动开闸。
更棘手的是:
-
市面上有几十种设备,供应商都说自己好
-
不可能把每种都买回来试一遍(成本太高)
-
就算现场测试,也只能测几个场景,覆盖不全
结果:设备买了,不好用,钱白花了,业务还受影响。
我的任务是:建立一套可量化的车牌识别设备评估体系,让每次采购都有数据支撑,不再靠供应商“说得好”做决定。
二、整体架构
测试图片生成器(PIL) → 测试集(500+张) → 调用设备API → 多维度评估 → HTML报告
↓
合成图片 + 真实场景图片
| 模块 | 技术栈 | 职责 |
|---|---|---|
| 图片生成 | PIL(Pillow) | 批量生成合成车牌(不同颜色、省份、格式) |
| 测试管理 | Excel + pandas | 管理图片路径、正确答案、关键词 |
| API调用 | requests | 调用设备/平台的车牌识别接口 |
| 评估器 | Python + 正则 | 关键词位置得分、BLEU、相似度等 |
| 报告生成 | HTML + CSS | 可视化测试报告,附带图片缩略图 |
三、核心设计
1. 测试图片生成(合成数据)
用PIL生成合成车牌图片,覆盖多维度:
def generate_plate_number(plate_type):
"""生成符合真实规则的车牌号"""
province = random.choice(provinces)
city_code = random.choice(city_letters)
if plate_type == "green": # 新能源绿牌
energy_type = random.choice(['D', 'F'])
middle_part = ''.join(random.choice(allowed_chars) for _ in range(4))
last_char = random.choice(digits)
rest = energy_type + middle_part + last_char
else: # 蓝牌
num_digits = random.choice([3, 4])
num_letters = 5 - num_digits
digits_part = ''.join(random.choice(digits) for _ in range(num_digits))
letters_part = ''.join(random.choice(letters) for _ in range(num_letters))
rest = ''.join(random.sample(list(digits_part + letters_part), 5))
return province + city_code + rest
生成的图片覆盖维度:
| 维度 | 覆盖场景 | 示例 |
|---|---|---|
| 省份 | 京、沪、粤、苏、浙、鲁、冀、辽、川、渝 | 冀J66618 |
| 颜色 | 蓝牌、绿牌、黄牌、白牌 | blue/ green/ yellow/ white |
| 字体 | 黑体,80px | — |
| 格式 | 带点(辽A·12345) | 符合真实车牌样式 |
此外,还采集了真实场景图片:夜间、地库、逆光、倾斜、脏污等,确保测试集覆盖真实环境。
2. API适配器
class PlateClient(BaseTranslator):
def translate(self, image_path: str) -> str:
with open(image_path, 'rb') as f:
files = {'file': (image_path.name, f, 'image/jpeg')}
response = requests.post(self.extract_url, files=files, headers=self.headers)
result = response.json()
# 解析返回的text字段,取第一个识别结果
return result['content']['text'][0]
3. 评估器强化:关键词位置得分
车牌识别和普通文字识别不同——前两个字符(省份+字母)的权重极高。识别错了省份,后面全对也没用。
def keyword_position_score(text, keywords):
"""带位置的关键词评分,专门用于车牌识别"""
if not text or not keywords:
return 0
if len(text) != len(keywords):
return 0
# 逐个字符比对,必须全部正确
for i, k in enumerate(keywords):
if i >= len(text) or text[i] != k:
return 0.0
return 1.0
权重分配:
| 指标 | 权重 | 说明 |
|---|---|---|
| 关键词位置得分 | 0.5 | 前两位字符权重极高 |
| BLEU分数 | 0.3 | 整体字符匹配度 |
| 相似度 | 0.1 | 辅助判断 |
| 长度比率 | 0.05 | 防止漏字 |
| 重复惩罚 | 0.05 | 检测重复 |
核心逻辑:如果省份或城市代码识别错误,综合得分直接打1折。
4. HTML报告增强
报告附带图片缩略图,点击可放大,便于人工复核:
<img src="file:///{image_path}"
style="width: 80px; cursor: pointer;"
onclick="this.style.width=this.style.width=='80px'?'400px':'160px'">
四、测试结果
总体统计
| 指标 | 数值 |
|---|---|
| 总测试图片 | 19张 |
| 通过数 | 7张 |
| 失败数 | 12张 |
| 通过率 | 36.8% |
| 综合得分 | 0.38 |
通过案例(部分)
| 图片 | 原始车牌 | 识别结果 | 得分 |
|---|---|---|---|
| blue_冀J66618.jpg | 冀J66618 | 冀J·66618 | 1.00 |
| green_京UREKHY.jpg | 京UREKHY | 京U·REKHY | 1.00 |
| green_浙DHMBW2.jpg | 浙DHMBW2 | 浙D·HMBW2 | 1.00 |
通过的都是标准角度、清晰度好的图片。
失败案例分析
| 图片 | 原始车牌 | 识别结果 | 问题类型 |
|---|---|---|---|
| T-blue_皖A37X93.jpg | 皖A37X93 | 峯A·37X93 | 省份识别错误 |
| T-blue_皖A3E388.jpg | 皖A3E388 | 225204 | 完全识别失败 |
| T-blue_皖AAV951.jpg | 皖AAV951 | 版1 5 百 3... | 乱码 |
| T-blue_皖ABG382.jpg | 皖ABG382 | 中国工商银行 | 识别成无关文字 |
| T-green_辽AD75582.jpg | 辽AD75582 | 1A.D75592 | 省份+字母全错 |
| T-green_辽AF46066.jpg | 辽AF46066 | 长安深蓝... | 识别成车辆型号 |
问题集中在:
-
倾斜角度图片(文件名带
T-前缀) -
绿牌识别率低
-
复杂背景干扰
-
省份和城市代码识别不准
五、采购验收流程
这套体系建立后,采购流程变为:
-
建立基准:用本地代码或已知好模型跑一遍测试集,得出基准准确率(约98%)
-
设备送测:供应商提供设备,用同一套测试集评估
-
数据对比:输出准确率报告,与基准对比
-
决策依据:准确率低于95%直接淘汰,低于98%要求供应商优化
实际效果:用这套体系评估了5家供应商,淘汰了2家(准确率分别只有82%和89%),避免了至少几十万的无效采购。
六、踩坑记录
坑1:车牌生成规则不真实
一开始随机生成字符,但真实车牌有规则(蓝牌5位,绿牌6位,新能源有D/F标识)。
解决:研究真实车牌规则,实现符合规范的生成逻辑。
坑2:识别结果带点,和正确答案不匹配
API返回冀J·66618,正确答案是冀J66618,直接比对失败。
解决:评估前去点(translation.replace('·', '')),再用关键词位置得分逐字比对。
坑3:前两位字符错,后面全对,综合得分还很高
用普通关键词匹配,1A.D75592和辽AD75582相似度很高,但实际完全错误。
解决:增加keyword_position_score,强制前两位必须完全正确。
坑4:图片文件路径在HTML报告中无法显示
报告中用相对路径或绝对路径,浏览器可能禁止访问本地文件。
解决:用file:///协议,添加点击放大功能,便于人工复核。
七、总结
这套框架的核心价值不在于“测准了”,而在于:
-
帮公司做决策:用数据说话,淘汰不合格供应商
-
建立可复用标准:后续人脸识别、车辆识别项目都参考了这个方案
-
量化评估:从“感觉不太行”变成“通过率36.8%,综合得分0.38”
-
框架复用:评估体系直接复用了翻译/音生文的5维指标
你不是在“做测试”,你是在“帮公司省钱”。
测试报告