基于ELFBoard开发板的车牌识别系统

news2025/1/12 10:48:07

本项目采用的是ElfBoard ELF1开发板作为项目的核心板主要实现的功能为通过USB 摄像头对车牌进行识别,如果识别成功则会亮绿灯,并将识别的车牌号上传到手机APP上面,车牌识别的实现是通过百度OCR进行实现,手机APP是用Java语言进行编写。

有关ElfBoard 相关的介绍大家可以点击:https://www.elfboard.com/

一、车牌识别的实现方法

1、车牌识别平台简介

本次车牌识别的实现方案是通过百度智能云平台进行实现,具体实现方法如下

进入百度智能云网页,选择文字识别 - > 车牌识别

进入车牌识别页面之后可通过阅读技术文档来学习车牌识别的使用方法,如果是新手入门可通过阅读官方入门指南,因为百度云官方的文档写得已经非常详细了,所以我这里就不在赘述。

2、安装 openSSL

因为百度智能云是通过libcurl的https进行访问,而https的访问需要openSSL的支持,所以先编译openSSL

wget https://www.openssl.org/source/openssl-1.1.1a.tar.gz
tar xvf openssl-1.1.1a.tar.gz
./config
make
sudo make install

3、安装curl

wget https://curl.se/download/curl-7.71.1.tar.bz2
tar -xjf curl-7.71.1.tar.bz2
cd curl-7.71.1/
./configure --prefix=$PWD/_INSTALL_ARM --host=arm-linux-gnueabihf --with-openssl
#./configure --prefix=$PWD/_INSTALL_GCC --with-openssl 为了在本地运行用GCC编译
make 
make install

4、车牌识别过程

(在做本次步骤之前请先去阅读百度智能云车牌识别的使用方法)

在本地实现之前可通过平台提供的在线验证方法进行验证,如下图,需要在旁边输入access_token(通过阅读文档可知怎么获取)和一张车牌图片的base64 编码的字符串即可进行在线识别。

本地实现车牌识别的方法需要将识别代码拷贝到本地,并需要实现一个将图片转换为base64编码的函数,详细代码如下:

#include <stdio.h>
#include <iostream>
#include <string.h>
#include <curl/curl.h>
#include <json/json.h>
#include <fstream>
#include <memory>
#include <cstdlib>
#include <regex>
#include <string>
#include <unistd.h>

inline size_t onWriteData(void * buffer, size_t size, size_t nmemb, void * userp)
{
    std::string * str = dynamic_cast<std::string *>((std::string *)userp);
    str->append((char *)buffer, size * nmemb);
    return nmemb;
}

std::string getFileBase64Content(const char * path,  bool urlencoded=false)
{
    const std::string base64_chars =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "0123456789+/";
    std::string ret;
    int i = 0;
    int j = 0;
    unsigned char char_array_3[3];
    unsigned char char_array_4[4];
    unsigned int bufferSize = 1024;
    unsigned char buffer[bufferSize];
    std::ifstream file_read;
    file_read.open(path, std::ios::binary);

    while (!file_read.eof()){

        file_read.read((char *) buffer, bufferSize * sizeof(char));
        int num = file_read.gcount();
        int m = 0;
        while (num--){

            char_array_3[i++] = buffer[m++];
            if(i == 3){

                char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
                char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
                char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
                char_array_4[3] = char_array_3[2] & 0x3f;

                for(i = 0; (i <4) ; i++)
                    ret += base64_chars[char_array_4[i]];
                i = 0;
            }
        }
    }
    file_read.close();
    if(i){

        for(j = i; j < 3; j++)
            char_array_3[j] = '\0';

        char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
        char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
        char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
        char_array_4[3] = char_array_3[2] & 0x3f;

        for(j = 0; (j < i + 1); j++)
            ret += base64_chars[char_array_4[j]];

        while((i++ < 3))
            ret += '=';
    }
    if (urlencoded)
        ret = curl_escape(ret.c_str(), ret.length());

    return ret;
}

