图像处理 连通域标记

前言

   连通域标记是二值图像分析中非常重要的一种方法,也是其他二值图像处理的基础与前提。
   所谓连通域标记,就是将一副二值图像中的每个白色像素进行标记,属于同一个连通域的白色像素标记相同,不同连通域的白色像素有不同的标记,从而能将图像中每个连通域提取出来。
   连通域标记的算法有很多种,这里介绍其中一种基于一次遍历图像,记录等价对的标记方法,这种方法效率很高。

算法原理

4 邻接与 8 邻接

  在介绍算法前,我们先了解一下什么算是连通。如果两个白色像素邻接,则我们认为这两个像素是连通的;同时,若像素 A 与像素 B 连通,像素 B 与像素 C 连通,则像素 A 与 C 也是连通的。
  因此,我们需要定义什么样的两个像素点算是邻接的。常见的邻接关系有两种:4 邻接与 8 邻接。如下图所示:
这里写图片描述
如果另一个像素点在上图中黑点的位置,则表示这个像素点与 X 点邻接。

遍历图像

算法描述:

for 二值图像中的每一行: //列也可以
   1. 记录此行白色像素的每一个序列的的起始位置,终止位置;
   2. 除第一行以外(第一行直接标记),判断是否与上一行序列有重叠:
        如果没有重叠,则分配一个新的标记;
        如果有一个重叠,则用上一行序列的标记进行标记;
        如果有一个以上的重叠,则用上一行重叠序列中最小的进行标记,同时将后面几个标记与此标记记为等价对;
end

这里写图片描述

如上图所示,我们采用 4 邻接,行遍历来说明一下上面的操作
第一行: 我们记录一个序列:(1,4)标记为 1;
第二行:我们记录两个序列:(0,3),与上一行的序列重叠,因此标记为 1,(6,8),与上一行没有重叠,标记为 2;
第三行:一个序列:(2,6),与上一行两个序列重叠,标记为上一行较小的标记,即 1,同时记录等价对 <1,2>;
第四行:两个序列:(1,2),标记为 1,(6,8)标记为 1。

消除等价对

  在上一步中,我们得到了若干个等价对,每一个等价对 <a,b> 表示被标记 a 区域与被标记 b 的区域是连通的,因此,我们希望将每个等价对中的标记更新为同一个标记。用图的遍历即可做到这一点。
   我们将每一个标记看作一个图的结点,每一个等价对看作是图的边,我们要做的就是通过图的遍历来寻找属于同一个最大连通子图的结点。这些结点锁表示的标记是等价的。

这里写图片描述

最后,我们将之前的标记更新为新的标记,如上图所示,之前标记为 1,2,5 的像素标记为 1;标记为 6 3 7 9 8 的像素重新标记为 2;标记为 4 的像素重新标记为 3。至此,拥有相同标记的像素点就构成了一个连通域,而标记的最大值就是连通域的个数。

代码

#include <opencv2\opencv.hpp>
#include <iostream>
#include <utility>
#include <vector>
#include <algorithm>
#include <ctime>
#incl
  • 4
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
二值图像连通域标记是指将二值图像中相邻的像素点组成的区域标记出来,以便于后续对这些区域进行分析处理,比如计算面积、周长等。 实现方法如下: 首先将二值图像中所有像素点初始化为未访问状态,并设置一个计数器count,用于记录连通域的数量。 然后从图像的左上角开始遍历每个像素点,对于每个未访问的像素点,将其标记为已访问,并设置一个连通域编号label,将该像素点的连通域编号保存到一个数组中。 接下来,对该像素点的上下左右四个邻居像素点进行判断,如果邻居像素点的值为1(即与当前像素点属于同一连通域),并且邻居像素点未被访问过,则将邻居像素点标记为已访问,并将其连通域编号设置为label,同时将该像素点的连通域编号保存到数组中。 遍历完整个图像后,count就是连通域的数量,数组中保存了每个像素点所属的连通域编号。 代码实现如下: ``` void labelConnectedComponent(Mat &img, Mat &labels) { int label = 0; int count = 0; int height = img.rows; int width = img.cols; labels = Mat::zeros(height, width, CV_32SC1); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { if (img.at<uchar>(i, j) == 255 && labels.at<int>(i, j) == 0) { label++; stack<Point> s; s.push(Point(j, i)); while (!s.empty()) { Point p = s.top(); s.pop(); int x = p.x; int y = p.y; if (x >= 0 && x < width && y >= 0 && y < height && img.at<uchar>(y, x) == 255 && labels.at<int>(y, x) == 0) { labels.at<int>(y, x) = label; s.push(Point(x - 1, y)); s.push(Point(x + 1, y)); s.push(Point(x, y - 1)); s.push(Point(x, y + 1)); } } } } } count = label; cout << "Connected component count: " << count << endl; } ``` 其中,img为输入的二值图像,labels为输出的连通域标记图像,其每个像素点的值为该点所属的连通域编号。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值