基于Django的智能停车场收费系统:车牌识别与计费逻辑实现
简介这是一套面向计算机相关专业毕业设计场景的停车场收费管理系统完整实现方案采用Python与Django框架开发并集成车牌识别模块适合需要完成毕设课题或学习Web系统开发的学习者参考。资源包共235个文件约3.82MB以25个py源码文件、15个html模板、15个js脚本与10个css样式文件为主体辅以sql数据库脚本、md说明文档及图片素材覆盖后端逻辑、前端页面与数据存储各层。已有58人学习下载。项目围绕车辆进出记录、费用计算、数据统计与自动化车牌识别等核心业务展开数据库采用规范化设计识别模块基于成熟图像处理算法实现代码注释详尽、目录结构清晰便于理解系统架构与实现细节。学习者可借此掌握Django Web开发、数据库设计与集成、图像识别技术应用等实践技能并作为同类管理系统开发的参考范例。1. 从一张 Excel 收费表说起这套 Django 停车场系统到底能跑多远如果你正在做毕业设计选题是停车场收费管理大概率经历过这样的场景用 Excel 记车牌、算时长、手填金额答辩前老师问一句「进出场时间怎么保证不重复计费」你就开始冒汗。这套基于 Python 与 Django 的智能停车场收费系统核心就是把这个手工流程换成一套能跑起来的 Web 应用——车牌识别负责把车「认出来」Django 负责把进出场记录、费率规则、收费流水管起来数据库负责让这些数据不丢、可查、可对账。它适合三类人一是毕业设计需要完整可演示系统的同学二是想拿一个真实业务练 Django 增删改查和 ORM 的初学者三是需要给小型车场做一套轻量管理后台的开发者。整套东西不依赖重型框架Python 装好、Django 跑起来、数据库连上就能在本地把「车进场—识别—计费—出场—放行」这条链路走通。下面我按实际拆项目的顺序把选型理由、建表逻辑、识别接入和踩坑点一层层摊开。2. 环境与数据模型先把 Django 项目和停车业务表搭对2.1 为什么用 Django 而不是 Flask 做这套系统停车场收费系统的本质是「一堆有状态的数据 一套规则计算 一个能看的后台」。Django 自带 ORM、Admin 后台、迁移工具和表单校验这四样东西恰好覆盖了停车业务里最费时间的部分。用 Flask 你得自己拼 SQLAlchemy、自己写后台页面、自己做表单验证毕业设计周期里这些重复劳动会吃掉大半时间。Django 的 Admin 还有一个隐性好处答辩现场老师让你「查一下今天所有未缴费的车」你直接进 Admin 按条件筛就行不用临时改代码。常见做法是把车辆、车位、进出场记录、费率规则拆成独立模型用外键关联这样计费逻辑只依赖记录表不会和车辆信息耦合死。选型上还有一个现实考量Django 的models.py写清楚字段类型和约束后makemigrations和migrate两条命令就能把表建好数据库管理这块基本不用手写 DDL。对新手来说这比直接怼 MySQL 建表语句稳得多。2.2 建项目与建 App 的完整命令先确认 Python 版本建议 3.8 及以上Django 用 4.x 稳定版。下面这套命令我一般会完整走一遍注意虚拟环境一定要建不然后面装依赖会污染系统 Python。# 创建项目目录并进入 mkdir parking_system cd parking_system # 创建虚拟环境Windows 用 python -m venv venv python3 -m venv venv source venv/bin/activate # 安装 Django 和数据库驱动 pip install django mysqlclient pillow # 创建 Django 项目注意结尾的点避免多一层目录 django-admin startproject parking . # 创建业务 App命名 parking_app python manage.py startapp parking_appstartproject结尾那个点很关键不加的话会在当前目录下再套一层parking/后面路径全要改。mysqlclient是连 MySQL 用的如果你先用 SQLite 练手可以暂时不装Django 默认就是 SQLite零配置能跑。2.3 停车业务四张核心表怎么设计数据模型是这套系统的地基设计错了后面计费全是坑。我一般拆成车辆表、车位表、进出场记录表、费率规则表四张。下面这段models.py可以直接抄字段注释写清楚了每个字段的业务含义。from django.db import models class Vehicle(models.Model): 车辆信息表一个车牌对应一条记录 plate_number models.CharField(max_length10, uniqueTrue, verbose_name车牌号) owner_name models.CharField(max_length20, blankTrue, verbose_name车主姓名) created_at models.DateTimeField(auto_now_addTrue) def __str__(self): return self.plate_number class ParkingSpace(models.Model): 车位表记录车位编号和占用状态 space_no models.CharField(max_length10, uniqueTrue, verbose_name车位编号) is_occupied models.BooleanField(defaultFalse, verbose_name是否占用) class RateRule(models.Model): 费率规则表按小时计费支持免费时长 name models.CharField(max_length30, verbose_name规则名称) free_minutes models.IntegerField(default15, verbose_name免费时长(分钟)) price_per_hour models.DecimalField(max_digits6, decimal_places2, verbose_name每小时单价) daily_cap models.DecimalField(max_digits8, decimal_places2, default50, verbose_name每日封顶) class EntryRecord(models.Model): 进出场记录表计费的核心依据 vehicle models.ForeignKey(Vehicle, on_deletemodels.CASCADE, verbose_name车辆) space models.ForeignKey(ParkingSpace, nullTrue, blankTrue, on_deletemodels.SET_NULL) entry_time models.DateTimeField(verbose_name进场时间) exit_time models.DateTimeField(nullTrue, blankTrue, verbose_name出场时间) fee models.DecimalField(max_digits8, decimal_places2, default0, verbose_name应收费用) is_paid models.BooleanField(defaultFalse, verbose_name是否已缴费)plate_number加了uniqueTrue防止同一车牌重复建档EntryRecord里exit_time允许为空因为车还在场内时没有出场时间计费逻辑要判断这个字段是否为 None。on_delete的选择也有讲究车辆删了记录跟着删用CASCADE车位删了记录保留但置空用SET_NULL这样历史流水不会因为删个车位就消失。建完模型执行迁移python manage.py makemigrations parking_app python manage.py migrate python manage.py createsuperusermakemigrations生成迁移文件migrate真正建表createsuperuser建后台账号。三步顺序不能乱跳过makemigrations直接migrate会发现表根本没建。3. 车牌识别接入从图片到车牌号的那一段怎么落地3.1 识别方案选型本地库还是调接口车牌识别这块毕业设计里常见两条路一是用本地 OCR 库比如 HyperLPR、EasyOCR 或 PaddleOCR 的车牌模型二是调第三方识别接口。本地库的好处是不依赖网络、答辩现场不怕断网缺点是环境配置容易翻车尤其是 CPU 版推理库的依赖冲突。第三方接口识别率高但要联网、要申请 key答辩时网络一抖就尴尬。我的建议是本地库为主选 HyperLPR3 或 PaddleOCR前者专门做中文车牌开箱即用程度高。如果你只是要演示流程甚至可以先写一个「模拟识别」函数把预置车牌号返回把计费链路先跑通再替换成真实识别。这样开发节奏不会被环境问题卡死。3.2 识别模块与 Django 的对接方式识别代码不要直接塞进视图函数里单独建一个recognition.py对外只暴露一个recognize_plate(image_path)函数。这样以后换识别库只改这一个文件视图层不动。下面是一个基于 HyperLPR3 的封装示例# parking_app/recognition.py import hyperlpr3 as lpr3 # 单例加载模型避免每次调用都重新初始化 _catcher lpr3.LicensePlateCatcher(detect_levellpr3.DETECT_LEVEL_LOW) def recognize_plate(image_path): 输入图片路径返回识别到的车牌号字符串 识别失败返回 None try: results _catcher(image_path) if results and len(results) 0: # results 结构为 [(车牌号, 置信度, 类型, 框), ...] plate, confidence, _, _ results[0] if confidence 0.7: # 置信度阈值低于此值视为不可信 return plate return None except Exception as e: print(f识别异常: {e}) return Nonedetect_level设成 LOW 是为了在 CPU 上跑得快答辩演示够用confidence 0.7这个阈值是血泪经验设太低会把模糊图片识别成乱七八糟的字符设太高又频繁识别失败0.7 是个平衡点。模型用单例加载因为初始化模型很耗时放在函数里每次调用都加载会让接口慢到无法忍受。3.3 视图层识别、建档、开闸一条龙视图函数负责把识别结果落到数据库并返回是否放行。下面这个vehicle_entry视图处理进场流程# parking_app/views.py from django.http import JsonResponse from django.utils import timezone from .models import Vehicle, EntryRecord, ParkingSpace from .recognition import recognize_plate def vehicle_entry(request): 车辆进场识别车牌 - 建档 - 分配车位 - 生成进场记录 if request.method ! POST: return JsonResponse({code: 405, msg: 仅支持 POST}) image_path request.POST.get(image_path) plate recognize_plate(image_path) if not plate: return JsonResponse({code: 400, msg: 车牌识别失败请重试}) # 车牌不存在则自动建档 vehicle, created Vehicle.objects.get_or_create(plate_numberplate) # 检查是否已有未出场的记录防止重复进场 if EntryRecord.objects.filter(vehiclevehicle, exit_time__isnullTrue).exists(): return JsonResponse({code: 409, msg: 该车已在场内}) # 找一个空闲车位并占用 space ParkingSpace.objects.filter(is_occupiedFalse).first() if space: space.is_occupied True space.save() EntryRecord.objects.create( vehiclevehicle, spacespace, entry_timetimezone.now() ) return JsonResponse({code: 200, msg: 进场成功, plate: plate})get_or_create一行搞定「有则取、无则建」省掉先查再建的两次数据库操作。exit_time__isnullTrue这个查询是防重复进场的关键没有它同一辆车可以刷无数次进场记录计费直接乱套。车位用filter(is_occupiedFalse).first()拿第一个空闲的简单但够用真实场景可以按区域或就近分配。3.4 出场计费时长、免费时段、封顶怎么算出场视图是整个系统逻辑最密的地方计费规则要处理免费时长、按小时向上取整、每日封顶三个条件。下面这段代码把计费逻辑单独抽成函数方便单元测试# parking_app/views.py 续 import math from decimal import Decimal def calculate_fee(entry_time, exit_time, rule): 根据进出场时间和费率规则计算费用 duration exit_time - entry_time minutes duration.total_seconds() / 60 # 未超过免费时长不收费 if minutes rule.free_minutes: return Decimal(0.00) # 扣除免费时长后按小时向上取整 billable_minutes minutes - rule.free_minutes hours math.ceil(billable_minutes / 60) fee hours * rule.price_per_hour # 应用每日封顶 if fee rule.daily_cap: fee rule.daily_cap return fee def vehicle_exit(request): 车辆出场计算费用 - 释放车位 - 更新记录 plate request.POST.get(plate) vehicle Vehicle.objects.filter(plate_numberplate).first() if not vehicle: return JsonResponse({code: 404, msg: 无此车辆记录}) record EntryRecord.objects.filter(vehiclevehicle, exit_time__isnullTrue).first() if not record: return JsonResponse({code: 404, msg: 无在场记录}) rule RateRule.objects.first() # 简化处理取第一条规则 now timezone.now() fee calculate_fee(record.entry_time, now, rule) record.exit_time now record.fee fee record.save() # 释放车位 if record.space: record.space.is_occupied False record.space.save() return JsonResponse({code: 200, msg: 出场成功, fee: str(fee)})math.ceil是向上取整停 61 分钟按 2 小时算这是停车场行业惯例用round会少收钱。Decimal类型不能和 float 直接混算所以单价和封顶都用Decimal返回时转成字符串避免 JSON 序列化报错。费率规则这里简化成取第一条真实项目应该按车型或时段匹配但毕业设计演示够用。4. 避坑与排查这套系统最容易翻车的五个地方4.1 车牌识别库装不上或一跑就崩现象pip install hyperlpr3卡在编译或者 import 时报缺少libGL.so之类的动态库。原因OCR 类库依赖 OpenCV 和底层图像库不同系统缺的包不一样Windows 和 Linux 报错还不同。解决Linux 下先apt install libgl1 libglib2.0-0Windows 下装opencv-python而不是opencv-contrib-python两者混装会冲突。实在搞不定就退回模拟识别函数先把业务链路跑通。4.2 时区导致计费差好几个小时现象明明停了 1 小时算出来 9 小时费用离谱。原因Django 默认USE_TZTrue用 UTC 存时间timezone.now()返回的是 UTC 时间如果你在模板或计算里又按本地时间理解就会差 8 小时。解决在settings.py里设TIME_ZONE Asia/Shanghai和USE_TZ True所有时间统一用timezone.now()不要用datetime.now()两者混用必出时区 bug。4.3 重复进场导致计费翻倍现象同一辆车短时间内生成多条未出场记录出场时只结算了一条其他记录永远挂着。原因进场视图没做「是否已在场内」校验或者校验条件写错。解决进场前必须查EntryRecord.objects.filter(vehiclevehicle, exit_time__isnullTrue).exists()为真直接拒绝。这个校验要放在创建记录之前顺序错了等于没校验。4.4 数据库迁移报「表已存在」或字段对不上现象改了模型字段后migrate报错或者新字段在数据库里查不到。原因手动改过数据库表结构或者迁移文件没提交、多台机器迁移状态不一致。解决开发阶段实在乱了就删掉migrations目录下除__init__.py外的文件删库重建重新makemigrations和migrate。生产环境不能这么干要用--fake谨慎处理但毕业设计阶段重建最省事。4.5 Admin 后台看不到新模型现象模型建好了migrate也跑了但登录 Admin 找不到对应的表。原因忘了在admin.py里注册模型。解决在parking_app/admin.py里加admin.site.register(Vehicle)等注册语句或者用admin.register装饰器。这个坑新手几乎必踩因为 Django 不会自动把模型暴露到后台。5. 进阶技巧用 Admin 自定义和单元测试把系统验一遍5.1 让 Admin 后台直接显示计费关键字段默认 Admin 只显示__str__返回的内容查记录很不方便。我一般会自定义list_display和筛选器让后台一眼能看出哪些车没缴费、哪些还在场内# parking_app/admin.py from django.contrib import admin from .models import Vehicle, EntryRecord, ParkingSpace, RateRule admin.register(EntryRecord) class EntryRecordAdmin(admin.ModelAdmin): list_display (vehicle, entry_time, exit_time, fee, is_paid) list_filter (is_paid, exit_time) search_fields (vehicle__plate_number,) date_hierarchy entry_timelist_display决定列表页显示哪些列list_filter右侧生成筛选面板search_fields里的双下划线是跨外键查询vehicle__plate_number表示按关联车辆的车牌搜。date_hierarchy顶部生成日期钻取条查某天的流水特别快。这几行加上去答辩演示时老师随便点几下就能看到数据关系比翻代码有说服力。5.2 用单元测试验证计费逻辑计费函数是纯逻辑最适合写测试。下面这个测试覆盖免费、正常计费、封顶三种情况# parking_app/tests.py from django.test import TestCase from datetime import timedelta from django.utils import timezone from decimal import Decimal from .models import RateRule from .views import calculate_fee class FeeTest(TestCase): def setUp(self): self.rule RateRule.objects.create( name标准, free_minutes15, price_per_hourDecimal(5.00), daily_capDecimal(50.00) ) def test_free_within_15min(self): entry timezone.now() exit_ entry timedelta(minutes10) self.assertEqual(calculate_fee(entry, exit_, self.rule), Decimal(0.00)) def test_one_hour_charge(self): entry timezone.now() exit_ entry timedelta(minutes70) # 扣15分钟免费剩55分钟按1小时 self.assertEqual(calculate_fee(entry, exit_, self.rule), Decimal(5.00)) def test_daily_cap(self): entry timezone.now() exit_ entry timedelta(hours20) # 远超封顶 self.assertEqual(calculate_fee(entry, exit_, self.rule), Decimal(50.00))跑python manage.py test就能验证。测试用例的价值在于你改计费逻辑时跑一遍就知道有没有把原来的规则改坏。我见过太多人改了一行取整方式结果免费时长判断跟着失效有测试就不会出这种事。5.3 一个我踩过的坑和现在的习惯早期做这套系统时我把计费逻辑直接写在视图函数里没有抽成独立函数结果想写测试时发现根本没法单独调用只能起服务发请求测效率极低。后来养成习惯凡是纯计算逻辑一律抽成不依赖 request 的函数视图只负责取参数、调函数、存结果。从那以后我每次写计费、排序、格式转换这类逻辑都强制先问自己「这个能不能单独测」能就抽出来。这套停车场系统里calculate_fee和recognize_plate都是这么拆出来的改起来心里有底。希望这套拆解能帮到你把毕业设计稳稳跑通。本文还有配套的精品资源点击获取