基于树莓派4B设计的智慧停车场(华为云在线车牌识别接口)

186 阅读7分钟

1. 项目介绍

车牌识别系统是计算机视频图像识别技术在车辆牌照识别中的一种经典应用,现在高速电子收费(ETC),违规驾驶、超速驾驶、停车场自动收费系统,等等很多场景都用到了车牌识别技术。

这篇文章就利用华为云的人工智能分类里的车牌号识别接口,快速搭建一个停车场自动收费系统,硬件采用树莓派开发板,摄像头采用普通的免驱USB摄像头,使用超声波测距模块检测是否有车辆靠近,车牌识别接口采用的是在线的方式;软件后台、UI界面采用QT、C++设计,支持跨平台,比较方便,代码一次写完,主流平台都可以编译运行。

本项目只是为了演示车牌号识别接口的使用,快速搭建了一个应用场景,很多细节还没考虑完善。

识别思路: 使用两个USB摄像头当做进口与出口,分别使用超声波测距模块不断测量摄像头前方的物体距离,当检测到车辆靠近的时候,读取当前摄像头的一帧数据,通过华为云的车牌号识别接口进行识别,返回识别结果;如果是入口摄像头,那么就将识别的车牌存放到数据库,并记录当前入场时间,如果是出口,就与当前数据库里的车牌数据进行对比,找到车牌入场的时间,与当前时间进行相减得到停车时间,再根据停车场设置的计费规则,完成费用提示,语音播报,告诉车主需要付款多少钱。

image-20211227011717512

image-20211227011113372

image-20211227011134316

2. 配置华为云接口

2.1 开通车牌识别服务

当前体验的是在线API车牌接口,需要先开通车牌识别服务,才可以使用接口(需要先注册华为云账号登录)。

车牌识别服务开通地址: console.huaweicloud.com/ocr/?region…

image-20211226231518223

接口的使用计费说明页面: www.huaweicloud.com/pricing.htm…

image-20211226231637159

可以看到,如果使用在线API接口实现车牌识别,每月免费1000次,作为体验来讲已经足够了。

2.2 车牌识别接口使用介绍

在线文档地址: support.huaweicloud.com/api-ocr/ocr…

在这个页面可以看到在线请求的接口地址,参数、响应结果等详细介绍。

image-20211226232044413

如果想快速体验效果,可以直接使用在线调试功能,这个功能非常好用,可以快速体验各种接口,参数的功能。

在线调试地址: apiexplorer.developer.huaweicloud.com/apiexplorer…

准备一张待测试识别的车牌:

image-20211226232703680

使用接口调试:

image-20211226233107253

调试的时候需要填入图片的base64编码,可以直接使用浏览器自带的功能实现。

官网文档: support.huaweicloud.com/ocr_faq/ocr…

image-20211226234844770

实操:

image-20211226234944137

2.3 接口总结

请求方式: post
 ​
 URL地址格式: POST https://{endpoint}/v2/{project_id}/ocr/license-plate
 ​
 实际地址: (下面填的是我的项目ID,需要替换成自己,服务器域名也是一样)
 https://ocr.cn-north-4.myhuaweicloud.com/v2/0e5957be8a00f53c2fa7c0045e4d8fbf/ocr/license-plate
     请求头: 
     {
      "User-Agent": "API Explorer",
      "X-Auth-Token": "******", 这里填Token
      "Content-Type": "application/json;charset=UTF-8"
     }
     ​
     ​
     请求体:
     {
      "image": "/9j/4AAQSkZJRgABAQEAkACQAAD/2wBDAAMCAgMCAgMDAwME.........这里是图片的base64编码,非常长,这里就省略了,明白意思就行....."
     }
     ​
     响应头:
     {
      "Darklaunch-Rule-Name": "s-bdc8-1254-202112061537",
      "Server": "api-gateway",
      "X-Request-Id": "6b9a88702fe419acd8b638d35a9bf523",
      "Connection": "keep-alive",
      "X-ModelArts-Trace": "6b9a88702fe419acd8b638d35a9bf523",
      "Content-Length": "544",
      "X-ModelArts-Latency": "100",
      "Date": "Sun, 26 Dec 2021 15:29:46 GMT",
      "Instance-Request-Count": "1",
      "Content-Type": "application/json"
     }
     ​
     响应体:
     {
      "result": [
       {
        "plate_number": "京A33333",
        "plate_color": "blue",
        "plate_location": [
         [
          236,
          331
         ],
         [
          882,
          331
         ],
         [
          882,
          542
         ],
         [
          236,
          542
         ]
        ],
        "confidence": 0.9964
       }
      ]
     }

2.4 接口参数解释

上面2.3小节里总结了接口地址一些详细参数,这里把接口里的一些重要参数解释一遍。

车牌识别的URL:

    POST https://{endpoint}/v2/{project_id}/ocr/license-plate

endpoint 是指定承载REST服务端点的服务器域名或IP,不同服务不同区域的endpoint不同,可以从终端节点中获取。

例如,OCR服务在“华北-北京四”区域的 “endpoint” 为“ocr.cn-north-4.myhuaweicloud.com”。

image-20211227000018117

URL里还有一个project_id参数,这是项目ID,可以从获取项目ID中获取。

image-20211227000326180

请求头里有个比较总要的参数: X-Auth-Token, 华为云上面几乎所有的API接口请求头都需要填X-Auth-Token,获取的方法在这里: bbs.huaweicloud.com/blogs/31775… 翻到第3小节。

image-20211227000642651

