当前位置: 首页 > news >正文

使用 C++/OpenCV 和 libevent 构建远程智能停车场管理系统

使用 C++/OpenCV 和 libevent 构建远程智能停车场管理系统

在之前的文章中,我们构建了一个本地运行的停车场视觉系统。本文将进行一次重大升级,通过集成高性能网络库 libevent,将其改造为一个可以通过网络 API 远程查询的后端服务。

最终,我们将得到一个 C++ 服务程序,它能:

  1. 使用 OpenCV 持续分析摄像头视频,判断车位占用情况。
  2. 利用 libevent 启动一个轻量级的 HTTP 服务器。
  3. 提供一个 JSON API 接口,任何客户端(如浏览器、手机 App、命令行工具)都可以通过网络请求,实时获取停车场车位的详细状态。

核心架构


🏗️ 核心技术栈

  • OpenCV: 负责所有核心的计算机视觉任务,包括图像处理和占用分析。
  • libevent: 一个轻量级、高性能的开源事件通知库。我们将使用它的 HTTP 模块,以极少的代码快速构建一个稳定、异步的 HTTP 服务器来提供我们的 API。
  • nlohmann/json: 一个非常流行的 C++ JSON 库。它以单个头文件的形式存在,使用简单,能轻松地将我们的数据结构序列化为 JSON 字符串。
  • 多线程 (C++ std::thread): 我们会将耗时的 OpenCV 视觉处理放在一个独立的工作线程中,而 libevent 的网络事件处理则在主线程中运行。这可以确保网络请求的响应不会被视频处理任务阻塞。

🛠️ 环境与准备

除了 C++ 编译器和 OpenCV 之外,你还需要准备:

  1. libevent 开发库:

    • 在 Debian/Ubuntu 系统上: sudo apt-get install libevent-dev
    • 在 macOS 上 (使用 Homebrew): brew install libevent
  2. nlohmann/json 头文件:

    • 这是一个仅头文件的库,无需编译。
    • 从 GitHub 下载最新的 json.hpp 文件,并将其放在你的项目目录中。
  3. 前期准备:

    • 你需要一个由上一篇文章中的校准程序生成的 parking_spots.xml 文件。
    • 一个用于测试的停车场视频文件,例如 parking_video.mp4

💻 代码实现 (parking_server.cpp)

我们将所有逻辑整合到一个名为 parking_server.cpp 的文件中。这个程序将同时负责视觉分析和网络服务。

1. 包含头文件与全局定义

#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <mutex>
#include <chrono>// OpenCV
#include <opencv2/opencv.hpp>// libevent
#include <event2/event.h>
#include <event2/http.h>
#include <event2/buffer.h>// JSON
#include "json.hpp" // 确保 json.hpp 在你的项目中
using json = nlohmann::json;// --- 全局共享数据 ---// 定义每个车位的状态
struct ParkingSpotStatus {int id;bool occupied;cv::RotatedRect position;
};// 存储所有车位状态的容器和用于保护它的互斥锁
std::vector<ParkingSpotStatus> g_spot_statuses;
std::mutex g_status_mutex;// 用于判断占用的边缘像素阈值
const int EDGE_PIXEL_THRESHOLD = 300;

2. 视觉处理工作线程

这个函数将在一个独立的线程中循环运行,不断处理视频帧并更新全局的车位状态。

