【opencv】dnn示例-segmentation.cpp 通过深度学习模型对图像进行实时语义分割

e55ff4c52cb530478497e60cff929474.png

模型下载地址:

http://dl.caffe.berkeleyvision.org/

9fbf6b2618efb630eaa23e41d28b604e.png

ccddd5b175882ee6394dcad749ba8689.png

594b2fb4f9371d3d9ba61189ad926654.png

配置文件下载:

https://github.com/opencv/opencv_extra/tree/4.x/testdata/dnn

该段代码是一个利用深度学习进行语义分割的OpenCV应用实例。下面将详细解释代码的功能和方法。

引入库

引入了一些必要的C++和OpenCV库,其中包括文件流、字符串处理、深度学习网络(dnn模块)、图像处理和高层图形界面。

参数字符串

定义了一些命令行参数,用于获取模型信息、图像处理以及设备选择等配置。

自定义函数

定义了showLegend和colorizeSegmentation两个函数,用于显示分类的图例及为分割结果上色。

主函数main

主函数执行以下主要步骤:

  • 解析命令行参数:使用OpenCV的CommandLineParser来获取命令行输入的参数。

  • 读取模型和配置:根据参数从文件中读取深度学习模型及其配置。

  • 读取类别和颜色信息:如果提供了类名和颜色的文件路径,则从文件中读取这些信息。

  • 设置网络:将模型和配置设置到网络中,并且选择计算后端和目标设备。

  • 处理视频帧或图片:循环从视频流或文件中读取帧,并对其进行处理:

  • 将帧转换为模型输入所需的blob。

  • 将blob作为网络的输入。

  • 进行网络前向传播,获取分割的score。

  • 使用colorizeSegmentation函数将分割的score转换成彩色分割图。

  • 结合原始帧和彩色分割图,显示在用户界面上。

  • 如果有类别信息,显示图例窗口。

图像分割和上色

colorizeSegmentation函数计算每个像素点的最大得分类别,并将对应颜色填充到分割图中。

showLegend函数在独立窗口中显示每个类别及其对应颜色的图例。

运行流程

用户通过命令行运行程序,可以选择加载本地视频文件、图像文件或打开摄像头。程序会连续读取帧,并将每一帧通过神经网络进行分析,实现实时的图像分割功能。分割结果彩色化后与原图结合,展现给用户

总的来说,这段代码实现了通过深度学习模型对图像进行实时语义分割,并通过OpenCV的GUI功能将结果呈现给用户。它可以很好地适用于视频流分析,如自动驾驶车辆的视觉系统中实时理解道路情况。

#include <fstream>  // 包含fstream库,用于文件的读写操作
#include <sstream>  // 包含sstream库,用于字符串流的操作


#include <opencv2/dnn.hpp>             // 包含OpenCV深度神经网络(dnn)部分的头文件
#include <opencv2/imgproc.hpp>         // 包含OpenCV图像处理部分的头文件
#include <opencv2/highgui.hpp>         // 包含OpenCV用户界面部分的头文件


#include "common.hpp"  // 包含示例代码中定义的通用函数和变量


