Opencv 基本操作五 各种连通域处理方法

news2024/12/23 20:46:27

在深度学习中,尤其是语义分割模型部署的结果后处理中,离不开各类形态学处理方法,其中以连通域处理为主;同时在一些传统的图像处理算法中,也需要一些形态学、连通域处理方法。为此,整理了一些常用的连通域处理函数:查找图像中最大的连通域、删除图像中小面积的连通域、删除图像中的黑色连通域、获取形状的骨架。关于博文代码中连通域处理中的图像D:/Img_data/15.bmp,如下所示,各位如果要运行出一样的效果的话可以用下图进行测试。
在这里插入图片描述

1、查找图像中最大的连通域

该功能基于connectedComponents函数实现,具体包含4步:
1、通过connectedComponents将每一个连通域的像素用相同的label值标记结果出入Mat labels中
2、然后遍历mat即可找出面积最大的连通域的label值
3、最后遍历Mat labels将像素值不等于最大的连通域的label值的置0即可。

1.1 函数实现

函数命名为findLargesrArea,传入CV_8UC1型的mat,返回结果也为CV_8UC1类型

//找图图中最大的连通域
Mat findLargesrArea(Mat srcImage)
{
    Mat temp;
    Mat labels;
    srcImage.copyTo(temp);

    //1. 标记连通域
    int n_comps = connectedComponents(temp, labels, 4, CV_16U);
    vector<int> histogram_of_labels;
    for (int i = 0; i < n_comps; i++)//初始化labels的个数为0
    {
        histogram_of_labels.push_back(0);
    }

    int rows = labels.rows;
    int cols = labels.cols;
    for (int row = 0; row < rows; row++) //计算每个labels的个数--即连通域的面积
    {
        for (int col = 0; col < cols; col++)
        {
            histogram_of_labels.at(labels.at<unsigned short>(row, col)) += 1;
        }
    }
    histogram_of_labels.at(0) = 0; //将背景的labels个数设置为0

    //2. 计算最大的连通域labels索引
    int maximum = 0;
    int max_idx = 0;
    for (int i = 0; i < n_comps; i++)
    {
        if (histogram_of_labels.at(i) > maximum)
        {
            maximum = histogram_of_labels.at(i);
            max_idx = i;
        }
    }

    //3. 将最大连通域标记为255,并将其他连通域置0
    for (int row = 0; row < rows; row++)
    {
        for (int col = 0; col < cols; col++)
        {
            if (labels.at<unsigned short>(row, col) == max_idx)
            {
                labels.at<unsigned short>(row, col) = 255;
            }
            else
            {
                labels.at<unsigned short>(row, col) = 0;
            }
        }
    }

    //4. 将图像更改为CV_8U格式
    labels.convertTo(labels, CV_8U);
    return labels;
}

1.2 调用效果

测试调用代码如下所示,im2既为找到的最大连通域结果图

void main() {
    string path = "D:/Img_data/15.bmp";
    Mat im1 = imread(path, 0);
    resize(im1, im1, {512,512});
    im1 = 255 - im1;
    imshow("im1", im1);
    Mat im2=findLargesrArea(im1);
    imshow("im2", im2);
    waitKey();
}

调用结果如下所示:
在这里插入图片描述

2、删除小面积的连通域

该功能基于connectedComponentsWithStats函数实现,具体包含4步:
1、通过connectedComponentsWithStats将每一个连通域的像素用相同的label值标记,标记结果存为Mat labels,并将每一个连通域的面积存入Mat stats中
2、为每一个连通域设置一个颜色转换表vector colors,将连通域面积小于阈值的连通域颜色转换值为黑色
3、建立一个Mat img_color,遍历Mat labels按照其像素值从vector colors中取值设置Mat img_color的值
4、将Mat img_color进行二值化

2.1 函数实现

删除小面积的连通域可以使用connectedComponents函数,这里改用connectedComponentsWithStats函数实现,相比于connectedComponents函数需要自行计算连通域面积,connectedComponentsWithStats函数的参数stats已经将连通域面积带出来了

