Spring Boot停车场管理系统方案设计:实现车辆进出场的自动识别与记录

一、背景

在这里插入图片描述
本文是记录最近接的一个单子项目的设计方案,并且已经投入使用,所以简述一下架构思路,可以借鉴来设计其他项目。

车牌识别和长期数据存储是停车场管理系统中的一个核心功能,本文是从一个正在运行的停车场系统抽取的部分代码逻辑,记录如何实现这个功能。
(ps:车位管理我未参与开发,后续了解一下~.另外本文是基于Spring Boot 3.x的解决方案)

二、技术难点与解决方案

2.1 车牌识别的准确性

车牌识别系统的核心在于其识别的准确性,具体影响因素如下:

  • 光照条件:不同的光照条件,如白天、夜晚、强光、阴影等,都可能对车牌识别产生影响。强光可能导致车牌反光,阴影可能导致车牌部分模糊,这些都会降低识别准确率。

  • 车牌污损:车牌如果受到污损、磨损或污垢覆盖,可能导致字符的不完整,从而影响识别过程。

  • 拍摄角度:车牌的拍摄角度直接影响识别效果。理想情况下,车牌应该垂直于摄像头并且居中对齐,但实际场景中,车辆的不同位置与行进方向可能导致角度偏差。

  • 车牌变形:部分车牌可能存在物理上的弯曲或变形,尤其是某些车辆可能由于设计原因或撞击造成车牌非标准平整,这会对识别产生干扰。

  • 字符类型:不同国家和地区的车牌字符类型各异,例如汉字、数字和字母的混合形式,增加了识别的复杂性。

2.2 系统响应速度

车辆进出场的高峰时段,系统需要快速处理每辆车的识别请求,以避免产生拥堵,保持车辆通行顺畅。这对系统的响应速度提出了较高要求。具体影响因素如下:

  • 处理性能:车牌识别本身是一个需要计算密集型的过程,尤其是在高峰期,进出场车辆多,处理性能要求较高。

  • 并发请求:高峰期大量车辆进出,系统需能够处理高并发请求,避免系统瓶颈。

  • 网络延迟:如果采用集中式识别服务器,网络延迟也将影响整体响应速度。

  • I/O 操作:图片上传和下载、日志记录等 I/O 操作也会对系统响应速度产生影响。

2.3 方案

  • 环境光优化:可以在车牌拍摄区域加装补光设备,确保光线均匀充足,避免强光造成的反光问题,并尽量减少阴影区域。

  • 定期维护:保证摄像头镜头的清洁,对摄像设备进行定期维护,清除车牌污损导致的识别问题,提高车牌的视觉质量。

  • 调整摄像头位置和角度:优化摄像头安装位置和角度,确保车牌尽可能垂直于镜头。此外,可以使用多角度摄像头系统,以捕捉车辆进出时的不同角度图像。

  • 算法优化:利用机器学习和深度学习技术,增强车牌识别算法的鲁棒性,能够应对各种复杂场景下的字符识别。比如,训练神经网络模型,利用大数据集进行学习,以提高识别准确度。

  • 性能优化:充分利用硬件资源,可以采取并行处理、硬件加速以及优化算法等手段,如使用 GPU 进行图像处理和识别,以提升处理性能。

  • 分布式架构:采用分布式架构,将车牌识别任务分散到多个节点上,避免单点瓶颈。同时,利用负载均衡技术,均衡分配请求到各处理节点。

  • 提高并发处理能力:在系统设计上,通过异步处理和消息队列(如 Kafka、RabbitMQ)来处理高并发请求,提升系统的并发处理能力。

  • 优化网络延迟:在车牌识别关键节点处配置本地服务器或使用边缘计算设备,减少数据传输的网络延迟。

  • 数据库性能调优:优化数据库的查询性能,例如通过适当的索引设计、分片机制、NoSQL 数据库等手段提高系统在高并发和大数据量下的操作效率。

三、实现逻辑

提供两种识别方案。
Spring Boot 后端服务:用于处理车牌识别请求,管理进出场日志记录。
车牌识别库:用于从图像中提取车牌信息并进行识别。
数据库:用于存储车辆的识别记录和日志。