// 声明并初始化一个存储命令行参数的字符串
std::string keys =
    "{ help  h     | | Print help message. }" // 帮助信息
    "{ @alias      |fcn8s | An alias name of model to extract preprocessing parameters from models.yml file. }" // 模型别名
    "{ zoo         | models.yml | An optional path to file with preprocessing parameters }" // models.yml文件路径
    "{ device      |  0 | camera device number. }" // 摄像头设备号
    "{ width      |  500 |  }"
    "{ height      |  500 |   }"
    "{ input i     |test1.mp4 | Path to input image or video file. Skip this argument to capture frames from a camera. }" // 输入图片或视频文件的路径
    "{ framework f | | Optional name of an origin framework of the model. Detect it automatically if it does not set. }" // 模型框架,默认自动检测
    "{ classes     | pascal-classes.txt| Optional path to a text file with names of classes. }" // 类名文件路径
    "{ colors      | | Optional path to a text file with colors for an every class. "
                      "An every color is represented with three values from 0 to 255 in BGR channels order. }" // 类颜色文件路径
    "{ backend     | 5 | Choose one of computation backends: "
                        "0: automatically (by default), "
                        "1: Halide language (http://halide-lang.org/), "
                        "2: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
                        "3: OpenCV implementation, "
                        "4: VKCOM, "
                        "5: CUDA }" // 计算后端,默认自动选择
    "{ target      | 6 | Choose one of target computation devices: "
                        "0: CPU target (by default), "
                        "1: OpenCL, "
                        "2: OpenCL fp16 (half-float precision), "
                        "3: VPU, "
                        "4: Vulkan, "
                        "6: CUDA, "
                        "7: CUDA fp16 (half-float preprocess) }"; // 计算设备,默认CPU


using namespace cv;  // 使用cv命名空间
using namespace dnn; // 使用dnn命名空间


std::vector<std::string> classes; // 存储类名的向量
std::vector<Vec3b> colors; // 存储每个类对应颜色的向量


void showLegend(); // 前向声明showLegend函数,用于显示图例


void colorizeSegmentation(const Mat &score, Mat &segm); // 前向声明colorizeSegmentation函数,用于给分割结果上色


