连通区域(Connected Component)一般是指图像中具有相同像素值且位置相邻的前景像素点组成的图像区域(Region,Blob)。连通区域分析(Connected Component Analysis,Connected Component Labeling)是指将图像中的各个连通区域找出并标记。
连通区域分析是一种在CVPR和图像分析处理的众多应用领域中较为常用和基本的方法。例如:OCR识别中字符分割提取(车牌识别、文本识别、字幕识别等)、视觉跟踪中的运动前景目标分割与提取(行人入侵检测、遗留物体检测、基于视觉的车辆检测与跟踪等)、医学图像处理(感兴趣目标区域提取)、等等。也就是说,在需要将前景目标提取出来以便后续进行处理的应用场景中都能够用到连通区域分析方法,通常连通区域分析处理的对象是一张二值化后的图像。

而这次我要做的是实现图像的快速连通域算法,可以提取出图像中的连通域,并将不同连通域用不同颜色表示。

寻找图像中的连通域的算法有两个,一个是Two-Pass方法

Two-Pass算法的简单步骤:

(1)第一次扫描:
访问当前像素B(x,y),如果B(x,y) == 1:

a、如果B(x,y)的领域中像素值都为0,则赋予B(x,y)一个新的label:
label += 1, B(x,y) = label;

b、如果B(x,y)的领域中有像素值 > 1的像素Neighbors:
1)将Neighbors中的最小值赋予给B(x,y):
B(x,y) = min{Neighbors}

2)记录Neighbors中各个值(label)之间的相等关系,即这些值(label)同属同一个连通区域;

labelSet[i] = { label_m, …, label_n },labelSet[i]中的所有label都属于同一个连通区域

图示为:

// 1. 第一次遍历_lableImg.release();_binImg.convertTo(_lableImg, CV_32SC1);int label = 1;  // start by 2std::vector<int> labelSet;labelSet.push_back(0);   // background: 0labelSet.push_back(1);   // foreground: 1int rows = _binImg.rows - 1;int cols = _binImg.cols - 1;for (int i = 1; i < rows; i++){int* data_preRow = _lableImg.ptr<int>(i - 1);int* data_curRow = _lableImg.ptr<int>(i);for (int j = 1; j < cols; j++){if (data_curRow[j] == 1){std::vector<int> neighborLabels;neighborLabels.reserve(2);int leftPixel = data_curRow[j - 1];int upPixel = data_preRow[j];if (leftPixel > 1){neighborLabels.push_back(leftPixel);}if (upPixel > 1){neighborLabels.push_back(upPixel);}if (neighborLabels.empty()){labelSet.push_back(++label);  // assign to a new labeldata_curRow[j] = label;labelSet[label] = label;}else{std::sort(neighborLabels.begin(), neighborLabels.end());int smallestLabel = neighborLabels[0];data_curRow[j] = smallestLabel;// save equivalencefor (size_t k = 1; k < neighborLabels.size(); k++){int tempLabel = neighborLabels[k];int& oldSmallestLabel = labelSet[tempLabel];if (oldSmallestLabel > smallestLabel){labelSet[oldSmallestLabel] = smallestLabel;oldSmallestLabel = smallestLabel;}else if (oldSmallestLabel < smallestLabel){labelSet[smallestLabel] = oldSmallestLabel;}}}}}}

(2)第二次扫描:
访问当前像素B(x,y),如果B(x,y) > 1:

a、找到与label = B(x,y)同属相等关系的一个最小label值,赋予给B(x,y);
完成扫描后,图像中具有相同label值的像素就组成了同一个连通区域。

图示为:

// 2. 第二遍扫描for (int i = 0; i < rows; i++){int* data = _lableImg.ptr<int>(i);for (int j = 0; j < cols; j++){int& pixelLabel = data[j];pixelLabel = labelSet[pixelLabel];}}
}

另一个方法就是Seed-Filling方法

种子填充法的连通区域分析方法:

(1)扫描图像,直到当前像素点B(x,y) == 1:

a、将B(x,y)作为种子(像素位置),并赋予其一个label,然后将该种子相邻的所有前景像素都压入栈中;

b、弹出栈顶像素,赋予其相同的label,然后再将与该栈顶像素相邻的所有前景像素都压入栈中;