3.1 创建 Spring Boot 项目,并添加基础的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

3.2 配置数据库

使用MySQL数据库存储车辆信息和日志记录。在 application.properties 中进行配置:

spring.datasource.url=jdbc:mysql://localhost:3306/parking_system
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.jpa.hibernate.ddl-auto=update

3.3 添加车牌识别库(OpenALPR 和 OCR)

3.3.1 添加车牌识别库OpenALPR

<dependency>
    <groupId>org.openalpr</groupId>
    <artifactId>openalpr-java</artifactId>
    <version>2.3.0</version>
</dependency>

定义车辆和日志记录的JPA实体类:

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
public class VehicleLog {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String licensePlate;

    private LocalDateTime entryTime;

    private LocalDateTime exitTime;

    // Getters and Setters
}

编写车牌识别服务类,通过OpenALPR库进行车牌识别:

import org.openalpr.OpenALPR;
import org.openalpr.OpenALPRConfig;
import org.openalpr.jni.Alpr;
import org.openalpr.jni.AlprResults;
import org.springframework.stereotype.Service;

@Service
public class LicensePlateRecognitionService {

    private static final String COUNTRY = "cn"; // 根据实际情况调整国家代码
    private static final String CONFIG_FILE = "/path/to/openalpr.conf";
    private static final String RUNTIME_DIR = "/path/to/runtime_data";

    public String recognizeLicensePlate(String imagePath) {
        Alpr alpr = new Alpr(COUNTRY, CONFIG_FILE, RUNTIME_DIR);
        AlprResults results = alpr.recognize(imagePath);
        alpr.unload();

        if (results.getPlates().isEmpty()) {
            return null;
        } else {
            return results.getPlates().get(0).getBestPlate().getCharacters();
        }
    }
}

创建控制器类,处理车辆的进出场记录:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;

@RestController
@RequestMapping("/api/vehicles")
public class VehicleController {

    @Autowired
    private LicensePlateRecognitionService recognitionService;

    @Autowired
    private VehicleLogRepository vehicleLogRepository;

    @PostMapping("/entry")
    public String vehicleEntry(@RequestParam("file") MultipartFile file) throws IOException {
        // 保存上传的图像文件
        File imageFile = new File("/path/to/uploaded_files/" + file.getOriginalFilename());
        file.transferTo(imageFile);

        // 调用车牌识别服务
        String licensePlate = recognitionService.recognizeLicensePlate(imageFile.getAbsolutePath());

        if (licensePlate == null) {
            return "车牌识别失败";
        }

        // 记录进场日志
        VehicleLog vehicleLog = new VehicleLog();
        vehicleLog.setLicensePlate(licensePlate);
        vehicleLog.setEntryTime(LocalDateTime.now());
        vehicleLogRepository.save(vehicleLog);

        return "车辆 " + licensePlate + " 进场记录成功";
    }

    @PostMapping("/exit")
    public String vehicleExit(@RequestParam("file") MultipartFile file) throws IOException {
        // 保存上传的图像文件
        File imageFile = new File("/path/to/uploaded_files/" + file.getOriginalFilename());
        file.transferTo(imageFile);

        // 调用车牌识别服务
        String licensePlate = recognitionService.recognizeLicensePlate(imageFile.getAbsolutePath());

        if (licensePlate == null) {
            return "车牌识别失败";
        }

        // 查找对应车辆的进场记录
        VehicleLog vehicleLog = vehicleLogRepository.findByLicensePlateAndExitTimeIsNull(licensePlate);
        if (vehicleLog == null) {
            return "未找到对应的进场记录";
        }

        // 记录出场时间
        vehicleLog.setExitTime(LocalDateTime.now());
        vehicleLogRepository.save(vehicleLog);

        return "车辆 " + licensePlate + " 出场记录成功";
    }
}

定义日志记录的JPA仓库接口:

import org.springframework.data.jpa.repository.JpaRepository;

public interface VehicleLogRepository extends JpaRepository<VehicleLog, Long> {
    VehicleLog findByLicensePlateAndExitTimeIsNull(String licensePlate);
}