// 主函数
int main(int argc, char** argv)
{
    CommandLineParser parser(argc, argv, keys); // 命令行参数解析器


    const std::string modelName = parser.get<String>("@alias"); // 获取模型别名参数
    const std::string zooFile = parser.get<String>("zoo"); // 获取zoo文件参数


    keys += genPreprocArguments(modelName, zooFile); // 为命令行参数解析器添加预处理参数


    parser = CommandLineParser(argc, argv, keys); // 使用更新后的参数集重新构建命令行参数解析器
    // 打印脚本使用帮助
    parser.about("Use this script to run semantic segmentation deep learning networks using OpenCV.");
    if (argc == 1 || parser.has("help"))
    {
        parser.printMessage(); // 打印帮助信息
        return 0; // 退出程序
    }


    float scale = parser.get<float>("scale"); // 获取缩放比例参数
    Scalar mean = parser.get<Scalar>("mean"); // 获取均值参数
    bool swapRB = parser.get<bool>("rgb"); // 获取是否交换红蓝通道的参数
    int inpWidth = parser.get<int>("width"); // 获取输入宽度参数
    int inpHeight = parser.get<int>("height"); // 获取输入高度参数
    String model = findFile(parser.get<String>("model")); // 查找模型文件
    String config = findFile(parser.get<String>("config")); // 查找配置文件
    String framework = parser.get<String>("framework"); // 获取框架参数
    int backendId = parser.get<int>("backend"); // 获取后端ID参数
    int targetId = parser.get<int>("target"); // 获取目标设备ID参数


    // 打开类名文件
    if (parser.has("classes"))
    {
        std::string file = parser.get<String>("classes");
        std::ifstream ifs(file.c_str());
        if (!ifs.is_open())
            CV_Error(Error::StsError, "File " + file + " not found"); // 文件未能打开,则报错
        std::string line;
        while (std::getline(ifs, line))
        {
            classes.push_back(line); // 将类名逐行读入classes向量
        }
    }


    // 打开颜色文件
    if (parser.has("colors"))
    {
        std::string file = parser.get<String>("colors");
        std::ifstream ifs(file.c_str());
        if (!ifs.is_open())
            CV_Error(Error::StsError, "File " + file + " not found"); // 文件未能打开,则报错
        std::string line;
        while (std::getline(ifs, line))
        {
            std::istringstream colorStr(line.c_str()); // 使用字符串流读取颜色信息


            Vec3b color;
            for (int i = 0; i < 3 && !colorStr.eof(); ++i)
                colorStr >> color[i];
            colors.push_back(color); // 将颜色逐行读入colors向量
        }
    }


    if (!parser.check())
    {
        parser.printErrors(); // 打印参数解析错误
        return 1; // 退出程序
    }


    CV_Assert(!model.empty()); //! [Read and initialize network] 确保模型路径不为空,并初始化网络
    Net net = readNet(model, config, framework); // 读取网络模型
    net.setPreferableBackend(backendId); // 设置计算后端
    net.setPreferableTarget(targetId);   // 设置目标计算设备
    //! [Read and initialize network]


    // 创建一个窗口
    static const std::string kWinName = "Deep learning semantic segmentation in OpenCV";
    namedWindow(kWinName, WINDOW_NORMAL);


    //! [Open a video file or an image file or a camera stream]
    VideoCapture cap; // 视频捕获对象
    if (parser.has("input"))
        cap.open(parser.get<String>("input")); // 打开输入的图片或视频文件
    else
        cap.open(parser.get<int>("device")); // 打开摄像头
    //! [Open a video file or an image file or a camera stream]


    // 处理帧数据
    Mat frame, blob; // 定义用来存放帧和blob的矩阵
    cap >> frame;
    VideoWriter video("fcn8s-heavy-pascal_video.avi", VideoWriter::fourcc('M', 'J', 'P', 'G'), 10, frame.size(), true);


    if (!video.isOpened())
    {
        std::cout << "Could not open the output video file for write\n";
        return -1;
    }
    while (waitKey(1) < 0) // 等待按键事件
    {
        cap >> frame; // 从视频捕获对象读取一帧
        if (frame.empty())
        {
            waitKey(); // 若帧为空,等待按键后退出循环
            break;
        }


        //! [Create a 4D blob from a frame]
        blobFromImage(frame, blob, scale, Size(inpWidth, inpHeight), mean, swapRB, false); // 从帧中创建一个4维blob
        //! [Create a 4D blob from a frame]


        //! [Set input blob]
        net.setInput(blob); // 设置网络输入
        //! [Set input blob]
        //! [Make forward pass]
        Mat score = net.forward(); // 执行前向传播
        //! [Make forward pass]


        Mat segm; // 用于存储分割结果的矩阵
        colorizeSegmentation(score, segm); // 给分割结果上色


        resize(segm, segm, frame.size(), 0, 0, INTER_NEAREST); // 调整分割结果的大小以匹配原始帧大小
        addWeighted(frame, 0.1, segm, 0.9, 0.0, frame); // 将帧和分割结果合并显示


        // 显示效率信息
        std::vector<double> layersTimes; // 存储每层时间的向量
        double freq = getTickFrequency() / 1000; // 获取时钟频率
        double t = net.getPerfProfile(layersTimes) / freq; // 计算网络执行时间
        std::string label = format("Inference time: %.2f ms", t); // 格式化时间信息
        putText(frame, label, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0)); // 在帧上绘制时间信息


        imshow(kWinName, frame); // 显示窗口
        video.write(frame);
        if (!classes.empty())
            showLegend(); // 显示图例
    }
    return 0; // 程序正常退出
}


