图像分析之连通组件标记算法

简介: 图像处理之连接组件标记算法   连接组件标记算法(connected component labeling algorithm)是图像分析中最常用的算法之一, 算法的实质是扫描一幅图像的每个像素,对于像素值相同的分为相同的组(group),最终得到 图像中所有的像素连通组件。

图像处理之连接组件标记算法

 

连接组件标记算法(connected component labeling algorithm)是图像分析中最常用的算法之一,

算法的实质是扫描一幅图像的每个像素,对于像素值相同的分为相同的组(group),最终得到

图像中所有的像素连通组件。扫描的方式可以是从上到下,从左到右,对于一幅有N个像

素的图像来说,最大连通组件个数为N/2。扫描是基于每个像素单位,对于二值图像而言,

连通组件集合可以是V={1}或者V={0}, 取决于前景色与背景色的不同。对于灰度图像来说,

连图组件像素集合可能是一系列在0 ~ 255之间的灰度值。

 

算法流程如下:

1.      首先扫描当前像素相邻的八邻域像素值,发现连通像素加以标记。

2.      完全扫描所有像素点之后,根据标记将所有连通组件合并。

 

算法实现Class文件解释:

AbstractConnectedComponentLabel一个抽象的Class定义了抽象方法doConntectedLabel()

同时完成了一些公共方法

ConnectedComponentLabelAlgOne一个容易读懂的连接组件算法完成,没有任何优化,

继承上面的自抽象类

ConnectedComponentLabelAlgTwo一个快速的连接组件算法,基于算法优化,取当前像素

的四邻域完成扫描与标记合并。

 

LabelPixelInfo是两个数据结构,用来存储算法计算过程中的中间变量。

 

ImageLabelFilter用来测试算法的驱动类,ImageAnalysisUI是现实测试结果的UI

 

算法运行结果:

 

根据标记的索引将组件着色。


定义数据结构的代码如下:

public class Label {
	
	private int index;
	private Label root;
	
	public Label(int index) {
		this.index = index;
		this.root = this;
	}
	
	public Label getRoot() {
		if(this.root != this) {
			this.root = this.root.getRoot();
		}
		return root;
	}

	public int getIndex() {
		return index;
	}

	public void setIndex(int index) {
		this.index = index;
	}

	public void setRoot(Label root) {
		this.root = root;
	}
}

Pixelnfo的代码如下:

package com.gloomyfish.image.analysis;

public class PixelInfo {
	private int value; // pixel value
	private int xp;
	private int yp;
	
	public PixelInfo(int pixelValue, int yp, int xp) {
		this.value = pixelValue;
		this.yp = yp;
		this.xp = xp;
	}
	
	public int getValue() {
		return value;
	}
	public void setValue(int value) {
		this.value = value;
	}
	public int getXp() {
		return xp;
	}
	public void setXp(int xp) {
		this.xp = xp;
	}
	public int getYp() {
		return yp;
	}
	public void setYp(int yp) {
		this.yp = yp;
	}
}
抽象的组件连通标记算法Class如下:

public abstract class AbstractConnectedComponentLabel {
	
	protected int width;
	protected int height;
	protected Color fgColor;
	protected int[] inPixels;
	protected int[][] chessborad;
	protected Map<Integer, Integer> neighbourMap;
	
	public int getWidth() {
		return width;
	}

	public void setWidth(int width) {
		this.width = width;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}
	
	public abstract Map<Integer, List<PixelInfo>> doConntectedLabel();

	public boolean isForeGround(int tr, int tg, int tb) {
		if(tr == fgColor.getRed() && tg == fgColor.getGreen() && tb == fgColor.getBlue()) {
			return true;
		} else {
			return false;
		}
		
	}

}
实现抽象类的算法one的代码如下:

import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ConnectedComponentLabelAlgOne extends AbstractConnectedComponentLabel {

	public ConnectedComponentLabelAlgOne(Color fgColor, int[] srcPixel, int width, int height) {
		this.fgColor = fgColor;
		this.width = width;
		this.height = height;
		this.inPixels = srcPixel;
		this.chessborad = new int[height][width];
		for(int i=0; i<height; i++) {
			for(int j=0; j<width; j++) {
				chessborad[i][j] = 0;
			}
		}
		this.neighbourMap = new HashMap<Integer, Integer>(); 
	}
	
	// assume the input image data is binary image.
	public Map<Integer, List<PixelInfo>> doConntectedLabel() {
		System.out.println("start to do connected component labeling algorithm");
		int index = 0;
		int labelCount = 0;
		Label currentLabel = new Label(0);
		HashMap<Integer, Label> allLabels = new HashMap<Integer, Label>();
        for(int row=0; row<height; row++) {
        	int ta = 0, tr = 0, tg = 0, tb = 0;
        	for(int col=0; col<width; col++) {
        		index = row * width + col;
        		ta = (inPixels[index] >> 24) & 0xff;
                tr = (inPixels[index] >> 16) & 0xff;
                tg = (inPixels[index] >> 8) & 0xff;
                tb = inPixels[index] & 0xff;
                if(isForeGround(tr, tg, tb)) {
                	getNeighboringLabels(row, col);
                	if(neighbourMap.size() == 0) {
                		currentLabel.setIndex(++labelCount);
                		allLabels.put(labelCount,new Label(labelCount));

                	} else {
                		for(Integer pixelLabel : neighbourMap.keySet().toArray(new Integer[0])) {
                			currentLabel.setIndex(pixelLabel);
                			break;
                		}
                		mergeLabels(currentLabel.getIndex(), neighbourMap, allLabels);
                	}
                	chessborad[row][col] = currentLabel.getIndex();
                }
        	}
        }
        
        Map<Integer, List<PixelInfo>> connectedLabels = consolidateAllLabels(allLabels);
		return connectedLabels;
	}
	
	private Map<Integer, List<PixelInfo>> consolidateAllLabels(HashMap<Integer, Label> allLabels) {
		Map<Integer, List<PixelInfo>> patterns = new HashMap<Integer, List<PixelInfo>>();
        int patternNumber;
        List<PixelInfo> shape;
        for (int i = 0; i < this.height; i++)
        {
            for (int j = 0; j < this.width; j++)
            {
                patternNumber = chessborad[i][j];
                if (patternNumber != 0)
                {
                    patternNumber = allLabels.get(patternNumber).getRoot().getIndex();
                    if (!patterns.containsKey(patternNumber))
                    {
                        shape = new ArrayList<PixelInfo>();
                        shape.add(new PixelInfo(Color.BLUE.getRGB(), i, j));
                    }
                    else
                    {
                        shape = patterns.get(patternNumber);
                        shape.add(new PixelInfo(Color.BLUE.getRGB(), i, j));
                    }
                    patterns.put(patternNumber, shape);
                }
            }
        }
		return patterns;
	}

	private void mergeLabels(int index, Map<Integer, Integer> neighbourMap,
			HashMap<Integer, Label> allLabels) {
		Label root = allLabels.get(index).getRoot();
		Label neighbour;
		for(Integer key : neighbourMap.keySet().toArray(new Integer[0])) {
			 if (key != index)
             {
				 neighbour = allLabels.get(key);
				 if(neighbour.getRoot() != root) {
					 neighbour.setRoot(neighbour.getRoot());// thanks zhen712,
				 }
             }
		}
		
	}

	/**
	 * get eight neighborhood pixels
	 * 
	 * @param row
	 * @param col
	 * @return
	 */
	public  void getNeighboringLabels(int row, int col) {
		neighbourMap.clear();
		for(int i=-1; i<=1; i++) {
			int yp = row + i;
			if(yp >=0 && yp < this.height) {
				for(int j=-1; j<=1; j++) {
					if(i == 0 && j==0) continue; // ignore/skip center pixel/itself
					int xp = col + j;
					if(xp >=0 && xp < this.width) {
						if(chessborad[yp][xp] != 0) {
							if(!neighbourMap.containsKey(chessborad[yp][xp])) {
								neighbourMap.put(chessborad[yp][xp],0);
							}
						}
					}
				}
			}
		}
	}
}
测试代码可以参考以前的文章 -  

