使用 C/C++ 和 OpenCV 构建智能停车场视觉管理系统
使用 C++ 和 OpenCV 构建智能停车场视觉管理系统
本文将详细介绍如何利用 C++ 和 OpenCV 库,从零开始创建一个智能停车场管理系统。该系统通过摄像头捕捉的画面,能自动完成两项核心任务:
- 车位识别:通过检测地面上的黄色停车线,自动识别并标定出所有停车位的位置。
- 状态监控:实时监控每个车位,判断其是**“空闲”还是“被占用”**,并进行可视化展示。
🧠 核心逻辑与方法
我们将整个系统分为两个主要阶段:校准阶段和监控阶段。
1. 校准阶段 (Calibration Phase)
这个阶段的目标是自动定义停车位。我们只需要对一张空停车场的图片进行一次性处理。
- 颜色分割: 将图像从 BGR 转换到 HSV 颜色空间。HSV 对光照变化不敏感,能更准确地提取出黄色。我们使用
cv::inRange
函数来创建一个只包含黄色标线的二值化“蒙版”。 - 轮廓检测: 在蒙版上,我们使用
cv::findContours
来寻找所有黄色区域的轮廓。 - 车位标定: 通过对轮廓的面积和形状进行筛选,我们可以过滤掉噪声,只保留代表停车位的矩形区域。我们使用
cv::minAreaRect
来获取这些区域的精确位置(包括旋转角度),并将这些位置信息保存到一个文件中(例如parking_spots.xml
)。
2. 监控阶段 (Monitoring Phase)
这个阶段是系统的核心,它在实时的视频流上运行。
- 加载车位数据: 程序首先从
parking_spots.xml
文件中加载所有预先标定好的停车位位置。 - 占用检测: 对每一个停车位区域(ROI),我们使用一种简单而有效的方法来判断是否有车:边缘密度分析。
- 一个空车位(通常是沥青或水泥地面)的纹理较少,因此边缘也较少。
- 一辆汽车具有复杂的轮廓、窗户、轮胎和车身线条,会产生大量的边缘。
- 状态判断: 我们在每个车位 ROI 上运行 Canny 边缘检测,然后计算边缘像素的数量。如果数量超过一个预设的阈值,我们就判定该车位**“被占用”,否则为“空闲”**。
- 可视化: 根据判断结果,在视频画面上用不同颜色的矩形(例如红色代表占用,绿色代表空闲)框出每个车位,并显示可用的车位总数。
🛠️ 环境与准备
- C++ 编译器: G++, Clang, 或 MSVC。
- OpenCV 库: 确保已正确安装并配置。
- 校准图像: 一张停车场空无一车时的图像,例如
empty_lot.jpg
。 - 测试视频/图像: 一段停车场有车辆进出的视频或图像,例如
parking_video.mp4
。
💻 代码实现
我们将代码分为两个独立的文件:一个用于校准,一个用于监控。
第1部分: 校准程序 (calibrate.cpp
)
这个程序只运行一次,用于生成车位坐标文件。
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>int main(int argc, char** argv) {if (argc != 2) {std::cout << "Usage: ./calibrate_app <empty_lot_image>" << std::endl;return -1;}cv::Mat frame = cv::imread(argv[1]);if (frame.empty()) {std::cerr << "Error: Could not read the image." << std::endl;return -1;}// 1. 转换为 HSVcv::Mat hsv;cv::cvtColor(frame, hsv, cv::COLOR_BGR2HSV);// 2. 颜色阈值分割 (针对黄色)// 注意: 这个范围可能需要根据你的实际光照进行微调cv::Scalar lower_yellow(20, 100, 100);cv::Scalar upper_yellow(30, 255, 255);cv::Mat mask;cv::inRange(hsv, lower_yellow, upper_yellow, mask);// 3. 形态学操作去噪cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));cv::morphologyEx(mask, mask, cv::MORPH_CLOSE, kernel, cv::Point(-1,-1), 2);// 4. 寻找轮廓std::vector<std::vector<cv::Point>> contours;cv::findContours(mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);std::vector<cv::RotatedRect> parkingSpots;for (const auto& contour : contours) {// 5. 过滤轮廓并获取最小外接旋转矩形double area = cv::contourArea(contour);if (area > 2000) { // 根据车位实际像素大小调整此阈值cv::RotatedRect rotatedRect = cv::minAreaRect(contour);parkingSpots.push_back(rotatedRect);}}// 6. 保存车位坐标到文件cv::FileStorage fs("parking_spots.xml", cv::FileStorage::WRITE);fs << "parking_spots" << parkingSpots;fs.release();std::cout << "Successfully detected and saved " << parkingSpots.size() << " parking spots." << std::endl;// 可选: 可视化检测到的车位for (const auto& spot : parkingSpots) {cv::Point2f vertices[4];spot.points(vertices);for (int i = 0; i < 4; i++) {cv::line(frame, vertices[i], vertices[(i + 1) % 4], cv::Scalar(0, 255, 0), 2);}}cv::imshow("Detected Parking Spots", frame);cv::waitKey(0);return 0;
}
第2部分: 监控程序 (monitor.cpp
)
这个程序加载校准数据,并对视频进行实时分析。
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>// 定义占用阈值
const int EDGE_PIXEL_THRESHOLD = 300; // 需要根据分辨率和场景微调int main(int argc, char** argv) {if (argc != 2) {std::cout << "Usage: ./monitor_app <video_file>" << std::endl;return -1;}// 1. 加载车位数据std::vector<cv::RotatedRect> parkingSpots;cv::FileStorage fs("parking_spots.xml", cv::FileStorage::READ);if (!fs.isOpened()) {std::cerr << "Error: Could not open parking_spots.xml. Run calibration first." << std::endl;return -1;}fs["parking_spots"] >> parkingSpots;fs.release();cv::VideoCapture cap(argv[1]);if (!cap.isOpened()) {std::cerr << "Error: Could not open video file." << std::endl;return -1;}cv::Mat frame, gray, roi, edges;while (cap.read(frame)) {int available_spots = 0;cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);for (const auto& spot : parkingSpots) {// 2. 提取每个车位的 ROIcv::Rect br = spot.boundingRect();// 保证 ROI 在图像边界内br &= cv::Rect(0, 0, frame.cols, frame.rows);if (br.width == 0 || br.height == 0) continue;roi = gray(br);// 3. 计算 ROI 内的边缘密度cv::Canny(roi, edges, 100, 200);int edge_pixels = cv::countNonZero(edges);// 4. 判断车位状态并可视化cv::Point2f vertices[4];spot.points(vertices);bool occupied = edge_pixels > EDGE_PIXEL_THRESHOLD;cv::Scalar color = occupied ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0);for (int i = 0; i < 4; i++) {cv::line(frame, vertices[i], vertices[(i + 1) % 4], color, 2);}if (!occupied) {available_spots++;}}// 5. 显示状态信息std::string status_text = "Available: " + std::to_string(available_spots) + "/" + std::to_string(parkingSpots.size());cv::putText(frame, status_text, cv::Point(20, 40), cv::FONT_HERSHEY_SIMPLEX, 1.5, cv::Scalar(255, 255, 0), 3);cv::imshow("Parking Lot Monitor", frame);if (cv::waitKey(30) >= 0) break;}return 0;
}
🚀 编译与运行
-
编译: 打开终端,使用
g++
和pkg-config
编译两个程序。# 编译校准程序 g++ -o calibrate_app calibrate.cpp $(pkg-config --cflags --libs opencv4)# 编译监控程序 g++ -o monitor_app monitor.cpp $(pkg-config --cflags --libs opencv4)
注意: 如果你的 OpenCV 版本不是 4,请将
opencv4
替换为你的版本。 -
运行:
- 第一步:运行校准程序,传入空停车场的图片。
这会生成一个./calibrate_app empty_lot.jpg
parking_spots.xml
文件。 - 第二步:运行监控程序,传入要分析的视频。
程序会加载./monitor_app parking_video.mp4
parking_spots.xml
并开始实时分析和显示结果。
- 第一步:运行校准程序,传入空停车场的图片。
总结与改进
这个项目展示了如何用标准 OpenCV 功能构建一个实用的计算机视觉应用。它的优点是逻辑清晰、实现简单。当然,它也有可以改进的地方:
- 鲁棒性: 在光照剧烈变化或有阴影的情况下,基于边缘的检测可能会不稳定。可以引入更高级的特征(如 HOG)和机器学习分类器(如 SVM)来提高准确性。
- 灵活性: 对于非黄色标线或不规则车位,需要修改颜色分割和轮廓筛选逻辑。
- 用户界面: 可以创建一个简单的 GUI,让用户通过鼠标点击来手动调整或标定车位,而不是完全依赖自动检测。