3.3.2 添加车牌识别库OCR

使用Tesseract OCR库来实现车牌的识别。开源的OCR工具,可以识别多种文字,并且可通过训练来识别特定的文字,非常适合车牌识别。
引入leptonica和tesseract两个依赖库:

<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>leptonica-platform</artifactId>
    <version>1.78.0-1.5.3</version>
</dependency>

<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>tesseract-platform</artifactId>
    <version>4.1.1-1.5.3</version>
</dependency>

实现图像处理技术时,可以使用OpenCV库。不管是二值化、去噪还是增强对比度等操作,OpenCV都提供现成的API。

import org.bytedeco.javacpp.*;
import org.bytedeco.leptonica.*;
import org.bytedeco.tesseract.*;

import java.io.File;

@Service
public class LicensePlateRecognitionService {

    public String recognizeLicensePlate(String imagePath) {

        // 创建TessBaseAPI对象
        TessBaseAPI api = new TessBaseAPI();

        // 初始化Tesseract
        // 这行代码需要指向你的Tesseract安装路径的`tessdata`目录
        if (api.Init("/path/to/tesseract/tessdata", "eng") != 0) {
            System.err.println("Could not initialize tesseract.");
            System.exit(1);
        }

        // 打开图片文件
        PIX image = pixRead(imagePath);
        if (image == null) {
            System.err.println("Could not open input image.");
            System.exit(1);
        }

        // 设置要识别的图像
        api.SetImage(image);

        // 获取识别结果并释放资源
        String result = api.GetUTF8Text().getString();
        api.End();
        pixDestroy(image);

        return result;
    }
}

处理特殊车牌和异常:包括:非标准颜色背景、非标准字体、破损或模糊的车牌等等。

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

@Service
public class SpecialLicensePlateService {

    static{ System.loadLibrary(Core.NATIVE_LIBRARY_NAME); }

    public String preprocess(String imagePath) {
        // 加载图像
        Mat src = Imgcodecs.imread(imagePath, Imgcodecs.IMREAD_GRAYSCALE);
        Mat dst = new Mat();

        // 去噪
        Imgproc.fastNlMeansDenoising(src, dst, 10, 7, 21);

        // 二值化
        Imgproc.threshold(dst, dst, 0, 255, Imgproc.THRESH_BINARY + Imgproc.THRESH_OTSU);

        // 增强对比度 - 创建CLAHE对象
        CLAHE clahe = Imgproc.createCLAHE();
        clahe.setClipLimit(4.0);
        clahe.apply(dst, dst);

        // 保存处理后的图像到指定路径
        String processedImagePath = "/path/to/processed/image";
        Imgcodecs.imwrite(processedImagePath, dst);

        return processedImagePath;
    }
}

当请求到达服务器,首先检查输入的车牌图片是否需要预处理,之后再调用车牌识别服务进行识别。

@RestController
public class LicensePlateController {

  @Autowired
  private SpecialLicensePlateService specialLicensePlateService;

  @Autowired
  private LicensePlateRecognitionService licensePlateRecognitionService;

  @RequestMapping("/recognize")
  public String recognize(@RequestParam("image") String imagePath) {
    // 先把车牌图片保存到本地
    String processedImagePath = specialLicensePlateService.preprocess(imagePath);
    String licensePlate = licensePlateRecognitionService.recognizeLicensePlate(processedImagePath);

    return licensePlate;
  }
}

3.4 停车数据的长期存储与分析

前面提到使用MySQL来存储车辆的停车数据信息,包含:时间、地点、车牌信息等,随着时间的推移,数据量会呈指数级增长,对这些海量数据进行高效存储并进行实时分析,MySQL数据库是无法承受这个数据量的。

方案:结合大数据存储框架(如 HBase 或 Cassandra)以及实时处理框架(如 Kafka 和 Spark),实现停车数据的高效存储与分析。

选型:

  • HBase:基于 Hadoop 的分布式数据库,适合存储海量的结构化数据。
  • Spark Streaming:实时数据处理框架,适合大规模数据的实时分析。
  • Kafka:分布式流处理平台,用于处理实时数据流。通过 Kafka,我能实现停车数据的实时接收与处理。

