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

基于线程池的配电房图像检测

目录

第1章 基础知识

1.1 帧率

1.2 分辨率

第2章 图像检测

2.1 图像检测流程

2.2 框架选择

2.3 图像处理脚手架

第3章 线程池处理一路视频流

3.1 基本思路

3.2 如何保证输出顺序与输入视频帧顺序一致

3.2 工程应用

3.2.1 图像检测任务

3.2.2 线程池

3.2.3 主线程

第4章 线程池处理多路视频流

4.1 工程应用一(不推荐)

4.1.1 为每路 RTSP 流独立管理一套上下文

4.2 工程应用二(推荐)


第1章 基础知识

1.1 帧率

       帧率(FPS, Frames Per Second)是指每秒显示的画面帧数。帧率越高,画面越流畅,动态效果越好。常见的帧率有24fps、30fps、60fps等。帧率:动态画面的流畅度。

  1. 24fps:电影行业的标准帧率,足以捕捉并展现大部分动态场景。
  2. 30fps:网络视频和电视节目的常见帧率,提供更为流畅的观看体验。
  3. 60fps及以上:高帧率视频在快速移动的场景中表现出色,但并非所有设备都支持高帧率播放。

1.2 分辨率

        分辨率是指视频画面在一定区域内包含的像素点的数量。像素是构成图像的最小单位,每个像素都有特定的颜色和亮度。分辨率通常以“宽×高”的形式表示,如1920×1080,这代表视频的水平方向有1920个像素,垂直方向有1080个像素。

        美国电影电视工程师协会(‌SMPTE)‌制定了一系列的高清数字电视格式标准,其中包含了常用的分辨率标准:

  1. 720P: ‌其有效显示格式为1280×720,‌采用逐行扫描(‌Progressive scanning)‌方式
  2. 1080P:‌其有效显示格式为1920×1080,‌也是采用逐行扫描方式。
  3. 1080i: 与1080p相对,‌1080i采用隔行扫描(‌Interlace scanning)‌,‌也是1920×1080的分辨率。‌这种格式在传输过程中可能提供更高的帧率,‌但图像质量相对于1080p可能会有所降低。
  4. 2K:水平分辨率达到约2000像素,最标准的2K分辨率为2048×1024,常见的2K分辨率为2560×1440(16:9)。(UHDTV1)4K:UHDTV1由SMPTE ST 2036-1标准定义的VHD超高清系统,其分辨率为3840×2160(16:9)。(UHDTV2)
  5. 8K:UHDTV2同样由SMPTE ST 2036-1标准定义的VHD超高清系统,分辨率达到7680×4320。

第2章 图像检测

2.1 图像检测流程

2.2 框架选择

目标检测:yolo系列

推理框架:OpenCV C++

2.3 图像处理脚手架

MFC动态创建类

第3章 线程池处理一路视频流

为什么要做线程池来做视频流检测?

答:配电房的RTSP帧率是25FPS。模型检测一帧图像需要18ms,想要做到实时推理检测是不可能,所以需要使用多线程。然而线程的创建和销毁很消耗CPU资源,所以我们为了减轻CPU的资源消耗提前创建好一个线程池。

3.1 基本思路

(1)创建一个线程池(线程池中包含4个线程对象,线程对象本质是一个图像处理对象);

(2)创建一个生产者线程,生产者线程获取视频流中每一帧图像,并且递交到线程池中的任务队列中。注意提交的时候需要将帧ID也提交过去。即任务队列是一个存放未处理帧ID和帧的容器。

std::queue<std::pair<int, cv::Mat>> tasks;

(3)创建一个消费者线程,消费者线程从线程池中的结果容器中获取检测好的结果。注意消费者线程在获取检测结果帧的同时需要同时带着帧ID去获取。即存放检测结果的容器是存放具体帧和具体帧对应的结果的容器。

std::map<int, cv::Mat> img_results;

3.2 如何保证输出顺序与输入视频帧顺序一致

帧ID线程ID处理时间完成时间
0020 ms20 ms
1140 ms40 ms
2210 ms10 ms
3318ms18 ms
425 ms 5 ms

工程jiegou是 生产者 + 消费者 + 线程池(多个线程) 的结构:

  • 生产者线程从 RTSP 获取视频帧,依次编号(例如 id = 0, 1, 2, 3, …),然后调用:

  • threadPool.submitTask(frame, id);

队列中的结构:

{(第0帧,cv::Mat), (第1帧,cv::Mat), (第2帧,cv::Mat), (第3帧,cv::Mat), (第4帧,cv::Mat), ..........}

        注意:在queue容器中未检测的视频帧,是按照帧顺序来存放的。

  • 多个工作线程(线程池中的图像处理对象)并发执行:

imgProcessObj->run(task.second, resultImg);

    所以帧的处理是并行异步完成的。

    • 处理完成的结果保存到map中
    {std::lock_guard<std::mutex> lock(mtx2);img_results.insert({task.first, resultImg});cv_result.notify_one();
    }

    map中的结构(仅仅针对表中的4帧数据)

    {(第2帧,cv::Mat), (第4帧,cv::Mat), (第3帧,cv::Mat), (第0帧,cv::Mat), (第1帧,cv::Mat), ..........}

            注意:在map容器中检测完的视频帧,并不是按照帧顺序来存放的,而是谁先检查完毕,先存放谁。

    • 消费者线程,从map中来获取检测结果时,就需要一个自增的ID来map中获取帧。

    3.2 工程应用

    3.2.1 图像检测任务

    #ifndef IMGPROCESS_H
    #define IMGPROCESS_H#include <opencv2/opencv.hpp>class CImgProcess {
    public:explicit CImgProcess();~CImgProcess();public:void run(cv::Mat &srcImg, cv::Mat &resultImg);};#endif /**IMGPROCESS_H */

    #include "imgprocess.h"CImgProcess::CImgProcess()
    {}CImgProcess::~CImgProcess()
    {}void CImgProcess::run(cv::Mat &srcImg, cv::Mat &resultImg)
    {// 确保源图像不是空的if(srcImg.empty()) {std::cerr << "src NULL" << std::endl;return;}// 使用OpenCV的cvtColor函数将图像转换为灰度图像cv::cvtColor(srcImg, resultImg, cv::COLOR_BGR2GRAY);
    }

    3.2.2 线程池

    #ifndef THREADPOOL_H
    #define THREADPOOL_H#include <opencv2/opencv.hpp>
    #include "imgprocess.h"class CThreadPool
    {
    public:explicit CThreadPool();~CThreadPool();public:/**设置线程池 */void setUp(int threadCount);/**提交要处理的任务 */int submitTask(const cv::Mat &img, int id);/**获取任务处理结果 */int getTargetResult(cv::Mat &resultImg, int id);/**停止所有线程 */void stopAll();private:/**线程池的工作场 */void worker(int id);private:/**线程池当前状态,默认是没有停止运行的。也就是stop = false*/bool stop;  /**处理任务的对象 */std::vector<std::shared_ptr<CImgProcess>> taskProcessorObj;/**线程对象 */std::vector<std::thread> threads;/**待处理的任务 */std::queue<std::pair<int, cv::Mat>> tasks;/**处理任务的对象去获取任务时需要一把锁 */std::mutex mtx1;/**处理任务的对象去获取任务时需要一把锁, 这把锁的条件变量 */std::condition_variable cv_task;/**处理对象,处理完成的结果 */std::map<int, cv::Mat> img_results;/**处理任务的对象,处理完成的结果需要放置到Map中,所以需要一把锁 */std::mutex mtx2;/**处理任务的对象,处理完成的结果需要放置到Map中,所以需要一把锁,这把锁自然要一个条件变量 */std::condition_variable cv_result;
    };#endif /**THREADPOOL_H */

    #include "threadpool.h"
    #include <iostream>
    #include <chrono>// 构造函数
    CThreadPool::CThreadPool(): stop(false)
    {
    }// 析构函数
    CThreadPool::~CThreadPool()
    {stopAll();
    }// 设置线程池大小和初始化
    void CThreadPool::setUp(int threadCount)
    {// 创建处理任务的对象for (int i = 0; i < threadCount; ++i) {std::shared_ptr<CImgProcess> taskProcessorObjItem = std::make_shared<CImgProcess>();taskProcessorObj.push_back(taskProcessorObjItem);}// 创建线程for (int i = 0; i < threadCount; ++i) {threads.emplace_back(&CThreadPool::worker, this, i); // 注意这里传递了 'this'}
    }// 工作线程执行的任务
    void CThreadPool::worker(int id)
    {while (!stop) {std::pair<int, cv::Mat> task;std::shared_ptr<CImgProcess> imgProcessObj = taskProcessorObj[id];{std::unique_lock<std::mutex> lock(mtx1);cv_task.wait(lock, [this] { return !tasks.empty() || stop; });if (stop) {return;}task = tasks.front();tasks.pop();}cv::Mat resultImg;imgProcessObj->run(task.second, resultImg);{std::lock_guard<std::mutex> lock(mtx2);img_results.insert({task.first, resultImg});cv_result.notify_one();}}
    }// 提交任务到线程池
    int CThreadPool::submitTask(const cv::Mat &img, int id)
    {{std::lock_guard<std::mutex> lock(mtx1);tasks.push({id, img});}cv_task.notify_one();return 0;
    }// 获取任务结果
    int CThreadPool::getTargetResult(cv::Mat &resultImg, int id)
    {int loop_cnt = 0;while (img_results.find(id) == img_results.end()) {std::this_thread::sleep_for(std::chrono::milliseconds(5));loop_cnt++;if (loop_cnt > 1000) {std::cout << "getTargetImgResult timeout" << std::endl;return -1;}}std::lock_guard<std::mutex> lock(mtx2);resultImg = img_results[id];img_results.erase(id);return 0;
    }// 停止所有工作线程
    void CThreadPool::stopAll()
    {stop = true;cv_task.notify_all();for (auto& thread : threads) {if (thread.joinable()) {thread.join();}}
    }

    3.2.3 主线程

    #include <iostream>
    #include <opencv2/opencv.hpp>
    #include <memory>
    #include "imgprocess.h"
    #include "threadpool.h"#if 0
    const std::string ImgPath = "E:/Mytest/test20250430/apple.jpg";int main(int argc, char **argv){cv::Mat srcImg = cv::imread(ImgPath, cv::IMREAD_COLOR);if(srcImg.empty()){std::cerr << "Failed to load image: " << ImgPath << std::endl;return -1;}cv::Mat resultImg;std::shared_ptr<CImgProcess> imgProcess = std::make_shared<CImgProcess>();imgProcess->run(srcImg, resultImg);cv::namedWindow("abc");cv::imshow("abc", resultImg);cv::waitKey(0);cv::destroyAllWindows();return 0;
    }#endif// 设置 RTSP 流地址
    std::string rtsp_url1 = "rtsp://admin:bpg123456@10.10.12.228:554/h264/2/main/av_stream";static int g_frame_start_id = 0; // 读取视频帧的索引
    static int g_frame_end_id = 0;   // 模型处理完的索引// 创建线程池
    static CThreadPool *yolov5_thread_pool = nullptr;
    bool end = false;/**获取视频流(生产者) */
    void read_stream(std::string rtsp_addr);/**获取处理结果(消费者) */
    void get_results(bool record = false);int main(int argc, char **argv){// 实例化线程池yolov5_thread_pool = new CThreadPool();yolov5_thread_pool->setUp(4);// 读取视频std::thread read_stream_thread(read_stream, rtsp_url1);// 启动结果线程std::thread result_thread(get_results, true);// 等待线程结束read_stream_thread.join();result_thread.join();return 0;
    }void read_stream(std::string rtsp_addr)
    {// 读取视频cv::VideoCapture cap(rtsp_addr);if (!cap.isOpened()){std::cout << "Failed to open video file: " << rtsp_addr << std::endl;}// 获取视频尺寸、帧率int width = cap.get(cv::CAP_PROP_FRAME_WIDTH);int height = cap.get(cv::CAP_PROP_FRAME_HEIGHT);int fps = cap.get(cv::CAP_PROP_FPS);printf("Video size: %d x %d, fps: %d", width, height, fps);// 画面cv::Mat img;while (true){// 读取视频帧cap >> img;if (img.empty()){printf("Video end.");// 等待一下没有处理结束的画面std::this_thread::sleep_for(std::chrono::milliseconds(5000));end = true;break;}// 提交任务,这里使用clone,因为不这样数据在内存中可能不连续,导致绘制错误yolov5_thread_pool->submitTask(img.clone(), g_frame_start_id++);}// 释放资源cap.release();
    }void get_results(bool record)
    {// 记录开始时间auto start_all = std::chrono::high_resolution_clock::now();int frame_count = 0;std::string fps_str;std::string duration_str;cv::VideoWriter writer;if(record){writer = cv::VideoWriter("thread_pool_demo.mp4", cv::VideoWriter::fourcc('a', 'v', 'c', '1'), 30, cv::Size(1280, 720));}// 开始计时auto start_1 = std::chrono::high_resolution_clock::now();while (true){// 结果cv::Mat img;auto ret = yolov5_thread_pool->getTargetResult(img, g_frame_end_id++);// 如果读取完毕,且模型处理完毕,结束if (end && ret != 0){break;}frame_count++;// all endauto end_all = std::chrono::high_resolution_clock::now();auto elapsed_all_2 = std::chrono::duration_cast<std::chrono::microseconds>(end_all - start_all).count() / 1000.f;// 每隔1秒打印一次if (elapsed_all_2 > 1000){printf("Method2 Time:%fms, FPS:%f, Frame Count:%d", elapsed_all_2, frame_count / (elapsed_all_2 / 1000.0f), frame_count);fps_str = std::to_string(frame_count) + "fps";frame_count = 0;start_all = std::chrono::high_resolution_clock::now();}if(record){auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_all - start_1).count() / 1000.f;duration_str = std::to_string(duration) + "ms";cv::putText(img, fps_str , cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0, 0, 255), 2);cv::putText(img, duration_str, cv::Point(10, 50), cv::FONT_HERSHEY_PLAIN, 1.2, cv::Scalar(0xFF, 0xFF, 0xFF), 2);// 写入视频帧writer << img;}}// 结束所有线程yolov5_thread_pool->stopAll();if (writer.isOpened()){writer.release();}printf("Get results end.");
    }

    第4章 线程池处理多路视频流

    4.1 工程应用一(不推荐)

    4.1.1 为每路 RTSP 流独立管理一套上下文

    struct StreamContext
    {std::string rtsp_url;std::atomic<int> frame_start_id{0};std::atomic<int> frame_end_id{0};std::atomic<bool> end{false};CThreadPool *thread_pool = nullptr;std::thread read_thread;std::thread result_thread;
    };int main(int argc, char **argv)
    {std::vector<std::string> rtsp_urls = {"rtsp://admin:pwd@10.10.12.228:554/h264/2/main/av_stream","rtsp://admin:pwd@10.10.12.229:554/h264/2/main/av_stream","rtsp://admin:pwd@10.10.12.230:554/h264/2/main/av_stream",// ... 共10路};std::vector<std::unique_ptr<StreamContext>> streams;for (auto &url : rtsp_urls){auto ctx = std::make_unique<StreamContext>();ctx->rtsp_url = url;ctx->thread_pool = new CThreadPool();ctx->thread_pool->setUp(4); // 每路4线程(或共享全局线程池)ctx->read_thread = std::thread(read_stream, std::ref(*ctx));ctx->result_thread = std::thread(get_results, std::ref(*ctx), true);streams.push_back(std::move(ctx));}// 等待所有线程结束for (auto &ctx : streams){ctx->read_thread.join();ctx->result_thread.join();ctx->thread_pool->stopAll();delete ctx->thread_pool;}return 0;
    }
    

            1路视频流创建一个线程池(4个处理线程) + 一个生产者线程 + 一个消费者线程 = 6个线程。假设现场有40路视频流,就会创建240个线程。

    4.2 工程应用二(推荐)

            把每路 StreamContext 的线程池改成 共享全局线程池,只保留每路的 read_thread 和 result_thread

    http://www.dtcms.com/a/516776.html

    相关文章:

  • 天河网站+建设信科网络龙口网页设计
  • 企业官网网站建设咨询手机网站模板下载免费
  • 线性代数直觉(三):特征值(eigenvalue)与特征向量(eigenvector)为何物?
  • 学生个人网站建设模板郑州开发软件公司
  • [Agent可视化] 智能体核心(Rust) | WASI沙箱 | `grpc_server.rs`
  • 东莞网站建设方案托管桂林做网站的公司有哪些
  • 所有搜索引擎蜘蛛不来网站了最新军事报道
  • Java | Lombok @Builder.Default 排障指南:为什么 build 时默认值丢失?
  • 东莞网站快速优化排名中国品牌网站建设
  • 计算机网络 网站代理公司注册新公司的费用
  • Linux下的TCP滑动窗口
  • 从CCLKIE到EtherCAT:汇川PLC在大型选矿厂的伺服通讯升级之路
  • wordpress 建站 知乎app开发app制作公司
  • wordpress代码创建子站点建设谷歌公司网站费用
  • 多资产回测实战 | 用 DolphinDB 搭建股票期货对冲与期权套利策略
  • 企业营销管理软件网站优化查询
  • 数字孪生大屏实战:山海鲸可视化如何让制造业‘透明化
  • Linux 内存 --- get_user_pages/pin_user_pages函数
  • 《探索 Docker+Neko+cpolar 实现跨网共享浏览器的无限可能》
  • 调用百度云语音识别服务——实现c++接口识别语音
  • 浅谈常见的八类数据库加密技术
  • Beyond Compare 5 for Mac 好用的文件对比工具
  • 济南专业做网站公司哪家好做爰全过程免费的视频凤凰网站
  • 顶俏模式分析:低价洗衣液与三级分销机制的市场争议
  • 云服务器网站解析专业拓客团队怎么收费
  • 嘉兴网站建设网站海报设计兼职平台
  • 标准网站建设费用阿里企业邮箱怎么用
  • 西宁市城乡规划建设局网站海外网络推广方案
  • VMware替代 | ZStack ZSphere镜像与模板管理在迁移与运维中的关键作用
  • 【C++ 入门】:从语言发展脉络到实战细节 —— 详解命名空间、输入输出、缺省参数与函数重载