树莓派4B-搭建一个本地车牌识别服务器

实现目标:

一、设备自启后能够获得服务的ip与端口号,用于计算机连接设备;
二、计算机可以通过服务ip与端口访问设备服务;
三、上传需要处理的数据,返回结果反馈给用户;
四、上传到服务器的数据不会导致设备内存耗尽,自动删除多余的数据;
进阶(未实现):
五、提供用户登陆功能,设备支持唯一damin管理员用户。其他用户可注册;
六、提供登陆界面,用户通过登陆界面输入用户名与密码登录;
七、支持已处理的数据记录在设备数据库当中,提供web页面可查;
八、查询的历史数据支持查看:数据处理时间、处理前上传图片和处理后车牌结果。

使用到的工具与方法

一、systemctl自启服务
二、oled
二、python
三、flask
四、bootstrap框架
五、hyperlpr车牌识别的v1版本

实现过程:

一、配置自启服务,开机显示服务ip与端口
使用systemctl配置开机自启由oled显示ip与端口(在前面另一篇文章)。

在这里插入图片描述
二、将基于flask的web程序加入到run.sh执行脚本当中

在这里插入图片描述

前提是bootShowIP.service文件已经放到/lib/usr/systemd/system目录当中并已启用:

在这里插入图片描述

三、在将flask程序部署到树莓派上前,在本地进行了测试。
1、本地可以正常安装使用hyperlpr3,但是在设备上运行时提示由于无法安装onnxruntime导致的无法使用车牌识别功能。
2、无法安装onnxruntime可能是因为onnxruntime没有支持设备的处理器架构的版本。尝试安装hyperlpr v1成功:

python -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple hyperlpr
#防止下载过慢,使用-i指定下载镜像

3、由于hyperlpr v1版本比较旧,依赖的numpy版本也比较旧,如果直接安装最新的numpy会导致无法使用:
尝试解决:

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgrade --force-reinstall numpy

在这里插入图片描述

安装失败,按提示“Can't roll back numpy; was not uninstalled”,卸载pip uninstall numpy

在这里插入图片描述

卸载之后发现hyperlpr正常了,并且已经存在numpy,可能是安装了多次

在这里插入图片描述

四、测试安装的hyperlpr模块正常后,将以hyperlpr作为后端的falsk程序部署到树莓派

车牌识别系统主页:

在这里插入图片描述

提交待识别车牌:

在这里插入图片描述

获取车牌识别结果与车牌抠图:

在这里插入图片描述

所有源代码

一、项目代码目录结构:

在这里插入图片描述
目录文件夹与文件作用说明:
1、css文件夹是boostrap框架的静态文件,用于控制web页面的样式,可以从bootstrap快捷简单的获得美观的表单;
2、static文件夹是字体(目录fonts)、web页面默认展示图片(目录src)、保存用户上传图片和经过处理的图片结果保存目录(目录img);
3、templates文件夹是web页面的html文件保存目录;
4、app.py文件是flask程序的入口函数文件;
5、mu_util.py文件是本人收集实现的一些工具函数文件;
6、plateNoRegPy.py文件是基于hyperlpr的车牌识别处理实现代码文件。

二、hyperlpr模块测试文件夹目录:

在这里插入图片描述

from hyperlpr import *
import cv2if __name__ == '__main__':image = cv2.imread("test.jpg")# 打印识别结果lpr_result = HyperLPR_plate_recognition(image)print("[0]")print(lpr_result[0])result = lpr_result[0]print("plateNo:")print(lpr_result[0][0])print("location:")print("x1{},y1{},x2{},y2{}".format(result[2][0], result[2][1], result[2][2], result[2][3]))

三、css文件中的文件由bootstrap中下载

在这里插入图片描述
四、static文件夹
1、fonts文件夹

在这里插入图片描述
2、img文件夹,程序第一次使用前为空

在这里插入图片描述
3、src文件夹

在这里插入图片描述
五、templates文件夹

