C++/OpenCV地砖识别系统结合 Libevent 实现网络化 AI 接入

🚀 C++/OpenCV 地砖识别系统:结合 Libevent 实现网络化 AI 接入

本项目旨在创建一个高性能的地砖识别服务。我们使用 C++/OpenCV 实现核心的图像识别算法,并利用 Libevent 库将其封装成一个高效的、事件驱动的 TCP 服务器。这样,任何客户端(如移动 App、Web 前端或 Python AI 脚本)都可以通过网络请求来进行地砖识别,极大地增强了系统的可扩展性。

🏗️ 系统架构

系统的核心思想是将计算密集型的视觉任务与网络通信分离:

  1. 客户端 (Client):通过 TCP 连接到服务器,发送一个简单的指令(例如 "detect")。
  2. Libevent 服务器 (Server):接收到客户端指令后,触发一个回调函数。
  3. OpenCV 处理器 (Processor):回调函数调用预先编写好的 OpenCV 函数,对服务器本地的一张图像进行地砖识别。
  4. 响应 (Response):服务器将识别结果(例如,找到的地砖数量)格式化成字符串,并发送回客户端。

这种架构使得 OpenCV 的处理逻辑可以独立更新,同时服务器能够高效处理多个并发连接,为将来集成需要大量计算的 AI 模型(例如,通过网络调用 Python 的 AI 推理服务)提供了完美的接口。


👁️ Part 1: OpenCV 地砖识别模块

首先,我们把上一篇文章中的地砖识别逻辑封装成一个独立的函数。这个函数接收一个 OpenCV Mat 对象,并返回检测到的地砖数量。这种封装使得代码更模块化,易于在网络回调中调用。

cpp 复制代码
#include <opencv2/opencv.hpp>
#include <vector>

/**
 * @brief 在给定的图像中检测地砖数量
 * @param image 输入的 BGR 图像
 * @return 检测到的地砖数量
 */
int detect_tiles(const cv::Mat& image) {
    if (image.empty()) {
        return 0;
    }

    // 1. 预处理
    cv::Mat gray, blurred, edges;
    cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
    cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);

    // 2. Canny 边缘检测
    cv::Canny(blurred, edges, 50, 150, 3);

    // 3. 寻找并筛选轮廓
    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(edges, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);

    int tile_count = 0;
    for (const auto& contour : contours) {
        // 面积过滤
        double area = cv::contourArea(contour);
        if (area < 1000) { // 根据图像分辨率调整
            continue;
        }

        // 多边形逼近
        std::vector<cv::Point> approx;
        double peri = cv::arcLength(contour, true);
        cv::approxPolyDP(contour, approx, 0.04 * peri, true);

        // 筛选四边形
        if (approx.size() == 4 && cv::isContourConvex(approx)) {
            tile_count++;
        }
    }
    return tile_count;
}

🌐 Part 2: Libevent 网络服务器模块

现在,我们使用 libevent 创建一个 TCP 服务器。Libevent 是一个高性能的事件通知库,非常适合编写高并发的网络程序。

核心概念
  • event_base: 事件循环的"心脏",负责管理所有的事件。
  • evconnlistener: 用于监听 TCP 连接的辅助对象。
  • 回调函数 : libevent 的精髓。当特定事件发生时(如新连接、数据可读),libevent 会调用我们预先注册的回调函数。

我们将创建一个服务器,它在 12345 端口上监听连接。当接收到数据时,它会检查收到的消息是否是 "detect"。


🤝 整合:将 OpenCV 接入 Libevent

这是最关键的一步。我们将在 libevent读回调函数 (read_cb) 中调用我们的 detect_tiles 函数。

为了让回调函数能访问到需要处理的图像,我们定义一个结构体 AppContext 来传递上下文数据。

完整代码示例

下面的完整代码整合了 OpenCV 和 Libevent。服务器启动后会加载一张名为 tiles.jpg 的图片,并等待客户端连接。

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>

#include <opencv2/opencv.hpp>

#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>

#include <arpa/inet.h>

// 上下文结构体,用于在回调间传递数据
struct AppContext {
    cv::Mat image_to_process;
};

// 声明地砖检测函数
int detect_tiles(const cv::Mat& image);

// 读事件回调函数
void read_cb(struct bufferevent *bev, void *ctx) {
    AppContext* context = static_cast<AppContext*>(ctx);
    struct evbuffer *input = bufferevent_get_input(bev);
    char buf[1024];
    int n;
    std::string received_data;

    while ((n = evbuffer_remove(input, buf, sizeof(buf) - 1)) > 0) {
        buf[n] = '\0';
        received_data += buf;
    }

    // 简单协议:如果收到 "detect\n",则执行识别
    if (received_data.find("detect") != std::string::npos) {
        std::cout << "Received 'detect' command. Processing image..." << std::endl;
        
        // 调用 OpenCV 函数
        int tile_count = detect_tiles(context->image_to_process);

        // 准备并发送响应
        std::string response = "Detected " + std::to_string(tile_count) + " tiles.\n";
        bufferevent_write(bev, response.c_str(), response.length());
        
        std::cout << "Response sent: " << response;
    }
}

// 事件回调函数(例如,连接关闭)
void event_cb(struct bufferevent *bev, short events, void *ctx) {
    if (events & BEV_EVENT_EOF) {
        printf("Connection closed.\n");
    } else if (events & BEV_EVENT_ERROR) {
        printf("Got an error on the connection: %s\n", strerror(errno));
    }
    bufferevent_free(bev);
}