std::string performCurlRequest(const char *pic_path, const std::string &token) {
    std::string result;
    char *web_curl = nullptr;
    CURL *curl = curl_easy_init();
    CURLcode res;
    if (!asprintf(&web_curl, "https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate?access_token=%s", token.c_str())) {
        perror("asprintf error");
    }

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
        curl_easy_setopt(curl, CURLOPT_URL, web_curl);
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
        curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https");

        struct curl_slist *headers = NULL;
        headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");
        headers = curl_slist_append(headers, "Accept: application/json");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

        std::string base64_image = getFileBase64Content(pic_path, true);
        std::string post_data = "image=" + base64_image + "&multi_detect=false&multi_scale=false";

        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data.c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, onWriteData);

        if(curl_easy_perform(curl) != CURLE_OK)
            fprintf(stderr, "Curl request failed: %s\n", curl_easy_strerror(res));
    }

    curl_easy_cleanup(curl);
    free(web_curl);
    return result;
}

int main(int argc, char *argv[]) {

    std::string access_token = "填自己的access_tocken";
    std::string result = performCurlRequest("./车牌图片.jpg", access_token);
    std::cout << result << std::endl;

    std::string json = result;
    std::regex pattern("\"number\":\"(.*?)\"");
    std::smatch match;
    if (std::regex_search(json, match, pattern)) {
        std::cout << "read car number is:" << match[1].str() << std::endl;
    }

    return 0;
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                

编译:

gcc demoCar.c -I ./curl-7.71.1/_INSTALL_GCC/include/ -L ./curl-7.71.1/_INSTALL_GCC/lib/ -l curl

编译完成将文件通过scp拷贝到开发板,进行运行即可,这样就可以将本地的车牌图片通过HTTPS发送到百度智能云进行识别,并将识别结果返回,完成车牌识别。

注意:这里运行时可能会出现CA证书验证失败

root@ELF1:~# ./a.out
OK:60

只需运行 date  --s="2024-01-12 21:40:00" 将时间设置正确即可。

二、移植 mjpg-streamer

在前面一个章节实现了对本地车牌图片的的识别,那如果需要通过摄像头进行车牌识别就需要借助 mjpg-streamer来实现,采用USB摄像头进行识别。

关于什么是 mjpg-streamer 我这里就不在解释,大家可以自行查阅资料进行了解,我这里只介绍一下 mjpg-streamer 移植到 elfboard 过程。

1、编译 jpeg 

(1) 下载 jpeg 源码压缩包,网址:jpeg

(2) tar -xvf jpegsrc.v8b.tar.gz

(3) 编译配置

cd jpeg-8d
./configure --prefix=$PWD/_INSTALL --host=arm-linux-gnueabihf
make -j8
make install

2、编译 mjpg-streamer

(1)下载 mjpg-streamer 源码包,网址:mjpg-streamer

svn checkout https://svn.code.sf.net/p/mjpg-streamer/code/ mjpg-streamer-code 

(2)tar -xvf mjpg-streamer.tar.gz

(3)配置

cd mjpg-streamer-code/mjpg-streamer/plugins/input_uvc
vim Makefile

打开 Makefile 文件按照下图进行修改

(4)编译 mjpg-streamer

因为mjpg-streamer默认是用GCC进行编译,所以要先将GCC改成自己的交叉编译工具,先安装需要用到的库。

sudo apt install graphicsmagick-imagemagick-compat
sudo apt install imagemagick-6.q16
sudo apt install imagemagick-6.q16hdri

更改GCC有两种方法:

方法一:

cd ~/mjpg-streamer-code/mjpg-streamer
make CC=arm-linux-gnueabihf-gcc

方法二:

find -name "Makefile" -exec sed -i "s/CC = gcc/CC = arm-linux-gnueabihf-gcc/g" {} \;
grep "arm-linux-gnueabihf-gcc" * -nR

搜索当前目录及其子目录下的所有Makefile文件,并将Makefile里的CC变量设置为arm-linux-gnueabihf-gcc

注:arm-linux-gnueabihf-gcc 需要换成自己的交叉编译工具。

如果所有目录下的Makefile中的CC都等于设置的交叉编译工具即可,如上图所示。

做完上面这些步骤就可以编译代码了

make -j8

编译完成后会生成如下图一些文件

.so :动态库
mjpg_streamer:提供可执行命令
www:摄像头输出的网页

(5)移植到elfboard开发板

scp -r mjpg-streamer/ root@192.168.0.106:~

我这里是将 mjpg-streamer 整个文件夹拷贝到开发板,当然也可以将对应的文件拷贝到开发板对应的目录,具体看自己实现。

(6)验证功能

登录开发板,运行mjpg_streamer

cd mjpg-streamer
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:~/mjpg-streamer
 ./mjpg_streamer

当开发板运行mjpg_streamer成功后,在浏览器中输入开发板的IP地址和8080端口号,比如我的是192.168.0.106:8080,点击Stream选项就会出现摄像头中的实时画面,如下图所示。

这样就完成了mjpg_streamer 的移植,后续就可以mjpg_streamer实现一些具体的需求。

比如打开摄像头视频:

 mjpg_streamer -i "input_uvc.so -d /dev/video2 -f 30 -q 90 -n" -o "output_http.so -w /opt/www"

截取摄像头中的画面:

wget http://192.168.0.106:8080/?action=snapshot -O ./1.jpg

在这里就可以和前面车牌识别结合起来了,比如摄像头里面的画面是一张车牌信息,通过截取摄像头中的实时画面到本地,然后上传到百度智能云的后台进行识别,至此就完成通过摄像头进行车牌识别。

三、Android APP的实现

Android APP 的实现很简单,主要功能就是将识别成功的车牌号在APP上面显示。具体的实现方法是当开发板成功识别车牌后,通过 Socket 将车牌发送到 Android APP 上面即可。由于这部分代码比较简单,所以我只大概介绍一下。

1、Android 端XML代码实现

<Button
    android:id="@+id/Z"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="license plate number"
    android:onClick="sendMessage"
    android:textColor="#ffffff"
    android:name=".MainActivity"/>
<TextView
    android:id="@+id/text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="30dp"
    android:textColor="#ffffff"
    android:layout_centerInParent="true"/>

XML 这部分只实现了两个功能,Button 用来显示车牌号的提示,TextView用来显示识别的车牌号。

2、 Android端Socket实现

private Handler handler;
private TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    textView = findViewById(R.id.text);

    handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Bundle bundle = msg.getData();
            String receivedMessage = bundle.getString("msg");
            textView.setText(receivedMessage);
        }
    };
}

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Socket client = new Socket("192.168.0.104", 8374);
            InputStream inputStream = client.getInputStream();
            while (true) {

                byte[] data = new byte[128];
                int len = inputStream.read(data);
                if (len > 0) {
                    String str = new String(data, 0, len);
                    Message message = new Message();
                    Bundle bundle = new Bundle();
                    bundle.putString("msg", str);
                    message.setData(bundle);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
}).start();