3.5 关键模块设计与实现

3.5.1 REST API

使用 Spring Boot 提供 REST API 接口,供客户端上传停车数据。当数据上传后,将这些数据写入 Kafka,以供后续处理。

@RestController
@RequestMapping("/api/parking")
public class ParkingController {

    private final KafkaTemplate<String, ParkingData> kafkaTemplate;

    public ParkingController(KafkaTemplate<String, ParkingData> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    @PostMapping("/upload")
    public ResponseEntity<String> uploadParkingData(@RequestBody ParkingData parkingData) {
        // 将停车数据发送到 Kafka
        kafkaTemplate.send("parking_topic", parkingData);
        return ResponseEntity.ok("数据上传成功");
    }
}
@Configuration
public class KafkaProducerConfig {

    @Bean
    public ProducerFactory<String, ParkingData> producerFactory() {
        Map<String, Object> configProps = new HashMap<>();
        configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
        return new DefaultKafkaProducerFactory<>(configProps);
    }

    @Bean
    public KafkaTemplate<String, ParkingData> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }
}

3.5.2 HBase 数据存储

使用 HBase 对停车数据进行存储。首先需要创建一个 HBase 表来存储停车数据。然后,通过 Spark Streaming 从 Kafka 中读取实时数据,并将其写入 HBase。
配置 HBase 表:

hbase(main):001:0> create 'parking_data', 'cf'

从 Kafka 读取数据并写入 HBase:

object ParkingDataToHBase {
    def main(args: Array[String]): Unit = {
        val sparkConf = new SparkConf().setAppName("ParkingDataToHBase").setMaster("local[*]")
        val ssc = new StreamingContext(sparkConf, Seconds(10))
        
        val kafkaParams = Map[String, Object](
            "bootstrap.servers" -> "localhost:9092",
            "key.deserializer" -> classOf[StringDeserializer],
            "value.deserializer" -> classOf[StringDeserializer],
            "group.id" -> "parking_group",
            "auto.offset.reset" -> "latest",
            "enable.auto.commit" -> (false: java.lang.Boolean)
        )

        val topics = Array("parking_topic")
        val stream = KafkaUtils.createDirectStream[String, String](
            ssc,
            PreferConsistent,
            Subscribe[String, String](topics, kafkaParams)
        )
        
        stream.foreachRDD { rdd =>
            rdd.foreachPartition { partitionOfRecords =>
                val connection = ConnectionFactory.createConnection(HBaseConfiguration.create())
                val table = connection.getTable(TableName.valueOf("parking_data"))
                
                partitionOfRecords.foreach { record =>
                    val data = parse(record.value()).extract[ParkingData]
                    val put = new Put(Bytes.toBytes(data.id))
                    put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("time"), Bytes.toBytes(data.time))
                    put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("location"), Bytes.toBytes(data.location))
                    put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("licensePlate"), Bytes.toBytes(data.licensePlate))
                    table.put(put)
                }
                
                table.close()
                connection.close()
            }
        }
        
        ssc.start()
        ssc.awaitTermination()
    }
}

3.5.3 实时数据分析

使用 Spark Streaming 对停车数据进行实时分析。例如,统计每小时的停车次数。通过 Spark Streaming,可以从 Kafka 中读取数据,并进行实时聚合计算,从而快速了解停车数据的变化情况。

