搜索
bottom↓
回复: 0

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

[复制链接]

出0入234汤圆

发表于 2020-12-11 10:20:36 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 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点击加入: QQ群头像.png

100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

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



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


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

图 50.1.1 数字几何特征

x1、x2是水平方向的两条直线,与数字长度成特定比例关系,y是竖直方向的直线,占数字宽度一半,这三条线与数字的交点可以得到数字的特征值。下面以数字0为例,如下图所示:
基于OV5640摄像头的数字识别实验532.png

图 50.1.2 数字0的几何特征

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

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

图 50.4.1 顶层系统框图

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

图 50.4.2 顶层模块原理图

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

图 50.4.3 vip模块的子模块

rgb2ycbcr是RGB转YCbCr模块、binarization是二值化模块、projection是投影分割模块、 digital recognition是特征匹配识别模块。
下面是vip模块的原理图。
基于OV5640摄像头的数字识别实验1928.png

图 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模块有三个参数,如下图所示:
基于OV5640摄像头的数字识别实验2317.png

图 50.4.5 vip模块的参数

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

在代码的89行至98行的含义是在投影结束后将计数得到的行列数字的个数寄存,如下图所示。
基于OV5640摄像头的数字识别实验8079.png

图 50.4.6 寄存行列数字的个数

在代码的139行至160行,这段代码是状态机的状态跳转,其跳转的波形如下所示。
基于OV5640摄像头的数字识别实验8183.png

图 50.4.7 状态转移图