上面这段代码就实现了通过Socket接收来自开发板的车牌数据并将显示到TextView

3、开发板端实现

开发板端主要就是将识别成功的车牌号码通过Socket发送到 Android APP上面,代码如下

int main(int argc, char *argv[]) {

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        std::cerr << "Error creating socket" << std::endl;
        return 1;
    }

    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("192.168.0.104");
    serv_addr.sin_port = htons(8374);

    if (bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
        return 1;

    if (listen(sockfd, 5) < 0)
        return 1;

    struct sockaddr_in cli_addr;
    socklen_t clilen = sizeof(cli_addr);
    int newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr, &clilen);
    if (newsockfd < 0)
            std::cerr << "Accept failed" << std::endl;
  
    const char* reply = match[1].str().c_str();
    int bytes_sent = send(newsockfd, reply, strlen(reply), 0);
    if (bytes_sent < 0)
        std::cerr << "Error sending data" << std::endl;
     
    close(newsockfd);
    close(sockfd);
    return 0;
}

上面这段代码就是SocketClient 端,将识别的车牌信息发送到手机APP进行显示。

Android APP 部分就介绍结束,具体的运行界面效果如下图所示

四、总结

整个项目的识别过程如下图所示,首先运行程序,启动摄像头运行,然后会获取摄像头中的实时画面进行识别,识别成功就会将车牌的关键字检索出来上传到手机APP上面,这就是整个项目的关键运行流程。

