第三十七章 车牌号识别实验
在上一章节中,介绍了利用KPU模块实现人脸识别功能,本章将继续介绍利用KPU模块实现车牌号识别功能。通过本章的学习,读者将学习到使用SDK编程技术实现车牌号识别应用。
本章分为如下几个小节:
37.1 KPU模块介绍
37.2 硬件设计
37.3 程序设计
37.4 运行验证
37.1 KPU模块介绍
有关KPU模块的介绍,请见第30.1小节《KPU介绍》。
37.2 硬件设计
37.2.1 例程功能
1. 获取摄像头输出的图像,并送入KPU进行车牌检测,接着对检测到的车牌分别进行车牌号识别,然后在LCD上显示检测到的车牌位置和识别出的车牌号码。
37.2.2 硬件资源
本章实验内容,主要讲解KPU模块的使用,无需关注硬件资源。
37.2.3 原理图
本章实验内容,主要讲解KPU模块的使用,无需关注原理图。
37.3 程序设计
37.3.1 main.c代码
main.c中的代码如下所示:
INCBIN(model_kpu_detect, "lp_detect.kmodel"); INCBIN(model_kpu_recog, "lp_recog.kmodel"); INCBIN(weight, "lp_weight.bin"); uint32_t lp_index_list[7]; image_t kpu_image,crop_image,ai_image; obj_info_t cla_obj_coord; static float g_anchor[ANCHOR_NUM * 2] = {8.30891522166988, 2.75630994889035, 5.18609903718768, 1.7863757404970702, 6.91480529053198, 3.825771881004435, 10.218567655549439, 3.69476690620971, 6.4088204258368195, 2.38813526350986}; /*车牌号检测*/ char *province_cn[31] = { "Wan", "Hu", "Jin", "Yu", "Ji", "Sx", "Meng", "Liao", "Jl", "Hei", "Su", "Zhe", "Jing", "Min", "Gan", "Lu", "Yu", "E", "Xiang", "Yue", "Gui", "Qiong", "Cuan", "Gui", "Yun", "Zang", "Shan", "Gan", "Qing", "Ning", "Xin" }; char *ads[34] = { "A","B","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","S","T","U","V","W","X","Y","Z","0","1","2","3","4","5","6","7","8","9" }; static volatile uint8_t ai_done_flag; void lp_recog_process(const float *features, size_t features_size, uint32_t *index) { float out1[34]; size_t chunk = (features_size / sizeof(float)) / 7; float *weight1 = (float *)weight_data; float *bias1 = weight1 + 51584; float *weight2 = bias1 + 31; float *bias2 = weight2 + 39936; float *weight3 = bias2+ 24; float *bias3 = weight3 + 56576; float *weight4 = bias3+ 34; float *bias4 = weight4 + 56576; float *weight5 = bias4+ 34; float *bias5 = weight5 + 56576; float *weight6 = bias5+ 34; float *bias6 = weight6 + 56576; float *weight7 = bias6+ 34; float *bias7 = weight7 + 56576; kpu_fully_connected(features, weight1, bias1, out1, chunk, 31); index[0] = max_index(out1, 31); features += chunk; kpu_fully_connected(features, weight2, bias2, out1, chunk, 24); index[1] = max_index(out1, 24); features += chunk; kpu_fully_connected(features, weight3, bias3, out1, chunk, 34); index[2] = max_index(out1, 34); features += chunk; kpu_fully_connected(features, weight4, bias4, out1, chunk, 34); index[3] = max_index(out1, 34); features += chunk; kpu_fully_connected(features, weight5, bias5, out1, chunk, 34); index[4] = max_index(out1, 34); features += chunk; kpu_fully_connected(features, weight6, bias6, out1, chunk, 34); index[5] = max_index(out1, 34); features += chunk; kpu_fully_connected(features, weight7, bias7, out1, chunk, 34); index[6] = max_index(out1, 34); } /* KPU运算完成回调 */ static void ai_done_callback(void *userdata) { ai_done_flag = 1; } int main(void) { uint8_t *disp; uint8_t *ai; char show_lp_str[15]; kpu_model_context_t task_kpu; kpu_model_context_t task_lp; uint16_t x1 = 0,y1 = 0,x2,y2,cut_width, cut_height; float *lp_box,*lp_number; size_t lp_box_size,lp_number_size; region_layer_t detect_kpu; sysctl_pll_set_freq(SYSCTL_PLL0, 800000000); sysctl_pll_set_freq(SYSCTL_PLL1, 400000000); sysctl_pll_set_freq(SYSCTL_PLL2, 45158400); sysctl_clock_enable(SYSCTL_CLOCK_AI); sysctl_set_power_mode(SYSCTL_POWER_BANK6, SYSCTL_POWER_V18); sysctl_set_power_mode(SYSCTL_POWER_BANK7, SYSCTL_POWER_V18); sysctl_set_spi0_dvp_data(1); lcd_init(); lcd_set_direction(DIR_YX_LRUD); camera_init(24000000); camera_set_pixformat(PIXFORMAT_RGB565); camera_set_framesize(320, 240); kpu_image.pixel = 3; kpu_image.width = 320; kpu_image.height = 240; // image_init(&kpu_image); ai_image.pixel = 3; ai_image.width = 208; ai_image.height = 64; image_init(&ai_image); if (kpu_load_kmodel(&task_kpu, (const uint8_t *)model_kpu_detect_data) != 0) { printf("Kmodel load failed!\n"); while (1); } if (kpu_load_kmodel(&task_lp, (const uint8_t *)model_kpu_recog_data) != 0) { printf("Kmodel load failed!\n"); while (1); } detect_kpu.anchor_number = ANCHOR_NUM; detect_kpu.anchor = g_anchor; detect_kpu.threshold = 0.7; detect_kpu.nms_value = 0.3; region_layer_init(&detect_kpu, 20, 15, 25, 320, 240); while (1) { if (camera_snapshot(&disp, &ai) == 0) { ai_done_flag = 0; if (kpu_run_kmodel(&task_kpu, (const uint8_t *)ai, DMAC_CHANNEL5, ai_done_callback, NULL) != 0) { printf("Kmodel run failed!\n"); while (1); } while (ai_done_flag == 0); if (kpu_get_output(&task_kpu, 0, (uint8_t **)&lp_box, &lp_box_size) != 0) { printf("Output get failed!\n"); while (1); } detect_kpu.input = lp_box; region_layer_run(&detect_kpu, &cla_obj_coord); for (size_t j = 0; j < cla_obj_coord.obj_number; j++) { if (cla_obj_coord.obj[j].x1 >= 2) { x1 = cla_obj_coord.obj[j].x1 - 2; /* 对识别框稍微放大点 */ } if (cla_obj_coord.obj[j].y1 >= 2) { y1 = cla_obj_coord.obj[j].y1 - 2; } x2 = cla_obj_coord.obj[j].x2 + 2; y2 = cla_obj_coord.obj[j].y2 + 2; draw_box_rgb565_image((uint16_t *)disp, 320, x1, y1, x2, y2, GREEN); cut_width = x2 - x1 ; cut_height = y2 - y1 ; kpu_image.addr = ai; crop_image.pixel = 3; crop_image.width = cut_width; crop_image.height = cut_height; image_init(&crop_image); image_crop(&kpu_image, &crop_image, x1 - 2, y1 - 2); image_resize(&crop_image,&ai_image); image_deinit(&crop_image); image_replace(ai_image.addr, 208, 64, 0, 1); ai_done_flag = 0; if (kpu_run_kmodel(&task_lp, (const uint8_t *)ai_image.addr, DMAC_CHANNEL5, ai_done_callback, NULL) != 0) { printf("Kmodel run failed!\n"); while (1); } while (ai_done_flag == 0); if (kpu_get_output(&task_lp, 0, (uint8_t **)&lp_number, &lp_number_size) != 0) { printf("Output get failed!\n"); while (1); } lp_recog_process(lp_number, lp_number_size, lp_index_list); sprintf(show_lp_str,"%s %s-%s%s%s%s%s", province_cn[lp_index_list[0]], ads[lp_index_list[1]], ads[lp_index_list[2]], ads[lp_index_list[3]], ads[lp_index_list[4]], ads[lp_index_list[5]], ads[lp_index_list[6]]); draw_string_rgb565_image((uint16_t *)disp, 320, 240, x1, y2 + 16, show_lp_str, RED); // printf("%s\r\n",show_lp_str); } lcd_draw_picture(0, 0, 320, 240, (uint16_t *)disp); camera_snapshot_release(); } } }
本实验使用了两个AI模型和一个KPU运算需要的bin文件。lp_detect.kmodel是车牌检测模型,用于获取车牌在图像中的坐标,网络运算的图片大小为320*240。lp_recog.kmodel是车牌号码识别的模型,用于提取车牌号的数据,网络运算的图片大小为208*64。
可以看到一开始是先初始化了LCD和摄像头,初始化完成后创建一个208*64的RGB888图片缓存区,然后加载上述两个需要用到的AI模型,并初始化YOLO2网络,配置region_layer_t结构体参数的数据。
最后在一个循环中不断地获取摄像头输出的图像,图像尺寸为320*240,将摄像头图像送入KPU中进行运算,然后将运算结果作为输入传入region_layer_run函数进行解析,该函数会把解析的车牌坐标放在cla_obj_coord结构体中,进而通过两个坐标点提取摄像头图像的车牌区域,我们将车牌区域切割下来,再缩放成208*64大小的图像,此时我们还需要将缩放后的图像用image_replace函数实现水平翻转(车牌号识别模型需要),最后将其送入KPU中进行运算,然后再进行YOLO2网络运算,将运算的结果传入lp_recog_process函数进行提取,并将运算结果绘制到LCD显示器上。
37.4 运行验证
将DNK210开发板连接到电脑主机,通过VSCode将固件烧录到开发板中,将摄像头对准车牌,让其采集到车牌图像,可以看到LCD上显示了车牌识别的结果,图像中的被检测到的车牌均被框出,并且显示了车牌对应识别出的车牌号码,如下图所示:
图37.4.1 LCD显示车牌识别实验结果