其中在状态st_init对ram进行初始化处理,这是为了防止对后续的边界的产生生成影响;在状态st_project对数据产生变化的横纵坐标进行存储;在状态st_process对数据产生变化的边界进行计算并存储。
  1. 162 //状态任务
  2. 163 always @(posedge clk or negedge rst_n) begin
  3. 164     if(!rst_n) begin
  4. 165         h_we    <= 1'b0;
  5. 166         h_waddr <= 11'b0;
  6. 167         h_raddr <= 11'b0;
  7. 168         h_di    <= 1'b0;
  8. 169         v_we    <= 1'b0;
  9. 170         v_waddr <= 11'b0;
  10. 171         v_raddr <= 11'b0;
  11. 172         v_di    <= 1'b0;
  12. 173         cnt     <= 11'd0;
  13. 174         num_col_t <=4'b0;
  14. 175         num_row_t <=4'b0;
  15. 176         col_border_ram_we<= 1'b0;
  16. 177         row_border_ram_we<= 1'b0;
  17. 178         project_done_flag<= 1'b0;
  18. 179     end
  19. 180     else case(nxt_state)
  20. 181         st_init: begin
  21. 182             if(cnt == h_total_pexel) begin
  22. 183                 cnt     <=  'd0;
  23. 184                 h_we    <= 1'b0;
  24. 185                 h_waddr <=  'd0;
  25. 186                 h_raddr <=  'd0;
  26. 187                 v_raddr <=  'd0;
  27. 188                 num_col_t <=4'b0;
  28. 189                 num_row_t <=4'b0;
  29. 190                 h_di    <= 1'b0;
  30. 191                 v_we    <= 1'b0;
  31. 192                 v_waddr <=  'd0;
  32. 193                 v_di    <= 1'b0;
  33. 194                 col_border_addr_wr <= 0;
  34. 195                 row_border_addr_wr <= 0;
  35. 196             end
  36. 197             else begin
  37. 198                 if(frame_de)begin
  38. 199                     cnt  <= cnt +1'b1;
  39. 200                     h_we <= 1'b1;
  40. 201                     h_waddr <= h_waddr + 1'b1;
  41. 202                     h_di <= 1'b0;
  42. 203                     v_we <= 1'b1;
  43. 204                     v_waddr <= v_waddr + 1'b1;
  44. 205                     v_di <= 1'b0;
  45. 206                 end
  46. 207                 else begin
  47. 208                     cnt  <= 0;
  48. 209                     h_we <= 1'b0;
  49. 210                     h_waddr <=0;
  50. 211                     h_di <= 1'b0;
  51. 212                     v_we <= 1'b0;
  52. 213                     v_waddr <= 0;
  53. 214                     v_di <= 1'b0;               
  54. 215                 
  55. 216                 end      
  56. 217             end
  57. 218         end
  58. 219         st_project:begin
  59. 220             if(frame_de &&(!monoc)) begin
  60. 221                 h_we <= 1'b1;
  61. 222                 h_waddr <= xpos;
  62. 223                 h_di <= 1'b1;
  63. 224                 v_we <= 1'b1;
  64. 225                 v_waddr <= ypos;
  65. 226                 v_di <= 1'b1;
  66. 227             end
  67. 228             else begin
  68. 229                 h_we <= 1'b0;
  69. 230                 h_waddr <= 'd0;
  70. 231                 h_di <= 1'b0;
  71. 232                 v_we <= 1'b0;
  72. 233                 v_waddr <= 'd0;
  73. 234                 v_di <= 1'b0;
  74. 235             end
  75. 236         end
  76. 237         st_process:begin
  77. 238             if(h_raddr == h_total_pexel - 1)    //标志投影结束信号
  78. 239                 project_done_flag <= 1'b1;
  79. 240             else begin
  80. 241                 cnt <= 'd0;
  81. 242                 h_raddr <= h_raddr + 1'b1;
  82. 243                 v_raddr <= (v_raddr == v_total_pexel - 1) ? v_raddr : (v_raddr + 1'b1);
  83. 244                 project_done_flag <= 1'b0;
  84. 245             end
  85. 246     
复制代码

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

图 50.4.8 ram初始化1

基于OV5640摄像头的数字识别实验11458.png

图 50.4.9 ram初始化2

在代码的219行至236行,这段代码是对数据产生变化的横纵坐标进行存储,其波形如下所示。
基于OV5640摄像头的数字识别实验11565.png

图 50.4.10 对横纵坐标进行存储

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

图 50.4.11 产生ram读地址

本次仿真用的是1280X800分辨率的屏,所以列地址读到了1279,行地址读到799就把所有数据全部读出。        
  1. 247             if(h_rise) begin                 //存左边界
  2. 248                 num_col_t <= num_col_t + 1'b1;
  3. 249                 col_border_addr_wr <= col_border_addr_wr + 1'b1;
  4. 250                 col_border_data_wr <= h_raddr - 2'd2;
  5. 251                 col_border_ram_we  <= 1'b1;
  6. 252             end
  7. 253             else if(h_fall) begin            //存右边界
  8. 254                 col_border_addr_wr <= col_border_addr_wr + 1'b1;
  9. 255                 col_border_data_wr <= h_raddr + 2'd2;
  10. 256                 col_border_ram_we  <= 1'b1;
  11. 257             end
  12. 258             else
  13. 259                 col_border_ram_we <= 1'b0;
  14. 260                 
  15. 261             if(v_rise) begin                 //存上边界
  16. 262                 num_row_t <= num_row_t + 1'b1;
  17. 263                 row_border_addr_wr <= row_border_addr_wr + 1'b1;
  18. 264                 row_border_data_wr <= v_raddr - 2'd2;
  19. 265                 row_border_ram_we  <= 1'b1;
  20. 266             end
  21. 267             else if(v_fall) begin            //存下边界   
  22. 268                 row_border_addr_wr <= row_border_addr_wr + 1'b1;
  23. 269                 row_border_data_wr <= v_raddr + 2'd2;
  24. 270                 row_border_ram_we  <= 1'b1;
  25. 271             end
  26. 272             else
  27. 273                 row_border_ram_we  <= 1'b0;
  28. 274         end
  29. 275     endcase
  30. 276 end
  31. 277
  32. 278 //垂直投影
  33. 279 myram #(
  34. 280     .WIDTH(1  ),
  35. 281     .DEPTH(H_PIXEL),
  36. 282     .DEPBIT(DEPBIT)
  37. 283 )u_h_myram(
  38. 284     //module clock
  39. 285     .clk(clk),
  40. 286     //ram interface
  41. 287     .we(h_we),
  42. 288     .waddr(h_waddr),
  43. 289     .raddr(h_raddr),
  44. 290     .dq_i(h_di),
  45. 291     .dq_o(h_do)
  46. 292 );
  47. 293
  48. 294 //水平投影
  49. 295 myram #(
  50. 296     .WIDTH(1  ),
  51. 297     .DEPTH(V_PIXEL),
  52. 298     .DEPBIT(DEPBIT)
  53. 299 )u_v_myram(
  54. 300     //module clock
  55. 301     .clk(clk),
  56. 302     //ram interface
  57. 303     .we(v_we),
  58. 304     .waddr(v_waddr),
  59. 305     .raddr(v_raddr),
  60. 306     .dq_i(v_di),
  61. 307     .dq_o(v_do)
  62. 308 );
  63. 309
  64. 310 //垂直投影边界
  65. 311 myram #(
  66. 312     .WIDTH(11),
  67. 313     .DEPTH(2 * NUM_COL),
  68. 314     .DEPBIT(11)
  69. 315 )u_col_border_myram(
  70. 316     //module clock
  71. 317     .clk    (clk),
  72. 318     //ram interface
  73. 319     .we     (col_border_ram_we ),
  74. 320     .waddr  (col_border_addr_wr),
  75. 321     .raddr  (col_border_addr_rd),
  76. 322     .dq_i   (col_border_data_wr),
  77. 323     .dq_o   (col_border_data_rd)
  78. 324 );
  79. 325
  80. 326 //水平投影边界
  81. 327 myram #(
  82. 328     .WIDTH(11),
  83. 329     .DEPTH(2 * NUM_ROW),
  84. 330     .DEPBIT(11)
  85. 331 )u_row_border_myram(
  86. 332     //module clock
  87. 333     .clk    (clk),
  88. 334     //ram interface
  89. 335     .we     (row_border_ram_we ),
  90. 336     .waddr  (row_border_addr_wr),
  91. 337     .raddr  (row_border_addr_rd),
  92. 338     .dq_i   (row_border_data_wr),
  93. 339     .dq_o   (row_border_data_rd)
  94. 340 );
  95. 341
  96. 342 endmodule
