拓冰建站拓冰建站
首页 / 资讯中心 / 正文

OpenCV停车场车位识别实战:从图像预处理到状态判断

简介基于Python与OpenCV的停车场车位识别项目提供了一套从图像采集、灰度化与滤波预处理、边缘检测、形态学变换、特征提取与匹配到车位占用状态判断的完整源码方案面向初学者和有经验的开发者。压缩包共1111个文件核心包括1103张车位场景JPG图像、3个Python源码、2个pyc编译文件、1个H5训练模型、1个MP4演示视频及1个pickle文件总大小约111.51MBJPG用于训练与测试py实现识别流程H5保存模型权重MP4展示运行效果。目前已有261人学习下载代码注释详尽、结构清晰可直接运行并观察识别效果。资源中还包含典型场景图像与训练好的模型可快速验证算法并迁移至智能交通、停车场管理等实际应用场景。1. 停车场车位识别为什么值得用传统视觉做一遍停车场车位识别是计算机视觉大作业里出现频率很高的一道题输入一段监控视频或几张俯拍图片输出每个车位当前是空还是被占。很多人第一反应是上目标检测模型但如果你是第一次做或者手里只有 CPU、没有标注数据用 OpenCV 做像素级判断反而是更聪明的起步方案。车位是固定矩形车辆纹理和地面差异明显这类强先验场景正好是传统图像处理的舒适区。本文会用 Python OpenCV 走完一条不需要深度学习的完整链路图像预处理、车位框定位、空占状态判断、批量验证结果。适合刚学完 OpenCV 基础操作、想完成一个完整项目实战的读者所有代码都能直接跑参数也给出了可调范围。2. 先想透车位识别依赖哪些视觉假设2.1 固定视角是传统方案成立的前提停车位识别在技术上能走通依赖一个非常强的假设摄像头位置固定车位边界不会移动。这意味着背景几乎是静止的变化的只有车。背景固定带来一个直接好处不需要逐帧做语义分割只需要对变化区域做判断。这一点和自动驾驶里的实时检测完全不同后者要处理移动相机带来的透视与运动模糊而停车位监控只需要处理静态帧。常见的拍摄角度有两种俯拍和斜拍。俯拍画面里车位是接近标准的矩形直接用cv2.boundingRect处理轮廓很方便。斜拍画面会有透视变形严格做法是用cv2.getPerspectiveTransform做四点校正把斜着的车位映射成俯视矩形。不过很多课程项目里摄像头摆得比较正透视变形不大直接按矩形处理也能达到九成以上准确率。你拿到一个项目时第一步应该观察画面里车位是不是明显歪斜再决定要不要引入透视校正。2.2 传统视觉做车位识别的三块基础能力整个识别流程可以在逻辑上拆成三块。第一块是图像预处理把彩色图转灰度、去噪、做二值化目的是把“车位线和车辆”从复杂背景中分离出来。第二块是车位定位用边缘、轮廓、矩形拟合找到每个停车位的具体坐标。第三块是状态判断在定位到的车位区域内统计特征区分空车位和占用车位。这三块分别对应 OpenCV 里的几个核心模块灰度变换、高斯滤波、阈值分割、形态学操作、轮廓检测、ROI 提取。这些操作单独拎出来都是最基础的知识但组合在一起就能完成一个完整的业务闭环。这也是 OpenCV 图像处理项目实战和单纯调 API 最大的区别你要知道每一个步骤为什么存在、参数调大调小会发生什么。2.3 传统视觉与深度学习的边界在哪里不同项目的技术选型差异很大。这里把常见判断维度的差异性列出来方便你对号入座。比较维度传统视觉方案深度学习方案如 YOLO数据需求不需要标注靠阈值与几何规则需要几百到几千张标注车位图实时性单帧毫秒级普通 CPU 足够需要 GPU 或较强 CPU 推理优化场景变化适应性光照突变、相机抖动会明显掉点数据覆盖广时更鲁棒开发复杂度OpenCV 函数组合调试直观训练环境、模型部署链路较重判断粒度按车位区域统计不输出车型输出车辆检测框可映射到车位如果你做的课题明确要求识别车牌或车型那必须上检测模型但单纯判断空占传统视觉完全够用而且源码可读性更好。3. 用 OpenCV 把车位框找出来可运行的定位流程3.1 先观察输入再写第一行代码拿到图片后不要急着写代码先用cv2.imshow看一眼画面。确认三件事车位的颜色、车位线与地面的对比度、画面中有没有其他矩形物体比如路牌、柱子阴影会被误检。这个步骤决定了后续阈值怎么选、要不要加形态学过滤。输入是固定摄像头拍摄的单张图片时常见做法是先把 BGR 转灰度再做高斯模糊降噪然后二值化。车位线一般是白色或黄色在灰度图中亮度较高可以通过阈值把它们从地面分离出来。3.2 定位车位的核心代码二值化、轮廓筛选、矩形外框下面的代码用 OpenCV 从一张停车场俯拍图中提取车位矩形框。流程是读取图片、灰度化、高斯模糊、二值化、形态学闭运算、找轮廓、按面积和宽高比筛选矩形。import cv2 import numpy as np def preprocess(img): gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blur cv2.GaussianBlur(gray, (5, 5), 0) # THRESH_BINARY 会把亮色车位线置为255暗色地面置为0 _, binary cv2.threshold(blur, 180, 255, cv2.THRESH_BINARY) # 闭运算先膨胀再腐蚀把断开的车位线接上 kernel np.ones((5, 5), np.uint8) closed cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations2) return closed def locate_slots(binary, min_area1500, min_aspect1.2, max_aspect4.0): contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) slots [] for cnt in contours: x, y, w, h cv2.boundingRect(cnt) area w * h aspect w / h if h 0 else 0 if area min_area and min_aspect aspect max_aspect: slots.append((x, y, w, h)) return slots img cv2.imread(parking_lot.jpg) binary preprocess(img) slots locate_slots(binary) print(f检测到 {len(slots)} 个疑似车位) for idx, (x, y, w, h) in enumerate(slots): cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(img, str(idx), (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) cv2.imwrite(slots_result.jpg, img)cv2.GaussianBlur的核大小(5, 5)适合分辨率在 1280 左右的输入如果画面更清晰可以换(7, 7)。cv2.threshold的阈值 180 是个经验起点实际要根据画面中最暗的车位线像素值来调常见范围是 150~200。min_area过滤掉小噪点min_aspect和max_aspect过滤掉明显不像车位的细长或方形区域。这里用RETR_EXTERNAL只取最外层轮廓避免车位内部的车辆纹理产生干扰。3.3 定位失败时先查这五个参数定位结果不理想时不要盲目改代码按下面这个顺序排查效率更高。第一查看二值化输出如果车位线断续严重说明阈值偏高或车位线本身太淡把阈值调低 10~20 再试。第二如果背景里的矩形物体被误检收紧宽高比范围并适当调大min_area。第三如果多个车位被合并成一个连通域说明闭运算的核太大把(5, 5)换成(3, 3)或者减少迭代次数。第四如果同一个车位被重复检出检查是不是findContours拿到了内外两层轮廓改用RETR_EXTERNAL即可。第五如果画面明显斜视矩形拟合会不准需要先做透视校正。4. 车位状态判断从“框出来”到“判断空与占”4.1 空/占用的三种可用特征定位到车位后处理就进入 ROI 层面。此时需要回答一个问题怎么判断一个车位里有没有车常见的传统视觉思路有三种它们各有各的适用条件。第一种是像素统计。车辆占据车位时车位区域内的灰度值分布会比较杂乱地面通常是均匀的浅色或深色。把 ROI 转成灰度图后再做一次阈值分割统计高于阈值的像素比例如果比例超过一个经验值就认为有车。这种方法最简单但对地面纹理敏感如果地面本身就脏误报率会上升。第二种是边缘密度。车辆轮廓会产生大量边缘响应而空车位地面相对平滑。对 ROI 做 Canny 边缘检测统计边缘像素数除以总面积数值越高越可能是被占用。这个方法能避开地面颜色干扰比如白色车位线被算进去的误差会小一些。第三种是前景帧差比。如果有连续视频流可以拿当前帧和背景模型做差分统计车位内变化像素占比。这是效果最好的方案但需要额外写背景建模逻辑适合作为进阶优化方向。下面先给出能跑通的前两种实现。4.2 用 ROI 统计判断车位状态的代码def is_slot_occupied(gray_img, rect, methodpixel, thresh180, ratio_limit0.08): x, y, w, h rect roi gray_img[y:y h, x:x w] if roi.size 0: return False if method pixel: _, roi_bin cv2.threshold(roi, thresh, 255, cv2.THRESH_BINARY) # 统计亮色区域占比大于阈值认为有车 bright_ratio np.count_nonzero(roi_bin) / (w * h) return bright_ratio ratio_limit if method edge: edges cv2.Canny(roi, 50, 150) edge_ratio np.count_nonzero(edges) / (w * h) return edge_ratio 0.05 raise ValueError(unknown method) gray cv2.cvtColor(cv2.imread(frame.jpg), cv2.COLOR_BGR2GRAY) for idx, rect in enumerate(slots): occupied is_slot_occupied(gray, rect, methodpixel) print(f车位 {idx}: {占用 if occupied else 空})pixel方法里的thresh180与前面定位车位线时的阈值逻辑一致地面在灰度图中通常偏暗车辆偏亮所以统计亮像素占比。ratio_limit0.08表示如果超过 8% 的像素是亮的就判定为占用。edge方法里Canny的阈值(50, 150)是 OpenCV 官方推荐的起始值边缘像素占比超过 5% 判为占用。实际项目中可以把两种方法结合先看像素占比再用边缘密度做二次确认能有效降低单一特征的误判。4.3 参数速查与误判排查表现象可能原因调整方向空车位被判定为占用地面反光、车位线太粗导致亮像素多调高thresh或调高ratio_limit占用车位判定为空深色车辆与深色地面相近调低thresh或改用edge方法相邻车位互相干扰车位框定位不准矩形重叠重新调定位阶段的宽高比与面积过滤白天正常、晚上误报光照变化剧烈固定相机增益或引入背景差分方案车辆停在两个车位中间单一摄像机视角有遮挡增加另一个视角做投票融合这些参数在单张图上效果不错换一组光照立刻失效的情况很常见。所以项目里最好把参数集中放在一个配置结构中而不是散落在代码各处方便批量调参。5. 让项目稳定交付的三个进阶操作5.1 用批量脚本统计准确率单张图片跑通不算完成你要用一组测试图片验证整体准确率。常见做法是维护一个简单的标签字典手动记录每张图的真实车位占用情况然后跑批统计。import os image_dir test_images labels { frame_001.jpg: [occupied, free], frame_002.jpg: [free, occupied], } total, correct 0, 0 for name, truth in labels.items(): path os.path.join(image_dir, name) img cv2.imread(path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) slots locate_slots(preprocess(img)) preds [ occupied if is_slot_occupied(gray, rect, methodpixel) else free for rect in slots ] total len(slots) correct sum(p t for p, t in zip(preds, truth)) print(f准确率: {correct / total:.2%})如果准确率低于 90%优先分析是误报在空车位还是漏报在占用车位再针对性调阈值。不要只调一个ratio_limit试图解决所有问题那会把其他正常样本搞坏。5.2 保存中间可视化结果调试时每帧都cv2.imshow效率不高。更好的做法是把二值化图、车位框叠加图、每个车位的状态文本一并写入输出目录然后快速翻看。比如用cv2.imwrite(debug/binary_01.jpg, binary)保存预处理结果用叠加框的那张图确认定位正确性。这样你可以快速定位是预处理阶段出错还是状态判断阶段出错。5.3 用视频流验证时序稳定性单帧识别准确不等于连续视频稳定。把上面的逻辑包进一个逐帧循环每处理 10 帧保存一次带车位状态的可视化结果如果画面有明显的连续误报原因是相邻帧某一辆车跨在两个车位之间这时可以为状态结果加一个时间平滑连续 3 帧都是“占用”才真正切换状态避免状态闪烁。这种处理在真实停车场项目里比微调阈值更管用。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门