使用 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,讓用戶通過鼠標點擊來手動調整或標定車位,而不是完全依賴自動檢測。