智慧停车AI开发:车位检测与导航,计算机视觉入门实战全流程
大家好,我是南木。最近后台收到很多零基础读者的咨询:“想入门计算机视觉,但不知道选什么项目练手?”“商场找车位太麻烦,能不能自己做一个智慧停车系统?”“学了YOLO和OpenCV,却不知道怎么结合起来解决实际问题?其实——它场景贴近生活(商场、小区停车场都能用到),技术栈轻量化(YOLOv8做车位检测+OpenCV做定位+简化A*算法做导航),而且不用专业硬件(普通电脑+手机摄像头就能跑通)。更重
大家好,我是南木。最近后台收到很多零基础读者的咨询:“想入门计算机视觉,但不知道选什么项目练手?”“商场找车位太麻烦,能不能自己做一个智慧停车系统?”“学了YOLO和OpenCV,却不知道怎么结合起来解决实际问题?”
其实智慧停车的“车位检测+导航”是计算机视觉入门的完美项目——它场景贴近生活(商场、小区停车场都能用到),技术栈轻量化(YOLOv8做车位检测+OpenCV做定位+简化A*算法做导航),而且不用专业硬件(普通电脑+手机摄像头就能跑通)。更重要的是,它的核心逻辑清晰,零基础能快速上手并看到实际效果。
这篇文章以“室内停车场智慧停车”为目标,从“工具准备→车位检测模型训练→车辆定位→路径规划→系统整合”全流程拆解,每个步骤都附“零基础可复制的代码+图文操作+避坑说明”。不用懂复杂的SLAM或深度学习理论,跟着敲代码、调参数,3周就能做出能“自动找空车位+规划导航路径”的智慧停车原型系统。
正文开始前 这里给大家准备了一份零基础入门资料 同时需要系统课程学习的同学 欢迎扫码交流
一、先搞懂:智慧停车AI的核心逻辑,普通人也能秒懂
很多人觉得智慧停车系统“高大上”,其实它的本质是“用计算机视觉技术模拟停车场管理员的工作”——管理员会“看哪里有空车位→告诉司机位置→指引路线”,AI系统也是一样,核心就3步:检测空车位→定位车辆位置→规划导航路径。
1. 智慧停车AI的“3步核心流程”(一张图看懂)
无论多复杂的智慧停车系统,底层逻辑都离不开这3个步骤,零基础也能快速理解:
- 第一步:检测空车位(YOLOv8负责)
从停车场的视频或图片中,识别出哪些车位是“空的”(无车辆停放),哪些是“占用的”(有车辆停放),用框标记空车位位置。 - 第二步:定位车辆位置(OpenCV负责)
通过停车场地面的“定位标识”(如箭头、编号、二维码)或“车辆特征”,确定当前车辆在停车场中的具体位置(用坐标表示)。 - 第三步:规划导航路径(简化A*算法负责)
以“当前车辆位置”为起点,“空车位位置”为终点,规划一条“最短、无遮挡”的导航路线,并用箭头在画面上标记指引方向。
简单说:找到空位→知道自己在哪→知道怎么走,这就是智慧停车AI的核心逻辑。
2. 为什么选“YOLOv8+OpenCV+简化A*”?零基础友好度拉满
智慧停车的技术方案有很多,但YOLOv8+OpenCV+简化A*算法是零基础的最优选择,原因有3个:
- YOLOv8:车位检测“开箱即用”
不用手动设计特征提取器(如传统的HOG+SVM),YOLOv8能自动学习车位的“空/占”特征,一行代码就能启动训练,检测准确率≥95%,普通CPU能跑15FPS,完全满足实时需求。 - OpenCV:定位与可视化“全能工具”
停车场地面的定位标识识别、导航箭头绘制、视频帧处理……这些功能OpenCV都有现成的API,几行代码就能实现,不用自己造轮子。 - 简化A*算法:路径规划“门槛最低”
不用学复杂的路径规划算法(如RRT*、Dijkstra),简化版A*算法逻辑清晰(“找最短路径+避障”),代码量少且容易理解,适合零基础入门。
3. 实战目标与环境说明(普通人能落地)
不用专业的停车场设备,普通电脑+手机就能完成所有开发,具体目标和环境如下:
- 实战目标:开发一个“室内停车场智慧停车原型系统”,支持“图片/视频空车位检测→手机摄像头定位车辆→规划并显示导航路径”,输出带“空车位标记+车辆位置+导航箭头”的可视化结果。
- 硬件要求:普通电脑(CPU即可,有GPU训练更快)、手机(用于拍摄停车场图片/视频,或作为实时摄像头)、打印纸(制作简易定位标识)。
- 软件环境:Python 3.9(兼容性最佳)、YOLOv8 8.0.200(车位检测)、OpenCV 4.8.0.74(定位与可视化)、LabelImg 1.8.6(数据标注)。
二、Day1-Day2:基础准备——4小时搞定工具与核心概念
这两天的目标是:搭建开发环境、理解核心概念、掌握YOLOv8和OpenCV的基础用法,为后续实战打基础。
1. 第一步:10分钟搭建开发环境(避坑版,零基础可复制)
零基础最容易踩“版本冲突”的坑,这里给出经过验证的版本组合,复制命令就能安装成功:
步骤1:安装Python环境(推荐Anaconda)
- 下载Anaconda:Anaconda官网,选择“Python 3.9”版本(别选最新版,避免兼容性问题);
- 安装Anaconda:双击安装包,一路默认下一步,最后勾选“Add Anaconda to my PATH environment variable”(方便命令行调用);
- 打开“Anaconda Prompt”(Windows)或“终端”(Mac/Linux),创建并激活专属环境:
# 创建名为smart_parking的环境 conda create -n smart_parking python=3.9 # 激活环境(每次开发前都要执行) conda activate smart_parking
步骤2:安装核心库(YOLOv8+OpenCV+标注工具)
在激活的“smart_parking”环境中,输入以下命令(国内用户用清华镜像源,速度快10倍):
# 安装YOLOv8官方库(固定版本,避免更新出问题)
pip install ultralytics==8.0.200 -i https://pypi.tuna.tsinghua.edu.cn/simple
# 安装OpenCV(图像处理核心)
pip install opencv-python==4.8.0.74 -i https://pypi.tuna.tsinghua.edu.cn/simple
# 安装数据标注工具LabelImg
pip install labelImg==1.8.6 -i https://pypi.tuna.tsinghua.edu.cn/simple
# 安装辅助库(可视化、数据处理)
pip install matplotlib==3.7.1 pandas==1.5.3 -i https://pypi.tuna.tsinghua.edu.cn/simple
步骤3:验证环境是否安装成功
运行以下代码,若能正常输出结果,说明环境搭建完成:
# 验证YOLOv8
from ultralytics import YOLO
model = YOLO("yolov8n.pt") # 加载YOLOv8-nano模型
print("YOLOv8加载成功")
# 验证OpenCV
import cv2
img = cv2.imread("test.jpg") # 自己找一张图片放在当前文件夹
print(f"OpenCV加载成功,图片尺寸:{img.shape}")
# 验证LabelImg(命令行输入,会自动打开工具)
# labelImg
2. 第二步:30分钟理解核心概念(避免后续 confusion)
零基础开发的最大障碍是“概念不清”,这里提前讲清3个核心概念,后续代码一看就懂:
概念1:车位检测的“空/占”分类逻辑(YOLOv8负责)
车位检测本质是“二分类问题”——判断每个车位是“空的”(empty)还是“占用的”(occupied),但实际开发中通常用“目标检测”实现:
- 标注时只标“空车位”(empty_parking),未标注的视为“占用车位”;
- YOLOv8检测到“empty_parking”类别,即判定为“空车位”,用绿色框标记;
- 未检测到的车位,判定为“占用车位”,用红色框标记。
为什么只标空车位? 因为停车场的车位布局固定,只要提前知道“所有车位的位置”,检测到的空车位用绿框,其余用红框即可,减少标注工作量。
概念2:车辆定位的“坐标体系”(OpenCV负责)
停车场定位需要建立“平面坐标体系”,把停车场视为一张“二维地图”,核心是“确定原点和单位”:
- 原点:通常设为停车场入口处的某个固定点(如(0,0));
- 单位:以“像素”为单位(简化版),或“厘米”为单位(精准版,需标定);
- 车辆位置:用坐标(x,y)表示,如车辆在原点右侧100像素、前方200像素,坐标即为(100,200)。
零基础可先用“像素坐标”,后续再进阶到“物理坐标”(需摄像头标定)。
概念3:路径规划的“简化A*算法”逻辑(核心是“找最短路”)
A*算法的核心是“通过评估函数找最短路径”,简化版逻辑适合零基础:
- 地图建模:把停车场划分为“网格”(如10x10网格),每个网格代表一个“可行走区域”或“障碍物”;
- 评估函数:对于每个网格,计算“从起点到当前网格的距离”+“当前网格到终点的预估距离”,优先选择评估值小的网格;
- 避障逻辑:遇到障碍物(如柱子、墙),自动绕开,重新规划路线。
简单说:选“看起来最近”的路,遇到障碍就绕开,这就是简化A*的核心。
3. 第三步:2小时掌握YOLOv8+OpenCV基础操作
学会“用YOLOv8检测目标”和“用OpenCV处理图像/坐标”,就能完成实战的基础操作:
基础操作1:用YOLOv8检测空车位(基础示例)
from ultralytics import YOLO
import cv2
# 1. 加载预训练模型(后续会用自己标注的数据训练专门的车位检测模型)
model = YOLO("yolov8n.pt")
# 2. 检测停车场图片(替换为你的图片路径)
img_path = "parking_lot.jpg"
results = model(img_path, conf=0.3)
# 3. 绘制检测结果(这里先看整体检测效果,后续会训练专门的车位模型)
img = cv2.imread(img_path)
for box in results[0].boxes:
x1, y1, x2, y2 = map(int, box.xyxy[0].tolist())
cls = model.names[int(box.cls[0])]
conf = round(box.conf[0].item(), 2)
# 绘制检测框(红色:非车位目标)
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.putText(img, f"{cls} {conf}", (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
# 4. 保存并显示结果
cv2.imwrite("parking_detection_test.jpg", img)
cv2.imshow("Parking Detection Test", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
基础操作2:用OpenCV建立坐标体系并标记位置
import cv2
# 1. 读取停车场图片
img_path = "parking_lot.jpg"
img = cv2.imread(img_path)
h, w = img.shape[:2]
# 2. 建立坐标体系(原点设为图片左上角,x向右,y向下)
# 绘制坐标轴
cv2.line(img, (0, 0), (100, 0), (255, 0, 0), 2) # x轴(蓝色)
cv2.line(img, (0, 0), (0, 100), (0, 255, 0), 2) # y轴(绿色)
cv2.putText(img, "Origin (0,0)", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
# 3. 标记车位坐标(示例:假设3个车位的中心坐标)
parking_centers = [(200, 300), (400, 300), (600, 300)] # 车位中心坐标
for i, (x, y) in enumerate(parking_centers):
# 绘制车位中心(红色圆点)
cv2.circle(img, (x, y), 5, (0, 0, 255), -1)
# 标注车位编号和坐标
cv2.putText(img, f"P{i+1} ({x},{y})", (x+10, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
# 4. 保存并显示结果
cv2.imwrite("parking_coords.jpg", img)
cv2.imshow("Parking Coordinate System", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
三、Day3-Day5:核心实战1——车位检测模型训练(YOLOv8)
这3天的目标是:搭建车位检测数据集、训练专门的空车位检测模型,实现“从停车场图片/视频中准确识别空车位”。
1. 第一步:1小时搭建车位检测数据集(零基础方案)
不用自己拍大量停车场图片,用“公开数据集+自制数据集”结合的方式,1小时就能搞定:
步骤1:数据集来源(2个渠道,零基础可选)
- 渠道1:公开数据集(推荐):直接用“Parking Lot Dataset”(含1000+张停车场图片,标注了空/占车位),文末资源包已整理好;
- 渠道2:自制数据集(简单场景):用手机拍摄小区/商场停车场图片(20-30张即可),确保包含“空车位”“占用车位”“不同角度”“不同光线”的场景。
步骤2:数据标注(LabelImg,点鼠标就行)
标注时只标“空车位”(类别名:empty_parking),未标注的视为“占用车位”,步骤如下:
- 启动LabelImg:在“smart_parking”环境中输入
labelImg
,打开工具; - 配置标注:
- 点击“Open Dir”,选择图片文件夹;
- 点击“Change Save Dir”,选择标注文件保存文件夹;
- 点击“View”,勾选“Auto Save mode”(自动保存)和“Dark Mode”(保护眼睛);
- 标注操作:
- 按“W”键或点击“Create RectBox”,框选“空车位”的完整区域(从车位线的左上角到右下角);
- 弹出类别输入框,输入
empty_parking
,点击“OK”; - 按“D”键切换到下一张图,重复标注。
步骤3:整理数据集(YOLOv8格式)
按YOLOv8要求整理文件夹结构(必须严格遵守,否则模型找不到数据):
parking_dataset/ # 数据集根目录
├─ train/ # 训练集(80%数据)
│ ├─ images/ # 训练图片
│ └─ labels/ # 训练标注文件(.txt格式)
└─ val/ # 验证集(20%数据)
├─ images/ # 验证图片
└─ labels/ # 验证标注文件(.txt格式)
例如:若有100张图片,80张放入train
,20张放入val
,对应的标注文件也同步放入。
步骤4:创建配置文件(data.yaml)
创建parking_data.yaml
文件(用记事本即可),定义数据集路径和类别,内容如下(替换为你的绝对路径):
# 训练集和验证集路径
train: C:/Users/Admin/parking_dataset/train/images
val: C:/Users/Admin/parking_dataset/val/images
# 类别数和类别名称(只标空车位,所以nc=1)
nc: 1
names: ["empty_parking"] # 类别名必须和标注时一致
2. 第二步:1小时训练空车位检测模型(YOLOv8实战)
用标注好的数据集训练专门的空车位检测模型,代码简单到只有几行,零基础也能跑通:
from ultralytics import YOLO
import torch
# 1. 检查GPU是否可用(有GPU训练快10倍,无GPU也能跑)
print(f"GPU可用:{torch.cuda.is_available()}")
device = 0 if torch.cuda.is_available() else "cpu"
# 2. 加载YOLOv8-nano模型(适合小数据集,速度快)
model = YOLO("yolov8n.pt")
# 3. 训练模型(核心参数已优化,零基础不用改)
train_results = model.train(
data="parking_data.yaml", # 配置文件路径
epochs=50, # 训练轮次(50轮足够收敛)
batch=8, # 批次大小(6G显存设8,4G设4)
imgsz=640, # 输入图片尺寸(640×640平衡速度和精度)
device=device, # 训练设备
name="parking_detection", # 模型名称(保存到runs/detect/parking_detection)
pretrained=True, # 用预训练权重加速训练
optimizer="Adam", # 优化器(适合小数据集)
lr0=0.001, # 初始学习率
val=True # 训练时同步验证
)
# 4. 输出训练结果(重点看mAP50,≥0.85为合格)
print("\n训练完成!关键指标:")
print(f"验证集mAP50:{train_results.results_dict['metrics/mAP50']:.4f}")
print(f"最佳模型路径:runs/detect/parking_detection/weights/best.pt")
训练结果解读(零基础也能看懂)
训练时会打印每轮的损失和精度,重点关注2个指标:
- mAP50:空车位检测的平均精度,≥0.85为合格(本文案例能到0.9以上);
- val_loss:验证集损失,逐渐下降说明训练正常,若上升说明过拟合(可减少epochs到30-40轮)。
测试模型效果(验证是否能准确识别空车位)
from ultralytics import YOLO
import cv2
# 1. 加载训练好的最佳模型
model = YOLO("runs/detect/parking_detection/weights/best.pt")
# 2. 测试单张停车场图片(替换为你的测试图路径)
test_img_path = "test_parking.jpg"
results = model(test_img_path, conf=0.5) # conf=0.5:过滤低置信度结果
# 3. 绘制检测结果(绿框=空车位,红框=预设的占用车位区域)
img = cv2.imread(test_img_path)
h, w = img.shape[:2]
# 3.1 绘制空车位(绿框)
for box in results[0].boxes:
x1, y1, x2, y2 = map(int, box.xyxy[0].tolist())
conf = round(box.conf[0].item(), 2)
# 绿框标记空车位
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(img, f"Empty {conf}", (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
# 3.2 绘制预设的占用车位(示例:假设已知3个车位位置,未检测到空车位的视为占用)
occupied_boxes = [(200, 400, 300, 500), (400, 400, 500, 500)] # 预设占用车位区域
for x1, y1, x2, y2 in occupied_boxes:
# 红框标记占用车位
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.putText(img, "Occupied", (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
# 4. 保存并显示结果
cv2.imwrite("parking_test_result.jpg", img)
cv2.imshow("Parking Detection Result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
print("空车位检测测试完成!")
3. 第三步:1小时实现视频流空车位检测(实时场景)
修改代码,实现从“视频文件”或“手机摄像头”中实时检测空车位,贴近实际停车场场景:
from ultralytics import YOLO
import cv2
class ParkingDetection:
def __init__(self, model_path="runs/detect/parking_detection/weights/best.pt"):
# 加载车位检测模型
self.model = YOLO(model_path)
# 预设停车场车位区域(根据你的停车场实际布局调整!)
# 格式:[(x1, y1, x2, y2), ...] 表示每个车位的左上角和右下角坐标
self.all_parking_boxes = [
(150, 350, 250, 450),
(280, 350, 380, 450),
(410, 350, 510, 450),
(540, 350, 640, 450),
(150, 480, 250, 580),
(280, 480, 380, 580)
]
def process_frame(self, frame):
"""处理单帧画面,返回带空/占车位标记的帧"""
# 检测空车位
results = self.model(frame, conf=0.5)
# 提取空车位坐标(转换为集合,方便比对)
empty_boxes = set()
for box in results[0].boxes:
x1, y1, x2, y2 = map(int, box.xyxy[0].tolist())
empty_boxes.add((x1, y1, x2, y2))
# 绘制所有车位(绿=空,红=占)
for box in self.all_parking_boxes:
x1, y1, x2, y2 = box
# 判断是否为空车位(简单匹配:若车位区域与检测到的空车位重叠,则视为空)
is_empty = any(self.box_overlap(box, eb) > 0.5 for eb in empty_boxes)
if is_empty:
color = (0, 255, 0) # 绿色:空车位
label = "Empty"
else:
color = (0, 0, 255) # 红色:占用车位
label = "Occupied"
# 绘制车位框和标签
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
cv2.putText(frame, label, (x1+10, y1+20), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)
# 统计空车位数量
empty_count = sum(1 for box in self.all_parking_boxes if any(self.box_overlap(box, eb) > 0.5 for eb in empty_boxes))
total_count = len(self.all_parking_boxes)
cv2.putText(frame, f"Empty: {empty_count}/{total_count}", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2)
return frame
def box_overlap(self, box1, box2):
"""计算两个框的重叠率(IOU),用于判断是否为同一车位"""
x1, y1, x2, y2 = box1
x1b, y1b, x2b, y2b = box2
# 计算重叠区域
overlap_x1 = max(x1, x1b)
overlap_y1 = max(y1, y1b)
overlap_x2 = min(x2, x2b)
overlap_y2 = min(y2, y2b)
if overlap_x1 >= overlap_x2 or overlap_y1 >= overlap_y2:
return 0.0
# 计算IOU
overlap_area = (overlap_x2 - overlap_x1) * (overlap_y2 - overlap_y1)
box1_area = (x2 - x1) * (y2 - y1)
return overlap_area / box1_area
def process_video(self, input_video_path, output_video_path="parking_output.mp4"):
"""处理视频文件"""
# 打开输入视频
cap = cv2.VideoCapture(input_video_path)
if not cap.isOpened():
print("无法打开输入视频!")
return
# 获取视频参数
fps = int(cap.get(cv2.CAP_PROP_FPS))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# 创建视频写入器
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_video_path, fourcc, fps, (width, height))
print(f"开始处理视频,输出路径:{output_video_path}")
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
# 处理帧
frame_with_result = self.process_frame(frame)
# 写入输出视频
out.write(frame_with_result)
# 显示结果
cv2.imshow("Parking Video Detection", frame_with_result)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放资源
cap.release()
out.release()
cv2.destroyAllWindows()
print("视频处理完成!")
# 测试视频检测
if __name__ == "__main__":
detector = ParkingDetection()
# 处理视频文件(替换为你的测试视频路径)
detector.process_video(input_video_path="parking_video.mp4")
关键说明:
self.all_parking_boxes
需要根据你的停车场实际布局调整,可先用OpenCV打开图片,手动标记所有车位的坐标;box_overlap
函数通过“重叠率”判断检测到的空车位是否属于预设车位,避免误检。
四、Day6-Day8:核心实战2——车辆定位与路径规划
这3天的目标是:实现“车辆在停车场中的定位”和“从当前位置到空车位的导航路径规划”,完成智慧停车的核心逻辑闭环。
1. 第一步:1小时实现车辆定位(OpenCV,零基础方案)
零基础不用学复杂的SLAM定位,用“停车场地面定位标识”就能实现简单定位,核心是“识别预设的标识,获取车辆坐标”,这里介绍两种简单方案:
方案1:基于“ArUco码”的定位(精度高,推荐)
ArUco码是一种专门用于定位的二维码,OpenCV有现成的识别API,步骤如下:
- 生成ArUco码:用以下代码生成ArUco码,打印出来贴在停车场地面;
import cv2 import cv2.aruco as aruco # 生成ArUco码(字典类型:DICT_4X4_50,ID:0) aruco_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_50) img = aruco.generateImageMarker(aruco_dict, 0, 200) # 200x200像素 cv2.imwrite("aruco_marker_0.png", img) print("ArUco码已生成:aruco_marker_0.png")
- 识别ArUco码并定位:通过手机摄像头识别地面的ArUco码,获取车辆坐标;
import cv2 import cv2.aruco as aruco class ArucoLocalization: def __init__(self): # 初始化ArUco字典和参数 self.aruco_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_50) self.aruco_params = aruco.DetectorParameters() def get_car_position(self, frame): """识别ArUco码,返回车辆坐标(x,y),未识别到返回None""" # 转换为灰度图 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 检测ArUco码 corners, ids, rejected = aruco.detectMarkers(gray, self.aruco_dict, parameters=self.aruco_params) if ids is not None: # 假设识别到ID=0的ArUco码,取其中心坐标作为车辆位置 for i, id in enumerate(ids): if id[0] == 0: # 获取ArUco码的四个角点 corner = corners[i][0] # 计算中心坐标 x = int((corner[0][0] + corner[2][0]) / 2) y = int((corner[0][1] + corner[2][1]) / 2) # 绘制ArUco码和中心 aruco.drawDetectedMarkers(frame, corners, ids) cv2.circle(frame, (x, y), 5, (0, 0, 255), -1) cv2.putText(frame, f"Car Pos: ({x},{y})", (x+10, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) return (x, y) return None # 测试ArUco定位(手机摄像头实时识别) if __name__ == "__main__": locator = ArucoLocalization() # 打开手机摄像头(需在同一局域网,或用USB连接) # 这里用电脑摄像头测试,实际可用手机摄像头APP(如IP Webcam) cap = cv2.VideoCapture(0) while cap.isOpened(): ret, frame = cap.read() if not ret: break # 水平翻转画面(操作更直观) frame = cv2.flip(frame, 1) # 获取车辆位置 car_pos = locator.get_car_position(frame) if car_pos is None: cv2.putText(frame, "No ArUco Marker Detected", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2) # 显示结果 cv2.imshow("Car Localization (ArUco)", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
方案2:基于“地面箭头/编号”的定位(简单场景)
如果没有打印机,可在地面画箭头或写编号,用OpenCV的“模板匹配”识别,适合简单场景:
import cv2
import numpy as np
class TemplateLocalization:
def __init__(self, template_path="arrow_template.png"):
# 加载模板图片(如地面箭头)
self.template = cv2.imread(template_path, 0)
self.template_h, self.template_w = self.template.shape[:2]
def get_car_position(self, frame):
"""识别模板,返回车辆位置"""
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 模板匹配
result = cv2.matchTemplate(gray, self.template, cv2.TM_CCOEFF_NORMED)
threshold = 0.7
loc = np.where(result >= threshold)
if len(loc[0]) > 0:
# 取第一个匹配区域的中心作为车辆位置
x = int(loc[1][0] + self.template_w/2)
y = int(loc[0][0] + self.template_h/2)
# 绘制匹配区域和中心
cv2.rectangle(frame, (loc[1][0], loc[0][0]), (loc[1][0]+self.template_w, loc[0][0]+self.template_h), (0, 255, 0), 2)
cv2.circle(frame, (x, y), 5, (0, 0, 255), -1)
cv2.putText(frame, f"Car Pos: ({x},{y})", (x+10, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2)
return (x, y)
return None
# 测试模板定位
if __name__ == "__main__":
locator = TemplateLocalization(template_path="arrow_template.png")
cap = cv2.VideoCapture(0)
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
frame = cv2.flip(frame, 1)
car_pos = locator.get_car_position(frame)
if car_pos is None:
cv2.putText(frame, "No Template Detected", (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 255), 2)
cv2.imshow("Car Localization (Template)", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
2. 第二步:1小时实现简化A*路径规划(零基础友好)
不用学复杂的A*理论,用“网格地图+贪心思想”实现简化版路径规划,核心是“找最短路径+避障”:
import numpy as np
class SimpleAStar:
def __init__(self, map_size=(10, 10), obstacles=[(3,3), (3,4), (4,3)]):
"""
初始化简化A*算法
map_size:网格地图尺寸(rows, cols)
obstacles:障碍物坐标列表(网格坐标)
"""
self.rows, self.cols = map_size
self.obstacles = set(obstacles)
def heuristic(self, a, b):
"""启发函数:计算两点间的曼哈顿距离(简化版)"""
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def get_neighbors(self, pos):
"""获取当前位置的邻居(上下左右,排除障碍物和边界)"""
x, y = pos
neighbors = []
# 上下左右四个方向
directions = [(-1,0), (1,0), (0,-1), (0,1)]
for dx, dy in directions:
nx = x + dx
ny = y + dy
# 检查是否在地图内且不是障碍物
if 0 <= nx < self.rows and 0 <= ny < self.cols and (nx, ny) not in self.obstacles:
neighbors.append((nx, ny))
return neighbors
def plan_path(self, start, goal):
"""
规划从起点到终点的路径
start:起点坐标(网格坐标)
goal:终点坐标(网格坐标)
return:路径列表(网格坐标),未找到路径返回None
"""
# 检查起点/终点是否为障碍物
if start in self.obstacles or goal in self.obstacles:
return None
# 初始化开放列表(待检查的节点)和关闭列表(已检查的节点)
open_list = [start]
closed_list = set()
# 记录每个节点的父节点(用于回溯路径)
parent = {}
# 记录每个节点的代价(g=起点到当前节点的距离,f=g+启发函数)
g_score = {start: 0}
f_score = {start: self.heuristic(start, goal)}
while open_list:
# 选择f_score最小的节点
current = min(open_list, key=lambda x: f_score.get(x, float('inf')))
# 到达终点,回溯路径
if current == goal:
path = []
while current in parent:
path.append(current)
current = parent[current]
path.append(start)
return path[::-1] # 反转路径,从起点到终点
# 移到关闭列表
open_list.remove(current)
closed_list.add(current)
# 检查邻居
for neighbor in self.get_neighbors(current):
if neighbor in closed_list:
continue
# 计算邻居的g_score
tentative_g = g_score[current] + 1
# 如果邻居不在开放列表,或新的g_score更小,更新信息
if neighbor not in open_list or tentative_g < g_score.get(neighbor, float('inf')):
parent[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + self.heuristic(neighbor, goal)
if neighbor not in open_list:
open_list.append(neighbor)
# 未找到路径
return None
# 测试简化A*算法
if __name__ == "__main__":
# 初始化地图(10x10网格,障碍物在(3,3),(3,4),(4,3))
astar = SimpleAStar(map_size=(10,10), obstacles=[(3,3), (3,4), (4,3)])
# 规划路径:起点(1,1)→终点(8,8)
start = (1,1)
goal = (8,8)
path = astar.plan_path(start, goal)
# 打印路径
if path:
print(f"规划路径:{path}")
# 可视化路径(网格地图)
map_grid = np.zeros((astar.rows, astar.cols), dtype=str)
# 标记障碍物
for obs in astar.obstacles:
map_grid[obs] = "X"
# 标记路径
for pos in path:
if pos == start:
map_grid[pos] = "S" # 起点
elif pos == goal:
map_grid[pos] = "G" # 终点
else:
map_grid[pos] = "*" # 路径
# 打印网格
print("网格地图(S=起点,G=终点,X=障碍物,*=路径):")
for row in map_grid:
print(" ".join(row))
else:
print("未找到路径!")
核心说明:
heuristic
函数用“曼哈顿距离”(适合网格地图),简化计算;plan_path
函数通过“开放列表+关闭列表”找最优路径,避免重复检查;- 输出的路径是“网格坐标”,后续需要转换为“像素坐标”,才能在停车场图片上绘制。
3. 第三步:1小时实现“定位+路径规划”可视化
将“车辆定位”和“路径规划”结合,在停车场图片上显示“车辆位置→空车位”的导航路径,用箭头标记方向:
import cv2
import numpy as np
from parking_detection import ParkingDetection # 导入之前的车位检测类
from aruco_localization import ArucoLocalization # 导入之前的ArUco定位类
from simple_astar import SimpleAStar # 导入之前的简化A*类
class ParkingNavigation:
def __init__(self):
# 初始化模块
self.parking_detector = ParkingDetection() # 车位检测
self.car_locator = ArucoLocalization() # 车辆定位
# 初始化路径规划(停车场网格地图:20x20网格,障碍物根据实际设置)
self.astar = SimpleAStar(
map_size=(20, 20),
obstacles=[(5,5), (5,6), (6,5)] # 停车场柱子等障碍物
)
# 网格坐标→像素坐标的转换参数(根据你的停车场实际尺寸调整)
self.grid_to_pixel = (30, 30) # 每个网格对应30x30像素
self.grid_origin = (50, 50) # 网格原点在像素图中的坐标
def grid_to_pixel_pos(self, grid_pos):
"""将网格坐标转换为像素坐标"""
x, y = grid_pos
pixel_x = self.grid_origin[0] + x * self.grid_to_pixel[0]
pixel_y = self.grid_origin[1] + y * self.grid_to_pixel[1]
return (int(pixel_x), int(pixel_y))
def draw_navigation_arrow(self, frame, start_pixel, end_pixel):
"""在帧上绘制导航箭头"""
# 计算箭头方向
dx = end_pixel[0] - start_pixel[0]
dy = end_pixel[1] - start_pixel[1]
# 绘制箭头(蓝色,厚度2)
cv2.arrowedLine(frame, start_pixel, end_pixel, (255, 0, 0), 2, tipLength=0.3)
def process_navigation(self, frame):
"""处理单帧导航:检测空车位→定位车辆→规划路径→绘制结果"""
# 1. 检测空车位,获取第一个空车位的像素坐标(作为导航终点)
frame_with_parking = self.parking_detector.process_frame(frame.copy())
empty_pixels = []
for box in self.parking_detector.all_parking_boxes:
x1, y1, x2, y2 = box
# 判断是否为空车位
is_empty = any(self.parking_detector.box_overlap(box, eb) > 0.5 for eb in self.parking_detector.empty_boxes)
if is_empty:
# 取车位中心作为终点像素坐标
center_x = (x1 + x2) // 2
center_y = (y1 + y2) // 2
empty_pixels.append((center_x, center_y))
if not empty_pixels:
cv2.putText(frame_with_parking, "No Empty Parking!", (20, 80), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
return frame_with_parking
goal_pixel = empty_pixels[0] # 选第一个空车位作为目标
# 2. 定位车辆,获取车辆像素坐标(作为起点)
car_pixel = self.car_locator.get_car_position(frame_with_parking)
if car_pixel is None:
cv2.putText(frame_with_parking, "Cannot Locate Car!", (20, 80), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
return frame_with_parking
# 3. 转换坐标(像素→网格),用于路径规划
# 像素→网格:(x - grid_origin_x) // grid_size_x
start_grid = (
(car_pixel[0] - self.grid_origin[0]) // self.grid_to_pixel[0],
(car_pixel[1] - self.grid_origin[1]) // self.grid_to_pixel[1]
)
goal_grid = (
(goal_pixel[0] - self.grid_origin[0]) // self.grid_to_pixel[0],
(goal_pixel[1] - self.grid_origin[1]) // self.grid_to_pixel[1]
)
# 4. 规划路径
path_grid = self.astar.plan_path(start_grid, goal_grid)
if path_grid is None:
cv2.putText(frame_with_parking, "No Path Found!", (20, 80), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
return frame_with_parking
# 5. 绘制导航路径(转换为像素坐标,用箭头连接)
path_pixel = [self.grid_to_pixel_pos(pos) for pos in path_grid]
for i in range(len(path_pixel)-1):
self.draw_navigation_arrow(frame_with_parking, path_pixel[i], path_pixel[i+1])
# 6. 标记起点和终点
cv2.circle(frame_with_parking, car_pixel, 8, (0, 0, 255), -1)
cv2.circle(frame_with_parking, goal_pixel, 8, (0, 255, 0), -1)
cv2.putText(frame_with_parking, "Car", (car_pixel[0]+10, car_pixel[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
cv2.putText(frame_with_parking, "Target", (goal_pixel[0]+10, goal_pixel[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
return frame_with_parking
# 测试导航系统(实时摄像头)
if __name__ == "__main__":
navigation = ParkingNavigation()
# 打开摄像头
cap = cv2.VideoCapture(0)
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
frame = cv2.flip(frame, 1)
# 处理导航
frame_with_nav = navigation.process_navigation(frame)
# 显示结果
cv2.imshow("Smart Parking Navigation", frame_with_nav)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
最终效果:
- 绿色框标记空车位,红色框标记占用车位;
- 红色圆点标记车辆位置,绿色圆点标记目标空车位;
- 蓝色箭头标记导航路径,指引车辆从当前位置驶向空车位。
五、Day9-Day10:优化与部署——让系统更实用
这两天的目标是:优化系统性能(解决速度慢、精度低的问题),并打包成可执行文件,方便零基础用户使用。
1. 第一步:1小时优化系统性能(解决3个常见问题)
零基础开发的系统可能存在“检测慢、定位不准、路径规划不合理”的问题,3个优化技巧就能解决:
优化1:提升检测速度(实时场景必做)
- 用轻量化模型:坚持用YOLOv8-nano模型,避免用更大的模型(如v8s、v8m),CPU上能稳定跑15FPS;
- 裁剪检测区域:只对停车场的“车位区域”进行检测,排除无关区域(如墙壁、天花板),减少计算量:
# 在ParkingDetection的process_frame中添加裁剪 def process_frame(self, frame): # 裁剪只包含车位的区域(根据实际调整坐标) roi = frame[300:600, 100:700] # y1:y2, x1:x2 # 只在roi中检测空车位 results = self.model(roi, conf=0.5) # 后续处理...
- 降低分辨率:将输入图片分辨率从640×640降至480×480,检测速度提升30%。
优化2:提升定位精度(解决“定位漂移”)
- 多标识融合:同时识别多个ArUco码,取平均坐标作为车辆位置,减少单标识识别误差;
- 添加滤波:对连续帧的车辆坐标进行“滑动平均”,避免瞬时抖动:
# 在ArucoLocalization中添加滤波 def __init__(self): self.car_pos_history = [] # 存储历史位置 self.history_size = 5 # 取最近5帧的平均 def get_car_position(self, frame): # 原有定位逻辑... if car_pos is not None: self.car_pos_history.append(car_pos) if len(self.car_pos_history) > self.history_size: self.car_pos_history.pop(0) # 计算平均位置 avg_x = int(sum(p[0] for p in self.car_pos_history)/len(self.car_pos_history)) avg_y = int(sum(p[1] for p in self.car_pos_history)/len(self.car_pos_history)) return (avg_x, avg_y) return None
优化3:优化路径规划(避免“绕远路”)
- 调整启发函数:用“欧氏距离”替代“曼哈顿距离”,更贴近实际路径长度:
def heuristic(self, a, b): return np.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2)
- 增加方向权重:优先选择“朝向终点”的方向,减少不必要的转向:
def get_neighbors(self, pos, goal): x, y = pos gx, gy = goal directions = [(-1,0), (1,0), (0,-1), (0,1)] # 根据与终点的方向排序,优先选择靠近终点的方向 directions.sort(key=lambda d: abs((x+d[0])-gx) + abs((y+d[1])-gy)) # 后续逻辑...
2. 第二步:1小时打包成exe文件(不用装Python也能运行)
用PyInstaller将整个系统打包成exe文件,发给没有Python环境的人也能直接运行:
步骤1:创建入口脚本(smart_parking_app.py
)
将所有模块整合到一个入口脚本中,确保代码可独立运行。
步骤2:执行打包命令
在“Anaconda Prompt”中进入脚本所在文件夹,输入以下命令:
pyinstaller -F -w -i parking_icon.ico --add-data "runs;runs" --add-data "aruco_marker_0.png;." smart_parking_app.py
-F
:打包成单个exe文件;-w
:隐藏命令行窗口,更友好;-i parking_icon.ico
:设置系统图标(文末资源包有);--add-data
:打包模型和ArUco码图片,确保exe能找到依赖文件。
步骤3:测试exe文件
打包完成后,exe文件在dist
文件夹中,双击即可运行,无需安装任何依赖。
六、避坑指南:零基础开发智慧停车系统的6个致命错误
零基础开发时容易踩坑,导致系统无法运行或效果差,这6个错误一定要避开:
1. 避坑1:数据集标注不规范,车位检测不准
- 坑点:标注空车位时,框选范围过大(包含相邻车位线)或过小(只框车位中心),导致模型学习到错误特征;
- 解决:严格按“车位线的左上角到右下角”框选空车位,确保框选范围与实际车位完全匹配,标注完成后抽查30%的图片。
2. 避坑2:预设车位坐标错误,检测结果混乱
- 坑点:凭感觉设置
all_parking_boxes
的坐标,导致“空车位”和“占用车位”标记错误; - 解决:用OpenCV打开停车场图片,手动标记每个车位的坐标:
运行后点击车位的左上角和右下角,即可获取准确坐标。# 坐标获取工具 import cv2 def get_mouse_pos(event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDOWN: print(f"坐标:({x},{y})") img = cv2.imread("parking_lot.jpg") cv2.namedWindow("Get Parking Coords") cv2.setMouseCallback("Get Parking Coords", get_mouse_pos) while True: cv2.imshow("Get Parking Coords", img) if cv2.waitKey(1) & 0xFF == ord('q'): break cv2.destroyAllWindows()
3. 避坑3:ArUco码打印尺寸不当,识别失败
- 坑点:将ArUco码打印成1cm×1cm的小尺寸,手机摄像头无法远距离识别;
- 解决:将ArUco码打印成5cm×5cm以上的尺寸,贴在地面时确保平整、无反光。
4. 避坑4:网格地图与实际场景不匹配,路径规划失效
- 坑点:网格地图的
grid_to_pixel
参数设置错误,导致规划的路径与停车场实际布局不符; - 解决:测量停车场的实际尺寸,计算“每个网格对应多少厘米”,再转换为像素坐标(如1米=50像素)。
5. 避坑5:摄像头角度不当,定位/检测失败
- 坑点:手机摄像头倾斜角度过大,无法清晰拍摄地面的ArUco码或车位线;
- 解决:将手机固定在“水平向下45°”的角度,确保能完整拍摄车辆前方的地面和车位。
6. 避坑6:未做异常处理,系统频繁崩溃
- 坑点:未处理“未检测到空车位”“未定位到车辆”等异常情况,导致系统报错崩溃;
- 解决:在代码中添加充分的异常判断(如
if car_pos is None
),并显示友好的提示信息,避免系统崩溃。
七、学习路径:零基础3周掌握智慧停车AI开发
为了让零基础用户有清晰的学习节奏,整理了3周学习计划,按这个来,3周就能独立开发系统:
阶段 | 天数 | 学习内容 | 目标成果 |
---|---|---|---|
基础准备 | 1-2 | 环境搭建+YOLOv8/OpenCV基础操作 | 能运行基础代码,理解核心概念 |
车位检测 | 3-5 | 数据集标注+模型训练+视频检测 | 能准确识别空车位,输出带标记的视频 |
定位与导航 | 6-8 | ArUco定位+简化A*路径规划+可视化 | 能定位车辆位置,规划并显示导航路径 |
优化与部署 | 9-10 | 性能优化+打包成exe | 系统运行流畅,生成可独立运行的exe文件 |
实战拓展 | 11-21 | 适配不同停车场场景+功能完善 | 能在小区/商场停车场实际使用,功能稳定 |
智慧停车的更多拓展方向(入门后可尝试)
掌握基础系统后,还可以拓展这些更实用的功能,难度适中,零基础也能上手:
- 车位预约功能:结合APP,用户可提前预约空车位,系统锁定车位并导航;
- 反向寻车功能:用户停车后记录位置,返回时通过手机摄像头识别标识,规划寻车路径;
- 多摄像头融合:用多个摄像头覆盖整个停车场,实现全区域空车位检测和车辆跟踪;
- 缴费联动功能:检测到车辆离开时,自动计算停车时间并推送缴费信息。
**我是南木 提供学习规划、就业指导、技术答疑和系统课程学习,感兴趣的同学 欢迎扫码交流
**
更多推荐
所有评论(0)