数字是人们日常生活中进行信息交流时不可缺少的信息载体,面对大量的数字如何让机器识别处理,包括身份证号识别、车牌号识别等就成为了一个研究点,同时,数字识别必然涉及到图像处理,本章我们通过数字特征识别入手对数字识别有一个基本的了解,以及对数字图像处理有一个基本的认识。
本章包括以下几个部分:
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 = 1 ,
3 parameter NUM_COL = 4 ,
4 parameter H_PIXEL = 1280,
5 parameter V_PIXEL = 800 ,
6 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(11)
315 )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(11)
331 )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 = 1 ,
3 parameter NUM_COL = 4 ,
4 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 if(row_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 if(col_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实验结果
至此,我们的数字识别实验就完成了。