//删除小面积的连通域
Mat deleteMinWhiteArea(Mat src,int min_area) {
    Mat labels, stats, centroids, img_color, grayImg;
    //1、连通域信息统计
    int nccomps = connectedComponentsWithStats(
        src, //二值图像
        labels,
        stats,
        centroids
    );

    //2、连通域状态区分
    //为每一个连通域初始化颜色表
    vector<Vec3b> colors(nccomps);
    colors[0] = Vec3b(0, 0, 0); // background pixels remain black.
    for (int i = 1; i < nccomps; i++)
    {
        colors[i] = Vec3b(rand() % 256, rand() % 256, rand() % 256);
        //面积阈值筛选
        if ((stats.at<int>(i, CC_STAT_AREA) < min_area))
        {
            //如果连通域面积不合格则置黑
            colors[i] = Vec3b(0, 0, 0);
        }
    }
    //3、连通域删除
    //按照label值,对不同的连通域进行着色
    img_color = Mat::zeros(src.size(), CV_8UC3);
    for (int y = 0; y < img_color.rows; y++)
    {
        int* labels_p = labels.ptr<int>(y);//使用行指针,加速运算
        Vec3b* img_color_p = img_color.ptr<Vec3b>(y);//使用行指针,加速运算
        for (int x = 0; x < img_color.cols; x++)
        {
            int label = labels_p[x];//取出label值
            CV_Assert(0 <= label && label <= nccomps);
            img_color_p[x] = colors[label];//设置颜色
        }
    }
    //return img_color;
    //如果是需要二值结果则将img_color进行二值化
    cvtColor(img_color, grayImg, COLOR_BGR2GRAY);
    threshold(grayImg, grayImg, 1, 255, THRESH_BINARY);
    return grayImg;
}

2.2 调用效果

调用deleteMinWhiteArea函数需要传入mat对象和面积阈值,函数会将小于面积阈值的连通域置黑。

void main() {
    string path = "D:/Img_data/15.bmp";
    Mat im1 = imread(path, 0);
    resize(im1, im1, {512,512});
    //im1 = 255 - im1;
    imshow("im1", im1);
    Mat im2= deleteMinWhiteArea(im1,100);
    imshow("im2", im2);
    waitKey();
}

在这里插入图片描述

3、删除小面积的黑色孔洞

删除图像中的删除小面积的黑色孔洞也可以理解为删除图像中黑色的小面积连通域,有了删除白色小面积连通域函数,删除黑色孔洞代码异常简单。

3.1 函数实现

deleteMinBlackArea函数通过调用deleteMinWhiteArea函数实现,先将图像的颜色翻转(将白边黑,黑变白),然后删除小面积的白色连通域,最后再翻转回颜色即可

//删除图形中小面积的黑色孔洞
Mat deleteMinBlackArea(Mat src, int min_area) {
    Mat inv = 255 - src;//颜色取反
    Mat res = deleteMinWhiteArea(inv, min_area);
    return 255-res;//颜色取反
}

3.2 调用效果

调用代码如下所示,左图为原图,右图为删除小面积黑色孔洞的效果图

void main() {
    string path = "D:/Img_data/15.bmp";
    Mat im1 = imread(path, 0);
    resize(im1, im1, {512,512});
    im1 = 255 - im1;
    imshow("im1", im1);
    Mat im2= deleteMinBlackArea(im1,100);
    imshow("im2", im2);
    waitKey();
}

在这里插入图片描述

4、获取形状的骨架

提取形状的骨架在python中可以由skimage库中morphology.skeletonize()函数实现,但是查找整个c++opencv库也没有实现,为此对提取骨架算法进行分析,最终实现该函数。

4.1 函数实现

核心思想,采用十字架结构的kernel对形状不断进行腐蚀,每一次腐蚀后再进行开运算,将腐蚀结果与开运算结果作差,作差结构既为骨架部分,直到将整个图像腐蚀为全黑为止。
通过以上步骤可知:
1、被腐蚀掉的区域非骨架区域
2、开运算可以腐蚀掉图形的骨架区域

因此将开运算与腐蚀不断作差迭代运行,使图形越来越细,即可得到骨架。

//获取脊线
Mat get_ridge_line(Mat dst,int ksize=3) {
    Mat skeleton, result, open_dst;
    Mat kernel = getStructuringElement(MORPH_CROSS, Size(ksize, ksize));
    skeleton = Mat::zeros(dst.rows, dst.cols, dst.type());
    while (true) {
        if (sum(dst)[0] == 0) {
            break;
        }
        morphologyEx(dst, dst, MORPH_ERODE, kernel);//消除毛刺,删除部分连通域
        morphologyEx(dst, open_dst, MORPH_OPEN, kernel);
        result = dst - open_dst;
        skeleton = skeleton + result;
    }
    return skeleton;
}

