正点原子 发表于 2020-12-11 10:20:36

【正点原子FPGA连载】第五十章基于OV5640摄像头的数字识别实验

本帖最后由 正点原子 于 2021-1-23 15:29 编辑

1)实验平台:正点原子达芬奇FPGA开发板
2)章节摘自【正点原子】达芬奇之FPGA开发指南
3)购买链接:https://detail.tmall.com/item.htm?id=624335496505
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_dafenqi.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)对正点原子FPGA感兴趣的同学可以加群讨论:905624739点击加入:





第五十章基于OV5640摄像头的数字识别实验


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


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

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

图 50.1.2 数字0的几何特征
红框是数字的边界,x1取上下边界的2/5处,x2取上下边界的2/3处,y取左右边界的1/2,可以看到x1与数字0有两个交点,左右(以y为分界)各一个,x2同样与数字0有两个交点,左右各一个,y与数字0有两个交点。以此统计数字特征实现识别,如下表所示:
表 50.1.1 数字特征表

50.2实验任务
本节实验任务是使用达芬奇开发板实现数字识别,利用RGB屏(支持目前正点原子推出的所有RGB-LCD屏)显示OV5640摄像头捕获到的数字,并将识别到的数字显示在数码管上。
50.3硬件设计
本章节中硬件设计前面的章节已经讲解过,此处不再赘述。
50.4程序设计
根据实验任务,首先设计如图 50.4.1所示的系统框图,本章实验的系统框架延续了“OV5640摄像头RGB-LCD灰度显示实验”的整体架构。本次实验包括以下模块:时钟模块、图像分辨率设置模块、DDR控制器模块、摄像头驱动模块、图像处理模块(vip)、数码管驱动模块和LCD顶层模块。
OV5640摄像头的数字识别实验框图如下图所示:

图 50.4.1 顶层系统框图
由上图可知,OV5640摄像头采集到的数据通过摄像头驱动模块写入DDR3,然后通过DDR控制器模块读出,读出的数据在LCD顶层模块的驱动下进入vip模块,在vip模块内部图像数据先由rgb2ycbcr模块将RGB转化为YCbCr,然后进行二值化处理,得到二值图像,对二值图像进行水平垂直投影即图像分割,得到各个数字的水平和垂直边界,将数字边界信息送入特征识别模块进行特征匹配,从而识别图像中的数字,将识别到的数字送入数码管驱动模块显示在数码管上。LCD显示器显示处理后的二值化图像和图像的边界。
顶层模块的原理图如下图所示:

图 50.4.2 顶层模块原理图
了解了整个处理流程后,我们来看一下底层硬件中各个模块的设计思路。由于除vip模块之外的模块都在先前的实验中介绍过,这里就不多做介绍。
vip模块是封装层模块,是对图像处理子模块的顶层封装,其内部模块如下图所示:

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

图 50.4.4 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模块有三个参数,如下图所示:

图 50.4.5 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                xpos            ,
18      input                ypos            ,
19
20      //project border ram interface
21      input          row_border_addr_rd,
22      output       row_border_data_rd,
23      input          col_border_addr_rd,
24      output       col_border_data_rd,
25
26      //user interface
27      input                h_total_pexel   ,
28      input                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
36localparam st_init    = 2'b00;
37localparam st_project = 2'b01;
38localparam st_process = 2'b10;
39
40//reg define
41reg [ 1:0]          cur_state         ;
42reg [ 1:0]          nxt_state         ;
43reg           cnt               ; //数据使能计数器   
44reg               h_we            ; //列ram写使能
45reg           h_waddr         ; //列ram写地址
46reg           h_raddr         ; //列ram读地址
47reg               h_di            ; //列ram写数据
48reg               h_do_d0         ;
49reg               v_we            ; //行ram写使能
50reg           v_waddr         ; //行ram写地址
51reg           v_raddr         ; //行ram读地址
52reg               v_di            ; //行ram写数据
53reg               v_do_d0         ;
54reg               frame_vsync_d0    ;
55reg     col_border_addr_wr; //列边界ram写地址
56reg     col_border_data_wr; //列边界ram写数据
57reg               col_border_ram_we ; //列边界ram写使能
58reg     row_border_addr_wr; //行边界ram写地址
59reg     row_border_data_wr; //行边界ram写数据
60reg               row_border_ram_we ; //行边界ram写使能
61reg          num_col_t         ; //一行待测数字个数
62reg          num_row_t         ; //一列待测个数
63
64//wire define
65wire                frame_vsync_fall;
66wire                h_do            ; //列ram读数据
67wire                v_do            ; //行ram读数据
68wire                h_rise            ;
69wire                h_fall            ;
70wire                v_rise            ;
71wire                v_fall            ;
72
73//*****************************************************
74//**                  main code
75//*****************************************************
76
77//列数据跳变的上升沿
78assign h_rise =h_do & ~h_do_d0;
79//列数据跳变的下降沿
80assign h_fall = ~h_do &h_do_d0;
81//行数据跳变的上升沿
82assign v_rise =v_do & ~v_do_d0;
83//行数据跳变的下降沿
84assign v_fall = ~v_do &v_do_d0;
85//场信号的下降沿
86assign frame_vsync_fall = frame_vsync_d0 & ~frame_vsync;
87
88//投影结束后输出采集到的行列数
89always @(*) 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
98end
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行的含义是在投影结束后将计数得到的行列数字的个数寄存,如下图所示。

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

图 50.4.7 状态转移图
其中在状态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的地址和使能进行清零操作,波形如下所示。

图 50.4.8 ram初始化1

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

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

图 50.4.11 产生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中,以供特征匹配识别模块调用,波形如下。

图 50.4.12 左右边界的确定
如上所示,信号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            xpos             ,//横坐标
14      input            ypos             ,//纵坐标
15      output reg       color_rgb      ,//输出图像数据
16
17      //project border ram interface
18      input            row_border_data,//行边界ram读数据
19      output reg       row_border_addr,//行边界ram读地址
20      input            col_border_data,//列边界ram读数据
21      output reg       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 digit               // 识别到的数字
29);
30
31//localparam define
32localparam FP_1_3 = 6'b010101;                   // 1/3 小数的定点化
33localparam FP_2_3 = 6'b101011;                   // 2/3
34localparam FP_2_5 = 6'b011010;                   // 2/5
35localparam FP_3_5 = 6'b100110;                   // 3/5
36localparam NUM_TOTAL = NUM_ROW * NUM_COL - 1'b1; // 需识别的数字共个数,始于0
37
38//reg define
39reg      col_border_l                  ;//左边界
40reg      col_border_r                  ;//右边界
41reg      row_border_low                  ;//下边界
42reg      row_border_high               ;//上边界
43reg      row_border_low_t                ;
44reg      row_border_high_t               ;
45reg                x1_l             ;//x1的左边特征数
46reg                x1_r             ;//x1的右边特征数
47reg                x2_l             ;//x2的左边特征数
48reg                x2_r             ;//x2的右边特征数
49reg[ 1:0]      y                ;//y的特征数
50reg[ 1:0]      y_flag             ;//y坐标上的数据
51reg                row_area    ;// 行区域
52reg                col_area    ;// 列区域
53reg[ 3:0]      row_cnt,row_cnt_t               ;//数字列计数
54reg[ 3:0]      col_cnt,col_cnt_t               ;//数字行计数
55reg      cent_y_t                        ;
56reg      v25                           ;// 行边界的2/5
57reg      v23                           ;// 行边界的2/3
58reg      v25_t                           ;
59reg      v23_t                           ;
60reg[ 5:0]      num_cnt                         ;//特征数计数
61reg                row_d0,row_d1                   ;
62reg                col_d0,col_d1                   ;
63reg                row_chg_d0,row_chg_d1,row_chg_d2;
64reg                row_chg_d3                      ;
65reg                col_chg_d0,col_chg_d1,col_chg_d2;
66reg[ 7:0]      real_num_total                  ;//被测数字总数
67reg[ 3:0]      digit_id                        ;
68reg[ 3:0]      digit_cnt                     ;//被测数字总个数计数器
69reg digit_t                         ;
70reg      cent_y                        ;//被测数字的中间横坐标
71
72//wire define
73wire      y_flag_fall ;
74wire      col_chg   ;
75wire      row_chg   ;
76wire      feature_deal;//数字特征检测有效信号            
77
78//*****************************************************
79//**                  main code
80//*****************************************************
81assign row_chg = row_d0 ^ row_d1;
82assign col_chg = col_d0 ^ col_d1;
83assign y_flag_fall= ~y_flag & y_flag;
84assign feature_deal = project_done_flag && frame_cnt == 2'd2; // 处理特征
85
86//实际采集到的数字总数
87always @(*) begin
88      if(project_done_flag)
89          real_num_total = num_col * num_row;
90end
91
92
93//检测行变化
94always @(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行的数据跳变情况。波形如下图所示。

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

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

图 50.4.15 被测数字行的变化
因为本次仿真只是验证一排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)
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)
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;
176   end
177 end
178
代码第109行至121行是从行边界ram中读出上下边界,波形如下图所示。

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

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

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