写到这里整个车牌识别的项目就算介绍结束了,实现的功能很有限,但总的来说我对这块Elfboard开发板的体验还是非常好的。首先是板子的资料很多也很全面,还有官方的视频教程,对于新手来说还是非常友好的,容易上手;其次是板子集成有很多的外设资源,比如WiFi、蓝牙、蜂鸣器、温湿度传感器、加速度传感器等等,这样就不需要自己去接线控制,非常的方便;最后我希望在后续的学习中继续使用Elfboard开发板,实现更多的功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1471294.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《低功耗方法学》翻译——第十四章:电源切换网络设计

第十四章&#xff1a;电源切换网络设计 功率门控是在待机或休眠模式下降低漏电功率最有效的方法&#xff0c;但这种方法存在诸如休眠晶体管占用的硅面积、永久和虚拟电源网络的布线资源以及复杂的功率门控设计和实现过程等开销&#xff0c;影响设计风险和进度。 除了开销外&a…

2024年Facebook自动回复优化指南:提升客户满意度的策略(内含自动回复中英文模板)

在这个数字化的时代&#xff0c;快速响应已经成为企业在与客户沟通的必备要素。但是当经常面对大量的相同信息时&#xff0c;如何可以提高效率呢&#xff1f;目前很多社交媒体平台都内设了自动回复功能&#xff0c;像是Facebook。这个功能确保无论何时有人联系你&#xff0c;都…

大气颗粒物与VOCs PMF源解析

原文&#xff1a;大气颗粒物与VOCs PMF源 第一&#xff1a;PMF源解析技术及其输入文件准备 1、大气污染源解析方法有哪些&#xff1f; 2、这些方法各自应用的条件以及它们的优缺点&#xff1f; 3、大气颗粒物的基础知识及各组分的主要来源 大气颗粒物的来源&#xff1a;…

Oracle EBS GL 外币折算逻辑

背景 由于公司财务在10月份期间某汇率维护错误,导致帐套折算以后并合传送至合并帐套生成合并日记帐凭证的借贷金额特别大,但是财务核对的科目余额有没有问题,始终觉得合并日记帐生成会计分发有问题,需要我们给出外币折算逻辑。 基础设置 汇率 Path: GL->设置->币种-&…

Java核心-核心类与API(3)

话接上回&#xff0c;继续核心类与API的学习&#xff0c;这次介绍一下枚举类以及与系统、交互有关的类&#xff0c;需要了解并能使用即可。 一、枚举类 1、概述 枚举也称穷举&#xff0c;简单理解就是把所有可能一一列举出来&#xff08;穷尽所有可能&#xff09;。枚举是一…

matlab新能源汽车三自由度操纵稳定性分析及优化

1、内容简介 略 可以交流、咨询、答疑 55-新能源汽车三自由度操纵稳定性分析及优化 2、内容说明 略 摘 要 电动化是节能减排、寻求替代能源的最佳途径&#xff0c;已成为行业共识&#xff0c;论文基于江西科技学院桑塔纳轿车油改气项目&#xff0c;在拆除发动机、变速…

JAVA工程师面试专题-《Redis》篇

目录 一、基础 1、Redis 是什么 2、说一下你对redis的理解 3、Redis 为什么这么快&#xff1f; 4、项目中如何使用缓存&#xff1f; 5、为什么使用缓存&#xff1f; 6、Redis key 和value 可以存储最大值分别多是多少&#xff1f; 7、Redis和memcache有什么区别&#xf…

普中51单片机学习(8*8LED点阵)

8*8LED点阵 实验代码 #include "reg52.h" #include "intrins.h"typedef unsigned int u16; typedef unsigned char u8; u8 lednum0x80;sbit SHCPP3^6; sbit SERP3^4; sbit STCPP3^5;void HC595SENDBYTE(u8 dat) {u8 a;SHCP1;STCP1;for(a0;a<8;a){SERd…

golang学习7,glang的web的restful接口结构体传参