// 新连接回调函数
void listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
                 struct sockaddr *sa, int socklen, void *user_data) {
    struct event_base *base = evconnlistener_get_base(listener);
    AppContext* context = static_cast<AppContext*>(user_data);

    // 为新连接创建一个 bufferevent
    struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    if (!bev) {
        fprintf(stderr, "Error constructing bufferevent!");
        event_base_loopbreak(base);
        return;
    }
    
    // 设置回调
    bufferevent_setcb(bev, read_cb, NULL, event_cb, context);
    bufferevent_enable(bev, EV_READ | EV_WRITE);
    
    printf("New connection accepted.\n");
}


int main(int argc, char **argv) {
    // ---- OpenCV 部分 ----
    AppContext context;
    context.image_to_process = cv::imread("tiles.jpg"); // 确保 tiles.jpg 在运行目录下
    if (context.image_to_process.empty()) {
        std::cerr << "Error: Could not load tiles.jpg" << std::endl;
        return 1;
    }
    std::cout << "Image 'tiles.jpg' loaded successfully." << std::endl;

    // ---- Libevent 部分 ----
    struct event_base *base;
    struct evconnlistener *listener;
    struct sockaddr_in sin;
    const int PORT = 12345;

    base = event_base_new();
    if (!base) {
        fprintf(stderr, "Could not initialize libevent!\n");
        return 1;
    }

    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(PORT);

    listener = evconnlistener_new_bind(base, listener_cb, &context,
        LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE, -1,
        (struct sockaddr*)&sin, sizeof(sin));

    if (!listener) {
        fprintf(stderr, "Could not create a listener!\n");
        return 1;
    }

    printf("Server listening on port %d...\n", PORT);

    // 启动事件循环
    event_base_dispatch(base);

    // 释放资源
    evconnlistener_free(listener);
    event_base_free(base);

    printf("Done.\n");
    return 0;
}

// 地砖检测函数的实现 (与上面相同)
int detect_tiles(const cv::Mat& image) {
    if (image.empty()) return 0;
    cv::Mat gray, blurred, edges;
    cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
    cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);
    cv::Canny(blurred, edges, 50, 150, 3);
    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(edges, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
    int tile_count = 0;
    for (const auto& contour : contours) {
        if (cv::contourArea(contour) < 1000) continue;
        std::vector<cv::Point> approx;
        cv::approxPolyDP(contour, approx, 0.04 * cv::arcLength(contour, true), true);
        if (approx.size() == 4 && cv::isContourConvex(approx)) {
            tile_count++;
        }
    }
    return tile_count;
}
编译与运行

你需要同时链接 OpenCV 和 Libevent 库。

bash 复制代码
# 编译命令 (确保已安装 libevent-dev 和 opencv)
g++ -o tile_server main.cpp `pkg-config --cflags --libs opencv4 event`

# 运行服务器 (确保同目录下有 tiles.jpg)
./tile_server
测试服务器

打开一个新的终端,使用 netcattelnet 作为客户端来测试服务器。

bash 复制代码
# 使用 netcat
nc localhost 12345

连接成功后,输入 detect 并按回车。

复制代码
detect
Detected 15 tiles.  <-- 这是服务器返回的响应

🧠 未来扩展:接入 AI 功能

这个网络化架构为集成真正的 AI 功能铺平了道路:

  1. 更复杂的请求:客户端可以将图片数据(例如 Base64 编码后)作为请求的一部分发送给服务器,而不是让服务器处理本地文件。
  2. AI 推理服务:C++ 服务器可以扮演一个"调度者"的角色。它在接收到图片后,可以调用一个独立的 Python AI 服务(例如,一个使用 Flask 或 FastAPI 搭建的,运行着 PyTorch/TensorFlow 模型的服务)来进行高级分析,如裂缝检测、材质分类等。
  3. 返回结构化数据:服务器的响应可以升级为 JSON 格式,包含更丰富的信息,如每个地砖的坐标、大小、以及 AI 模型的分析结果。

通过这种方式,你可以充分利用 C++ 的高性能来处理图像 I/O 和基础视觉任务,同时利用 Python 生态系统在 AI/ML 领域的强大能力。

相关推荐
leo__5209 分钟前
matlab实现非线性Granger因果检验
人工智能·算法·matlab
struggle20259 分钟前
Burn 开源程序是下一代深度学习框架,在灵活性、效率和可移植性方面毫不妥协
人工智能·python·深度学习·rust
呃m24 分钟前
双重特征c++
c++
CareyWYR33 分钟前
每周AI论文速递(2506209-250613)
人工智能
MYH51642 分钟前
无监督的预训练和有监督任务的微调
人工智能
景彡先生43 分钟前
C++ 中文件 IO 操作详解
开发语言·c++
Jet45051 小时前
玩转ChatGPT:DeepSeek实战(核酸蛋白序列核对)
人工智能·chatgpt·kimi·deepseek
几夏经秋1 小时前
图文教程——Deepseek最强平替工具免费申请教程——国内edu邮箱可用
人工智能
无影无踪的青蛙1 小时前
[C++] STL大家族之<map>(字典)容器(附洛谷)
开发语言·c++
二进制人工智能1 小时前
【OpenGL学习】(四)统一着色和插值着色
c++·opengl