|
本帖最后由 正点原子 于 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 [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行的含义是在投影结束后将计数得到的行列数字的个数寄存,如下图所示。
图 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 [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行的数据跳变情况。波形如下图所示。
图 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[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
- 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[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倍来运算的,波形如下图所示。
图 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[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
复制代码
代码第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[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,波形如下图所示。
图 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实验结果
至此,我们的数字识别实验就完成了。
|
阿莫论坛20周年了!感谢大家的支持与爱护!!
如果想吃一顿饺子,就得从冰箱里取出肉,剁馅儿,倒面粉、揉面、醒面,擀成皮儿,下锅……
一整个繁琐流程,就是为了出锅时那一嘴滚烫流油的热饺子。
如果这个过程,禁不住饿,零食下肚了,饺子出锅时也就不香了……《非诚勿扰3》
|