图 50.4.19 确定被测数字的中心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;
200             v23 <= v23_t;
201         end
202   end
203 end
204
205 //行区域
206 always @(*) begin
207   row_area = ypos >= row_border_high && ypos <= row_border_low;
208 end
209
210 //列区域
211 always @(*) begin
212   col_area = xpos >= col_border_l   && xpos <= col_border_r;
213 end
214
代码第180行至203行是确定X1和X2 的值。因为X1和X2是小数,而fpga逻辑代码不支持小数运算,所以必须把X1和X2 的值先扩大一定的整数倍,再缩小相同的整数倍以此来运算。本次实验将X1和X2 的值先扩大64倍,再缩小64倍来运算的,波形如下图所示。

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

图 50.4.21 数字的行区域范围1

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

图 50.4.23 数字的列区域范围
215 //确定col_cnt
216 always @(posedge clk) begin
217   if(project_done_flag) begin
218         if(row_area && 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 <= 1'b1;
253             else if(xpos > cent_y && xpos < col_border_r && monoc_fall)
254               x1_r <= 1'b1;
255         end
256         else if(ypos == v23) begin
257             if(xpos >= col_border_l && xpos <= cent_y && monoc_fall)
258               x2_l <= 1'b1;
259             else if(xpos > cent_y && xpos < col_border_r && monoc_fall)
260               x2_r <= 1'b1;
261         end
262   end
263   else begin
264         x1_l <= 1'b0;
265         x1_r <= 1'b0;
266         x2_l <= 1'b0;
267         x2_r <= 1'b0;
268   end
269 end
270
271 //寄存y_flag,找下降沿
272 always @(posedge clk) begin
273   if(feature_deal) begin
274         if(row_area && xpos == cent_y)
275             y_flag <= {y_flag,monoc};
276   end
277   else
278         y_flag <= 2'd3;
279 end
代码第216行至223行是对被测数字的个数进行计数,在行区域的范围内并且当横坐标记到每个数字的右边界时,计数器自动加1,波形如下图所示。

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

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

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

图 50.4.27 中心值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 <= y + 1'd1;
286   end
287   else
288         y <= 2'd0;
289 end
290
291 //特征匹配
292 always @(*) begin
293   case({y,x1_l,x1_r,x2_l,x2_r})
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,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 && ( 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 && (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,波形如下图所示。

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

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

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

图 50.5.1 需识别的数字
从下图实验结果中我们可以看到RGB显示屏上显示出捕获到的数字,并框出数字的边界,数码管显示2345。


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

页: [1]
查看完整版本: 【正点原子FPGA连载】第五十章基于OV5640摄像头的数字识别实验