大家好,我是南木。最近后台收到很多零基础读者的咨询:“想入门计算机视觉,但不知道选什么项目练手?”“商场找车位太麻烦,能不能自己做一个智慧停车系统?”“学了YOLO和OpenCV,却不知道怎么结合起来解决实际问题?”

其实智慧停车的“车位检测+导航”是计算机视觉入门的完美项目——它场景贴近生活(商场、小区停车场都能用到),技术栈轻量化(YOLOv8做车位检测+OpenCV做定位+简化A*算法做导航),而且不用专业硬件(普通电脑+手机摄像头就能跑通)。更重要的是,它的核心逻辑清晰,零基础能快速上手并看到实际效果。

这篇文章以“室内停车场智慧停车”为目标,从“工具准备→车位检测模型训练→车辆定位→路径规划→系统整合”全流程拆解,每个步骤都附“零基础可复制的代码+图文操作+避坑说明”。不用懂复杂的SLAM或深度学习理论,跟着敲代码、调参数,3周就能做出能“自动找空车位+规划导航路径”的智慧停车原型系统。

正文开始前 这里给大家准备了一份零基础入门资料 同时需要系统课程学习的同学 欢迎扫码交流

在这里插入图片描述
在这里插入图片描述

一、先搞懂:智慧停车AI的核心逻辑,普通人也能秒懂

很多人觉得智慧停车系统“高大上”,其实它的本质是“用计算机视觉技术模拟停车场管理员的工作”——管理员会“看哪里有空车位→告诉司机位置→指引路线”,AI系统也是一样,核心就3步:检测空车位→定位车辆位置→规划导航路径

1. 智慧停车AI的“3步核心流程”(一张图看懂)

无论多复杂的智慧停车系统,底层逻辑都离不开这3个步骤,零基础也能快速理解:

  1. 第一步:检测空车位(YOLOv8负责)
    从停车场的视频或图片中,识别出哪些车位是“空的”(无车辆停放),哪些是“占用的”(有车辆停放),用框标记空车位位置。
  2. 第二步:定位车辆位置(OpenCV负责)
    通过停车场地面的“定位标识”(如箭头、编号、二维码)或“车辆特征”,确定当前车辆在停车场中的具体位置(用坐标表示)。
  3. 第三步:规划导航路径(简化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)
  1. 下载Anaconda:Anaconda官网,选择“Python 3.9”版本(别选最新版,避免兼容性问题);
  2. 安装Anaconda:双击安装包,一路默认下一步,最后勾选“Add Anaconda to my PATH environment variable”(方便命令行调用);
  3. 打开“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*算法的核心是“通过评估函数找最短路径”,简化版逻辑适合零基础:

  1. 地图建模:把停车场划分为“网格”(如10x10网格),每个网格代表一个“可行走区域”或“障碍物”;
  2. 评估函数:对于每个网格,计算“从起点到当前网格的距离”+“当前网格到终点的预估距离”,优先选择评估值小的网格;
  3. 避障逻辑:遇到障碍物(如柱子、墙),自动绕开,重新规划路线。

简单说:选“看起来最近”的路,遇到障碍就绕开,这就是简化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),未标注的视为“占用车位”,步骤如下:

  1. 启动LabelImg:在“smart_parking”环境中输入labelImg,打开工具;
  2. 配置标注
    • 点击“Open Dir”,选择图片文件夹;
    • 点击“Change Save Dir”,选择标注文件保存文件夹;
    • 点击“View”,勾选“Auto Save mode”(自动保存)和“Dark Mode”(保护眼睛);
  3. 标注操作
    • 按“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,步骤如下:

  1. 生成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")
    
  2. 识别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,用户可提前预约空车位,系统锁定车位并导航;
  • 反向寻车功能:用户停车后记录位置,返回时通过手机摄像头识别标识,规划寻车路径;
  • 多摄像头融合:用多个摄像头覆盖整个停车场,实现全区域空车位检测和车辆跟踪;
  • 缴费联动功能:检测到车辆离开时,自动计算停车时间并推送缴费信息。

**我是南木 提供学习规划、就业指导、技术答疑和系统课程学习,感兴趣的同学 欢迎扫码交流
**
在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