void vision_processing_thread(const std::string& video_path) {// 加载预先校准的车位位置std::vector<cv::RotatedRect> parkingSpots;cv::FileStorage fs("parking_spots.xml", cv::FileStorage::READ);if (!fs.isOpened()) {std::cerr << "Vision Thread Error: Could not open parking_spots.xml." << std::endl;return;}fs["parking_spots"] >> parkingSpots;fs.release();// 初始化全局状态{std::lock_guard<std::mutex> lock(g_status_mutex);for (size_t i = 0; i < parkingSpots.size(); ++i) {g_spot_statuses.push_back({(int)i, false, parkingSpots[i]});}}cv::VideoCapture cap(video_path);if (!cap.isOpened()) {std::cerr << "Vision Thread Error: Could not open video file." << std::endl;return;}cv::Mat frame, gray, roi, edges;while (true) {if (!cap.read(frame)) {// 视频播放完毕后,重置到开头继续循环cap.set(cv::CAP_PROP_POS_FRAMES, 0);continue;}cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);std::vector<ParkingSpotStatus> current_statuses;// 分析每个车位for (size_t i = 0; i < parkingSpots.size(); ++i) {cv::Rect br = parkingSpots[i].boundingRect();br &= cv::Rect(0, 0, frame.cols, frame.rows);if (br.width == 0 || br.height == 0) continue;roi = gray(br);cv::Canny(roi, edges, 100, 200);int edge_pixels = cv::countNonZero(edges);bool occupied = edge_pixels > EDGE_PIXEL_THRESHOLD;current_statuses.push_back({(int)i, occupied, parkingSpots[i]});}// 使用互斥锁安全地更新全局状态{std::lock_guard<std::mutex> lock(g_status_mutex);g_spot_statuses = current_statuses;}// 等待一小段时间,避免 CPU 100%std::this_thread::sleep_for(std::chrono::milliseconds(50));}
}

3. libevent HTTP 请求处理函数

这是我们的 API 核心。每当有 HTTP 请求进来,libevent 就会调用这个函数。

void http_request_handler(struct evhttp_request *req, void *arg) {json response_json;int available_spots = 0;// 使用互斥锁安全地读取全局状态{std::lock_guard<std::mutex> lock(g_status_mutex);json spots_array = json::array();for (const auto& status : g_spot_statuses) {if (!status.occupied) {available_spots++;}json spot_obj;spot_obj["id"] = status.id;spot_obj["occupied"] = status.occupied;spot_obj["center_x"] = status.position.center.x;spot_obj["center_y"] = status.position.center.y;spots_array.push_back(spot_obj);}response_json["total_spots"] = g_spot_statuses.size();response_json["available_spots"] = available_spots;response_json["spots"] = spots_array;}// 创建响应struct evbuffer *buf = evbuffer_new();if (!buf) {std::cerr << "Failed to create response buffer." << std::endl;return;}// 设置 HTTP 头,告诉客户端我们返回的是 JSONevhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "application/json");// 将 JSON 字符串添加到响应体std::string json_str = response_json.dump(4); // dump(4) for pretty-printingevbuffer_add_printf(buf, "%s", json_str.c_str());// 发送响应evhttp_send_reply(req, HTTP_OK, "OK", buf);evbuffer_free(buf);
}

4. main 函数: 启动一切

主函数负责初始化 libevent,启动视觉线程,并进入 libevent 的事件循环。

int main(int argc, char **argv) {if (argc != 3) {std::cout << "Usage: ./parking_server <video_file> <port>" << std::endl;return -1;}std::string video_path = argv[1];int port = std::atoi(argv[2]);// 1. 在新线程中启动视觉处理std::thread vision_thread(vision_processing_thread, video_path);vision_thread.detach(); // 让视觉线程在后台自由运行// 2. 初始化 libeventstruct event_base *base = event_base_new();struct evhttp *http = evhttp_new(base);// 3. 设置通用的请求处理回调函数evhttp_set_gencb(http, http_request_handler, NULL);// 4. 绑定端口并监听if (evhttp_bind_socket(http, "0.0.0.0", port) != 0) {std::cerr << "Error: Could not bind to port " << port << std::endl;return -1;}std::cout << "Server started. Listening on http://0.0.0.0:" << port << std::endl;std::cout << "Waiting for vision thread to initialize..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(5)); // 等待视觉线程完成第一次分析std::cout << "Ready to accept requests." << std::endl;// 5. 启动事件循环 (此函数会阻塞)event_base_dispatch(base);// 清理资源evhttp_free(http);event_base_free(base);return 0;
}