4.2 调用效果

通过get_ridge_line函数获取轮廓骨架需要传入两个参数: mat和ksize,通过以下效果可以看出,ksize越大提取的骨架越粗。

void main() {
    string path = "D:/Img_data/a1.png";
    Mat im1 = imread(path, 0);
    resize(im1, im1, {512,512});
    imshow("im1", im1);
    Mat im2= get_ridge_line(im1,3);
    imshow("im2", im2);
    Mat im3 = get_ridge_line(im1, 7);
    imshow("im3", im3);
    waitKey();
}

在这里插入图片描述

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

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

相关文章

leetcode每日一题寒假版:1691. 堆叠长方体的最大高度 (hard)( 换了皮的最长递增子序列)

2022-12-10 1691. 堆叠长方体的最大高度 (hard) &#x1f6a9; 学如逆水行舟&#xff0c;不进则退。 —— 《增广贤文》 题目描述&#xff1a; 给你 n 个长方体 cuboids &#xff0c;其中第 i 个长方体的长宽高表示为 cuboids[i] [width(i), length(i), height(i)]&#xf…

Docker补充知识点--自定义网络实现直连容器

前面介绍docker镜像的秘密这篇知识点的时候&#xff0c;https://blog.csdn.net/dudadudadd/article/details/128200522&#xff0c;提到了docker容器也有属于自己的IP的概念&#xff0c;默认的Docker容器是采用的是bridge网络模式。并且提到了一嘴自定义网卡配置&#xff0c;本…

java基于Springboot的健身房课程预约平台-计算机毕业设计

项目介绍 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven 本健身网站系统是针…

Unity纹理优化:缩小包体

Android打包apk大小约&#xff1a;475M 查看打包日志&#xff1a;Console→Open Editor Log; 或者依赖第三方插件&#xff1a;build reports tool&#xff08;在unity store里可以下载&#xff09;&#xff1b; 定位问题 经过排查后&#xff0c;发现项目中纹理占比很高&#…