// 推及到四个邻居if (_lableImg.at<int>(curX, curY - 1) == 1){// 左边的像素neighborPixels.push(std::pair<int, int>(curX, curY - 1));}if (_lableImg.at<int>(curX, curY + 1) == 1){// 右边的像素neighborPixels.push(std::pair<int, int>(curX, curY + 1));}if (_lableImg.at<int>(curX - 1, curY) == 1){// 上面的像素neighborPixels.push(std::pair<int, int>(curX - 1, curY));}if (_lableImg.at<int>(curX + 1, curY) == 1){// 下面的像素neighborPixels.push(std::pair<int, int>(curX + 1, curY));}

c、重复b步骤,直到栈为空;
此时,便找到了图像B中的一个连通区域,该区域内的像素值被标记为label;

(2)重复第(1)步,直到扫描结束;

扫描结束后,就可以得到图像B中所有的连通区域;

而我选择的是种子填充方法。
对下面这张图片做处理
得到结果为:

改变连通域颜色:

我用的方法是在刚开始的时候就随机设置三个RGB 值,然后为填充不同连通域(每个连通域的像素的RGB值都是随机的)。

cv::Scalar icvprGetRandomColor()
{uchar r = 255 * (rand() / (1.0 + RAND_MAX));uchar g = 255 * (rand() / (1.0 + RAND_MAX));uchar b = 255 * (rand() / (1.0 + RAND_MAX));return cv::Scalar(b, g, r);
}

代码自取