3. 项目实现代码

3.1 车牌识别请求代码

    //车牌识别接口
     void Widget::car_distinguish(QImage imag)
     {
         function_select=0;
         QString requestUrl;
         QNetworkRequest request;
     ​
         //存放图片BASE64编码
         QString imgData;
     ​
         //设置请求地址
         QUrl url;
     ​
         //车牌识别请求地址
         requestUrl = QString("https://ocr.%1.myhuaweicloud.com/v2/%2/ocr/license-plate")
                 .arg(SERVER_ID)
                 .arg(PROJECT_ID);
     ​
         //设置数据提交格式
         request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=UTF-8"));
     ​
         //将图片进行Base64编码
         imgData = QString(toBase64(imag)); //编码后的图片大小不超过2M
         //设置token
         request.setRawHeader("X-Auth-Token",Token);
     ​
         //构造请求
         url.setUrl(requestUrl);
         request.setUrl(url);
     ​
         QString post_param=QString("{"image": "%1"}").arg(imgData);
     ​
         //发送请求
         manager- >post(request, post_param.toUtf8());
     }

3.2 图片base64编码

    /*
     将图片进行base64编码
     */
     QByteArray Widget::toBase64(const QImage &image)
     {
         //将要检测的图片进行BASE64编码
         QByteArray ba;
         QBuffer buffer(&ba);
         buffer.open(QIODevice::WriteOnly);
         //以png格式将图片数据写入ba
         image.save(&buffer,"jpg");
     ​
         buffer.close();
         return ba.toBase64();
     }

### 3.3 超声波模块驱动代码

    #include < linux/kernel.h >
     #include < linux/module.h >
     #include < linux/miscdevice.h >
     #include < linux/fs.h >
     #include < linux/uaccess.h >
     #include < linux/io.h >
     #include < linux/irq.h >
     #include < linux/delay.h >
     #include < linux/workqueue.h >
     #include < linux/gpio.h >
     #include < mach/gpio.h >
     #include < plat/gpio-cfg.h >
     #include < linux/timer.h >
     #include < linux/wait.h >
     #include < linux/sched.h >
     #include < linux/poll.h >
     #include < linux/fcntl.h >
     #include < linux/interrupt.h >
     #include < linux/ktime.h >static unsigned int distance_irq; /*存放中断号*/
     static u32 *GPB_DAT=NULL;
     static u32 *GPB_CON=NULL;
     ​
     /*
     工作队列处理函数: 
     */
     static void distance_work_func(struct work_struct *work)
     {
         u32 time1,time2;
         time1=ktime_to_us(ktime_get()); /*获取当前时间,再转换为 us 单位*//*等待高电平时间结束*/
         while(gpio_get_value(EXYNOS4_GPX1(0))){}
         
         time2=ktime_to_us(ktime_get()); /*获取当前时间,再转换为 us 单位*/printk("us=%dn",time2-time1);  /*us/58=厘米*/
     }
     ​
     /*静态方式初始化工作队列*/
     static DECLARE_WORK(distance_work,distance_work_func);
     ​
     /*
     中断处理函数: 用于检测超声波测距的回波
     */
     static irqreturn_t distance_handler(int irq, void *dev)
     {
         /*调度工作队列*/
         schedule_work(&distance_work);
         return IRQ_HANDLED;
     }
     ​
     static void distance_function(unsigned long data);
     /*静态方式定义内核定时器*/
     static DEFINE_TIMER(distance_timer,distance_function,0,0);
     ​
     /*内核定时器超时处理函数: 触发超声波发送方波*/
     static void distance_function(unsigned long data)
     {
         static u8 state=0;
         state=!state;
         
         /*更改GPIO口电平*/
         if(state)
         {
             *GPB_DAT|=1< < 7;
         }
         else
         {
             *GPB_DAT&=~(1< < 7);
         }
         
         /*修改定时器的超时时间*/
         mod_timer(&distance_timer,jiffies+msecs_to_jiffies(100));
     }
     ​
     static int __init tiny4412_distance_dev_init(void) 
     {
         int err;
         /*1. 映射GPIO口地址*/
         GPB_DAT=ioremap(0x11400044,4);
         GPB_CON=ioremap(0x11400040,4);
     ​
         *GPB_CON&=~(0xF< < 4*7);
         *GPB_CON|=0x1< < 4*7; /*配置输出模式*/
         
         /*2. 根据GPIO口编号,获取中断号*/
         distance_irq=gpio_to_irq(EXYNOS4_GPX1(0));
         
         /*3. 注册中断*/
         err=request_irq(distance_irq,distance_handler,IRQ_TYPE_EDGE_RISING,"distance_device",NULL);
         if(err!=0)printk("中断注册失败!n");
         else printk("中断:超声波测距驱动安装成功!n");
     ​
         /*4. 修改定时器超时时间*/
         mod_timer(&distance_timer,jiffies+msecs_to_jiffies(100));
         
         return 0;
     }
     ​
     static void __exit tiny4412_distance_dev_exit(void) 
     {
         /*5. 注销中断*/
         free_irq(distance_irq,NULL);
     ​
         /*6. 停止定时器*/
         del_timer(&distance_timer);
         
         /*7. 取消IO映射*/
         iounmap(GPB_DAT);
         iounmap(GPB_CON);
         printk("中断:超声波测距驱动卸载成功!n");
     }
     ​
     module_init(tiny4412_distance_dev_init);
     module_exit(tiny4412_distance_dev_exit);
     MODULE_LICENSE("GPL");