Java数字图像处理基础知识 - 必读

目录
相关文章
|
24天前
|
人工智能 算法 BI
第一周算法设计与分析 D : 两面包夹芝士
这篇文章介绍了解决算法问题"两面包夹芝士"的方法,通过找出两个数组中的最大最小值,计算这两个值之间的整数个数,包括特判不存在整数的情况。
|
16天前
|
算法 数据可视化
基于SSA奇异谱分析算法的时间序列趋势线提取matlab仿真
奇异谱分析(SSA)是一种基于奇异值分解(SVD)和轨迹矩阵的非线性、非参数时间序列分析方法,适用于提取趋势、周期性和噪声成分。本项目使用MATLAB 2022a版本实现从强干扰序列中提取趋势线,并通过可视化展示了原时间序列与提取的趋势分量。代码实现了滑动窗口下的奇异值分解和分组重构,适用于非线性和非平稳时间序列分析。此方法在气候变化、金融市场和生物医学信号处理等领域有广泛应用。
|
2天前
|
机器学习/深度学习 存储 人工智能
文本情感识别分析系统Python+SVM分类算法+机器学习人工智能+计算机毕业设计
使用Python作为开发语言,基于文本数据集(一个积极的xls文本格式和一个消极的xls文本格式文件),使用Word2vec对文本进行处理。通过支持向量机SVM算法训练情绪分类模型。实现对文本消极情感和文本积极情感的识别。并基于Django框架开发网页平台实现对用户的可视化操作和数据存储。
11 0
文本情感识别分析系统Python+SVM分类算法+机器学习人工智能+计算机毕业设计
|
24天前
|
算法
算法设计与分析作业
这篇文章是关于算法设计与分析的作业,其中包含了两个算法实现:一个是使用分治算法实现的十进制大整数相乘(包括加法、减法和乘法函数),并进行了正确性和健壮性测试;另一个是使用快速排序思想实现的分治查找第K小元素的程序,并分析了其平均和最坏时间复杂度。
算法设计与分析作业
|
18天前
|
算法 数据可视化 数据安全/隐私保护
基于LK光流提取算法的图像序列晃动程度计算matlab仿真
该算法基于Lucas-Kanade光流方法,用于计算图像序列的晃动程度。通过计算相邻帧间的光流场并定义晃动程度指标(如RMS),可量化图像晃动。此版本适用于Matlab 2022a,提供详细中文注释与操作视频。完整代码无水印。
|
20天前
|
编解码 算法 图形学
同一路RTSP|RTMP流如何同时回调YUV和RGB数据实现渲染和算法分析
我们播放RTSP|RTMP流,如果需要同时做渲染和算法分析的话,特别是渲染在上层实现(比如Unity),算法是python这种情况,拉两路流,更耗费带宽和性能,拉一路流,同时回调YUV和RGB数据也可以,但是更灵活的是本文提到的按需转算法期望的RGB数据,然后做算法处理
|
24天前
|
人工智能 算法
第一周算法设计与分析:C : 200和整数对之间的情缘
这篇文章介绍了解决算法问题"200和整数对之间的情缘"的方法,通过统计数组中每个数模200的余数,并计算每个同余类中数的组合数来找出所有满足条件的整数对(i, j),使得\( A_i - A_j \)是200的整数倍。
|
24天前
|
人工智能 算法
第一周算法设计与分析 G : 排队援救
这篇文章介绍了解决算法问题"排队援救"的方法,通过使用队列和映射来模拟救援点的排队过程,并确定最终得到救援的人的顺序和编号。
|
24天前
|
算法 C++
第一周算法设计与分析 E : 构造回文串
这篇文章介绍了解决算法问题"构造回文串"的方法,即判断给定的整数N(视为字符串)是否可以通过在前面添加任意个0(或不添加)来构造一个回文串,并给出了相应的C++代码实现。
|
24天前
|
算法 C++
第一周算法设计与分析 H : 括号匹配
这篇文章介绍了解决算法问题"括号匹配"的方法,通过使用栈来检查给定字符串中的括号是否合法匹配,并提供了相应的C++代码实现。