// 给分割结果上色的函数
void colorizeSegmentation(const Mat &score, Mat &segm)
{
    const int rows = score.size[2]; // 获取score的行数
    const int cols = score.size[3]; // 获取score的列数
    const int chns = score.size[1]; // 获取score的通道数


    if (colors.empty())
    {
        // 产生颜色
        colors.push_back(Vec3b()); // 添加黑色
        for (int i = 1; i < chns; ++i)
        {
            Vec3b color; // 定义颜色
            for (int j = 0; j < 3; ++j)
                color[j] = (colors[i - 1][j] + rand() % 256) / 2; // 随机生成颜色
            colors.push_back(color); // 添加颜色到colors向量
        }
    }
    else if (chns != (int)colors.size())
    {
        CV_Error(Error::StsError, format("Number of output classes does not match "
                                         "number of colors (%d != %zu)", chns, colors.size())); // 检测颜色数是否与通道数匹配
    }


    Mat maxCl = Mat::zeros(rows, cols, CV_8UC1); // 创建类别的索引矩阵
    Mat maxVal(rows, cols, CV_32FC1, score.data); // 创建分数矩阵
    // 遍历通道,通道数从1开始,因为通道0为背景
    for (int ch = 1; ch < chns; ch++)
    {
        // 遍历得分图的每一行
        for (int row = 0; row < rows; row++)
        {
            // 获取当前行的得分数据指针
            const float *ptrScore = score.ptr<float>(0, ch, row);
            // 获取最大类别的索引的行指针
            uint8_t *ptrMaxCl = maxCl.ptr<uint8_t>(row);
            // 获取最大值的行指针
            float *ptrMaxVal = maxVal.ptr<float>(row);
            // 遍历当前行的每一列
            for (int col = 0; col < cols; col++)
            {
                // 如果当前位置的得分大于之前的最大值,则更新最大值和最大类别索引
                if (ptrScore[col] > ptrMaxVal[col])
                {
                    ptrMaxVal[col] = ptrScore[col];
                    ptrMaxCl[col] = (uchar)ch;
                }
            }
        }
    }
    
    // 根据最大类别索引创建分割图
    segm.create(rows, cols, CV_8UC3);
    for (int row = 0; row < rows; row++)
    {
        // 获取最大类别索引的指针
        const uchar *ptrMaxCl = maxCl.ptr<uchar>(row);
        // 获取分割图的行指针
        Vec3b *ptrSegm = segm.ptr<Vec3b>(row);
        for (int col = 0; col < cols; col++)
        {
            // 根据类别索引设置分割图的颜色
            ptrSegm[col] = colors[ptrMaxCl[col]];
        }
    }
}


// 显示类别的图例
void showLegend()
{
    // 定义图例块的高度
    static const int kBlockHeight = 30;
    // 定义图例Mat对象
    static Mat legend;
    // 如果图例为空,则创建一个新的图例
    if (legend.empty())
    {
        // 获取类别的数量
        const int numClasses = (int)classes.size();
        // 如果颜色数量和类别数量不匹配,则报错
        if ((int)colors.size() != numClasses)
        {
            CV_Error(Error::StsError, format("Number of output classes does not match "
                                             "number of labels (%zu != %zu)", colors.size(), classes.size()));
        }
        // 创建图例Mat对象
        legend.create(kBlockHeight * numClasses, 200, CV_8UC3);
        for (int i = 0; i < numClasses; i++)
        {
            // 获取每个类别的图例块
            Mat block = legend.rowRange(i * kBlockHeight, (i + 1) * kBlockHeight);
            // 设置图例块的颜色
            block.setTo(colors[i]);
            // 在图例块上写上类别的名称
            putText(block, classes[i], Point(0, kBlockHeight / 2), FONT_HERSHEY_SIMPLEX, 0.5, Vec3b(255, 255, 255));
        }
        // 创建一个窗口显示图例
        namedWindow("Legend", WINDOW_NORMAL);
        imshow("Legend", legend);
    }
}

这段代码是一个基于OpenCV实现的语义分割深度学习网络的应用。其中包含处理图像数据转化为Blob,通过神经网络前向传播输出分割得分图,再对得分图进行处理,提取出每个像素点的最大得分对应的类别,并根据这个最大类别类别的索引来进行图像分割的颜色填充。此外,还有一个showLegend函数用于生成并展示一个包含所有类别及其对应颜色的图例。整体而言,这段代码是实现图像语义分割功能的一个部分。

函数colorizeSegmentation负责对图像分类得分进行上色,从而生成彩色的分割图。函数首先计算输入得分score的行数、列数以及通道数。然后,它检查是否已经定义了颜色映射,如果没有定义,则生成一组颜色映射。接下来,函数遍历每个像素位置,找到具有最大得分的通道,并记录这个通道索引到maxCl中。最后,根据通道索引,在最终的分割图像segm上应用对应的颜色。这样做的结果是得到一个彩色标记了各个类别区域的图像,便于视觉分析和理解。

