基于FPGA的车牌识别,其中包括常规FPGA图像处理算法: rgb转yuv, sobel边缘检测, 腐蚀膨胀, 特征值提取与卷积模板匹配。 有bit流可以直接烧录实验。 保证无错误,完好,2018.3vivado版本,正点达芬奇Pro100t,板卡也可以自己更改移植一下。 所以建的IP都有截图记录下来。
最近在折腾FPGA图像处理时整了个有意思的活------基于正点原子达芬奇Pro100t开发板的车牌识别系统。这玩意儿跑在2018.3版本的Vivado环境里,实测能用且效果不错,顺手把IP核创建过程都截了图存档。
先从摄像头采集说起,OV5640的RGB565输出需要转YUV。这里用Vivado的HLS做了个转换模块,核心算法其实就三行代码:
c++
Y = (76 * R + 150 * G + 29 * B) >> 8;
U = (-43 * R - 85 * G + 128 * B) >> 8 + 128;
V = (128 * R - 107 * G - 21 * B) >> 8 + 128;
但硬件实现时得注意时序对齐,我专门用Shift Register搭了三级流水线。生成的IP核配置界面截图里能看到参数化的位宽设置,方便移植时调整。

边缘检测用了改进版Sobel算子,X/Y方向的卷积核分别配置成:
text
[-1 0 1
-2 0 2
-1 0 1]
[-1 -2 -1
0 0 0
1 2 1]
实际在FPGA里实现时发现直接用DSP48做累加会溢出,后来改成符号位扩展+截断处理。Vivado生成的原理图里能看到3x3窗口生成模块连着两个卷积计算单元,这个结构后来复用到了模板匹配环节。
形态学处理阶段,腐蚀膨胀操作写了段参数化的Verilog代码:
verilog
always @(posedge clk) begin
if(en) begin
dilate_out <= (window[0][0] | window[0][1] | ... | window[2][2]);
erode_out <= (window[0][0] & window[0][1] & ... & window[2][2]);
end
end
这里有个坑------结构元素大小别超过3x3,否则时序容易崩。实测车牌区域经过五次膨胀三次腐蚀后,连通域效果最佳。
特征值提取部分搞了个动态阈值算法,根据图像亮度分布自动调整二值化门限。具体是用Histogram统计模块找像素分布拐点,这部分代码里用到了双端口Block RAM做统计缓存:
verilog
always @(posedge clk) begin
if(hist_we) begin
hist_ram[bin_addr] <= hist_ram[bin_addr] + 1;
end
end
最后的模板匹配环节,预存了各省份简称的12x24点阵字模。卷积时采用汉明距离代替传统相关运算,这样在Xilinx FPGA上可以用LUT实现异或逻辑,实测速度比用DSP快三倍。

整个工程编译后资源占用情况:LUT用了37%,BRAM用了62%,时序收敛在480MHz。实际测试时从图像输入到车牌输出延迟8个时钟周期,挂上HDMI显示模块能看到实时处理效果。
移植到其他板卡主要注意两点:1. 修改时钟约束文件 2. 调整VDMA的帧存地址范围。源码包里的ip_repo文件夹已经包含所有自定义IP核,直接导入Vivado就能用。烧录bitstream后记得用ATK-DAP给DDR3初始化,不然图像缓存会挂掉。
效果展示视频里能看到,普通小车牌在阴天环境下识别率大约85%,夜间开补光灯能提到92%。核心代码和IP核截图都扔在Github上了,需要自取的朋友评论区留邮箱。下次打算试试用zynq的PS端跑神经网络做字符识别,应该能再提一波准确率。
