恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
FPGA车牌识别系统:Verilog硬件流水线设计与工程实践
首页
资讯中心
/
FPGA车牌识别系统:Verilog硬件流水线设计与工程实践
FPGA车牌识别系统:Verilog硬件流水线设计与工程实践
发布时间:2026/9/1 1:04:55
简介本资源是一套基于FPGA实现的车牌识别系统完整工程面向数字电路、嵌入式视觉与计算机视觉方向的学习者与开发者解决图像处理算法硬件化落地的关键实践问题。压缩包共含多个文件以Verilog源代码、Quartus II工程文件、系统架构图PNG、详细设计报告PDF/DOC及实机上板演示视频MP4为主涵盖图像采集、预处理、车牌定位、字符分割与识别等全流程模块的RTL级实现总大小18.34MB。目前已有1166人学习下载体现了其在FPGA图像应用教学与课程设计中的高实用价值。用户可直接导入Quartus II编译下载至主流Altera/Intel FPGA开发板运行验证配套报告详述设计原理、模块划分、时序约束与性能分析视频直观展示实时识别效果是深入理解硬件加速视觉算法不可多得的闭环学习案例。1. 项目背景与核心价值从“黑盒”到“白盒”的硬件车牌识别最近在整理硬盘时翻出了一个老项目压缩包文件名是“车牌识别verilog_quartusII_带录制视频_带报告.rar”。这个压缩包就像是一个时间胶囊里面封存了一段典型的数字系统设计学习历程。对于很多电子工程、FPGA现场可编程门阵列方向的同学来说车牌识别项目几乎是迈入“算法硬件化”大门的第一块敲门砖。它不像调用一个现成的OpenCV库函数那么简单而是要求你从最底层的像素操作开始用硬件描述语言如Verilog去构建一个完整的图像处理流水线最终在FPGA开发板上实时输出识别结果。这个项目之所以经典是因为它串联了数字图像处理、数字逻辑设计、FPGA开发工具链使用以及系统集成测试等多个核心技能点。市面上有很多成熟的车牌识别软件方案基于YOLO等深度学习框架的算法精度高、适应性强。但为什么我们还要用Verilog在FPGA上“从头造轮子”呢这背后的核心价值在于“确定性”和“效率”。软件方案运行在通用处理器CPU/GPU上其执行时间是波动的受操作系统调度、内存访问延迟等因素影响。而用Verilog设计的硬件电路一旦烧录到FPGA中其处理每一帧图像的时钟周期数是固定的实现了严格的实时性。这对于一些对时序有苛刻要求的工业视觉场景如高速流水线检测至关重要。此外将算法固化到硬件中可以摆脱对高性能计算平台的依赖实现低功耗、高可靠性的嵌入式解决方案。这个“带录制视频_带报告”的项目包其完整意义在于它不仅仅是一段代码.v文件更是一个可复现的工程闭环。它包含了用于逻辑综合与布局布线的Quartus II工程文件、可能用于仿真的Testbench、记录实际运行效果的视频、以及阐述设计思路和结果的分析报告。对于学习者而言拥有这样一个完整包意味着你可以跳过最令人头疼的环境搭建和框架设计直接切入核心算法的硬件实现与优化理解从算法到硬件的映射过程。接下来我将以这个项目包为蓝本拆解一个基于Verilog和FPGA的实时车牌识别系统的完整设计与实现细节分享其中关键的技术抉择、容易踩坑的环节以及从仿真到上板的调试心得。2. 系统架构设计构建图像处理流水线一个完整的车牌识别硬件系统其核心是一个精心设计的图像处理流水线。这条流水线的输入是摄像头采集的原始RGB图像数据流输出是识别出的车牌字符文本。整个流程必须在像素时钟的驱动下像工厂流水线一样每个环节处理上一个环节的输出并产生给下一个环节的输入以此实现实时处理。2.1 顶层模块划分与接口定义首先我们需要用Verilog定义一个清晰的顶层模块Top Module它就像系统的总蓝图规定了各个子模块如何连接以及如何与外部世界摄像头、显示器、存储器等通信。module license_plate_recognition_top ( input wire clk, // 系统主时钟例如50MHz input wire rst_n, // 低电平有效的全局复位信号 // 摄像头输入接口 (假设为RGB565格式) input wire cam_vsync, // 摄像头场同步信号 input wire cam_href, // 摄像头行有效信号 input wire cam_pclk, // 摄像头像素时钟 input wire [7:0] cam_data, // 摄像头数据8位需拼接为16位RGB565 // VGA显示输出接口 output wire vga_hsync, output wire vga_vsync, output wire [4:0] vga_r, // VGA通常为5-6-5位RGB output wire [5:0] vga_g, output wire [4:0] vga_b, // 串口输出接口用于输出识别结果 output wire uart_tx );在这个顶层模块中我们实例化几个核心子模块图像采集与缓存模块负责在cam_pclk域下接收摄像头数据并将其写入一个FIFO先进先出存储器或双端口RAM中完成从摄像头时钟域到系统主时钟域clk的跨时钟域处理。这是第一个容易出问题的地方如果异步FIFO的深度设计不当会导致图像数据丢失或溢出。图像预处理流水线这是算法的核心部分运行在主时钟域。通常包括灰度化将RGB图像转换为单通道的灰度图像减少后续处理的数据量。Gray 0.299*R 0.587*G 0.114*B在硬件中我们常用移位和加法来近似这个公式例如Gray (R2) (R5) (G1) (G4) (B3)以节省乘法器资源。高斯滤波/滑动窗口滤波用于平滑图像抑制噪声。这里就需要实现一个滑动窗口例如3x3的窗口。对于窗口内的9个像素使用预先计算好的系数进行加权求和。关键点需要设计一个行缓冲器Line Buffer通常由两个RAM或移位寄存器实现用于缓存前两行的图像数据以便与当前行数据共同构成3x3窗口。边缘检测如Sobel算子突出车牌的边缘。同样需要3x3窗口分别计算水平和垂直方向的梯度然后求模。在硬件中求平方和开根号开销大常用绝对值求和来近似G |Gx| |Gy|。二值化将灰度或梯度图像转换为黑白二值图便于后续形态学操作。阈值的选择至关重要可以采用固定阈值、全局OTSU阈值计算量大或局部自适应阈值。车牌定位模块在二值化图像中寻找可能是车牌的矩形区域。常用方法包括形态学操作通过腐蚀、膨胀、闭运算等连接车牌字符区域形成一个连通域。轮廓查找与筛选通过扫描图像标记连通域然后根据车牌的先验知识长宽比、面积、占空比等筛选出候选区域。这个模块会输出车牌区域的坐标左上角x,y宽度高度。字符分割与识别模块对定位到的车牌区域进行进一步处理。字符分割将车牌区域图像进行垂直投影根据投影的波峰波谷切分出单个字符。这里要处理字符间隔、粘连字符如“京”和“A”可能连在一起等问题。字符识别对分割出的单个字符进行识别。在资源有限的FPGA上实现完整的OCR光学字符识别神经网络比较困难。因此这个老项目很可能采用模板匹配法。即预先存储标准字体0-9 A-Z部分汉字的二值化模板然后将待识别字符的二值化图像与所有模板进行比对如计算汉明距离或相关系数取距离最小的模板作为识别结果。这种方法简单、速度快但对字体、光照变化敏感。显示与控制模块负责将原始图像、处理中间结果或识别结果叠加显示在VGA显示器上并通过UART将识别出的车牌号发送给上位机PC保存或进一步处理。注意在Quartus II中组织这些模块时务必理清编译顺序。将底层模块如FIFO、RAM、滤波器核先加入工程再添加上层调用模块。否则在分析综合时可能会报找不到模块定义的错误。2.2 关键数据结构行缓冲器与窗口生成器图像处理算法的硬件实现其效率瓶颈往往在于数据访问。以3x3滑动窗口滤波为例我们不能为了处理当前像素P(x,y)就去存储器里随机访问P(x-1,y-1),P(x,y-1)... 这会造成巨大的访问延迟和带宽压力。正确的做法是使用行缓冲器。我们使用两个寄存器组或小块RAMBuffer0, Buffer1每个能存储一行的像素数据。系统工作流程如下第N行数据到来时存入Buffer0同时将Buffer0的数据输出给处理单元作为“上一行”。第N1行数据到来时存入Buffer1同时将Buffer1的数据输出作为“当前行”Buffer0的数据作为“上一行”。第N2行数据到来时数据再次存入Buffer0覆盖旧数据此时Buffer0输出为“下一行”Buffer1输出为“当前行”。 如此循环我们就在每个时钟周期都能同时获得同一列的三个连续行像素再配合一个3级的移位寄存器用于缓存当前行的前两个像素就能在每个像素时钟周期动态地组装出一个完整的3x3窗口[P22, P23, P24; P32, P33, P34; P42, P43, P44]供给滤波器使用。这种设计确保了数据流的连续性是硬件图像处理的基础范式。3. 核心算法模块的Verilog实现细节有了顶层架构我们深入到几个关键算法的硬件实现中这里藏着许多从理论公式到实际电路的“魔鬼细节”。3.1 滑动窗口滤波器的实现与优化我们以实现一个3x3的均值滤波或高斯滤波为例。均值滤波的公式很简单窗口内9个像素值求和然后除以9。但在硬件里除法尤其是非2的幂次方的除法是昂贵的操作。module sliding_window_filter_3x3 ( input wire clk, input wire rst_n, input wire [7:0] pixel_in, // 输入的灰度像素流 input wire pixel_in_valid, // 输入像素有效信号 output reg [7:0] pixel_out, // 滤波后的像素输出 output reg pixel_out_valid ); // 行缓冲器用两个双端口RAM或移位寄存器实现 reg [7:0] line_buffer_0 [0:IMG_WIDTH-1]; reg [7:0] line_buffer_1 [0:IMG_WIDTH-1]; // 窗口寄存器存储3x3窗口的9个像素值 reg [7:0] win_11, win_12, win_13; // 上一行 reg [7:0] win_21, win_22, win_23; // 当前行中心为win_22 reg [7:0] win_31, win_32, win_33; // 下一行 // 列计数器与行计数器用于控制数据填充和窗口滑动 reg [10:0] col_cnt; reg [10:0] row_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin // 复位计数器、缓冲器等 col_cnt 0; row_cnt 0; // ... 其他寄存器复位 end else if (pixel_in_valid) begin // 1. 数据流进入更新行缓冲器 line_buffer_1[col_cnt] pixel_in; // 当前行存入buffer1 win_31 line_buffer_0[col_cnt]; // buffer0的数据作为“下一行”进入窗口 win_21 line_buffer_1[col_cnt]; // buffer1的旧数据作为“当前行” win_11 line_buffer_0[col_cnt]; // 需要另一个缓冲这里逻辑简化实际需更精细控制 // 2. 横向滑动窗口使用移位寄存器链 win_13 win_12; win_12 win_11; win_23 win_22; win_22 win_21; win_33 win_32; win_32 win_31; // 3. 当窗口填满col_cnt 2且 row_cnt 2时计算滤波结果 if (col_cnt 2 row_cnt 2) begin // 求和 wire [10:0] sum win_11 win_12 win_13 win_21 win_22 win_23 win_31 win_32 win_33; // 除以9因为981可以近似为 sum/8 - sum/64 ...或者直接用查找表 // 简单处理pixel_out sum[10:3]; (即除以8会有误差但速度快) pixel_out sum[10:3]; // 相当于右移3位 pixel_out_valid 1b1; end else begin pixel_out_valid 1b0; end // 4. 更新列计数器一行结束时更新行计数器并交换行缓冲器 if (col_cnt IMG_WIDTH - 1) begin col_cnt 0; row_cnt row_cnt 1; // 交换line_buffer_0和line_buffer_1的角色 // 实际是通过读写地址控制来实现这里概念性交换 end else begin col_cnt col_cnt 1; end end end endmodule实操心得上面代码是一个高度简化的概念模型。实际工程中行缓冲器通常用FPGA内部的Block RAM实现并通过读写地址指针精确控制。一个常见的坑是边界处理对于图像边缘的像素其3x3窗口不完整。通常有两种策略一是复制边缘像素padding二是不处理边缘像素导致输出图像尺寸略小。需要在设计初期统一约定。3.2 二值化与车牌定位的逻辑设计二值化模块接收滤波后的灰度图像流输出二值图像流。如果采用固定阈值实现非常简单binary_out (gray_in THRESHOLD) ? 1‘b1 : 1’b0。但固定阈值对环境光线变化敏感。更鲁棒的方法是局部自适应阈值比如计算当前像素所在局部区域如15x15窗口的灰度均值以此作为阈值。这意味着我们需要在二值化模块前再级联一个用于计算局部均值的滑动窗口滤波器这会增加逻辑资源和延迟。在资源紧张的设计中需要权衡。车牌定位模块是算法成败的关键。它工作在二值图像流上。一种实用的硬件友好算法是连通域标记与矩形拟合的简化版水平投影与垂直投影我们不需要存储整幅二值图像。可以实时计算每一行和每一列的白色像素值为1的累加和。当扫描完一帧图像后我们就得到了两个一维数组水平投影曲线和垂直投影曲线。寻找波峰车牌的矩形区域在投影曲线上会表现为一个明显的波峰。通过设置一个阈值找到水平投影上连续超过阈值的行区间候选车牌高度以及垂直投影上连续超过阈值的列区间候选车牌宽度。几何筛选对找到的候选矩形根据预设的车牌长宽比范围例如中国车牌约为3.14:1、最小/最大面积进行筛选。这个筛选逻辑可以用比较器Comparator和乘法器计算长宽比轻松实现。这个方法的优点是无需存储整帧图像节省内存且计算量小非常适合流水线处理。缺点是对于倾斜、弯曲的车牌效果会变差。3.3 基于模板匹配的字符识别字符识别模块接收定位模块切割出的单个字符图像块例如20x40像素的二值图。模板匹配法的Verilog实现核心是一个距离计算与比较电路。假设我们有36个模板数字0-9字母A-Z每个模板也是20x40的二值图。识别过程如下并行化为了速度我们可以实例化36个相同的“距离计算单元”每个单元负责计算待识别字符与一个模板的距离。距离计算最常用的是汉明距离Hamming Distance即两个二值图像对应位不同的数量。对于二值图这等价于进行位异或XOR操作然后统计结果为1的位数即popcount操作。// 单个距离计算单元示例假设字符已展开为800位的向量 module template_matcher_unit ( input wire [799:0] char_bits, // 待识别字符 input wire [799:0] tmpl_bits, // 模板字符 output reg [9:0] hamming_dist // 汉明距离 ); wire [799:0] diff_bits; assign diff_bits char_bits ^ tmpl_bits; // 按位异或 // 计算diff_bits中1的个数popcount always (*) begin // 这是一个组合逻辑实际实现可能需要多级加法树以优化时序 integer i; reg [9:0] count; count 0; for (i0; i800; ii1) begin if (diff_bits[i]) count count 1; end hamming_dist count; end endmodule最小值查找36个距离计算单元同时输出36个距离值。我们需要一个比较树Comparator Tree来找出其中的最小值并记录其对应的模板索引。这个索引就对应了识别出的字符0-9 A-Z。模板存储36个800位的模板需要存储在FPGA的ROM只读存储器中。在Quartus II中我们可以用.mifMemory Initialization File文件来初始化ROM的内容这些.mif文件由MATLAB或Python脚本根据标准字体图片预先生成。踩坑记录模板匹配对字符的归一化大小、位置要求很高。如果定位切割模块输出的字符图像有轻微偏移或缩放匹配距离就会很大导致误识别。因此在字符分割后必须增加一个归一化子模块将切割出的字符图像缩放到与模板完全相同的尺寸如20x40并使其居中。这个缩放操作在硬件中可以用最近邻插值法实现虽然会损失一些质量但计算简单。4. Quartus II工程管理、仿真与上板调试拥有Verilog代码只是第一步将其变成能在FPGA上运行的电路还需要经过综合、布局布线、编程等步骤而Quartus II正是完成这些工作的集成环境。4.1 工程创建与约束文件编写在Quartus II中新建工程时选择正确的FPGA器件型号至关重要例如Cyclone IV EP4CE10。这决定了后续可用的逻辑资源、存储资源和IO引脚数量。添加所有Verilog源文件后最重要的一步是编写引脚分配约束文件.qsf文件或通过GUI分配。约束主要包括两类引脚位置约束将顶层模块的输入输出信号clk,rst_n,cam_*,vga_*,uart_tx分配到FPGA开发板上实际物理引脚。必须查阅开发板的原理图。例如set_location_assignment PIN_E1 -to clk set_location_assignment PIN_M1 -to rst_n set_location_assignment PIN_G1 -to uart_tx时序约束告诉时序分析工具时钟信号的频率这是保证设计稳定运行的关键。通过.sdc文件定义create_clock -name sys_clk -period 20.000 [get_ports clk] # 50MHz时钟一个巨坑摄像头接口如OV7670的像素时钟cam_pclk和系统主时钟clk是异步的。直接使用cam_pclk去驱动采样cam_data然后试图在clk域使用这些数据会导致亚稳态。必须使用异步FIFO进行跨时钟域处理。在Quartus II中可以直接调用IP Catalog中的“FIFO”IP核将其配置为异步模式Independent Clocks写时钟接cam_pclk读时钟接clk。深度要设置合理通常能存储若干行图像数据为宜。4.2 功能仿真与Modelsim协同在烧录到板子之前必须进行充分的仿真。Quartus II自带的仿真工具功能较弱通常与ModelSim协同使用。编写Testbench创建一个Verilog测试文件tb_license_plate.v在其中实例化你的顶层模块。Testbench的任务是模拟真实环境生成系统时钟clk和复位信号rst_n。模拟摄像头数据流按照cam_vsync,cam_href,cam_pclk的时序从文本文件或数组中将图像数据可以用MATLAB将图片转为十六进制文本赋值给cam_data。这个过程要严格遵循所用摄像头的时序图。监视输出信号如uart_tx检查发送的数据是否正确识别出了车牌号。联合仿真在Quartus II中设置仿真工具为ModelSim并编译仿真库。然后运行RTL仿真。在ModelSim的波形窗口中你可以清晰地看到信号的变化检查图像数据是否被正确采集、预处理流水线每个阶段的输出、定位模块是否输出了正确的坐标、字符识别模块最终输出的UART字节是否符合预期。调试技巧在Testbench中可以将关键中间信号如二值化后的图像、定位坐标写入文件。然后使用MATLAB或Python脚本读取并显示直观地判断算法在哪个环节出了问题。这比看波形图要高效得多。4.3 上板调试与问题排查当仿真通过后就可以将编译生成的.sof文件通过JTAG下载到FPGA开发板了。上板调试是“见证奇迹”也是“排查噩梦”的时刻。无图像显示首先检查VGA接口。确保引脚分配正确VGA的行同步和场同步时序符合标准可以先用一个简单的彩条发生器测试VGA模块是否正常。如果VGA显示一片混乱很可能是图像缓存器的读写地址逻辑有错误导致显示错位。图像有但处理结果不对图像颜色异常检查RGB到灰度的转换公式是否正确特别是位宽处理是否导致数据溢出或截断。滤波效果模糊或异常检查滑动窗口滤波器的边界处理逻辑。可能是行缓冲器更新逻辑错误导致窗口内的像素不是预期的空间相邻像素。车牌定位不到这是最常见的问题。首先通过VGA将二值化后的图像输出到显示器观察在车牌区域是否形成了良好的白色连通域。如果二值化效果不好太亮或太暗导致车牌区域与背景区分不开需要调整二值化阈值。其次检查定位模块的投影计算和阈值判断逻辑可能需要将阈值参数做成可通过板载按键调节的寄存器方便动态调试。字符识别错误率高检查字符分割通过VGA在原始图像上画出分割线观察分割是否准确。垂直投影法对车牌倾斜很敏感。如果车牌有倾斜需要先进行倾斜校正。一个简单的硬件校正方法是计算车牌的上下边界根据其倾斜角度对切割出的区域进行插值重排但这会显著增加设计复杂度。检查模板匹配确认ROM中的模板数据是否正确加载。可以在Testbench中直接读取ROM内容与预期值对比。检查字符归一化模块确保切割出的字符被正确地缩放和居中到20x40的网格中。系统不稳定随机错误首要怀疑时钟和复位用示波器测量板载晶振给FPGA的时钟是否干净。检查复位电路确保上电后复位信号有足够长时间的稳定低电平。检查跨时钟域确认摄像头数据到系统时钟域的异步FIFO是否被正确例化和连接。查看Quartus II的时序分析报告TimeQuest确保没有建立时间Setup Time或保持时间Hold Time违例。如果有违例可能需要优化代码或添加时序约束。录制视频与报告撰写项目包中的“带录制视频”通常指用手机或摄像机拍摄FPGA开发板连接显示器实时识别车牌的演示过程。而“报告”则应系统性地阐述项目需求、整体方案设计、各模块详细设计框图、状态机、关键代码解释、仿真波形与分析、上板实测结果附照片或视频截图、遇到的问题与解决方案、以及总结与展望。一份好的报告本身就是对项目的再次梳理和升华。通过这样一个从理论到代码从仿真到实物的完整流程你收获的不仅仅是一个车牌识别系统更是一套应对复杂数字系统设计的工程方法论。每一个信号、每一行代码、每一次约束都直接对应着硬件电路的行为这种掌控感是软件编程无法比拟的。虽然现在深度学习在车牌识别上效果更好但理解这套经典的、确定性的硬件处理流程对于从事高性能计算、边缘AI加速、实时控制系统等领域依然是无比宝贵的底层经验。本文还有配套的精品资源点击获取