【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) }"; // 计算设备,默认CPUusing 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")); // 打开输入的图片或视频文件elsecap.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.mzph.cn/bicheng/246.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(最详细)关于List和Set的区别与应用

关于List与Set的区别 List和Set都继承自Collection接口&#xff1b; List接口的实现类有三个&#xff1a;LinkedList、ArrayList、Vector。Set接口的实现类有两个&#xff1a;HashSet(底层由HashMap实现)、LinkedHashSet。 在List中&#xff0c;List.add()是基于数组的形式来添…

内部类

一.概念 当一个事物内部&#xff0c;还有一个部分需要一个完整的结构进行描述&#xff0c;而这个内部的完整的结构又只为外部事物提供服务&#xff0c;那么将这个内部的完整结构最好使用内部类。在Java中&#xff0c;可以将一个类定义在另一个类或者一个方法内部&#xff0c;前…

将gdip-yolo集成到yolov9模型项目中(支持预训练的yolov9模型)

1、yolov9模型概述 1.1 yolov9 YOLOv9意味着实时目标检测的重大进步&#xff0c;引入了可编程梯度信息&#xff08;PGI&#xff09;和通用高效层聚合网络&#xff08;GELAN&#xff09;等开创性技术。该模型在效率、准确性和适应性方面取得了显著改进&#xff0c;在MS COCO数…

GNU Radio使用Python Block实现模块运行时间间隔获取

文章目录 前言一、timestamp_sender 模块二、timestamp_receiver 模块三、测试 前言 GNU Radio 中没有实现测量两个模块之间的时间测量模块&#xff0c;本文记录一下通过 python block 制作一个很简单的测时 block。 一、timestamp_sender 模块 使用 python block 做一个发送…

【python】super()函数的用法详解!

今天分享一个我在实际项目中用到过的super()函数&#xff0c;来说说该函数的主要功能是什么&#xff0c;简单来说&#xff0c;super()函数是用来做调用父类的一个方法。 super() 是用来解决多重继承问题的&#xff0c;直接用类名调用父类方法在使用单继承的时候没问题&#xf…

外包干了30天,技术倒退明显

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01; 而我已经在一个企业干了四年的功能…

【详细的Kylin使用心得】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

超级USB设备工具箱V2.0 绿色版-供大家学习研究参考

1、芯片精灵 ChipGenius V3.01 2、惠普U盘格式化工具 V2.2.0 3、MFormat V1.00中文版 4、MyDisk V2.50 5、安全移除USB磁盘&#xff08;Disk Ejector&#xff09; 6、U盘烧录修复工具&#xff08;PortFree Production Program&#xff09; V3.38 7、护U使者 V1.28 8、闪存精灵2…

【缺啥补啥】图表征学习-1

图表征学习&#xff1a; 学习图中的元素&#xff0c;如节点、边、子图或整个图的向量表征&#xff0c;并应用基于向量的机器学习算法进行图数据的建模和分析 动态环境&#xff1a; 图数据包括结构、特征、性质、任务等&#xff0c;随时间发生变化 开放环境&#xff1a; 泛指数据…

如何批量给Word文件增加前缀序号?“汇帮批量重命名”帮助你批量给word文件增加前缀序号。

批量给Word文件增加前缀序号的过程&#xff0c;对于经常处理大量文档的人来说&#xff0c;是一项既繁琐又必要的任务。首先&#xff0c;我们需要明确为什么要给Word文件增加前缀序号。在很多情况下&#xff0c;当我们需要按照一定的顺序对多个文档进行管理和归档时&#xff0c;…

基于STC15系列库操作LED灯

一、准备工作 1. 基于STC15系列库的工程模板 参考&#xff1a;51单片机工程模板的建立&#xff08;基于STC15系列库&#xff09;-CSDN博客 2. Keil编译器 二、程序编写 1. 新建 led.c 和 led.h 文件并存放于 user/led 文件夹下&#xff1b; 2. 新建 user.c 和 user.h 文件并…

如何辨别:DNS污染or DNS劫持?

DNS劫持和DNS污染的情况在互联网中并不少见&#xff0c;到底是出现了DNS污染还是DNS劫持。什么是DNS污染&#xff1f;什么是DNS劫持&#xff1f;我们该如何辨别DNS污染和DNS劫持&#xff1f; DNS劫持&#xff1a; DNS 劫持是指恶意攻击者通过非法手段篡改了网络中的 DNS 服务…

android 创建module

文章目的&#xff1a; 快速创建module并使用 创建步骤&#xff1a; 1 创建module 2 修改module下的build.gradle文件 3 修改清单文件中MainActivity属性&#xff0c;否则APP会因为有多个启动界面而崩溃 4 在主项目build.gradle引用该object Module 至此&#xff0c;可在APP中…

针对springcloud gateway 跨域问题解决方案

springcloud gateway版本 <spring-boot.version>2.3.3.RELEASE</spring-boot.version> <spring-cloud.version>Hoxton.SR8</spring-cloud.version>跨域问题说明 application:1 Access to XMLHttpRequest at https://xxxxxxxxxx from origin http://l…

Goland远程连接Linux进行项目开发

文章目录 1、Linux上安装go的环境&#xff12;、配置远程连接3、其他配置入口 跑新项目&#xff0c;有个confluent-Kafka-go的依赖在Windows上编译不通过&#xff0c;报错信息&#xff1a; undefined reference to __imp__xxx似乎是这个依赖在Windows上不支持&#xff0c;选择让…

IMX6ULL-UBOOT驱动移植

介绍 IMX6ULL正点原子开发板使用的是14x14_evk的芯片 其中14x14代表的是芯片的尺寸。 本教程的标识符以nsouther或者 NSOUTHER NSouther为主 添加板子自己的配置文件 板子的默认配置文件保存在 configs目录下&#xff0c;我们以mx6ull_14x14_evk_emmc_defconfig为主&#xf…

SOT23-6封装单键触摸感应触发芯片TC233A

前言&#xff1a; 触摸芯片很多&#xff0c;现在触摸按键已经应用到很多行业&#xff0c;虽然不能覆盖所有的按键&#xff0c;但确实用的越来越多&#xff0c;国产的价格也便宜的令人发指&#xff0c;比如这个TC233A&#xff0c;也就一毛多一点。 TC233A概述 TC233A 是一个单…

【Shell语言】linux中awk命令

linux中awk命令 看这里放声嘶吼谁也不舍得沉默 宽阔也抓不住我下一秒钟的echo ——《暂时失控》苏打绿 awk命令简介 AWK 是一种处理文本文件的语言&#xff0c;是一个强大的文本分析工具。 之所以叫 AWK 是因为其取了三位创始人 Alfred Aho&#xff0c;Peter Weinberger, 和 B…

请陪伴Kimi和GPT成长

经验的闪光汤圆 但是我想要写实的 你有吗&#xff1f; 岁数大了&#xff0c;希望如何学习新知识呢&#xff1f;又觉得自己哪些能力亟需补强呢&#xff1f; 看论文自然得用Kimi&#xff0c;主要是肝不动了&#xff0c;眼睛也顶不住了。 正好昨天跟专业人士学会了用工作流的办法跟…

Python中的Super方法实现问题及解决方案

1、问题背景 在Python中&#xff0c;super方法用于在子类中调用父类的方法。Guido van Rossum曾给出了一个纯Python实现的super方法&#xff0c;以便更好地理解其工作原理。然而&#xff0c;在这个实现中&#xff0c;存在一个问题&#xff1a;当传入的对象不是要调用的父类的实…