一、实现流程
1. 定义线程安全的队列结构
需要封装std::queue
,用std::mutex
保证互斥访问,用std::condition_variable
触发唤醒:
cpp
#include <queue>
#include <mutex>
#include <condition_variable>
#include <opencv2/opencv.hpp> // 假设用OpenCV存储图像
// 线程安全的图像队列
class ImageQueue {
private:
std::queue<cv::Mat> queue_; // 存储图像数据
std::mutex mtx_; // 互斥锁,保护队列操作
std::condition_variable cv_; // 条件变量,用于线程同步
bool is_stopped_ = false; // 退出标志(用于优雅终止线程)
public:
// 生产者调用:放入图像
void push(const cv::Mat& img) {
std::lock_guard<std::mutex> lock(mtx_); // 自动加锁/解锁
queue_.push(img);
cv_.notify_one(); // 通知等待的消费者(唤醒一个线程)
}
// 消费者调用:取出图像(无数据时阻塞)
bool pop(cv::Mat& img) {
std::unique_lock<std::mutex> lock(mtx_); // 可手动解锁的锁
// 等待条件:队列非空 或 线程需终止
cv_.wait(lock, [this]{ return !queue_.empty() || is_stopped_; });
if (is_stopped_) return false; // 线程终止,退出
img = queue_.front();
queue_.pop();
return true;
}
// 终止线程时调用
void stop() {
std::lock_guard<std::mutex> lock(mtx_);
is_stopped_ = true;
cv_.notify_all(); // 唤醒所有等待的线程
}
};
2. 实现生产者线程(相机采集)
生产者的核心是快速获取图像并放入队列,避免在回调 / 采集函数中做耗时操作:
cpp
#include <thread>
#include <相机SDK头文件> // 替换为实际相机SDK
ImageQueue img_queue; // 全局队列(或通过指针传递)
bool is_running = true;
// 相机回调函数(生产者的核心逻辑)
void cameraCallback(const CameraImage& raw_img) {
if (!is_running) return;
// 轻量操作:将原始图像转为OpenCV格式(快速转换,避免复杂计算)
cv::Mat img(raw_img.height, raw_img.width, CV_8UC3, raw_img.data);
// 放入队列(耗时极短)
img_queue.push(img);
}
// 启动相机采集线程
void startProducer() {
// 初始化相机(根据SDK文档配置)
Camera::init();
Camera::setCallback(cameraCallback); // 设置回调
Camera::startCapture(); // 开始采集(如30fps)
// 保持线程运行(或由SDK内部线程驱动)
while (is_running) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
Camera::stopCapture();
Camera::release();
}
3. 实现消费者线程(图像处理)
消费者从队列中取图并处理,处理速度慢也不会阻塞生产者(队列会暂存数据):
cpp
// 图像处理函数(耗时操作)
void processImage(const cv::Mat& img) {
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY); // 示例:转灰度
cv::GaussianBlur(gray, gray, cv::Size(5,5), 0); // 高斯模糊
// ... 其他耗时操作(如目标检测、特征提取等)
}
// 消费者线程
void startConsumer() {
cv::Mat img;
while (img_queue.pop(img)) { // 无数据时阻塞,不占用CPU
processImage(img); // 处理图像(耗时操作)
}
}
4. 主线程控制启动与退出
cpp
int main() {
// 启动生产者和消费者线程
std::thread producer_thread(startProducer);
std::thread consumer_thread(startConsumer);
// 运行一段时间(或等待用户输入退出)
std::cout << "按Enter键退出..." << std::endl;
std::cin.get();
// 终止流程
is_running = false; // 停止生产者
img_queue.stop(); // 通知消费者退出
producer_thread.join(); // 等待生产者线程结束
consumer_thread.join(); // 等待消费者线程结束
return 0;
}
二、关键优势
-
避免 CPU 空转 :消费者无数据时通过
cv.wait()
进入休眠,不占用 CPU;生产者放入数据后通过cv.notify_one()
唤醒,仅在必要时工作。 -
解耦采集与处理:相机采集(快)和图像处理(慢)独立运行,采集线程不会被处理耗时阻塞,避免丢帧;处理线程也不会因采集速度快而超负荷。
-
灵活扩展 :可根据需求增加多个消费者线程(如 2 个处理线程并行处理队列数据),充分利用多核 CPU:
cpp// 启动多个消费者线程 std::vector<std::thread> consumers; for (int i = 0; i < 2; ++i) { consumers.emplace_back(startConsumer); }
三、注意事项
-
队列长度控制 :若处理速度长期慢于采集速度,队列会无限增长导致内存溢出。可在
push
时增加限制:cppvoid push(const cv::Mat& img) { std::lock_guard<std::mutex> lock(mtx_); if (queue_.size() > 10) { // 限制最大缓存10帧 queue_.pop(); // 丢弃最旧的帧 } queue_.push(img); cv_.notify_one(); }
-
图像数据拷贝 :若图像数据较大,
push
时的cv::Mat
拷贝(浅拷贝,仅复制头信息)是高效的,但需确保原始数据生命周期足够长(避免相机 SDK 过早释放)。 -
线程安全:所有对队列的操作必须加锁,否则可能导致队列数据混乱或崩溃。