复制代码

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

图 50.4.12 左右边界的确定

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

代码第83行,这句代码表示取被测数字中间位置相邻2行的数据跳变情况。波形如下图所示。
基于OV5640摄像头的数字识别实验19756.png

图 50.4.13被测数字中间位置的数据跳变

代码第87行至90行是对实际检测到的数字个数进行统计,波形如下所示。
基于OV5640摄像头的数字识别实验19859.png

图 50.4.14 数字个数计算

代码第94行至106行是检测被测数字行的变化,波形如下图。
基于OV5640摄像头的数字识别实验19951.png

图 50.4.15 被测数字行的变化

因为本次仿真只是验证一排4个数字的检测,所以这里的行计数为0。
  1. 108 //获取数字的行边界
  2. 109 always @(posedge clk) begin
  3. 110     if(row_chg)
  4. 111         row_border_addr <= (row_cnt << 1'b1) + 1'b1;
  5. 112     else
  6. 113         row_border_addr <= row_cnt << 1'b1;
  7. 114 end
  8. 115
  9. 116 always @(posedge clk) begin
  10. 117     if(row_border_addr[0])
  11. 118         row_border_low <= row_border_data;
  12. 119     else
  13. 120         row_border_high <= row_border_data;
  14. 121 end
  15. 122
  16. 123 always @(posedge clk) begin
  17. 124     row_chg_d0 <= row_chg;
  18. 125     row_chg_d1 <= row_chg_d0;
  19. 126     row_chg_d2 <= row_chg_d1;
  20. 127     row_chg_d3 <= row_chg_d2;
  21. 128 end
  22. 129
  23. 130 //检测列变化
  24. 131 always @(posedge clk) begin
  25. 132     if(project_done_flag) begin
  26. 133         col_cnt_t <= col_cnt;
  27. 134         col_d1    <= col_d0;
  28. 135         if(col_cnt_t != col_cnt)
  29. 136             col_d0 <= ~col_d0;
  30. 137     end
  31. 138     else begin
  32. 139         col_d0 <= 1'b1;
  33. 140         col_d1 <= 1'b1;
  34. 141         col_cnt_t <= 4'hf;
  35. 142     end
  36. 143 end
  37. 144
  38. 145 //获取单个数字的列边界
  39. 146 always @(posedge clk) begin
  40. 147     if(col_chg)
  41. 148         col_border_addr <= (col_cnt << 1'b1) + 1'b1;
  42. 149     else
  43. 150         col_border_addr <= col_cnt << 1'b1;
  44. 151 end
  45. 152
  46. 153 always @(posedge clk) begin
  47. 154     if(col_border_addr[0])
  48. 155         col_border_r <= col_border_data;
  49. 156     else
  50. 157         col_border_l <= col_border_data;
  51. 158 end
  52. 159
  53. 160 always @(posedge clk) begin
  54. 161     col_chg_d0 <= col_chg;
  55. 162     col_chg_d1 <= col_chg_d0;
  56. 163     col_chg_d2 <= col_chg_d1;
  57. 164 end
  58. 165
  59. 166
  60. 167 //数字中心y
  61. 168 always @(posedge clk or negedge rst_n) begin
  62. 169     if(!rst_n)
  63. 170         cent_y_t <= 12'd0;
  64. 171     else if(project_done_flag) begin
  65. 172         if(col_chg_d1)
  66. 173             cent_y_t <= col_border_l + col_border_r;
  67. 174         if(col_chg_d2)
  68. 175             cent_y = cent_y_t[11:1];
  69. 176     end
  70. 177 end
  71. 178
复制代码

代码第109行至121行是从行边界ram中读出上下边界,波形如下图所示。
基于OV5640摄像头的数字识别实验21863.png

图 50.4.16 确定被测数字的上下边界

图中地址0读出的是上边界,地址1读出的是下边界。
代码第131行至143行是对被测数字的个数变化进行检测,波形如下所示。
基于OV5640摄像头的数字识别实验21991.png

图 50.4.17 被测数字的个数变化

代码第146行至158行是从列边界ram中读出左右边界,波形如下图所示。
基于OV5640摄像头的数字识别实验22093.png

图 50.4.18 确定被测数字的左右边界

代码第168行至176行是确定每个被测数字的中心y值。中心值为数字的左右边界之和除以2得到的,波形如下所示。
基于OV5640摄像头的数字识别实验22215.png

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

  1. 179 //x1、x2
  2. 180 always @(posedge clk or negedge rst_n) begin
  3. 181     if(!rst_n) begin
  4. 182         v25 <= 11'd0;
  5. 183         v23 <= 11'd0;
  6. 184         v25_t <= 23'd0;
  7. 185         v23_t <= 23'd0;
  8. 186         row_border_low_t <= 17'b0;
  9. 187         row_border_high_t <= 17'b0;
  10. 188     end
  11. 189     else if(project_done_flag) begin
  12. 190         if(row_chg_d1) begin
  13. 191             row_border_low_t <= { row_border_low,6'b0};
  14. 192             row_border_high_t <= { row_border_high,6'b0};
  15. 193         end
  16. 194         if(row_chg_d2) begin
  17. 195             v25_t <= row_border_low_t * FP_2_5 + row_border_high_t * FP_3_5;// x1
  18. 196             v23_t <= row_border_low_t * FP_2_3 + row_border_high_t * FP_1_3;// x2
  19. 197         end
  20. 198         if(row_chg_d3) begin
  21. 199             v25 <= v25_t[22:12];
  22. 200             v23 <= v23_t[22:12];
  23. 201         end
  24. 202     end
  25. 203 end
  26. 204
  27. 205 //行区域
  28. 206 always @(*) begin
  29. 207     row_area[row_cnt] = ypos >= row_border_high && ypos <= row_border_low;
  30. 208 end
  31. 209
  32. 210 //列区域
  33. 211 always @(*) begin
  34. 212     col_area[col_cnt] = xpos >= col_border_l   && xpos <= col_border_r;
  35. 213 end
  36. 214
复制代码

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

图 50.4.20 确定被测数字的X1和X2值

代码第206行至208行是确定一排数字的行区域范围,波形如下所示。
基于OV5640摄像头的数字识别实验23626.png

图 50.4.21 数字的行区域范围1

基于OV5640摄像头的数字识别实验23691.png

图 50.4.22 数字的行区域范围2

代码第211行至213行是确定一列数字的列区域范围,波形如下所示。
基于OV5640摄像头的数字识别实验23790.png

图 50.4.23 数字的列区域范围

  1. 215 //确定col_cnt
  2. 216 always @(posedge clk) begin
  3. 217     if(project_done_flag) begin
  4. 218         if(row_area[row_cnt] && xpos == col_border_r)
  5. 219             col_cnt <= col_cnt == num_col - 1'b1 ? 'd0 : col_cnt + 1'b1;
  6. 220     end
  7. 221     else
  8. 222         col_cnt <= 4'd0;
  9. 223 end
  10. 224
  11. 225 //确定row_cnt
  12. 226 always @(posedge clk) begin
  13. 227     if(project_done_flag) begin
  14. 228         if(ypos == row_border_low + 1'b1)
  15. 229             row_cnt <= row_cnt == num_row - 1'b1 ? 'd0 : row_cnt + 1'b1;
  16. 230     end
  17. 231     else
  18. 232         row_cnt <= 12'd0;
  19. 233 end
  20. 234
  21. 235 //num_cnt用于清零特征点和计数特征点
  22. 236 always @(posedge clk or negedge rst_n) begin
  23. 237     if(!rst_n)
  24. 238         num_cnt <= 'd0;
  25. 239     else if(feature_deal)
  26. 240         num_cnt <= row_cnt * num_col + col_cnt;
  27. 241     else if(num_cnt <= NUM_TOTAL)
  28. 242         num_cnt <= num_cnt + 1'b1;
  29. 243     else
  30. 244         num_cnt <= 'd0;
  31. 245 end
  32. 246
  33. 247 //x1与x2的特征数
  34. 248 always @(posedge clk) begin
  35. 249     if(feature_deal) begin
  36. 250         if(ypos == v25) begin
  37. 251             if(xpos >= col_border_l && xpos <= cent_y && monoc_fall)
  38. 252                 x1_l[num_cnt] <= 1'b1;
  39. 253             else if(xpos > cent_y && xpos < col_border_r && monoc_fall)
  40. 254                 x1_r[num_cnt] <= 1'b1;
  41. 255         end
  42. 256         else if(ypos == v23) begin
  43. 257             if(xpos >= col_border_l && xpos <= cent_y && monoc_fall)
  44. 258                 x2_l[num_cnt] <= 1'b1;
  45. 259             else if(xpos > cent_y && xpos < col_border_r && monoc_fall)
  46. 260                 x2_r[num_cnt] <= 1'b1;
  47. 261         end
  48. 262     end
  49. 263     else begin
  50. 264         x1_l[num_cnt] <= 1'b0;
  51. 265         x1_r[num_cnt] <= 1'b0;
  52. 266         x2_l[num_cnt] <= 1'b0;
  53. 267         x2_r[num_cnt] <= 1'b0;
  54. 268     end
  55. 269 end
  56. 270
  57. 271 //寄存y_flag,找下降沿
  58. 272 always @(posedge clk) begin
  59. 273     if(feature_deal) begin
  60. 274         if(row_area[row_cnt] && xpos == cent_y)
  61. 275             y_flag[num_cnt] <= {y_flag[num_cnt][0],monoc};
  62. 276     end
  63. 277     else
  64. 278         y_flag[num_cnt] <= 2'd3;
  65. 279 end
复制代码

代码第216行至223行是对被测数字的个数进行计数,在行区域的范围内并且当横坐标记到每个数字的右边界时,计数器自动加1,波形如下图所示。
基于OV5640摄像头的数字识别实验25938.png

图 50.4.24 被测数字的个数计数

代码第216行至223行被测数字的排数进行计数,当纵坐标记到每个数字的下边界时,计数器自动加1,波形如下图所示。
基于OV5640摄像头的数字识别实验26060.png

图 50.4.25 被测数字的排数计数

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

图 50.4.26 特征值 X1和X2 的判断

代码第272行至279行是对在行区域内所有横坐标在中心值y上的所在列的像素数据的跳变情况。波形如下图所示。
基于OV5640摄像头的数字识别实验26363.png

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

  1. 281 //Y方向的特征数
  2. 282 always @(posedge clk) begin
  3. 283     if(feature_deal) begin
  4. 284         if(xpos == cent_y + 1'b1 && y_flag_fall)
  5. 285             y[num_cnt] <= y[num_cnt] + 1'd1;
  6. 286     end
  7. 287     else
  8. 288         y[num_cnt] <= 2'd0;
  9. 289 end
  10. 290
  11. 291 //特征匹配
  12. 292 always @(*) begin
  13. 293     case({y[digit_cnt],x1_l[digit_cnt],x1_r[digit_cnt],x2_l[digit_cnt],x2_r[digit_cnt]})
  14. 294         6'b10_1_1_1_1: digit_id = 4'h0; //0
  15. 295         6'b01_1_0_1_0: digit_id = 4'h1; //1
  16. 296         6'b11_0_1_1_0: digit_id = 4'h2; //2
  17. 297         6'b11_0_1_0_1: digit_id = 4'h3; //3
  18. 298         6'b10_1_1_1_0: digit_id = 4'h4; //4
  19. 299         6'b11_1_0_0_1: digit_id = 4'h5; //5
  20. 300         6'b11_1_0_1_1: digit_id = 4'h6; //6
  21. 301         6'b10_0_1_1_0: digit_id = 4'h7; //7
  22. 302         6'b11_1_1_1_1: digit_id = 4'h8; //8
  23. 303         6'b11_1_1_0_1: digit_id = 4'h9; //9
  24. 304         default: digit_id <= 4'h0;
  25. 305     endcase
  26. 306 end
  27. 307
  28. 308 //识别数字
  29. 309 always @(posedge clk) begin
  30. 310     if(feature_deal && ypos == row_border_low + 1'b1) begin
  31. 311         if(real_num_total == 1'b1)
  32. 312             digit_t <= digit_id;
  33. 313         else if(digit_cnt < real_num_total) begin
  34. 314             digit_cnt <= digit_cnt + 1'b1;
  35. 315             digit_t   <= {digit_t[NUM_WIDTH-4:0],digit_id};
  36. 316         end
  37. 317     end
  38. 318     else begin
  39. 319         digit_cnt <= 'd0;
  40. 320         digit_t   <= 'd0;
  41. 321     end
  42. 322 end
  43. 323
  44. 324 //输出识别到的数字
  45. 325 always @(posedge clk) begin
  46. 326     if(feature_deal && digit_cnt == real_num_total)
  47. 327         digit <= digit_t;
  48. 328 end
  49. 329
  50. 330 //输出边界和图像
  51. 331 always @(posedge clk or negedge rst_n) begin
  52. 332     if(!rst_n)
  53. 333         color_rgb <= 16'h0000;
  54. 334     else if(row_area[row_cnt] && ( xpos == col_border_l || xpos == col_border_r ||
  55. 335             xpos == (col_border_l -1) || xpos == (col_border_r+1)))
  56. 336         color_rgb <= 16'hf800; //左右竖直边界线
  57. 337     else if(col_area[col_cnt] && (ypos == row_border_high || ypos== row_border_low ||
  58. 338             ypos==( row_border_high - 1) || ypos== (row_border_low + 1)))
  59. 339         color_rgb <= 16'hf800; //上下水平边界线
  60. 340     else if(monoc)
  61. 341         color_rgb <= 16'hffff; //white
  62. 342     else
  63. 343         color_rgb <= 16'h0000; //dark
  64. 344 end
  65. 345
  66. 346 endmodule
复制代码

代码第282行至289行是对Y方向的特征数的判断,当横坐标在中心值y上,并且列数据有跳变的情况则特征数加1,波形如下图所示。
基于OV5640摄像头的数字识别实验28730.png

图 50.4.28 Y方向的特征数计数

代码第292行至306行是根据特征数的个数来进行特征匹配,以输出对应的数字。
代码第309行至328行是对识别到的数字进行移位寄存并输出。
基于OV5640摄像头的数字识别实验28865.png

图 50.4.29 数字输出

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

图 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摄像头前面。这里的数字必须是微软雅黑,其他字体识别不是太准确。
基于OV5640摄像头的数字识别实验29533.png

图 50.5.1 需识别的数字

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

基于OV5640摄像头的数字识别实验29646.png

图 50.5.2实验结果

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

阿莫论坛20周年了!感谢大家的支持与爱护!!

如果想吃一顿饺子,就得从冰箱里取出肉,剁馅儿,倒面粉、揉面、醒面,擀成皮儿,下锅……
一整个繁琐流程,就是为了出锅时那一嘴滚烫流油的热饺子。

如果这个过程,禁不住饿,零食下肚了,饺子出锅时也就不香了……《非诚勿扰3》
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-28 18:23

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表