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

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

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



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


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


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


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


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


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



算法流程如下:


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


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



算法实现Class文件解释:


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


同时完成了一些公共方法


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


继承上面的自抽象类


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


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



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



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



算法运行结果:

1334936488_3384.png


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




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

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数字图像处理基础知识 - 必读

相关文章
|
2月前
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【10月更文挑战第4天】在大数据时代,算法效率至关重要。本文从理论入手,介绍时间复杂度和空间复杂度两个核心概念,并通过冒泡排序和快速排序的Python实现详细分析其复杂度。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1);快速排序平均时间复杂度为O(n log n),空间复杂度为O(log n)。文章还介绍了算法选择、分而治之及空间换时间等优化策略,帮助你在大数据挑战中游刃有余。
92 4
|
3天前
|
缓存 算法 搜索推荐
Java中的算法优化与复杂度分析
在Java开发中,理解和优化算法的时间复杂度和空间复杂度是提升程序性能的关键。通过合理选择数据结构、避免重复计算、应用分治法等策略,可以显著提高算法效率。在实际开发中,应该根据具体需求和场景,选择合适的优化方法,从而编写出高效、可靠的代码。
16 6
|
27天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
61 1
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
【MM2024】阿里云 PAI 团队图像编辑算法论文入选 MM2024
阿里云人工智能平台 PAI 团队发表的图像编辑算法论文在 MM2024 上正式亮相发表。ACM MM(ACM国际多媒体会议)是国际多媒体领域的顶级会议,旨在为研究人员、工程师和行业专家提供一个交流平台,以展示在多媒体领域的最新研究成果、技术进展和应用案例。其主题涵盖了图像处理、视频分析、音频处理、社交媒体和多媒体系统等广泛领域。此次入选标志着阿里云人工智能平台 PAI 在图像编辑算法方面的研究获得了学术界的充分认可。
【MM2024】阿里云 PAI 团队图像编辑算法论文入选 MM2024
|
1月前
|
JSON 算法 数据挖掘
基于图论算法有向图PageRank与无向图Louvain算法构建指令的方式方法 用于支撑qwen agent中的统计相关组件
利用图序列进行数据解读,主要包括节点序列分析、边序列分析以及结合节点和边序列的综合分析。节点序列分析涉及节点度分析(如入度、出度、度中心性)、节点属性分析(如品牌、价格等属性的分布与聚类)、节点标签分析(如不同标签的分布及标签间的关联)。边序列分析则关注边的权重分析(如关联强度)、边的类型分析(如管理、协作等关系)及路径分析(如最短路径计算)。结合节点和边序列的分析,如子图挖掘和图的动态分析,可以帮助深入理解图的结构和功能。例如,通过子图挖掘可以发现具有特定结构的子图,而图的动态分析则能揭示图随时间的变化趋势。这些分析方法结合使用,能够从多个角度全面解读图谱数据,为决策提供有力支持。
|
2月前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
2月前
|
机器学习/深度学习 人工智能 算法
【MM2024】面向 StableDiffusion 的多目标图像编辑算法 VICTORIA
阿里云人工智能平台 PAI 团队与华南理工大学合作在国际多媒体顶级会议 ACM MM2024 上发表 VICTORIA 算法,这是一种面向 StableDiffusion 的多目标图像编辑算法。VICTORIA 通过文本依存关系来修正图像编辑过程中的交叉注意力图,从而确保关系对象的一致性,支持用户通过修改描述性提示一次性编辑多个目标。
|
2月前
|
存储 算法 Java
【JVM】垃圾释放方式:标记-清除、复制算法、标记-整理、分代回收
【JVM】垃圾释放方式:标记-清除、复制算法、标记-整理、分代回收
70 2
|
2月前
|
算法
PID算法原理分析
【10月更文挑战第12天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
3月前
|
XML JavaScript 前端开发
学习react基础(1)_虚拟dom、diff算法、函数和class创建组件
本文介绍了React的核心概念,包括虚拟DOM、Diff算法以及如何通过函数和类创建React组件。
39 3