笔记:

blobFromImage(frame, blob, scale, Size(inpWidth, inpHeight), mean, swapRB, false);

8eab5364f25366c7c31c72116d4633b1.png

Mat score = net.forward();

d3975cbddbf81eed0e42fec55e6b5582.png

The End

作者陈晓永:智能装备专业高级职称,软件工程师,机械设计中级职称,机器人与自动化产线仿真动画制作 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/555776.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

统一SQL-支持cast函数

统一SQL介绍 https://www.light-pg.com/docs/LTSQL/current/index.html 源和目标 源数据库&#xff1a;Oracle 目标数据库&#xff1a;Postgresql&#xff0c;TDSQL-MySQL&#xff0c;达梦8&#xff0c;LightDB-Oracle 操作目标 在Oracle中&#xff0c;cast函数允许将一种…

代码随想录-链表 | 142环形链表

代码随想录-链表 | 142环形链表 LeetCode 142-环形链表解题思路代码复杂度难点总结 LeetCode 142-环形链表 题目链接 题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#x…

死磕GMSSL通信-C/C++系列(一)

死磕GMSSL通信-C/C++系列(一) 最近再做国密通信的项目开发,以为国密也就简单的集成一个库就可以完事了,没想到能有这么多坑。遂写下文章,避免重复踩坑。以下国密通信的坑有以下场景 1、使用GMSSL guanzhi/GmSSL进行通信 2、使用加密套件SM2-WITH-SMS4-SM3 使用心得 ​…

Java学习-详述main方法、可变参数、数组的工具类、二维数组

详述main方法 【1】main方法&#xff1a;程序的入口&#xff0c;在同一个类中&#xff0c;如果有多个方法&#xff0c;那么虚拟机就会识别main方法&#xff0c;从这个方法作为程序的入口 【2】main方法格式严格要求&#xff1a; public static void main(String[] args){} p…

【NOI-题解】1320. 时钟旋转1323. 扩建花圃问题1462. 小明的游泳时间1565. 成绩(score)1345. 玫瑰花圃

文章目录 一、前言二、问题问题&#xff1a;1320. 时钟旋转问题&#xff1a;1323. 扩建花圃问题问题&#xff1a;1462. 小明的游泳时间问题&#xff1a;1565. 成绩&#xff08;score&#xff09;问题&#xff1a;1345. 玫瑰花圃 三、感谢 一、前言 本章节主要对基本运算中整数…

Windows系统搭建Plex网站结合内网穿透实现公网访问本地影音文件

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频&#xff0c;已经算是生活中稀松平常的场景了&#xff0c;特别是各…

奶酪——并查集,BFS,DFS(NOIP2017提高组)

目录 题目 思路 并查集 代码&#xff08;java&#xff09; BFS&#xff08;DFS同理&#xff09; 代码&#xff08;C&#xff09; 题目 思路 这个题目意思是有很多个球分布在一个三维空间内&#xff0c;如果这些球相切或者相交都可以互相到达&#xff0c;我们需要判断能否…

二、e2studio VS STM32CubeIDE之功能对比

目录 一、概述/目的 二、官网资料 2.1 stm32cubeide 2.2 e2studio 三、功能对比 二、e2studio VS STM32CubeIDE之功能对比 一、概述/目的 通过对比学习&#xff0c;更快速的掌握两款IDE 二、官网资料 2.1 stm32cubeide https://www.stmcu.com.cn/ecosystem/Cube/STM32C…

41、二叉树-二叉树的层序遍历

思路&#xff1a; 层序遍历就是从左到右依次遍历。这个时候就可以使用队列的方式。例如先把头节点入队&#xff0c;然后遍历开始&#xff0c;首先计算队列长度&#xff0c;第一层&#xff0c;长度为了&#xff0c;遍历一次&#xff0c;依次出队&#xff0c;头结点出队&#xff…

