【正点原子FPGA连载】第五十四章 基于OV5640摄像头的数字识别实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

数字是人们日常生活中进行信息交流时不可缺少的信息载体,面对大量的数字如何让机器识别处理,包括身份证号识别、车牌号识别等就成为了一个研究点,同时,数字识别必然涉及到图像处理,本章我们通过数字特征识别入手对数字识别有一个基本的了解,以及对数字图像处理有一个基本的认识。
本章包括以下几个部分:
53.1 简介
53.2 实验任务
53.3 硬件设计
53.4 程序设计
53.5 下载验证

54.1 简介
数字识别一般通过特征匹配及特征判别的方法来进行处理,前者一般适用于规范化的印刷体字符识别,现今该技术基本成熟,后者多用于手写字符识别,其研究还处于探索阶段,识别率还比较低。本章我们通过对印刷体数字识别入手,了解特征匹配识别的应用。
数字特征识别是通过对数字的形状以及结构等几何特征进行分析与统计,通过对数字特征的匹配从而达到对图像中数字的识别,如下图所示:

图 54.1.1 数字几何特征
x1、x2是水平方向的两条直线,与数字长度成特定比例关系,y是竖直方向的直线,占数字宽度一半,这三条线与数字的交点可以得到数字的特征值。下面以数字0为例,如下图所示:

图 54.1.2 数字0的几何特征
红框是数字的边界,x1取上下边界的2/5处,x2取上下边界的2/3处,y取左右边界的1/2,可以看到x1与数字0有两个交点,左右(以y为分界)各一个,x2同样与数字0有两个交点,左右各一个,y与数字0有两个交点。以此统计数字特征实现识别,如下表所示:
表 54.1.1 数字特征表
在这里插入图片描述
54.2 实验任务
本节实验任务是使用新起点开发板实现数字识别,利用RGB屏(支持目前正点原子推出的所有RGB-LCD屏)显示OV5640摄像头捕获到的数字,并将识别到的数字显示在数码管上。
54.3 硬件设计
本章节中硬件设计前面的章节已经讲解过,此处不再赘述。
54.4 程序设计
根据实验任务,首先设计如图 54.4.1所示的系统框图,本章实验的系统框架延续了“OV5640摄像头RGB-LCD灰度显示实验”的整体架构。时钟模块、SDRAM控制器模块、IIC驱动模块、IIC配置模块、图像尺寸配置模块、数码管显示模块、摄像头采集模块、图像处理模块和LCD顶层模块。其中时钟模块、SDRAM控制器模块、IIC驱动模块、IIC配置模块、摄像头采集模块和图像尺寸配置模块本次实验没有做任何修改,这些模块在“OV5640摄像头LCD显示实验”中已经说明过,这里不再详述,本次实验只对图像处理模块和LCD顶层模块进行了修改。
OV5640摄像头的数字识别实验框图如下图所示:
在这里插入图片描述
图 54.4.1 顶层系统框图
由上图可知,OV5640摄像头采集到的数据通过摄像头驱动模块写入SDRAM,然后通过SDRAM控制器模块读出,读出的数据在LCD顶层模块的驱动下进入vip模块,在vip模块内部图像数据先由rgb2ycbcr模块将RGB转化为YCbCr,然后进行二值化处理,得到二值图像,对二值图像进行水平垂直投影即图像分割,得到各个数字的水平和垂直边界,将数字边界信息送入特征识别模块进行特征匹配,从而识别图像中的数字,将识别到的数字再送入LCD顶层模块,最后在LCD屏的左上端显示。
了解了整个处理流程后,我们来看一下底层硬件中各个模块的设计思路。由于除vip模块之外的模块都在先前的实验中介绍过,这里就不多做介绍。
vip模块是封装层模块,是对图像处理子模块的顶层封装,其内部模块如下图所示:

图 54.4.2 vip模块的子模块
rgb2ycbcr是RGB转YCbCr模块、binarization是二值化模块、projection是投影分割模块、 digital recognition是特征匹配识别模块。
下面是vip模块的原理图。

图 54.4.3 vip模块原理图
vip模块的输入端有帧数据使能信号pre_frame_de、帧行同步信号pre_frame_hsync、帧场同步信号pre_frame_vsync、坐标信号xpos和ypos和像素pre_rgb,这些信号由LCD驱动模块输入。vip模块的输出端除了vip模块处理后的帧数据使能信号post_frame_de、帧行同步信号post_frame_hsync、帧场同步信号post_frame_vsync外,还有一个识别后的数字信号digit。由于新起点开发板板载6位数码管,每位数码管用8421BCD编码显示,总共需要4*6=24位,即digit信号位宽为24位,该信号输出给数码管驱动模块在数码管上显示识别到的数字。
vip模块有三个参数,如下图所示:

图 54.4.4 vip模块的参数
NUM_ROW和NUM_COL分别指需识别的数字的行数和列数,这里我们指定识别1行4列的数字; DEPBIT是数据的位宽,主要用于确定数字边界大小的位宽,与水平和垂直像素大小有关。
vip模块中RGB转YCbCr模块和二值化模块在前面实验已经讲解过,这里不再讲述。下面是投影分割模块的代码。

1   module projection #(
2       parameter NUM_ROW =  13       parameter NUM_COL =  44       parameter H_PIXEL = 12805       parameter V_PIXEL = 8006       parameter DEPBIT  = 12
7   )(
8       //module clock
9       input                      clk               ,    // 时钟信号
10      input                      rst_n             ,    // 复位信号(低有效)
11  
12      //Image data interface
13      input                      frame_vsync       ,    // vsync信号
14      input                      frame_hsync       ,    // hsync信号
15      input                      frame_de          ,    // data enable信号
16      input                      monoc             ,    // 单色图像像素数据
17      input         [10:0]       xpos              ,
18      input         [10:0]       ypos              ,
19  
20      //project border ram interface
21      input         [DEPBIT-1:0] row_border_addr_rd,
22      output        [DEPBIT-1:0] row_border_data_rd,
23      input         [DEPBIT-1:0] col_border_addr_rd,
24      output        [DEPBIT-1:0] col_border_data_rd,
25  
26      //user interface
27      input         [10:0]       h_total_pexel     ,
28      input         [10:0]       v_total_pexel     ,
29      output   reg  [ 3:0]       num_col           ,    // 采集到的数字列数
30      output   reg  [ 3:0]       num_row           ,    // 采集到的数字行数
31      output   reg  [ 1:0]       frame_cnt         ,    // 当前帧
32      output   reg               project_done_flag      // 投影完成标志
33;
34  
35  //localparam define
36  localparam st_init    = 2'b00;
37  localparam st_project = 2'b01;
38  localparam st_process = 2'b10;
39  
40  //reg define
41  reg [ 1:0]          cur_state         ;
42  reg [ 1:0]          nxt_state         ;
43  reg [12:0]          cnt               ; //数据使能计数器   
44  reg                 h_we              ; //列ram写使能
45  reg [12:0]          h_waddr           ; //列ram写地址 
46  reg [12:0]          h_raddr           ; //列ram读地址 
47  reg                 h_di              ; //列ram写数据
48  reg                 h_do_d0           ;
49  reg                 v_we              ; //行ram写使能
50  reg [12:0]          v_waddr           ; //行ram写地址 
51  reg [12:0]          v_raddr           ; //行ram读地址 
52  reg                 v_di              ; //行ram写数据
53  reg                 v_do_d0           ;
54  reg                 frame_vsync_d0    ;
55  reg [DEPBIT-1:0]    col_border_addr_wr; //列边界ram写地址
56  reg [DEPBIT-1:0]    col_border_data_wr; //列边界ram写数据
57  reg                 col_border_ram_we ; //列边界ram写使能
58  reg [DEPBIT-1:0]    row_border_addr_wr; //行边界ram写地址
59  reg [DEPBIT-1:0]    row_border_data_wr; //行边界ram写数据
60  reg                 row_border_ram_we ; //行边界ram写使能
61  reg [3:0]           num_col_t         ; //一行待测数字个数
62  reg [3:0]           num_row_t         ; //一列待测个数
63  
64  //wire define
65  wire                frame_vsync_fall  ;
66  wire                h_do              ; //列ram读数据
67  wire                v_do              ; //行ram读数据
68  wire                h_rise            ;
69  wire                h_fall            ;
70  wire                v_rise            ;
71  wire                v_fall            ;
72  
73  //*****************************************************
74  //**                    main code
75  //*****************************************************
76  
77  //列数据跳变的上升沿
78  assign h_rise =  h_do & ~h_do_d0;
79  //列数据跳变的下降沿
80  assign h_fall = ~h_do &  h_do_d0;
81  //行数据跳变的上升沿
82  assign v_rise =  v_do & ~v_do_d0;
83  //行数据跳变的下降沿
84  assign v_fall = ~v_do &  v_do_d0;
85  //场信号的下降沿
86  assign frame_vsync_fall = frame_vsync_d0 & ~frame_vsync;
87  
88  //投影结束后输出采集到的行列数
89  always @(*) begin
90      if(project_done_flag && cur_state == st_process)begin
91          num_col = num_col_t;
92          num_row = num_row_t;
93       end
94        else begin
95          num_col = num_col;
96          num_row = num_row;
97       end
98  end
99  
100 //打拍采沿
101 always @(posedge clk or negedge rst_n) begin
102     if!rst_n) begin
103         h_do_d0 <= 1'b0;
104         v_do_d0 <= 1'b0;
105     end
106     else begin
107         h_do_d0 <= h_do;
108         v_do_d0 <= v_do;
109     end
110 end
111 
112 //打拍采沿
113 always @(posedge clk or negedge rst_n) begin
114     if!rst_n)
115         frame_vsync_d0 <= 1'b0;
116     else
117         frame_vsync_d0 <= frame_vsync;
118 end
119 
120 //帧计数
121 always @(posedge clk or negedge rst_n) begin
122     if!rst_n)
123         frame_cnt <= 2'd0;
124     else if(frame_cnt == 2'd3)
125         frame_cnt <= 2'd0;
126     else if(frame_vsync_fall)
127         frame_cnt <= frame_cnt + 1'd1;
128 end
129 
130 //(三段式状态机)状态转移
131 always @(posedge clk or negedge rst_n) begin
132   if!rst_n)
133       cur_state <= st_init;
134   else
135       cur_state <= nxt_state;
136 end
137 
138 //状态转移条件
139 always @( * ) begin
140     case(cur_state)
141         st_init: begin
142             if(frame_cnt == 2'd1)      // 初始化 myram
143                 nxt_state = st_project;
144             else
145                 nxt_state = st_init;
146         end
147         st_project:begin               //记录所有数据跳变的横纵坐标
148             if(frame_cnt == 2'd2)
149                 nxt_state = st_process;
150             else
151                 nxt_state = st_project;
152         end
153         st_process:begin              //记录数据跳变的横纵坐标的边界 
154             if(frame_cnt == 2'd0)
155                 nxt_state = st_init;
156             else
157                 nxt_state = st_process;
158         end
159     endcase
160 end
161 

在代码的89行至98行的含义是在投影结束后将计数得到的行列数字的个数寄存,如下图所示。

图 54.4.5 寄存行列数字的个数
在代码的139行至160行,这段代码是状态机的状态跳转,其跳转的波形如下所示。

图 54.4.6 状态转移图
其中在状态st_init对ram进行初始化处理,这是为了防止对后续的边界的产生生成影响;在状态st_project对数据产生变化的横纵坐标进行存储;在状态st_process对数据产生变化的边界进行计算并存储。

162 //状态任务
163 always @(posedge clk or negedge rst_n) begin
164     if!rst_n) begin
165         h_we    <= 1'b0;
166         h_waddr <= 11'b0;
167         h_raddr <= 11'b0;
168         h_di    <= 1'b0;
169         v_we    <= 1'b0;
170         v_waddr <= 11'b0;
171         v_raddr <= 11'b0;
172         v_di    <= 1'b0;
173         cnt     <= 11'd0;
174         num_col_t <=4'b0;
175         num_row_t <=4'b0;
176         col_border_ram_we<= 1'b0;
177         row_border_ram_we<= 1'b0;
178         project_done_flag<= 1'b0;
179     end
180     else case(nxt_state)
181         st_init: begin
182             if(cnt == h_total_pexel) begin
183                 cnt     <=  'd0;
184                 h_we    <= 1'b0;
185                 h_waddr <=  'd0;
186                 h_raddr <=  'd0;
187                 v_raddr <=  'd0;
188                 num_col_t <=4'b0;
189                 num_row_t <=4'b0;
190                 h_di    <= 1'b0;
191                 v_we    <= 1'b0;
192                 v_waddr <=  'd0;
193                 v_di    <= 1'b0;
194                 col_border_addr_wr <= 0;
195                 row_border_addr_wr <= 0;
196             end
197             else begin
198                 if(frame_de)begin
199                     cnt  <= cnt +1'b1;
200                     h_we <= 1'b1;
201                     h_waddr <= h_waddr + 1'b1;
202                     h_di <= 1'b0;
203                     v_we <= 1'b1;
204                     v_waddr <= v_waddr + 1'b1;
205                     v_di <= 1'b0;
206                 end
207                 else begin
208                     cnt  <= 0;
209                     h_we <= 1'b0;
210                     h_waddr <=0;
211                     h_di <= 1'b0;
212                     v_we <= 1'b0;
213                     v_waddr <= 0;
214                     v_di <= 1'b0;               
215                 
216                 end       
217             end
218         end
219         st_project:begin
220             if(frame_de &&!monoc)) begin
221                 h_we <= 1'b1;
222                 h_waddr <= xpos;
223                 h_di <= 1'b1;
224                 v_we <= 1'b1;
225                 v_waddr <= ypos;
226                 v_di <= 1'b1;
227             end
228             else begin
229                 h_we <= 1'b0;
230                 h_waddr <= 'd0;
231                 h_di <= 1'b0;
232                 v_we <= 1'b0;
233                 v_waddr <= 'd0;
234                 v_di <= 1'b0;
235             end
236         end
237         st_process:begin
238             if(h_raddr == h_total_pexel - 1//标志投影结束信号
239                 project_done_flag <= 1'b1;
240             else begin
241                 cnt <= 'd0;
242                 h_raddr <= h_raddr + 1'b1;
243                 v_raddr <= (v_raddr == v_total_pexel - 1? v_raddr : (v_raddr + 1'b1);
244                 project_done_flag <= 1'b0;
245             end
246     

在代码的181行至218行,这段代码是对ram的初始化操作。当frame_de有效时,ram的写地址进行累加同时写使能打开,数据赋0。当cnt计数到设定值h_total_pexel时,对ram的地址和使能进行清零操作,波形如下所示。

图 54.4.7 ram初始化1

图 54.4.8 ram初始化2
在代码的219行至236行,这段代码是对数据产生变化的横纵坐标进行存储,其波形如下所示。

图 54.4.9 对横纵坐标进行存储
因为本次实验所检测的目标是白纸上的黑色数字,所以这里是对信号monoc的低电平进行统计,如果是黑纸白纸则对信号monoc的高电平进行统计。当检测到信号monoc为低电平时,把此时的横纵坐标写到对应的ram中,方便后面计算边界用。
在代码的238行至245行,这段代码是将行列ram中地址的数据全部读出,在地址全部读出后产生一个投影结束的标志信号,波形如下。

图 54.4.10 产生ram读地址
本次仿真用的是1280X800分辨率的屏,所以列地址读到了1279,行地址读到799就把所有数据全部读出。

247             if(h_rise) begin                 //存左边界
248                 num_col_t <= num_col_t + 1'b1;
249                 col_border_addr_wr <= col_border_addr_wr + 1'b1;
250                 col_border_data_wr <= h_raddr - 2'd2;
251                 col_border_ram_we  <= 1'b1;
252             end
253             else if(h_fall) begin            //存右边界
254                 col_border_addr_wr <= col_border_addr_wr + 1'b1;
255                 col_border_data_wr <= h_raddr + 2'd2;
256                 col_border_ram_we  <= 1'b1;
257             end
258             else
259                 col_border_ram_we <= 1'b0;
260                 
261             if(v_rise) begin                 //存上边界 
262                 num_row_t <= num_row_t + 1'b1;
263                 row_border_addr_wr <= row_border_addr_wr + 1'b1;
264                 row_border_data_wr <= v_raddr - 2'd2;
265                 row_border_ram_we  <= 1'b1;
266             end
267             else if(v_fall) begin            //存下边界    
268                 row_border_addr_wr <= row_border_addr_wr + 1'b1;
269                 row_border_data_wr <= v_raddr + 2'd2;
270                 row_border_ram_we  <= 1'b1;
271             end
272             else
273                 row_border_ram_we  <= 1'b0;
274         end
275     endcase
276 end
277 
278 //垂直投影
279 myram #(
280     .WIDTH(1  ),
281     .DEPTH(H_PIXEL),
282     .DEPBIT(DEPBIT)
283 )u_h_myram(
284     //module clock
285     .clk(clk),
286     //ram interface
287     .we(h_we),
288     .waddr(h_waddr),
289     .raddr(h_raddr),
290     .dq_i(h_di),
291     .dq_o(h_do)
292;
293 
294 //水平投影
295 myram #(
296     .WIDTH(1  ),
297     .DEPTH(V_PIXEL),
298     .DEPBIT(DEPBIT)
299 )u_v_myram(
300     //module clock
301     .clk(clk),
302     //ram interface
303     .we(v_we),
304     .waddr(v_waddr),
305     .raddr(v_raddr),
306     .dq_i(v_di),
307     .dq_o(v_do)
308;
309 
310 //垂直投影边界
311 myram #(
312     .WIDTH(11),
313     .DEPTH(2 * NUM_COL),
314     .DEPBIT(11315 )u_col_border_myram(
316     //module clock
317     .clk    (clk),
318     //ram interface
319     .we     (col_border_ram_we ),
320     .waddr  (col_border_addr_wr),
321     .raddr  (col_border_addr_rd),
322     .dq_i   (col_border_data_wr),
323     .dq_o   (col_border_data_rd)
324;
325 
326 //水平投影边界
327 myram #(
328     .WIDTH(11),
329     .DEPTH(2 * NUM_ROW),
330     .DEPBIT(11331 )u_row_border_myram(
332     //module clock
333     .clk    (clk),
334     //ram interface
335     .we     (row_border_ram_we ),
336     .waddr  (row_border_addr_wr),
337     .raddr  (row_border_addr_rd),
338     .dq_i   (row_border_data_wr),
339     .dq_o   (row_border_data_rd)
340;
341 
342 endmodule

在代码的247行至259行,是对被测数字左右边界的确定并将左右边界的坐标存入对应的ram中,以供特征匹配识别模块调用,波形如下。

图 54.4.11 左右边界的确定
如上所示,信号h_do的边沿就是数字的左右边界,代码250行和255行分别对边界加减2是为了将边界扩充4行,是为了特征匹配识别模块中画的边界线和被测数字不是那么靠近,这里也可以设成其他数值。
在代码的261行至274行与247行至259行的原理一样,这里不再说明。
在代码的279行至340行是对几个ram模块的例化。
下面是特征匹配识别模块的代码。

1   module digital_recognition #(
2       parameter NUM_ROW =  13       parameter NUM_COL =  44       parameter NUM_WIDTH = (NUM_ROW*NUM_COL<<2-1
5   )(
6       //module clock
7       input                    clk              ,  // 时钟信号
8       input                    rst_n            ,  // 复位信号(低有效)
9   
10      //image data interface
11      input                    monoc            ,  // 单色图像像素数据
12      input                    monoc_fall       ,  // 图像数据变化
13      input      [10:0]        xpos             ,  //横坐标
14      input      [10:0]        ypos             ,  //纵坐标
15      output reg [15:0]        color_rgb        ,  //输出图像数据
16  
17      //project border ram interface
18      input      [10:0]        row_border_data  ,  //行边界ram读数据
19      output reg [10:0]        row_border_addr  ,  //行边界ram读地址
20      input      [10:0]        col_border_data  ,  //列边界ram读数据
21      output reg [10:0]        col_border_addr  ,  //列边界ram读地址
22  
23      //user interface
24      input      [ 1:0]        frame_cnt        ,  // 当前帧
25      input                    project_done_flag,  // 投影完成标志
26      input      [ 3:0]        num_col          ,  // 采集到的数字列数
27      input      [ 3:0]        num_row          ,  // 采集到的数字行数
28      output reg [NUM_WIDTH:0] digit               // 识别到的数字
29;
30  
31  //localparam define
32  localparam FP_1_3 = 6'b010101;                   // 1/3 小数的定点化
33  localparam FP_2_3 = 6'b101011;                   // 2/3 
34  localparam FP_2_5 = 6'b011010;                   // 2/5 
35  localparam FP_3_5 = 6'b100110;                   // 3/5 
36  localparam NUM_TOTAL = NUM_ROW * NUM_COL - 1'b1; // 需识别的数字共个数,始于0
37  
38  //reg define
39  reg  [10:0]        col_border_l                    ;  //左边界
40  reg  [10:0]        col_border_r                    ;  //右边界
41  reg  [10:0]        row_border_low                  ;  //下边界
42  reg  [10:0]        row_border_high                 ;  //上边界
43  reg  [16:0]        row_border_low_t                ;
44  reg  [16:0]        row_border_high_t               ;  
45  reg                x1_l     [NUM_TOTAL:0]          ;  //x1的左边特征数
46  reg                x1_r     [NUM_TOTAL:0]          ;  //x1的右边特征数 
47  reg                x2_l     [NUM_TOTAL:0]          ;  //x2的左边特征数
48  reg                x2_r     [NUM_TOTAL:0]          ;  //x2的右边特征数
49  reg  [ 1:0]        y        [NUM_TOTAL:0]          ;  //y的特征数
50  reg  [ 1:0]        y_flag   [NUM_TOTAL:0]          ;  //y坐标上的数据  
51  reg                row_area [NUM_ROW - 1'b1:0]     ;  // 行区域
52  reg                col_area [NUM_TOTAL     :0]     ;  // 列区域
53  reg  [ 3:0]        row_cnt,row_cnt_t               ;  //数字列计数
54  reg  [ 3:0]        col_cnt,col_cnt_t               ;  //数字行计数
55  reg  [11:0]        cent_y_t                        ;
56  reg  [10:0]        v25                             ;  // 行边界的2/5
57  reg  [10:0]        v23                             ;  // 行边界的2/3
58  reg  [22:0]        v25_t                           ;
59  reg  [22:0]        v23_t                           ;
60  reg  [ 5:0]        num_cnt                         ;  //特征数计数
61  reg                row_d0,row_d1                   ;
62  reg                col_d0,col_d1                   ;
63  reg                row_chg_d0,row_chg_d1,row_chg_d2;
64  reg                row_chg_d3                      ;
65  reg                col_chg_d0,col_chg_d1,col_chg_d2;
66  reg  [ 7:0]        real_num_total                  ;  //被测数字总数
67  reg  [ 3:0]        digit_id                        ; 
68  reg  [ 3:0]        digit_cnt                       ;  //被测数字总个数计数器
69  reg  [NUM_WIDTH:0] digit_t                         ;
70  reg  [10:0]        cent_y                          ;  //被测数字的中间横坐标
71  
72  //wire define
73  wire        y_flag_fall ;
74  wire        col_chg     ;
75  wire        row_chg     ;
76  wire        feature_deal;  //数字特征检测有效信号              
77  
78  //*****************************************************
79  //**                    main code
80  //*****************************************************
81  assign row_chg = row_d0 ^ row_d1;
82  assign col_chg = col_d0 ^ col_d1;
83  assign y_flag_fall  = ~y_flag[num_cnt][0] & y_flag[num_cnt][1];
84  assign feature_deal = project_done_flag && frame_cnt == 2'd2; // 处理特征
85  
86  //实际采集到的数字总数
87  always @(*) begin
88      if(project_done_flag)
89          real_num_total = num_col * num_row;
90  end
91  
92  
93  //检测行变化
94  always @(posedge clk) begin
95      if(project_done_flag) begin
96          row_cnt_t <= row_cnt;
97          row_d1    <= row_d0 ;
98          ifrow_cnt_t != row_cnt)
99              row_d0 <= ~row_d0;
100     end
101     else begin
102         row_d0 <= 1'b1;
103         row_d1 <= 1'b1;
104         row_cnt_t <= 4'hf;
105     end
106 end
107 

代码第83行,这句代码表示取被测数字中间位置相邻2行的数据跳变情况。波形如下图所示。

图 54.4.12被测数字中间位置的数据跳变
代码第87行至90行是对实际检测到的数字个数进行统计,波形如下所示。

图 54.4.13 数字个数计算
代码第94行至106行是检测被测数字行的变化,波形如下图。

图 54.4.14 被测数字行的变化
因为本次仿真只是验证一排4个数字的检测,所以这里的行计数为0。

108 //获取数字的行边界
109 always @(posedge clk) begin
110     if(row_chg)
111         row_border_addr <= (row_cnt << 1'b1) + 1'b1;
112     else
113         row_border_addr <= row_cnt << 1'b1;
114 end
115 
116 always @(posedge clk) begin
117     if(row_border_addr[0]118         row_border_low <= row_border_data;
119     else
120         row_border_high <= row_border_data;
121 end
122 
123 always @(posedge clk) begin
124     row_chg_d0 <= row_chg;
125     row_chg_d1 <= row_chg_d0;
126     row_chg_d2 <= row_chg_d1;
127     row_chg_d3 <= row_chg_d2;
128 end
129 
130 //检测列变化
131 always @(posedge clk) begin
132     if(project_done_flag) begin
133         col_cnt_t <= col_cnt;
134         col_d1    <= col_d0;
135         ifcol_cnt_t != col_cnt)
136             col_d0 <= ~col_d0;
137     end
138     else begin
139         col_d0 <= 1'b1;
140         col_d1 <= 1'b1;
141         col_cnt_t <= 4'hf;
142     end
143 end
144 
145 //获取单个数字的列边界
146 always @(posedge clk) begin
147     if(col_chg)
148         col_border_addr <= (col_cnt << 1'b1) + 1'b1;
149     else
150         col_border_addr <= col_cnt << 1'b1;
151 end
152 
153 always @(posedge clk) begin
154     if(col_border_addr[0]155         col_border_r <= col_border_data;
156     else
157         col_border_l <= col_border_data;
158 end
159 
160 always @(posedge clk) begin
161     col_chg_d0 <= col_chg;
162     col_chg_d1 <= col_chg_d0;
163     col_chg_d2 <= col_chg_d1;
164 end
165 
166 
167 //数字中心y
168 always @(posedge clk or negedge rst_n) begin
169     if!rst_n)
170         cent_y_t <= 12'd0;
171     else if(project_done_flag) begin
172         if(col_chg_d1)
173             cent_y_t <= col_border_l + col_border_r;
174         if(col_chg_d2)
175             cent_y = cent_y_t[11:1];
176     end
177 end

代码第109行至121行是从行边界ram中读出上下边界,波形如下图所示。

图 54.4.15 确定被测数字的上下边界
图中地址0读出的是上边界,地址1读出的是下边界。
代码第131行至143行是对被测数字的个数变化进行检测,波形如下所示。

图 54.4.16 被测数字的个数变化
代码第146行至158行是从列边界ram中读出左右边界,波形如下图所示。

图 54.4.17 确定被测数字的左右边界
代码第168行至176行是确定每个被测数字的中心y值。中心值为数字的左右边界之和除以2得到的,波形如下所示。

图 54.4.18 确定被测数字的中心y值

179 //x1、x2
180 always @(posedge clk or negedge rst_n) begin
181     if!rst_n) begin
182         v25 <= 11'd0;
183         v23 <= 11'd0;
184         v25_t <= 23'd0;
185         v23_t <= 23'd0;
186         row_border_low_t <= 17'b0;
187         row_border_high_t <= 17'b0;
188     end
189     else if(project_done_flag) begin
190         if(row_chg_d1) begin
191             row_border_low_t <= { row_border_low,6'b0};
192             row_border_high_t <= { row_border_high,6'b0};
193         end
194         if(row_chg_d2) begin
195             v25_t <= row_border_low_t * FP_2_5 + row_border_high_t * FP_3_5;// x1
196             v23_t <= row_border_low_t * FP_2_3 + row_border_high_t * FP_1_3;// x2
197         end
198         if(row_chg_d3) begin
199             v25 <= v25_t[22:12];
200             v23 <= v23_t[22:12];
201         end
202     end
203 end
204 
205 //行区域
206 always @(*) begin
207     row_area[row_cnt] = ypos >= row_border_high && ypos <= row_border_low;
208 end
209 
210 //列区域
211 always @(*) begin
212     col_area[col_cnt] = xpos >= col_border_l   && xpos <= col_border_r;
213 end
214 

代码第180行至203行是确定X1和X2 的值。因为X1和X2是小数,而fpga逻辑代码不支持小数运算,所以必须把X1和X2 的值先扩大一定的整数倍,再缩小相同的整数倍以此来运算。本次实验将X1和X2 的值先扩大64倍,再缩小64倍来运算的,波形如下图所示。

图 54.4.19 确定被测数字的X1和X2值
代码第206行至208行是确定一排数字的行区域范围,波形如下所示。

图 54.4.20 数字的行区域范围1

图 54.4.21 数字的行区域范围2
代码第211行至213行是确定一列数字的列区域范围,波形如下所示。

图 54.4.22 数字的列区域范围

215 //确定col_cnt
216 always @(posedge clk) begin
217     if(project_done_flag) begin
218         if(row_area[row_cnt] && xpos == col_border_r)
219             col_cnt <= col_cnt == num_col - 1'b1 ? 'd0 : col_cnt + 1'b1;
220     end
221     else
222         col_cnt <= 4'd0;
223 end
224 
225 //确定row_cnt
226 always @(posedge clk) begin
227     if(project_done_flag) begin
228         if(ypos == row_border_low + 1'b1)
229             row_cnt <= row_cnt == num_row - 1'b1 ? 'd0 : row_cnt + 1'b1;
230     end
231     else
232         row_cnt <= 12'd0;
233 end
234 
235 //num_cnt用于清零特征点和计数特征点
236 always @(posedge clk or negedge rst_n) begin
237     if!rst_n)
238         num_cnt <= 'd0;
239     else if(feature_deal)
240         num_cnt <= row_cnt * num_col + col_cnt;
241     else if(num_cnt <= NUM_TOTAL)
242         num_cnt <= num_cnt + 1'b1;
243     else
244         num_cnt <= 'd0;
245 end
246 
247 //x1与x2的特征数
248 always @(posedge clk) begin
249     if(feature_deal) begin
250         if(ypos == v25) begin
251             if(xpos >= col_border_l && xpos <= cent_y && monoc_fall)
252                 x1_l[num_cnt] <= 1'b1;
253             else if(xpos > cent_y && xpos < col_border_r && monoc_fall)
254                 x1_r[num_cnt] <= 1'b1;
255         end
256         else if(ypos == v23) begin
257             if(xpos >= col_border_l && xpos <= cent_y && monoc_fall)
258                 x2_l[num_cnt] <= 1'b1;
259             else if(xpos > cent_y && xpos < col_border_r && monoc_fall)
260                 x2_r[num_cnt] <= 1'b1;
261         end
262     end
263     else begin
264         x1_l[num_cnt] <= 1'b0;
265         x1_r[num_cnt] <= 1'b0;
266         x2_l[num_cnt] <= 1'b0;
267         x2_r[num_cnt] <= 1'b0;
268     end
269 end
270 
271 //寄存y_flag,找下降沿
272 always @(posedge clk) begin
273     if(feature_deal) begin
274         if(row_area[row_cnt] && xpos == cent_y)
275             y_flag[num_cnt] <= {y_flag[num_cnt][0],monoc};
276     end
277     else
278         y_flag[num_cnt] <= 2'd3;
279 end
280 

代码第216行至223行是对被测数字的个数进行计数,在行区域的范围内并且当横坐标记到每个数字的右边界时,计数器自动加1,波形如下图所示。

图 54.4.23 被测数字的个数计数
代码第216行至223行被测数字的排数进行计数,当纵坐标记到每个数字的下边界时,计数器自动加1,波形如下图所示。

图 54.4.24 被测数字的排数计数
代码第236行至269行是对特征数X1和X2进行计数,当纵坐标在2/5处并且横坐标大于左边界小于中间值y时,对此时的数据的变化信号进行判断,若数据变化信号为1则左边的特征值为1;同理其他几个特征值也是类似的判断,波形如下图所示。

图 54.4.25 特征值 X1和X2 的判断
代码第272行至279行是对在行区域内所有横坐标在中心值y上的所在列的像素数据的跳变情况。波形如下图所示。

图 54.4.26 中心值y上的所在列的像素数据的跳变

281 //Y方向的特征数
282 always @(posedge clk) begin
283     if(feature_deal) begin
284         if(xpos == cent_y + 1'b1 && y_flag_fall)
285             y[num_cnt] <= y[num_cnt] + 1'd1;
286     end
287     else
288         y[num_cnt] <= 2'd0;
289 end
290 
291 //特征匹配
292 always @(*) begin
293     case{y[digit_cnt],x1_l[digit_cnt],x1_r[digit_cnt],x2_l[digit_cnt],x2_r[digit_cnt]}294         6'b10_1_1_1_1: digit_id = 4'h0; //0
295         6'b01_1_0_1_0: digit_id = 4'h1; //1
296         6'b11_0_1_1_0: digit_id = 4'h2; //2
297         6'b11_0_1_0_1: digit_id = 4'h3; //3
298         6'b10_1_1_1_0: digit_id = 4'h4; //4
299         6'b11_1_0_0_1: digit_id = 4'h5; //5
300         6'b11_1_0_1_1: digit_id = 4'h6; //6
301         6'b10_0_1_1_0: digit_id = 4'h7; //7
302         6'b11_1_1_1_1: digit_id = 4'h8; //8
303         6'b11_1_1_0_1: digit_id = 4'h9; //9
304         default: digit_id <= 4'h0;
305     endcase
306 end
307 
308 //识别数字
309 always @(posedge clk) begin
310     if(feature_deal && ypos == row_border_low + 1'b1) begin
311         if(real_num_total == 1'b1)
312             digit_t <= digit_id;
313         else if(digit_cnt < real_num_total) begin
314             digit_cnt <= digit_cnt + 1'b1;
315             digit_t   <= {digit_t[NUM_WIDTH-4:0],digit_id};
316         end
317     end
318     else begin
319         digit_cnt <= 'd0;
320         digit_t   <= 'd0;
321     end
322 end
323 
324 //输出识别到的数字
325 always @(posedge clk) begin
326     if(feature_deal && digit_cnt == real_num_total)
327         digit <= digit_t;
328 end
329 
330 //输出边界和图像
331 always @(posedge clk or negedge rst_n) begin
332     if!rst_n)
333         color_rgb <= 16'h0000;
334     else if(row_area[row_cnt] && ( xpos == col_border_l || xpos == col_border_r ||
335             xpos == (col_border_l -1|| xpos == (col_border_r+1)))
336         color_rgb <= 16'hf800; //左右竖直边界线
337     else if(col_area[col_cnt] && (ypos == row_border_high || ypos== row_border_low ||
338             ypos==( row_border_high - 1|| ypos== (row_border_low + 1)))
339         color_rgb <= 16'hf800; //上下水平边界线
340     else if(monoc)
341         color_rgb <= 16'hffff; //white
342     else
343         color_rgb <= 16'h0000; //dark
344 end
345 

346 endmodule
代码第282行至289行是对Y方向的特征数的判断,当横坐标在中心值y上,并且列数据有跳变的情况则特征数加1,波形如下图所示。

图 54.4.27 Y方向的特征数计数
代码第292行至306行是根据特征数的个数来进行特征匹配,以输出对应的数字。
代码第309行至328行是对识别到的数字进行移位寄存并输出。

图 54.4.28 数字输出
当纵坐标等于下边界加1的时候,证明此时数字的特征值已经检测完成,可以进行识别数字。如果实际检测的数字个数为1个的时候就直接将信号digit_id赋给digit_t;如果是多个数字则进行移位赋值。当所有的检测的数字都移位寄存完成就将最后的检测数字输到模块接口,以供给数码管显示。
代码第331行至344行是对检测数字的边界画红色的框,并进行1位到16位的数据转换。
介绍完了vip整个模块,我们还需要对lcd驱动模块进行相应的修改,关键的修改点如下:

图 54.4.29 修改lcd驱动模块
只所以需要修改是因为之前我们使用的是lcd_de信号,现在我们需要使用lcd_hs和lcd_vs信号。
本次仿真所用的文件均在新起点FPGA开发板资料盘(A盘) → 4_SourceCode→1_Verilog→digital_recognition→sim的目录下。
54.5 下载验证
连接JTAG接口和电源线,并打开电源开关。最后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。接下来我们下载程序,验证基于OV5640摄像头的数字识别实验的功能。下载完成后,我们将下图中的数字图片合适的放在OV5640摄像头前面。

图 54.5.1 需识别的数字
从下图实验结果中我们可以看到RGB显示屏上显示出捕获到的数字,并框出数字的边界,最后在屏的左上端显示识别到的数字,如下图所示:

图 54.5.2实验结果
至此,我们的数字识别实验就完成了。

  • 0
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值