// CVE6.cpp: 定义控制台应用程序的入口点。
//#include "stdafx.h"
#include <iostream>
#include <string>
#include <list>
#include <vector>
#include <map>
#include <stack>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <stdio.h>
using namespace cv;
//Two Pass方法
void icvprCcaByTwoPass(const cv::Mat& _binImg, cv::Mat& _lableImg)
{/*Two-Pass算法的简单步骤:(1)第一次扫描:访问当前像素B(x,y),如果B(x,y) == 1:a、如果B(x,y)的领域中像素值都为0,则赋予B(x,y)一个新的label:label += 1, B(x,y) = label;b、如果B(x,y)的领域中有像素值 > 1的像素Neighbors:1)将Neighbors中的最小值赋予给B(x,y):B(x,y) = min{Neighbors} 2)记录Neighbors中各个值(label)之间的相等关系,即这些值(label)同属同一个连通区域;labelSet[i] = { label_m, .., label_n },labelSet[i]中的所有label都属于同一个连通区域(2)第二次扫描:访问当前像素B(x,y),如果B(x,y) > 1:a、找到与label = B(x,y)同属相等关系的一个最小label值,赋予给B(x,y);完成扫描后,图像中具有相同label值的像素就组成了同一个连通区域。*/if (_binImg.empty() ||_binImg.type() != CV_8UC1){return;}// 1. 第一次遍历_lableImg.release();_binImg.convertTo(_lableImg, CV_32SC1);int label = 1;  // start by 2std::vector<int> labelSet;labelSet.push_back(0);   // background: 0labelSet.push_back(1);   // foreground: 1int rows = _binImg.rows - 1;int cols = _binImg.cols - 1;for (int i = 1; i < rows; i++){int* data_preRow = _lableImg.ptr<int>(i - 1);int* data_curRow = _lableImg.ptr<int>(i);for (int j = 1; j < cols; j++){if (data_curRow[j] == 1){std::vector<int> neighborLabels;neighborLabels.reserve(2);int leftPixel = data_curRow[j - 1];int upPixel = data_preRow[j];if (leftPixel > 1){neighborLabels.push_back(leftPixel);}if (upPixel > 1){neighborLabels.push_back(upPixel);}if (neighborLabels.empty()){labelSet.push_back(++label);  // assign to a new labeldata_curRow[j] = label;labelSet[label] = label;}else{std::sort(neighborLabels.begin(), neighborLabels.end());int smallestLabel = neighborLabels[0];data_curRow[j] = smallestLabel;// save equivalencefor (size_t k = 1; k < neighborLabels.size(); k++){int tempLabel = neighborLabels[k];int& oldSmallestLabel = labelSet[tempLabel];if (oldSmallestLabel > smallestLabel){labelSet[oldSmallestLabel] = smallestLabel;oldSmallestLabel = smallestLabel;}else if (oldSmallestLabel < smallestLabel){labelSet[smallestLabel] = oldSmallestLabel;}}}}}}// 更新标签// 用每个连通域中最小的标签来表示这个连通域for (size_t i = 2; i < labelSet.size(); i++){int curLabel = labelSet[i];int preLabel = labelSet[curLabel];while (preLabel != curLabel){curLabel = preLabel;preLabel = labelSet[preLabel];}labelSet[i] = curLabel;}// 2. 第二遍扫描for (int i = 0; i < rows; i++){int* data = _lableImg.ptr<int>(i);for (int j = 0; j < cols; j++){int& pixelLabel = data[j];pixelLabel = labelSet[pixelLabel];}}
}void icvprCcaBySeedFill(const cv::Mat& _binImg, cv::Mat& _lableImg)
{/*种子填充法的连通区域分析方法:
(1)扫描图像,直到当前像素点B(x,y) == 1:
a、将B(x,y)作为种子(像素位置),并赋予其一个label,然后将该种子相邻的所有前景像素都压入栈中;
b、弹出栈顶像素,赋予其相同的label,然后再将与该栈顶像素相邻的所有前景像素都压入栈中;
c、重复b步骤,直到栈为空;
此时,便找到了图像B中的一个连通区域,该区域内的像素值被标记为label;
(2)重复第(1)步,直到扫描结束;
扫描结束后,就可以得到图像B中所有的连通区域;*/if (_binImg.empty() ||_binImg.type() != CV_8UC1){return;}_lableImg.release();_binImg.convertTo(_lableImg, CV_32SC1);int label = 1;  // start by 2int rows = _binImg.rows - 1;int cols = _binImg.cols - 1;for (int i = 1; i < rows - 1; i++){int* data = _lableImg.ptr<int>(i);for (int j = 1; j < cols - 1; j++){if (data[j] == 1){std::stack<std::pair<int, int>> neighborPixels;neighborPixels.push(std::pair<int, int>(i, j));     // 像素坐标: <i,j>++label;  //从一个新label开始while (!neighborPixels.empty()){// 栈中最上面的像素给予和与其连通的像素相同的labelstd::pair<int, int> curPixel = neighborPixels.top();int curX = curPixel.first;int curY = curPixel.second;_lableImg.at<int>(curX, curY) = label;// 弹出最上面的像素neighborPixels.pop();// 推及到四个邻居if (_lableImg.at<int>(curX, curY - 1) == 1){// 左边的像素neighborPixels.push(std::pair<int, int>(curX, curY - 1));}if (_lableImg.at<int>(curX, curY + 1) == 1){// 右边的像素neighborPixels.push(std::pair<int, int>(curX, curY + 1));}if (_lableImg.at<int>(curX - 1, curY) == 1){// 上面的像素neighborPixels.push(std::pair<int, int>(curX - 1, curY));}if (_lableImg.at<int>(curX + 1, curY) == 1){// 下面的像素neighborPixels.push(std::pair<int, int>(curX + 1, curY));}}}}}
}//为连通域加上颜色
cv::Scalar icvprGetRandomColor()
{uchar r = 255 * (rand() / (1.0 + RAND_MAX));uchar g = 255 * (rand() / (1.0 + RAND_MAX));uchar b = 255 * (rand() / (1.0 + RAND_MAX));return cv::Scalar(b, g, r);
}void icvprLabelColor(const cv::Mat& _labelImg, cv::Mat& _colorLabelImg)
{if (_labelImg.empty() ||_labelImg.type() != CV_32SC1){return;}std::map<int, cv::Scalar> colors;int rows = _labelImg.rows;int cols = _labelImg.cols;_colorLabelImg.release();_colorLabelImg.create(rows, cols, CV_8UC3);_colorLabelImg = cv::Scalar::all(0);for (int i = 0; i < rows; i++){const int* data_src = (int*)_labelImg.ptr<int>(i);uchar* data_dst = _colorLabelImg.ptr<uchar>(i);for (int j = 0; j < cols; j++){int pixelValue = data_src[j];if (pixelValue > 1){if (colors.count(pixelValue) <= 0){colors[pixelValue] = icvprGetRandomColor();}cv::Scalar color = colors[pixelValue];*data_dst++ = color[0];*data_dst++ = color[1];*data_dst++ = color[2];}else{data_dst++;data_dst++;data_dst++;}}}
}int main(int argc, char** argv)
{cv::Mat binImage = cv::imread("E:/C++/CVE6/图片2.png", 0);cv::imshow("img", binImage);//cv::Mat binImage2;cv::threshold(binImage, binImage, 50, 1, CV_THRESH_BINARY_INV);cv::Mat labelImg;//icvprCcaByTwoPass(binImage, labelImg);icvprCcaBySeedFill(binImage, labelImg) ;// 展示结果cv::Mat grayImg;//结果*10,更突出labelImg *= 10;labelImg.convertTo(grayImg, CV_8UC1);cv::imshow("labelImg", grayImg);cv::Mat colorLabelImg;//更改连通域颜色icvprLabelColor(labelImg, colorLabelImg);cv::imshow("colorImg", colorLabelImg);cv::waitKey(0);return 0;
}