object ParkingDataAnalysis {
    def main(args: Array[String]): Unit = {
        val sparkConf = new SparkConf().setAppName("ParkingDataAnalysis").setMaster("local[*]")
        val ssc = new StreamingContext(sparkConf, Seconds(10))
        
        val kafkaParams = Map[String, Object](
            "bootstrap.servers" -> "localhost:9092",
            "key.deserializer" -> classOf[StringDeserializer],
            "value.deserializer" -> classOf[StringDeserializer],
            "group.id" -> "parking_analysis_group",
            "auto.offset.reset" -> "latest",
            "enable.auto.commit" -> (false: java.lang.Boolean)
        )

        val topics = Array("parking_topic")
        val stream = KafkaUtils.createDirectStream[String, String](
            ssc,
            PreferConsistent,
            Subscribe[String, String](topics, kafkaParams)
        )
        
        val parkingData = stream.map(record => parse(record.value()).extract[ParkingData])
        
        // 按小时统计停车次数
        val windowedData = parkingData.map(data => (data.time.split(" ")(1).split(":")(0), 1))
                                         .reduceByKeyAndWindow((a: Int, b: Int) => a + b, Minutes(60), Minutes(10))
        
        windowedData.print()
        
        ssc.start()
        ssc.awaitTermination()
    }
}

3.5.4 注意事项

  • 数据存储的可扩展性:使用 HBase 等分布式存储框架来应对海量数据,同时确保数据分区的合理性,提高读写效率。在设计 HBase 表时,需要合理设计行键和列族,避免热点问题,以提高数据访问性能。

  • 数据分析的实时性:使用 Spark Streaming 进行实时数据处理,通过合理设置批处理间隔,实现数据的实时分析。同时,结合 Kafka 的回溯功能,可以实现数据的重放和重新计算,保证数据分析的准确性。

  • 系统的扩展性和可维护性:系统设计时,应采用模块化设计,确保各个模块可以独立扩展和替换。例如,可以通过增加 Kafka 分区数和 HBase RegionServer 数量,来提升系统的整体处理能力。同时,使用监控工具如 Prom

  • etheus 和 Grafana,对系统的各个组件进行实时监控,及时发现和解决系统瓶颈问题。

四、总结

结合 Spring Boot、OCR/OpenALPR、Kafka、HBase 和 Spark 等技术框架,可以实现车牌有效识别、停车数据的高效存储和实时分析,满足停车系统对海量数据处理的需求。

方案优势:

  • 高并发与高可用性:利用 Kafka 和 HBase 的分布式架构,能够应对高并发的停车数据写入和查询需求。同时,通过配置多节点的 Kafka 集群和 HBase 集群,确保系统的高可用性。

  • 灵活的扩展性:无论是增加 Kafka 分区数还是扩展 HBase RegionServer 数量,都能有效提高系统的吞吐量和处理性能。通过 Spring Boot 微服务架构,可以随时扩展新的功能模块,增强系统的灵活性和适应性。

  • 实时的数据处理与分析:借助 Spark Streaming 的强大流处理能力,能够对停车数据进行实时分析,及时发现和预警停车问题。结合 HBase 强大的数据存储和检索能力,实现从数据采集、存储到分析的全链条闭环和高效。

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-23 06:32:04       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-23 06:32:04       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-23 06:32:04       45 阅读
  4. Python语言-面向对象

    2024-07-23 06:32:04       55 阅读

热门阅读

  1. 什么是服务器带宽

    2024-07-23 06:32:04       13 阅读
  2. 在VS IDE中搜索所有带有中文的字符串

    2024-07-23 06:32:04       16 阅读
  3. Python面试整理-字符串处理

    2024-07-23 06:32:04       12 阅读
  4. SQL中的函数

    2024-07-23 06:32:04       16 阅读
  5. Logback 配置文件加载步骤

    2024-07-23 06:32:04       14 阅读
  6. Ubuntu 20.04搭建OpenCV 4.5.0 & C++环境

    2024-07-23 06:32:04       12 阅读
  7. 定制Mojo模型的魔法:特征转换的艺术

    2024-07-23 06:32:04       16 阅读
  8. 自定义预测逻辑:Mojo模型的高级应用

    2024-07-23 06:32:04       17 阅读
  9. Mojo模型动态批处理:智能预测的终极武器

    2024-07-23 06:32:04       16 阅读
  10. Android 自定义系统版本号

    2024-07-23 06:32:04       12 阅读
  11. 曼哈顿距离与切比雪夫距离

    2024-07-23 06:32:04       16 阅读
  12. 技术文档总结----思维导图

    2024-07-23 06:32:04       16 阅读
  13. C语言强化-1.数据结构概述

    2024-07-23 06:32:04       14 阅读