🚀 编译与运行

  1. 编译:
    这个命令比之前复杂,因为它需要链接 libeventpthread

    g++ -o parking_server parking_server.cpp $(pkg-config --cflags --libs opencv4 libevent) -lpthread -std=c++17
    

    确保你使用的是支持 C++17 的编译器。

  2. 运行:

    • 第一步: 确保你的 parking_spots.xml 文件在同一目录下。
    • 第二步: 启动服务器,指定视频文件和端口号。
      ./parking_server parking_video.mp4 8080
      
      服务器启动后,你会在终端看到 “Server started. Listening on http://0.0.0.0:8080”。
    • 第三步: 远程访问。打开一个新的终端(可以在另一台电脑上,只需将 127.0.0.1 替换为服务器的 IP 地址),使用 curl 工具来请求 API:
      curl http://127.0.0.1:8080/
      
    • 预期输出: 你将会看到一个格式化的 JSON 响应,实时反映了停车场的状况:
      {"available_spots": 18,"total_spots": 25,"spots": [{"center_x": 150.5,"center_y": 230.0,"id": 0,"occupied": true},{"center_x": 250.8,"center_y": 232.1,"id": 1,"occupied": false},// ... more spots]
      }
      

总结与展望

我们成功地将一个本地的 OpenCV 应用改造成了一个功能强大的、基于网络的远程服务。通过 libevent,我们以非常高效和简洁的方式实现了网络通信。

下一步可以做什么?

  • Web 前端: 创建一个简单的 HTML 和 JavaScript 前端页面,使用 fetch API 定期调用这个后端接口,并在网页上以图形化方式动态展示停车场地图和状态。
  • 数据持久化: 将车位状态的变化记录到数据库(如 SQLite 或 PostgreSQL)中,用于历史数据分析。
  • 功能扩展: 增加更多的 API 端点,例如 /api/spot/{id} 来获取单个车位的详细信息,或者 /api/history 来查询历史占用率。
  • 部署: 使用 Docker 将此服务容器化,以便在任何服务器上轻松部署。
http://www.xdnf.cn/news/1017451.html

相关文章:

  • 每天宜搭宜搭小知识—报表组件—柱线混合图
  • 算法第15天:继续二叉树|前序递归+回溯与前序递归的场景总结、最大二叉树、合并二叉树、二叉搜索树中的搜索、验证二叉搜索树
  • Mac电脑 系统监测工具 System Dashboard Pro
  • 【leetcode】543. 二叉树的直径
  • uni-app项目实战笔记4--使用组件具名插槽slot定义公共标题模块
  • 案例:城市“光革命”背后,塔能科技的智能照明进化方程式
  • 欧美简洁时尚风格通用PPT模版分享
  • 麒麟信安支撑2025年电力监控系统安全运维新技能推广应用示范培训班顺利举办
  • Java + easyexcel 新旧数据对比,单元格值标红
  • 优化 Excel 文件可以提升文件性能、减少文件大小并加快计算速度
  • mysql中替换字符串(正则)
  • mapbox进阶,切片网格生成实现
  • 深入理解Python协程:asyncio、异步并发、事件循环
  • 开疆智能ModbusTCP转Devicenet网关连接三菱PLC与ABB机器人配置案例
  • NAS 年中成果汇报:从入门到高阶的影视/音乐/小说/资源下载 等好玩Docker 全集合
  • Python让自动驾驶“看见未来”:环境建模那些事儿
  • AWS知识点和技术面试模拟题
  • 基于python大数据的nba球员可视化分析系统
  • 大模型驱动数据分析革新:美林数据智能问数解决方案破局传统 BI 痛点
  • CSS基础学习1
  • Python 数据分析10
  • 【Three.js】初识 Three.js
  • 【论文阅读33】滑坡易发性 PINN ( EG2025 )
  • 基于 SpaCy DependencyMatcher 编写复杂依存关系规则实战指南
  • java 将多张图片合成gif动态图
  • 国产数据库StarRocks在数栈轻量化数据开发的全流程实践
  • 普通人怎样用好Deepseek?
  • MySQL 8.0 OCP 英文题库解析(十九)
  • 26-数据结构-线性表2
  • linux alignment fault对齐造成设备挂死问题定位梳理