在这里插入图片描述

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>车牌识别</title><link rel="stylesheet" href="/static/css/bootstrap.css"><style>/*static 文件夹是默认用于存放静态文件的,比如 CSS、JavaScript、图片和字体文件等。Flask 会自动为 static 文件夹下的所有文件提供静态文件的路由,使得这些文件可以被直接访问,而不需要你为每个文件单独编写路由。*/@font-face {font-family: 'KingHwa'; /* 自定义字体名称 *//*此处将字体文件加入到static文件夹当中,就省去了编写路由的工作,ttf文件对应路由格式truetype*/src: url('../static/fonts/KingHwa_OldSong.ttf') format('truetype');/* 字体文件路径和格式 */font-weight: normal;font-style: normal;}body {background-color: rgba(173, 216, 230, 0.5); /*设置页面背景颜色*/font-family: "KingHwa", sans-serif; /*设置字体*/}.center-image {/*position: fixed;*/display: block;margin-top: 4%;margin-left: 40%;margin-right: 40%;border-radius: 4%; /* 设置圆角大小 */width: 20%; /* 你可以根据需要调整宽度 */}.center-bnt {/*position: fixed;*/display: block;{#margin-top: 10%;#}margin-top: 5%;margin-left: 45%;margin-right: 45%;width: 10%; /* 你可以根据需要调整宽度 */}.rounded-font {display: block;margin-top: 8%;border-radius: 2%; /* 设置圆角大小 */font-size: 360%; /* 设置字体大小 */text-align: center; /* 将文本居中 */}#backToTop {position: fixed;bottom: 20px;right: 30px;z-index: 99;border: none;outline: 1px solid black;/*设置轮廓*/background-color: rgba(0, 0, 230, 0.5);color: white;cursor: pointer;padding: 4px 5px;border-radius: 2px;/*设置圆角*/}.default-img {/*position: fixed;*/display: block;{#margin-top: 10%;#}{#margin-top: 5%;#}margin-left: 30%;margin-right: 30%;width: 20%; /* 你可以根据需要调整宽度 */border-radius: 2%;/*设置圆角*/}.back-home {position: fixed;bottom: 15px; /* 初始时,将元素移出视口 */right: 100px;/* 其他样式 */}</style>
</head>
<h1 class="rounded-font">车牌识别</h1>
<body>
{#    <form style="width:100%;margin:2% auto;" method="post">#}
{#      <button type="submit"  class="btn btn-primary center-bnt" style="font-size: 150%">提交图片文件</button>#}
{#    </form>#}<center><div class="body-container"><div class="jumbotron"><form style="width:100%;margin:2% auto;" method="post" enctype = "multipart/form-data"><h3> {{ data.success }} </h3><input class = "btn btn-primary" type="file" name="image" accept="image/*"><input class = "btn btn-primary" type="submit"></form><img src="static/img/{{ name }}" alt="{{ name }}" class="default-img">{% for d in data.predictions %}<h5> {{ d.label}}, 置信度:{{d.probability }}% </h5>{% endfor %}</div></div></center>
{#    <a href="{{ url_for('process_plateReg')}}" class="back-home">返回首页</a>#}
{#  <button onclick="topFunction()" id="backToTop" title="回到顶部">#}
{#    <img src="{{ url_for('send_image', path='src/toTop.jpg') }}" alt="返回顶部">#}
{#  </button>#}<script>function topFunction() {window.scrollTo(0, 0);}</script>
</body>
</html>

六、app.py

from flask import Flask,render_template,request,redirect,send_file
import os
import plateNoRegPyapp = Flask(__name__)# 设置配置
UPLOAD_FOLDER = os.path.join(app.root_path, 'static', 'img')
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
SOURCE_FOLDER = os.path.join(app.root_path, 'static', 'src')
app.config['SOURCE_FOLDER'] = SOURCE_FOLDER
# 注册路由和其他配置
app.register_blueprint(plateNoRegPy.blueprint)  # 车牌图片识别蓝图注册@app.route('/src/<path:path>')#网页的所有文件都是来自服务器
def send_image(path):return send_file(path, mimetype='image/jpeg')@app.route('/')
def hello_world():  # put application's code herereturn redirect('/plateReg')#跳转车牌识别return 'Hello World!'if __name__ == '__main__':# app.run()app.run(host='0.0.0.0', port=8000)#服务访问ip与端口

七、plateNoRegPy.py

# 功能实现模型来源
# hyperlpr3在树莓派上可能无法安装 https://github.com/szad670401/HyperLPR?tab=readme-ov-file
# https://github.com/szad670401/HyperLPR/tree/v1
import cv2
# import hyperlpr3 as lpr3
from hyperlpr import *
from PIL import Image
from flask import request, url_for, render_template, redirect, Blueprint, current_app
from my_util import Logger, copy_file, delete_files
import os
import random# initialize our Flask application and the lpr object
blueprint = Blueprint('processPlatNoImg', __name__)lprObject = None
loger = Logger()
default_pic_name = "defaultPlate.jpg"def init_default_source():# 删除多余的图片max_files_allowed = 30 #后续拷贝一个默认图片,加上上传图片,实际文件夹当中有32个文件delete_files(current_app.config['UPLOAD_FOLDER'], max_files_allowed)# 默认图片拷贝source_path = os.path.join(current_app.config['SOURCE_FOLDER'], default_pic_name)dest_path = os.path.join(current_app.config['UPLOAD_FOLDER'], default_pic_name)if not os.path.exists(dest_path) or not os.path.isfile(dest_path):copy_file(source_path, dest_path)# Instantiate object
def load_object():# 默认图片拷贝source_path = os.path.join(current_app.config['SOURCE_FOLDER'], default_pic_name)dest_path = os.path.join(current_app.config['UPLOAD_FOLDER'], default_pic_name)copy_file(source_path, dest_path)global lprObjectlprObject = HyperLPR_plate_recognition'''
def load_object3():# 默认图片拷贝source_path = os.path.join(current_app.config['SOURCE_FOLDER'], default_pic_name)dest_path = os.path.join(current_app.config['UPLOAD_FOLDER'], default_pic_name)copy_file(source_path, dest_path)global lprObjectlprObject = lpr3.LicensePlateCatcher()
'''def prepare_image(image):loger.debug("Preparing image"+image)image = cv2.imread(image)return imagedef crop_image(image1, image2, x1, y1, x2, y2):# 打开图片img = Image.open(image1)# 截取图片,参数为左上角和右下角的坐标cropped_img = img.crop((x1, y1, x2, y2))# 保存截取的图片cropped_img.save(image2)loger.debug("recognize plateNo: " + image2)@blueprint.route('/plateReg', methods=['GET', 'POST'])  # 访问的路径
def process_plateReg():init_default_source()data = {"success": "未上传"}title = "Upload an image"name = default_pic_nameif request.method == "POST":if request.files.get("image"):image1 = request.files["image"]imagePath = os.path.join(current_app.config['UPLOAD_FOLDER'], image1.filename)image = image1.save(imagePath)loger.debug("接受车牌图片路径"+imagePath)processed_image = prepare_image(imagePath)data["predictions"] = []'''try:if lprObject == None:load_object3()loger.info("lpr object not initialzed")lpr3_results = lprObject(processed_image)except Exception as e:print("Exception  load hyperlpr3", e)'''try:if lprObject == None:load_object()loger.info("lpr object not initialzed")lpr_results = lprObject(processed_image)except Exception as e:print("Exception load hyperlpr", e)lpr_results = ""if len(lpr_results):print("lpr_results:")print(lpr_results[0])result = lpr_results[0]# image2Name = "outputPlatNo"image2Name = result[0] + ".jpg"image2 = os.path.join(current_app.config['UPLOAD_FOLDER'], image2Name)crop_image(imagePath, image2, result[2][0], result[2][1], result[2][2], result[2][3])r = {"label": result[0], "probability": int(100*result[1])}else:image2Name = image1.filenamer = {"label": "unkonw", "probability": int(0)}data["predictions"].append(r)# indicate that the request was a successdata["success"] = "已上传"title = "predict"return render_template('plateNoReg.html', data=data, title=title, name=image2Name)return render_template('plateNoReg.html', data=data, title=title, name=name)

八、my_util.py

import os
import sys
import time
import shutil
import logging
import time
from datetime import datetime#进度条
def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=100, fill='█', print_end="\r"):"""调用在Python终端中打印自定义进度条的函数iteration - 当前迭代(Int)total - 总迭代(Int)prefix - 前缀字符串(Str)suffix - 后缀字符串(Str)decimals - 正数的小数位数(Int)length - 进度条的长度(Int)fill - 进度条填充字符(Str)print_end - 行尾字符(Str)"""percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))filled_length = int(length * iteration // total)bar = fill * filled_length + '-' * (length - filled_length)print(f'\r{prefix} |{bar}| {percent}% {suffix}', end=print_end)# 打印新行,完成进度条if iteration == total:print()class Logger(object):"""终端打印不同颜色的日志"""ch = logging.StreamHandler()  # 创建日志处理器对象,在__init__外创建,是类当中的静态属性,不是__init__中的实例属性# #创建静态的日志处理器可以减少内存消耗# # 创建 FileHandler 实例,指定日志文件路径# ch = logging.FileHandler(filename='app1.log')def __init__(self):self.logger = logging.getLogger()  # 创建日志记录对象self.logger.setLevel(logging.DEBUG)  # 设置日志等级info,其他低于此等级的不打印def debug(self, message):self.fontColor('\033[0;37m%s\033[0m')self.logger.debug(message)def info(self, message):self.fontColor('\033[0;32m%s\033[0m')self.logger.info(message)def warning(self, message):self.fontColor('\033[0;33m%s\033[0m')self.logger.warning(message)def error(self, message):self.fontColor('\033[0;31m%s\033[0m')self.logger.error(message)def fontColor(self, color):formatter = logging.Formatter(color % '%(asctime)s - %(name)s - %(levelname)s - %(message)s')  # 控制日志输出颜色self.ch.setFormatter(formatter)self.logger.addHandler(self.ch)  # 向日志记录对象中加入日志处理器对象def delete_files(folder_path, max_files):"""监控指定文件夹中的文件数量,并在超过max_files时删除最旧的文件。"""print("进入删除图片文件夹"+folder_path)print("需要删除文件数量")print(max_files)if True:# 获取文件夹中的文件列表files = os.listdir(folder_path)file_count = len(files)print(f"当前文件夹 {folder_path} 中的文件数量: {file_count}")# 如果文件数量超过max_files,则删除最旧的文件if file_count > max_files:# 获取文件夹中所有文件的完整路径,并带上修改时间file_paths_with_mtime = [(os.path.join(folder_path, f), os.path.getmtime(os.path.join(folder_path, f))) forf in files]# 按修改时间排序sorted_files = sorted(file_paths_with_mtime, key=lambda x: x[1])# 删除最旧的文件,直到文件数量在阈值以下for file_path, mtime in sorted_files[:file_count - max_files]:try:os.remove(file_path)print(f"已删除文件: {file_path}")except OSError as e:print(f"删除文件时出错: {e.strerror}")def copy_file(src, dst):shutil.copy2(src, dst)  # copy2会尝试保留文件的元数据

整体实现效果

[稍后上传]

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

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

相关文章

亲测-wordpress文章实时同步发布修改删除多个站点的WP2WP插件

一款将wordpress文章同步到其他WordPress网站的插件&#xff0c;通过这款插件&#xff0c;可以保持不同博客之间文章发布、修改、删除的同步。 安装步骤&#xff1a; 主站和分站都要上传这个插件 1.把插件上传到wp-content\plugins解压出来wp2wp文件夹&#xff0c;然后启用插…

本地运行.net项目

有时候需要我们自己做一个.net的课设项目&#xff0c;但是我们有了代码后却不知道怎么运行。我们0基础来学习一下如何运行一个.net项目 1.安装visual studio 2022 不用安装老版本&#xff0c;新版就可以。安装好了2022版本&#xff0c;这是一个支持web的IDE&#xff0c;我们可…

C++入门指南(上)

目录 ​编辑 一、祖师爷画像 二、什么是C 三、C发展史 四、C在工作领域的应用 1. 操作系统以及大型系统软件开发 2. 服务器端开发 3. 游戏开发 4. 嵌入式和物联网领域 5. 数字图像处理 6. 人工智能 7. 分布式应用 五、如何快速上手C 一、祖师爷画像 本贾尼斯特劳斯…

JavaScript基础(六)

break & continue continue跳出本次循环&#xff0c;继续下面的循环。 break跳出终止循环。 写个简单的例子: <script> for (var i1; i<5; i){ if (i3){ continue; } console.log(i); } </script> 结果就是跳过i等于3的那次循环&#xff0c;而break: f…

Stable Diffusion是什么?

目录 一、Stable Diffusion是什么&#xff1f; 二、Stable Diffusion的基本原理 三、Stable Diffusion有哪些运用领域&#xff1f; 一、Stable Diffusion是什么&#xff1f; Stable Diffusion是一个先进的人工智能图像生成模型&#xff0c;它能够根据文本描述创造出高质量的图…

牛客NC404 最接近的K个元素【中等 二分查找+双指针 Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/b4d7edc45759453e9bc8ab71f0888e0f 知识点 二分查找&#xff1b;找到第一个大于等于x的数的位置idx;然后从idx开始往两边扩展Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、…

【基于 PyTorch 的 Python 深度学习】6 视觉处理基础:卷积神经网络(1)

前言 文章性质&#xff1a;学习笔记 &#x1f4d6; 学习资料&#xff1a;吴茂贵《 Python 深度学习基于 PyTorch ( 第 2 版 ) 》【ISBN】978-7-111-71880-2 主要内容&#xff1a;根据学习资料撰写的学习笔记&#xff0c;该篇主要介绍了卷积神经网络的卷积层部分。 预&#xff1…

Spring编程使用DDD的小把戏

场景 现在流行充血领域层&#xff0c;在原本只存储对象的java类中&#xff0c;增加一些方法去替代原本写在service层的crud&#xff0c; 但是例如service这种一般都是托管给spring的&#xff0c;我们使用的ORM也都托管给spring&#xff0c;这样方便在service层调用mybatis的m…

【大数据】HDFS

文章目录 [toc]HDFS 1.0NameNode维护文件系统命名空间存储元数据解决NameNode单点问题 SecondaryNameNode机架感知数据完整性校验校验和数据块检测程序DataBlockScanner HDFS写流程HDFS读流程HDFS与MapReduce本地模式Block大小 HDFS 2.0NameNode HANameNode FederationHDFS Sna…

windows环境下 postgresql v12 绿色版+postgis 3.4.1版本配置,空间数据库迁移

windows环境下 postgresql v12 绿色版+postgis 3.4.1版本配置,空间数据库迁移 一、软件环境 操作系统:windows 11 pg免安装版数据库:postgresql-12.17-1-windows-x64-binaries.zip 下载地址:https://get.enterprisedb.com/postgresql/postgresql-12.18-1-windows-x64-bina…

ISIS学习二——与OSPF相比的ISIS报文以及路由计算

目录 一.ISIS支持的网络类型 1.OSPF支持 2.ISIS支持 二.ISIS最优路径的选取 &#xff08;1&#xff09;.ISIS开销值设置 1.全局开销 2.接口开销 3.根据带宽设置开销 &#xff08;2&#xff09;.ISIS的次优路径 三.ISIS报文格式 1.ISIS专用报头——TLV 2.ISIS通用头…

JavaScript APIs

控制网页元素交互等各种网页交互效果。 一、Web API基本认知 声明数组和变量优先使用const 使用let声明变量的情况&#xff1a; 1、如果基本数据类型的值或者引用类型的地址发生变化的时候&#xff0c;需要用let 2、比如 一个变量进行加减运算&#xff0c;比如 for循环中的…

FreeRTOS的列表和列表项 list.c文件详解

列表、列表项的定义以及初始化 列表相当于链表&#xff0c;列表项相当于节点&#xff0c;FreeRTOS中的列表相当于一个双向环形链表。 列表使用指针指向列表项。一个列表&#xff08;list&#xff09;下面可能有很多个列表项&#xff08;list item&#xff09;&#xff0c;每个…

第十三篇:智慧之网:深度探索关系型数据库的数学奥秘与实战技艺

智慧之网&#xff1a;深度探索关系型数据库的数学奥秘与实战技艺 1. 引言 1.1 数据时代的基石 在数字化的浪潮中&#xff0c;数据已成为新时代的石油&#xff0c;而关系型数据库则是这座数据矿藏的精炼厂。自E.F. Codd在1970年提出关系模型以来&#xff0c;关系型数据库以其坚…

LeetCode - 0088 合并两个有序数组

题目地址&#xff1a;https://leetcode.cn/problems/merge-sorted-array/description/ 引言&#xff1a;话接上回&#xff0c;由于上次面试官着急下班&#xff0c;面试不得不提前终止&#xff0c;这不&#xff0c;他又找我去面试了 面试官&#xff1a;你好&#xff0c;小伙子&a…

【二叉树】Leetcode 二叉树的锯齿形层序遍历

题目讲解 103. 二叉树的锯齿形层序遍历 算法讲解 这道题其实是和N叉树层序遍历是一样的&#xff0c;只不过是要求每一次的遍历的方向不一样&#xff1b;注意&#xff1a;这一次的使用的队列不能够是queue了&#xff0c;因为需要从后往前遍历容器&#xff0c;所以就可以使用v…

vs code中如何使用git

由于本地代码有了一些储备&#xff0c;所以想通过网址托管形式&#xff0c;之前一直使用了github&#xff0c;但是鉴于一直被墙&#xff0c;无法登录账号&#xff0c;所以选择了国内的gitee来作为托管网站。 gitee的网址&#xff1a;Gitee - 基于 Git 的代码托管和研发协作平台…

事件高级部分

一&#xff0c;注册事件 即给元素添加事件 1.传统注册方式 2.方法监听注册方式 事件类型&#xff1a;字符串形式&#xff0c;不用带on 可以给一个元素添加多个程序 二.删除事件 1.方式 参数见上文 三.DOM事件流 事件的传播过程叫做事件流 js代码只能获取一个阶段&#xf…