分布式能源的不确定性——风速测试(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…

(6)Pytorch数据处理

Pytorch 数据处理 要点总结 1、功能 Dataset&#xff1a;准备数据集&#xff0c;一般会针对自己的数据集格式重写Dataset&#xff0c;定义数据输入输出格式 Dataloader&#xff1a;用于加载数据&#xff0c;通常不用改这部分内容 2、看代码时请关注 Dataloader中collate_fn 传入…

【云原生】K8s Ingress rewrite与TCP四层转发讲解与实战操作

文章目录一、背景二、K8s Ingress安装三、K8s Ingress rewrite 讲解与使用1&#xff09;配置说明2&#xff09;示例演示1、部署应用2、配置ingress rewrite转发&#xff08;http&#xff09;3、配置ingress rewrite转发&#xff08;https&#xff09;【1】创建证书&#xff08;…

音视频- iOS图像采集

本文主要总结一下&#xff0c;如何使用AVFoundation的功能来实现图像的采集&#xff0c;主要用到了AVFoundation中的一些类&#xff0c;采集的结构如下图&#xff0c;引用自iOS开发者官网&#xff1a; AVCaptureSession 采集会话&#xff0c;其主要功能从整体上来掌管图像采集的…

MOSFET 和 IGBT 栅极驱动器电路的基本原理学习笔记(五)交流耦合栅极驱动电路

交流耦合栅极驱动电路 1.计算耦合电容 2.耦合电容器的启动瞬变 3.总结 栅极驱动路径中的交流耦合可为栅极驱动信号提供简单的电平位移。交流耦合的主要作用是修改主MOSFET 的开通和关断栅极电压&#xff0c;而高侧栅极驱动则不同&#xff0c;它最需要关注的是缩小较大的电势差…

软件安全测试-web安全测试基础

目录 1. Web安全的测试范围 2.Web安全的四要素 3. Web安全的分类 4. Web安全的类别排名​ 5. 零时差攻击 6. Web安全的载体 7. 了解软件安全测试相关的Cooike&#xff0c;Session&#xff0c;Token 7.1 会话级鉴权及认证技术 7.2 会话安全管理需要授权和鉴权两个步骤 …

单例模式(史上最全)

文章很长&#xff0c;而且持续更新&#xff0c;建议收藏起来&#xff0c;慢慢读&#xff01;疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 &#xff1a; 免费赠送 :《尼恩Java面试宝典》 持续更新 史上最全 面试必备 2000页 面试必备 大厂必备 涨薪必备 免费赠送 经典…

0121 动态规划 Day10

剑指 Offer 46. 把数字翻译成字符串 给定一个数字&#xff0c;我们按照如下规则把它翻译为字符串&#xff1a;0 翻译成 “a” &#xff0c;1 翻译成 “b”&#xff0c;……&#xff0c;11 翻译成 “l”&#xff0c;……&#xff0c;25 翻译成 “z”。一个数字可能有多个翻译。…

Python——翻转字符串

题目介绍 以空格为分割&#xff0c;将字符串中的每个单词的字母位置不变&#xff0c;单词顺序从后往前翻转 例如&#xff1a;I am a student. 变成&#xff1a;student. a am I Python中的标准库是为了提高程序员开发效率&#xff0c;减少学习成本&#xff0c;而设计的一系列方…

spring——Spring Bean定义

在 XML 配置的<beans> 元素中可以包含多个属性或子元素&#xff0c;常用的属性或子元素如下表所示。 属性名称描述idBean 的唯一标识符&#xff0c;Spring IoC 容器对 Bean 的配置和管理都通过该属性完成。id 的值必须以字母开始&#xff0c;可以使用字母、数字、下划线等…

SpringCloud Gateway网关的使用与介绍

目录 1. gateway简介 1.1 是什么 1.2 作用 1.3 主要特征 1.4 与zuul的主要区别 1.5 主要组件 1.6 架构图 2. 开发示例 2.1 创建一个gateway模块 2.2 与nacos结合使用 2.2.1 默认规则 2.2.2 通过配置文件配置路由 2.2.3 动态路由 1. gateway简介 1.1 是什么 Spri…

Vulnhub靶机:PRIME_ 1

目录介绍信息收集主机信息探测主机信息探测网站探测目录爆破排雷dirsearch强制访问文件包含漏洞利用WordPress提权wordpress配置文件内核提权介绍 系列&#xff1a;Prime&#xff08;此系列共1台&#xff09; 发布日期&#xff1a;2019年9月1日 难度&#xff1a;初-中 运行环境…

在 Istio 服务网格中使用 Argo Rollouts 实现智能的渐进式发布

1 Argo Rollouts 介绍 Kubernetes 原生的 Deployment 利用 Rolling Update 滚动更新的策略在应用升级时提供基本的安全保证&#xff08;例如就绪探针&#xff09;。然而默认的滚动更新策略存在着一些明显的缺点&#xff0c;例如&#xff1a; 无法控制流向新版本的流量。无法控…

tensorflow入门(四)如何用tensorflow训练神经网络

参考 如何用tensorflow训练神经网络 - 云社区 - 腾讯云 在使用神经网络解决实际的分类或回归问题时需要设置好参数取值。下面介绍使用监督学习的方式来合理地设置参数取值&#xff0c;同时也将给出tensorflow程序来完成这个过程。设置神经网络参数的过程就是神经网络的训练过…

基于JDBC的MySQL数据库编程

✨博客主页: 荣 ✨系列专栏: MySQL ✨一句短话: 难在坚持,贵在坚持,成在坚持! 文章目录一. JDBC概述二. JDBC前置工作1. 准备好MySQL驱动包2. 创建项目三. JDBC的使用步骤1. 创建数据源DataSourece2. 连接数据库3. 构造并执行sql语句4. 释放资源5. sql语句不要写死(以插入为例)…

C++入门基础07:函数定义与声明、函数传参(传值、传地址、传引用)、函数重载

C入门基础07&#xff1a;函数定义与声明、函数传参&#xff08;传值、传地址、传引用&#xff09;、函数重载 1、函数定义与声明 函数是一起执行一个任务的一组语句。每个程序&#xff08;C/C&#xff09;都有一个主函数 main() &#xff0c; 所有简单的程序都可以定义其他额…