图像处理(五)——连通域相关推荐

  1. 《OpenCv视觉之眼》Python图像处理五 :Opencv图像去噪处理之均值滤波、方框滤波、中值滤波和高斯滤波

    本专栏主要介绍如果通过OpenCv-Python进行图像处理,通过原理理解OpenCv-Python的函数处理原型,在具体情况中,针对不同的图像进行不同等级的.不同方法的处理,以达到对图像进行去噪.锐 ...

  2. 【数字图像处理】LeetCode与图像处理(连通域的计算)

    基本概念 在数字图像处理中,有个连通域的概念 连通区域(Connected Component)一般是指图像中具有相同像素值且位置相邻的前景像素点组成的图像区域(Region,Blob). 在图像中, ...

  3. [Python图像处理] 五.图像融合、加法运算及图像类型转换

    该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门.OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子.图像增强技术.图像分割等,后期结合深度学习研究图像识别 ...

  4. 图像处理五:python读取图片的几种方式

    一.读取图片方式 PIL.opencv.scikit-image: (1)PIL和Pillow只提供最基础的数字图像处理,功能有限: (2)opencv实际上是一个c++库,只是提供了python接口 ...

  5. python灰度图片格式_[Python图像处理] 十五.图像的灰度线性变换

    [Python图像处理] 十五.图像的灰度线性变换 发布时间:2019-03-28 00:08, 浏览次数:619 , 标签: Python 该系列文章是讲解Python OpenCV图像处理知识,前 ...

  6. 数字图像处理知识点总结

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达本文转自|新机器视觉 数字图像处理知识点总结 第一章 导论 1.   ...

  7. [Python从零到壹] 三十七.图像处理基础篇之图像融合处理和ROI区域绘制

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  8. 数字图像处理与机器视觉光盘资料_机器视觉——数字图像处理知识点总结

    数字图像处理知识点总结 第一章 导论 1. 图像:对客观对象的一种相似性的生动性的描述或写真. 2. 图像分类:按可见性(可见图像.不可见图像),按波段数(单波段.多波段.超波段),按空间坐标和亮度的 ...

  9. [转载] [Python图像处理] 二十二.Python图像傅里叶变换原理及实现

    参考链接: Python中的复数3(三角函数和双曲线函数) 该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门.OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化 ...

  10. 《OpenCv视觉之眼》Python图像处理六 :Opencv图像傅里叶变换和傅里叶逆变换原理及实现

    本专栏主要介绍如果通过OpenCv-Python进行图像处理,通过原理理解OpenCv-Python的函数处理原型,在具体情况中,针对不同的图像进行不同等级的.不同方法的处理,以达到对图像进行去噪.锐 ...

最新文章

  1. mysql 加快复制进程_MySQL并发复制进程演进
  2. Exchange Server 2003多服务器安装以及管理工具介绍
  3. zoj 1670 Jewels from Heaven
  4. UA MATH566 统计理论5 假设检验:p值
  5. SQL语句,统计一段时间内有多少个工作日
  6. 利用FS寄存器获取KERNEL32.DLL基址算法的证明(ZZ)
  7. window.createPopup()用法以及短消息提示框代码
  8. The Unique MST 判断生成树是否唯一
  9. [知乎] 端游、手游服务端架构演变
  10. 社交平台在网络诈骗类黑产对抗的防控
  11. openGL加载obj三维模型
  12. 页面关键词密度 和布局 ,内页
  13. layim之绑定未读消息数量
  14. 工业自动化数据采集方案
  15. 错宗复杂的进程标识PID
  16. 人活到了30岁,月薪还停留在20岁怎么办?
  17. JS一个元素怎么绑定多个事件
  18. 地球同步轨道、太阳同步轨道知识
  19. 一次搞清五种 I/O 模型(生动形象版)
  20. 哪种灯对眼睛视力保护最好?盘点五款全光谱光照的护眼台灯

热门文章

  1. aws beanstalk mysql_教程和示例 - AWS Elastic Beanstalk
  2. 使用GIZA++进行平行语料的词对齐
  3. 关注的股票博客与微博
  4. UNIX IO 简介
  5. 自媒体市场规模由2015年的296亿元增涨至2021年的2500亿元
  6. 基于低代码平台的疫情管理系统,源码交付更放心
  7. 华为软挑赛2023-初赛笔记
  8. 对计算机的态度作文,人工智能改变了生活态度作文
  9. [英语阅读]法国拟禁止非法网络下载
  10. t-SNE:可视化效果最好的降维算法