Codeforces Round 924 (Div. 2) ---- F. Digital Patterns ---- 题解

F. Digital Patterns&#xff1a; 题目描述&#xff1a; 思路解析&#xff1a; 要求在一个方块中&#xff0c;任意相邻的方块中他的透明度系数不能相同&#xff0c;这样的方块称为趣味性方块&#xff0c;问这样的方块有多少种。 那么我们可以相当&#xff0c;假设 a1 a2, 那…

iOS ------ Block的总结

前面看了Block的基本知识&#xff0c;和一些源码。但对于block怎么用的还不了解&#xff0c;代码中出现block会看不懂&#xff0c;现在来具体看一下Block的用法并做个总结。 1.Block是什么 block对象是一个C语言结构体&#xff0c;可以并入C和OC的代码中&#xff0c;Block本质…

每日OJ题_多源BFS①_力扣542. 01 矩阵(多源BFS解决最短路原理)

目录 多源BFS解决最短路算法原理 力扣542. 01 矩阵 解析代码 多源BFS解决最短路算法原理 什么是单源最短路 / 多源最短路&#xff1f; 之前的BFS解决最短路都是解决的单源最短路。 画图来说&#xff0c;单源最短路问题即为&#xff1a; 而对于多源最短路问题: 如何解决此…

微信小程序之点击事件

微信小程序中常用的点击事件主要是 tap&#xff0c;但除此之外还有其他的触摸类事件&#xff0c;用于不同的交互场景。以下是一些常见的点击和触摸相关的事件及其区别&#xff1a; 1、tap——最基本的点击事件&#xff0c;适用于一般的轻触交互&#xff0c;类似于 HTML 中的 c…

Pixverse:开启文生视频与图生视频新纪元

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

NPU流式输出-torch_npu和transformers框架-多线程Streamer-昇腾910B-EE1001

前情提要 torch_npu框架不支持多线程自动set_device 报错详情 直接使用transformers的TextIteratorStreamer进行流式推理&#xff0c;会报错 Exception in thread Thread-6: Traceback (most recent call last):File "/root/anaconda3/envs/AI/lib/python3.9/threadin…

Linux shell 脚本基础与部署SpringCloud实战

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

L2正则化——解释为什么可以减少模型的复杂度

L2正则化是一种用于机器学习模型的技术&#xff0c;旨在减少模型的复杂度&#xff0c;从而提高其泛化能力。在L2正则化中&#xff0c;通过添加一个惩罚项&#xff0c;模型的权重被迫保持较小的值&#xff0c;这有助于防止过拟合&#xff0c;即模型在训练数据上表现良好但在未见…

python学习笔记B-07:序列结构之列表--列表的常用函数和方法

以xx_函数名(列表名)的形式出现的是函数&#xff1b;以xx_列表名.xx_方法名的形式出现的是方法。 列表常用函数如下&#xff1a; len()&#xff1a;计算列表元素数量 max()&#xff1a;获取列表元素最大值 min():获取列表元素最小值 sum():计算列表中各元素之和 列表常用方法如…

【Java EE】文件操作

目录 1.认识文件 2.树型结构组织和目录 3.文件路径&#xff08;Path&#xff09; 4.其他知识 5.Java中操作文件 5.1File概述 5.1.1属性 5.1.2构造方法 5.1.3方法 5.2代码示例 1.认识文件 我们先来认识狭义的文件&#xff08;file&#xff09;。针对1硬盘这种持久化存…

HTML重要标签梳理学习

1、HTML文件的框架 使用VS Code编码时&#xff0c;输入!选中第一个&#xff01;就可以快速生成一个HTML文件框架。 2、标签 <hr> <!--下划线--> <br> <!--换行--> <strong>加粗</strong> &…
最新文章