接口&#xff1a; //POST请求 返回json 接口传参json r.POST("/postJson", controller.PostUserInfo) 1.定义结构体 //定义结构体 type Search struct {Id intName string }2.结构体传参 //结构体传参 func PostUserInfo(c *gin.Context) {search : &Searc…

GO语言基础总结

多态&#xff1a; 定义一个父类的指针&#xff08;接口&#xff09;&#xff0c;然后把指针指向子类的实例&#xff0c;再调用这个父类的指针&#xff0c;然后子类的方法被调用了&#xff0c;这就是多态现象。 Golang 高阶 goroutine 。。。。。 channel channel的定义 …

FPS游戏漫谈优化包体传输

在游戏服务器的部署环境中&#xff0c;机房的网络带宽都是有限制的。如果通信传输的数据总量太大&#xff0c;会挤占带宽甚至达到带宽上限&#xff0c;影响正常消息发送。另外&#xff0c;如果包体太大&#xff0c;在弱网环境下的通信质量会变差&#xff0c;更容易发生丢包重传…

VB6添加资源文件总是内存溢出?最终我还是治好了这胎里病!

网管小贾 / sysadm.cc 说来也奇怪&#xff0c;话说不久前我刚刚解决了 VB6 释放资源文件的一个 BUG &#xff0c;心里正美滋滋的。 不料居然还有个巨大的 BUG 在后边等着我呢&#xff01; 真是不说不知道&#xff0c;一说吓一跳&#xff0c;十天找 BUG &#xff0c;N把辛酸泪…

【国产MCU】-CH32V307-定时器同步模式

定时器同步模式 文章目录 定时器同步模式1、定时器同步模式介绍2、驱动API介绍3、定时器同步模式实例1、定时器同步模式介绍 CH32V307的定时器能够输出时钟脉冲(TRGO),也能接收其他定时器的输入(ITRx)。不同的定时器的ITRx的来源(别的定时器的TRGO)是不一样的。 通用定…

Python高性能web框架--Fastapi快速入门

文章目录 fastapi框架一、预备知识点1.1、http协议一、简介二、 http协议特性三、http请求协议与响应协议 1.2、api接口 二、quick start简单案例 fastapi框架 Fastapi&#xff0c;一个用于构建 API 的现代、快速&#xff08;高性能&#xff09;的web框架。 fastapi的两个核心…

单步调试Linux内核论证水位线

哈喽&#xff0c;我是子牙&#xff0c;一个很卷的硬核男人 深入研究计算机底层、Windows内核、Linux内核、Hotspot源码……聚焦做那些大家想学没地方学的课程。为了保证课程质量及教学效果&#xff0c;一年磨一剑&#xff0c;三年先后做了这些课程&#xff1a;手写JVM、手写OS…

Linux安装jdk、tomcat、MySQL离线安装与启动

一、JDK和Tomcat的安装 1.JDK安装 直接上传到Linux服务器的&#xff0c;上传jdk、tomcat安装包 解压JDK安装包 //解压jdk tar -zxvf jdk-8u151-linux-x64.tar.gz 置环境变量(JAVA_HOME和PATH) vim /etc/profile 在文件末尾添加以下内容&#xff1a; //java environment expo…

matlab一维二维和三维RBF插值方法

1、内容简介 略 50-可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 matlab一维二维和三维RBF插值方法_哔哩哔哩_bilibili 4、参考论文 略

【Linux运维系列】vim操作

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

03-Linux权限

root用户 root用户&#xff08;超级管理员&#xff09; 无论是Windows、MacOS、Linux均采用多用的管理模式进行权限管理 在Linux系统中&#xff0c;拥有最大权限的账户名为&#xff1a;root&#xff08;超级管理员&#xff09;刚开始学习的时候&#xff0c;大多时间都是用的…

MySQL 篇-深入了解 DDL 语言(一)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 MySQL 说明 2.0 DDL 语言 2.1 DDL 语言 - 定义数据库 2.1.1 创建数据库操作 2.1.2 查看数据库操作 2.1.3 使用数据库操作 2.1.4 删除数据库操作 2.2 DDL 语言 …