资讯动态

Halcon车牌识别全流程实战:从预处理、定位到字符分割与OCR识别

发布时间:2026/10/20 21:55:39 来源:尧图企业网站定制
1. 车牌识别项目的真实需求拆解很多人第一次接触车牌识别脑子里想的都是“拍张照把车牌上的字读出来”觉得这事儿无非就是OCR。但真到项目落地的时候你会发现客户提的需求远比“读出来”复杂得多。我做过几个不同场景的车牌识别项目有停车场出入口的有高速卡口的也有小区门禁的每个场景对识别的侧重点完全不同。停车场关心的是识别速度和通行效率高速卡口关心的是大角度、高速度下的抓拍准确率小区门禁则更在意夜间和恶劣天气下的稳定性。Halcon在这类项目里的定位很明确它是一套底层视觉算法库不是开箱即用的成品软件。这意味着你需要自己搭建从图像采集、预处理、车牌定位、字符分割到字符识别的完整链路。好处是灵活坏处是每个环节都得自己调。我见过不少同行一开始想用现成的车牌识别SDK结果发现要么授权费用高要么对特定车牌样式支持不好最后还是回到Halcon自己搭。这个项目的核心需求可以归纳为三点第一能稳定定位到车牌区域不管车牌在图像里是倾斜的、有反光的还是被部分遮挡的第二能准确分割出单个字符尤其是中文车牌里汉字和字母数字混排的情况第三能识别出字符内容并且对易混淆字符比如0和O、1和I、8和B有足够的区分能力。这三点听起来简单但每一步都有坑。适合谁来参考这篇内容如果你已经会用Halcon做基本的图像处理比如阈值分割、形态学操作、模板匹配那这篇内容能帮你把零散的知识点串成一条完整的车牌识别流水线。如果你是完全的新手建议先补一下Halcon的基础算子不然直接看车牌识别的流程会有点吃力。2. 图像预处理把“脏图”变成能用的图2.1 为什么预处理决定了后续所有环节的上限车牌识别的第一步不是找车牌而是把图像质量提上来。我踩过最大的坑就是拿着一堆低质量图像硬做定位结果参数怎么调都不对。后来才明白预处理没做好后面所有环节都是在跟噪声较劲。预处理的目标很明确增强车牌区域的对比度抑制背景干扰把图像变成“车牌突出、背景干净”的状态。Halcon里常用的预处理算子有几个rgb1_to_gray做灰度化emphasize做锐化增强scale_image做对比度拉伸。但具体用哪个、怎么组合得看你的图像来源。如果是地感线圈触发抓拍的图像通常光照比较均匀灰度化之后直接做边缘提取就行。如果是视频流里截取的帧可能会有运动模糊这时候得先做shock_filter或者derivate_gauss来增强边缘。2.2 灰度化不是简单取平均很多人以为灰度化就是把RGB三个通道取平均但Halcon的rgb1_to_gray用的是加权平均权重接近人眼对亮度的感知。这个细节在车牌识别里很重要因为车牌底色蓝底白字、黄底黑字、绿底白字在不同通道下的对比度差异很大。我实测过对于蓝底白字的车牌在蓝色通道下字符和底色的对比度最高直接提取蓝色通道做后续处理比灰度化效果更好。* 读取图像 read_image (Image, car_plate.jpg) * 分离通道 decompose3 (Image, R, G, B) * 对于蓝底车牌蓝色通道对比度更好 * 对于黄底车牌红色通道更合适这个技巧在项目里很实用先判断车牌类型再选择对应的通道做处理。判断方法也简单统计图像里蓝色像素和黄色像素的比例就行。2.3 光照不均的处理策略实际场景里光照不均是最头疼的问题。地下车库出入口一边是暗的一边是强光同一张图里车牌区域可能一半过曝一半欠曝。Halcon里处理光照不均的经典方法是illuminate算子它通过估计背景光照分布来做校正。但illuminate的参数不好调掩膜尺寸选大了校正效果不明显选小了会把车牌字符也当成光照变化给抹掉。我的经验是先用大掩膜做一次粗校正再用小掩膜做局部增强。具体参数得根据图像分辨率来定一般掩膜尺寸取车牌宽度的1.5到2倍比较合适。如果图像分辨率是1920×1080车牌宽度大概200像素那掩膜尺寸取300到400之间。注意预处理阶段不要过度追求“好看”目标是让后续的定位和分割更稳定。有时候看起来有点噪点的图像反而比过度平滑的图像更容易定位。3. 车牌定位从整张图里把车牌“抠”出来3.1 基于颜色特征的定位方法车牌定位最直接的方法是颜色定位。中国车牌的颜色规范很明确蓝底白字、黄底黑字、绿底白字新能源、白底黑字警用。Halcon里做颜色定位的流程是转换到HSV空间用threshold提取特定色调范围再用connection和select_shape筛选出候选区域。* 转换到HSV空间 trans_from_rgb (Image, H, S, V, hsv) * 提取蓝色区域蓝底车牌 threshold (H, BlueRegion, 180, 240) * 连通域分析 connection (BlueRegion, ConnectedRegions) * 筛选矩形度高的区域 select_shape (ConnectedRegions, PlateCandidates, rectangularity, 0.8, 1.0)这个方法在光照均匀、背景简单的场景下效果很好但在复杂背景里容易误检。比如蓝色的车身、蓝色的广告牌都会被选进来。所以颜色定位通常只作为粗定位后面还得用形状特征和纹理特征来验证。3.2 基于边缘和形态学的定位方法边缘定位的思路是车牌区域有密集的垂直边缘字符的笔画用sobel_amp提取边缘后做水平方向的形态学闭运算把字符边缘连成一片再筛选出矩形区域。* 边缘提取 sobel_amp (GrayImage, EdgeAmplitude, sum_abs, 3) * 水平方向闭运算连接字符边缘 gen_rectangle1 (Rect, 0, 0, 1, 20) closing (EdgeAmplitude, RegionClosing, Rect) * 连通域分析 connection (RegionClosing, ConnectedRegions) * 筛选宽高比在2.5到5之间的区域车牌典型宽高比 select_shape (ConnectedRegions, PlateRegions, ratio, 2.5, 5.0)这个方法的优势是不依赖颜色对褪色车牌、污损车牌也能工作。缺点是计算量大而且对参数敏感。闭运算的结构元素尺寸很关键太小了字符边缘连不起来太大了会把相邻的车牌和背景连在一起。我的经验是结构元素宽度取字符笔画宽度的3到5倍比较合适。3.3 两种方法的融合策略单独用颜色或者边缘都有局限实际项目里我通常把两种方法结合起来。先用颜色定位快速筛选出候选区域再用边缘特征验证。验证的方法也简单在候选区域里统计垂直边缘的密度车牌区域的边缘密度明显高于背景。还有一种更稳的策略是多尺度定位。先在降采样后的图像上做粗定位找到大概位置后再在原图上做精定位。这样既能保证速度又能保证精度。降采样倍数一般取2到4倍太小了速度提升不明显太大了会丢失小目标。定位方法优势局限适用场景颜色定位速度快实现简单受光照和背景颜色影响大光照均匀、背景简单边缘定位不依赖颜色鲁棒性好计算量大参数敏感复杂背景、褪色车牌融合定位兼顾速度和鲁棒性实现复杂度高大多数实际项目3.4 定位后的倾斜校正定位到的车牌往往不是水平的有倾斜角度。倾斜校正的方法有两种一种是基于smallest_rectangle2获取车牌的倾斜角度然后用rotate_image旋转校正另一种是基于投影法通过统计水平和垂直方向的投影来估计倾斜角。* 获取最小外接矩形 smallest_rectangle2 (PlateRegion, Row, Column, Phi, Length1, Length2) * 旋转校正 rotate_image (Image, ImageRotated, -Phi, constant)smallest_rectangle2返回的Phi是弧度值旋转的时候要注意方向。我踩过的坑是直接拿Phi去旋转结果角度方向反了车牌越转越歪。正确的做法是先测试一下或者用rotate_image的interpolation参数控制插值方式一般用bilinear就够了。4. 字符分割把车牌“切”成单个字4.1 投影法分割的原理和参数调优字符分割最常用的方法是垂直投影法。原理很简单把车牌区域二值化后统计每一列的白色像素数量字符所在列的投影值高字符之间的间隙投影值低。通过找投影曲线的波谷来确定字符的分割位置。* 二值化 threshold (PlateImage, Region, 100, 255) * 垂直投影 gray_projections (Region, Region, simple, HorProjection, VertProjection) * 找波谷 * 这里需要对投影数组做后处理找到连续的零值区间投影法的难点在于波谷的判定。如果车牌有污损或者字符粘连投影曲线可能没有明显的波谷。我的经验是设置一个动态阈值先计算投影值的平均值然后取平均值的0.2到0.3倍作为波谷判定阈值。这样比固定阈值更适应不同的图像质量。4.2 处理粘连字符和断裂字符粘连字符是分割里最麻烦的情况。两个字符粘在一起投影曲线上没有波谷直接分割会切错。处理方法有两种一种是基于字符宽度的先验知识如果某个区域的宽度明显大于单个字符的平均宽度就强制从中间切开另一种是基于连通域分析用connection把字符区域分开再根据面积和宽高比筛选。断裂字符相对好处理通常是字符笔画太细或者二值化阈值太高导致的。解决方法是用形态学闭运算把断裂的部分连起来或者降低二值化阈值。但阈值降太低会把背景噪声也带进来所以得权衡。* 形态学闭运算连接断裂字符 gen_rectangle1 (Rect, 0, 0, 1, 3) closing (CharRegion, CharRegionClosed, Rect) * 连通域分析 connection (CharRegionClosed, CharRegions) * 按面积筛选 select_shape (CharRegions, ValidChars, area, 50, 5000)4.3 中文车牌的字符分割特殊性中文车牌的第一个字符是汉字后面是字母和数字。汉字的笔画比字母数字复杂得多投影曲线也更不规则。如果统一用一套参数分割汉字部分很容易切错。我的做法是分两段处理先分割出第一个汉字剩下的部分再按字母数字的规则分割。汉字分割的关键是确定汉字的宽度范围。中国车牌的汉字宽度一般在车牌总宽度的1/7到1/6之间可以根据这个先验知识来约束分割位置。另外汉字区域的投影曲线通常比字母数字区域更“毛糙”可以用sigma滤波先平滑一下再找波谷。提示字符分割的准确率直接影响最终识别率。如果分割阶段切错了后面的识别再准也没用。所以在这个环节多花时间调参数是值得的。5. 字符识别从“看到”到“认出”5.1 基于OCR分类器的识别流程Halcon提供了read_ocr和read_ocr_class_mlp等算子来做字符识别。基本流程是先加载预训练的分类器然后把分割好的字符区域传给分类器得到识别结果。* 加载OCR分类器 read_ocr_class_mlp (Industrial_0-9A-Z_NoRej.omc, OCRHandle) * 识别字符 do_ocr_multi_class_mlp (CharRegions, PlateImage, OCRHandle, Class, Confidence) * 清除句柄 clear_ocr_class_mlp (OCRHandle)Halcon自带的分类器有几种Industrial_0-9A-Z适合工业场景的字母数字识别Document_0-9A-Z适合文档扫描Universal_0-9A-Z通用性更强但精度稍低。对于车牌识别我一般用Industrial系列的因为车牌字符的字体比较规范工业分类器训练时用的样本更接近。5.2 训练自己的OCR分类器Halcon自带的分类器对中国车牌的汉字支持不好因为汉字不在它的训练集里。所以实际项目里通常需要自己训练分类器。训练流程是收集大量车牌字符样本标注好每个样本对应的字符然后用train_ocr_class_mlp训练。* 创建OCR分类器 create_ocr_class_mlp (8, 10, constant, default, [0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,J,K,L,M,N,P,Q,R,S,T,U,V,W,X,Y,Z,京,沪,粤,苏,浙,鲁,川,渝,冀,豫,云,辽,黑,湘,皖,闽,赣,鄂,桂,甘,晋,蒙,陕,吉,贵,青,藏,新,宁,琼], 0.01, 10, normalization, 10, 42, OCRHandle) * 训练 train_ocr_class_mlp (OCRHandle, TrainingFile)训练样本的质量比数量更重要。我试过用5000个样本训练但样本里有很多模糊的、倾斜的结果识别率还不如用2000个清晰样本训练出来的。样本采集的时候要注意覆盖不同光照条件、不同拍摄角度、不同车牌磨损程度的情况。5.3 易混淆字符的后处理即使分类器训练得很好0和O、1和I、8和B这些字符还是容易混淆。后处理的思路是利用车牌字符的排列规则来纠正。中国车牌的格式是汉字字母字母数字混合5位。第一位一定是汉字第二位一定是字母后面五位是字母数字混合但字母不能出现在某些位置。* 后处理规则示例 * 第二位如果是数字0根据规则应该是字母O * 第五位如果是字母I根据规则应该是数字1这个规则表可以根据实际车牌规范来定。我一般会维护一个“位置-允许字符集”的映射表识别结果如果不在允许字符集里就找最接近的允许字符替换。替换的依据是分类器返回的置信度选置信度最高的那个。易混淆对区分方法后处理策略0和O位置规则第二位只允许字母第五位只允许数字1和I位置规则同上8和B位置规则同上2和Z位置规则同上5和S位置规则同上6. 完整流程串联与参数调优实战6.1 从读图到输出的完整代码框架把前面几个环节串起来一个完整的车牌识别流程大概长这样* 1. 读取图像 read_image (Image, car_plate.jpg) * 2. 预处理 rgb1_to_gray (Image, GrayImage) emphasize (GrayImage, ImageEmphasize, 7, 7, 1.0) * 3. 车牌定位 sobel_amp (ImageEmphasize, EdgeAmplitude, sum_abs, 3) gen_rectangle1 (Rect, 0, 0, 1, 20) closing (EdgeAmplitude, RegionClosing, Rect) connection (RegionClosing, ConnectedRegions) select_shape (ConnectedRegions, PlateRegions, ratio, 2.5, 5.0) * 4. 倾斜校正 smallest_rectangle2 (PlateRegions, Row, Column, Phi, Length1, Length2) rotate_image (Image, ImageRotated, -Phi, bilinear) * 5. 字符分割 threshold (ImageRotated, Region, 100, 255) gray_projections (Region, Region, simple, HorProjection, VertProjection) * 根据投影曲线分割字符 * 6. 字符识别 read_ocr_class_mlp (Industrial_0-9A-Z_NoRej.omc, OCRHandle) do_ocr_multi_class_mlp (CharRegions, ImageRotated, OCRHandle, Class, Confidence) * 7. 后处理 * 根据位置规则纠正易混淆字符这个框架是基础版实际项目里每个环节都需要根据具体场景调整参数。比如emphasize的掩膜尺寸图像分辨率高的时候要调大分辨率低的时候要调小。sobel_amp的滤波核大小也是一般取3到5之间太大了边缘会太粗太小了边缘会断裂。6.2 参数调优的优先级和顺序调参不能瞎调得有优先级。我的经验是先调定位参数定位准了再调分割参数分割准了最后调识别参数。因为定位错了后面调得再好也没用。定位参数里最重要的是边缘提取的阈值和形态学结构元素的尺寸。边缘阈值太低会引入大量噪声太高会丢失弱边缘。我一般先用edges_image算子看一下边缘强度的分布然后取分布曲线的拐点作为阈值。形态学结构元素的尺寸前面说过取字符笔画宽度的3到5倍。分割参数里最重要的是二值化阈值和投影波谷的判定阈值。二值化阈值可以用binary_threshold自动计算也可以用gray_histo看直方图手动选。投影波谷的判定阈值我一般取投影平均值的0.25倍这个值在大多数场景下都能工作。6.3 实测中的意外情况和处理实测中最常见的意外是车牌定位到了但位置偏了导致分割的时候把边框也切进去了。处理方法是定位之后用reduce_domain把车牌区域抠出来再做一次精定位把边框去掉。精定位的方法可以用boundary提取区域边界然后向内收缩几个像素。另一个意外是图像里有多辆车牌定位到了多个候选区域。这时候需要根据区域的位置和大小来筛选。一般离图像中心近的、面积大的候选区域是目标车牌。如果还是分不清可以结合车辆检测的结果来辅助判断。还有一种情况是车牌被部分遮挡比如被保险杠挡住了一部分。这种情况下定位可能只能定位到车牌的一部分分割出来的字符数量不对。处理方法是根据车牌的标准宽高比来推断完整车牌的位置然后从原图里重新抠取。注意实际项目里不要追求100%的识别率能达到95%以上就很好了。剩下的5%可以通过多帧投票或者人工复核来解决。我见过有人为了最后几个百分点的识别率调了一个月参数投入产出比太低了。7. 性能优化与工程化落地7.1 算法加速的几种手段Halcon的算法在CPU上跑速度是个瓶颈。一张1080P的图像完整流程跑下来可能要200到500毫秒。如果要做实时识别比如视频流每秒25帧那必须优化。优化的手段有几个第一降低处理分辨率。把图像降采样到原来的1/2或1/4处理速度能提升4到16倍。车牌识别对分辨率的要求没那么高车牌宽度有100到150像素就足够识别了。第二缩小处理区域。如果相机位置固定车牌只会出现在图像的某个区域那就只处理那个区域其他部分不处理。第三用Halcon的并行计算功能。parallelize_operators可以把一些算子分配到多个线程上跑。* 降采样 zoom_image_factor (Image, ImageZoomed, 0.5, 0.5, bilinear) * 处理完后再把结果映射回原图坐标7.2 多帧融合提升稳定性视频流场景下单帧识别可能会出错但多帧融合能大幅提升稳定性。思路是对连续多帧的识别结果做投票出现次数最多的结果作为最终输出。投票的窗口大小一般取5到10帧太小了起不到融合效果太大了响应延迟高。还有一种融合策略是利用车牌的物理尺寸约束。同一辆车在连续帧里车牌的像素宽度变化应该是连续的。如果某一帧识别到的车牌宽度突然跳变那这一帧的结果大概率是错的可以丢弃。7.3 工程化部署的注意事项工程化部署的时候有几个坑我踩过。第一Halcon的运行时授权问题。开发的时候用的是开发版授权部署到客户机器上需要运行时授权。这个授权是绑定硬件的换机器就得重新申请。第二不同版本的Halcon算子行为可能有差异。我在Halcon 18上调好的参数换到Halcon 20上跑结果就不一样了。所以部署的时候要锁定版本。第三图像采集的接口兼容性。不同品牌的相机SDK不一样Halcon的open_framegrabber支持的相机品牌有限不支持的得自己写接口。部署问题表现解决方案授权绑定换机器后无法运行提前申请运行时授权版本差异参数行为不一致锁定Halcon版本相机兼容无法采集图像用相机SDK采集后转Halcon图像7.4 识别结果的置信度过滤Halcon的OCR分类器会返回每个字符的置信度。置信度低的字符大概率是识别错了。我一般设置一个置信度阈值比如0.7低于这个值的字符标记为“不确定”然后触发人工复核或者多帧投票。置信度阈值不能设太高太高了会导致大量正常字符被标记为不确定增加复核工作量。也不能设太低太低了起不到过滤作用。我的经验是先用一批测试图像跑一下看置信度分布然后取分布曲线谷底附近的值作为阈值。8. 一些踩坑之后的个人体会车牌识别这个方向入门容易做精难。我刚开始做的时候觉得Halcon算子这么多随便组合一下就能出结果。实际做下来才发现每个算子背后的参数都有讲究每个场景都需要单独调优。最深的体会是不要迷信“通用参数”网上找到的参数配置直接拿来用大概率效果不好。因为图像来源、光照条件、车牌样式都不一样必须根据实际情况调。另一个体会是预处理和定位环节值得花80%的时间。这两个环节做好了后面的分割和识别就是水到渠成的事。我见过很多人把大量时间花在训练OCR分类器上但定位不准分类器再好也没用。最后分享一个小技巧调试的时候把中间结果都可视化出来。Halcon的dev_display可以显示每个环节的处理结果定位到的区域、分割出的字符、识别的置信度都显示出来。这样一眼就能看出哪个环节出了问题比盲调参数效率高得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