Click here to Skip to main content
Click here to Skip to main content

Connected Component Labeling Algorithm

By , 4 Feb 2013
 

Overview

In five seconds 

Detection of connected objects in an image, mainly used in image analysis and OCR.

In five minutes

Connected-component labeling (alternatively connected-component analysis, blob extraction, region labeling, blob discovery, or region extraction) is an algorithmic application of graph theory, where subsets of connected components are uniquely labeled based on a given heuristic. Connected-component labeling is not to be confused with segmentation.

Connected-component labeling is used in computer vision to detect connected regions in binary digital images, although color images and data with higher-dimensionality can also be processed.[1][2] When integrated into an image recognition system or human-computer interaction interface, connected component labeling can operate on a variety of information.[3][4] Blob extraction is generally performed on the resulting binary image from a thresholding step. Blobs may be counted, filtered, and tracked.

Blob extraction is related to but distinct from blob detection.

What to expect

Input

An image containing two shapes:

336915/input.png

Output

Now each is separated into single images:

336915/1.png

336915/2.png

Code

The Interface IConnectedComponentLabeling holds one function That takes a Bitmap as an input an returns a collection of discovered objects. 

    public interface IConnectedComponentLabeling
    {
        IDictionary<int, Bitmap> Process(Bitmap input);
    }  

Usage:

IConnectedComponentLabeling target = new CCL();
            Bitmap input = new Bitmap(AppDomain.CurrentDomain.BaseDirectory + @"\Test.bmp");

            var images= target.Process(input);
            foreach (var image in images)
            {
                image.Value.Save(savePath + image.Key + ".bmp");
            } 


The implementation class contains the virtual function CheckIsBackGround(), so you can extend the class, and override this method it to suit the background condition of your image:

#region Protected Functions

        protected virtual bool CheckIsBackGround(Pixel currentPixel)
        {
            return currentPixel.color.A == 255 && currentPixel.color.R == 255 && currentPixel.color.G == 255 && currentPixel.color.B == 255;
        }

        #endregion

How it works

first pass (assigning labels)


second pass (aggregation)

Step by step walkthrough

In the beginning, we have this image, we start with currentLabelCount = 1.

336915/Start.png

We found our non-background pixel:

336915/step_1.png

get its non-background neighbors:

336915/step_2.png

None of the neighbors is labeled yet, we set the current pixel to the currentLabelCount and increment it, we also set the label's parent to itself (we'll get into that in a second):

336915/step_3.png

on to the next pixel, this one has a neighbour which is already labeled:

336915/step_4.png

assigns the pixel's parent label to that of the neighbour:

336915/step_5.png

We continue on, none of the neighbours of this pixel is labeled:

336915/step_6.png

We increment currentLabelCount and assign it to the pixel, again its parent is set to itself: 

336915/step_7.png

It gets interesting here, when neighbours have different labels:

336915/step_8.png

  1. We choose main label,i.e: that would be the smallest label in the discovered list--> (1)
  2. We set it to be the parent of the other labels

336915/step_9.png

A few more rounds and we should end up with this. Notice the blue number in the upper right corner, that's the parent label, the de facto one upon which we aggregate later.

336915/step_10.png

That's it, now all we have to do is pass the image again pixel by pixel, getting the root of each (if labeled)
and store it in our patterns' list.

        private Dictionary<int, List<Pixel>> AggregatePatterns(Dictionary<int, Label> allLabels, int width, int height)
        {
            var patterns = new Dictionary<int, List<Pixel>>();

            for (int i = 0; i < height; i++)
            {
                for (int j = 0; j < width; j++)
                {
                    int patternNumber = _board[j, i];

                    if (patternNumber != 0)
                    {
                        patternNumber = allLabels[patternNumber].GetRoot().Name;

                        if (!patterns.ContainsKey(patternNumber))
                        {
                            patterns.Add(patternNumber, new List<Pixel>());
                        }

                        patterns[patternNumber].Add(new Pixel(new Point(j, i), Color.Black));
                    }
                }
            }

            return patterns;
        }

Tricky part: Merging labels 

To join labels in a same set, we have the following class:

using System;
using System.Collections.Generic;
using System.Text;

namespace ConnectedComponentLabeling
{
    internal class Label
    {
        #region Public Properties

        public int Name { get; set; }

        public Label Root { get; set; }

        public int Rank { get; set; }
        #endregion

        #region Constructor

        public Label(int Name)
        {
            this.Name = Name;
            this.Root = this;
            this.Rank = 0;
        }

        #endregion

        #region Public Methods

        internal Label GetRoot()
        {
            if (this.Root != this)
            {
                this.Root = this.Root.GetRoot();
            }

            return this.Root;
        }

        internal void Join(Label root2)
        {
            if (root2.Rank < this.Rank)//is the rank of Root2 less than that of Root1 ?
            {
                root2.Root = this;//yes! then Root1 is the parent of Root2 (since it has the higher rank)
            }
            else //rank of Root2 is greater than or equal to that of Root1
            {
                this.Root = root2;//make Root2 the parent
                if (this.Rank == root2.Rank)//both ranks are equal ?
                {
                    root2.Rank++;//increment Root2, we need to reach a single root for the whole tree
                }
            }
        }

        #endregion
    }
}

Pay special attention to the recursive function GetRoot(), that's how we reach the parent of any label.

Remember this part? this is what the function Join(Label root) does
Now let's say we have 3 labels, 1, 2, and 3, and we picked 1 to be our current label; all we have to do is loop the other labels, if their roots don't match, set their root to that of the label we just picked.  

Conclusion

Hope I delivered a clear explanation, feel free to comment or ask Wink | <img src=
Drawings by zwibbler

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Omar Gameel Salem
Software Developer
Egypt Egypt
Member
Enthusiastic programmer/researcher, passionate to learn new technologies, interested in problem solving,data structures, algorithms and automation.
 
If you have a question\suggestion about one of my articles, or you want an algorithm implemented in C#, feel free to contact me.
 
Résumé
 
vWorker Account

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberscrchhrpn17 May '13 - 21:30 
great work and explanation salem!
GeneralMy vote of 5memberUilleam12 Feb '13 - 11:36 
lovely walk through of the algorithm
QuestionSome problemmemberMember 917258413 Dec '12 - 5:13 
Excellent article! However, it might be some bugs in the code as I tested with one image and the label images are not exactly as I would expect. I can send you the test image if you can take a look. Thanks.
AnswerRe: Some problemmemberOmar Gamil13 Dec '12 - 5:49 
do send! Smile | :)
omargamil@live.com
Questionsome issues [modified]memberfeversky30 Oct '12 - 1:59 
there may be more than two different roots in one region, so we must set Root of these roots to one root in UnifyNeighboringLabelsParents() .
if some black points are on the border, GetDistinctNeighboringLabels() is not correct.
and thanks so much.

modified 30 Oct '12 - 8:55.

QuestionText detectionmemberbutterflyzx412 Aug '12 - 6:42 
Can u plz do give an explanation and demonstration on using CC-labeling for text detection in images? or at least forward me to an example, I especially want to know how to filter out non-text regions in an image. thanks
AnswerRe: Text detectionmemberOmar Gamil12 Aug '12 - 11:13 
Detection of objects is only the first step, how do you know that "2" is 2?
its through pattern recognition[^], teaching the computer how to interpret objects and discard noise using neural networks[^].
 
It's a wholesome topic, there's a lot of good articles here on the CodeProject, but are you trying to create your own OCR engine ? chances are you are better off taking up where others left off, using OpenCV[^] is a good open source project in C++, I don't know if there's one in .net (not just a wrapper).
 
good luck!
QuestionIt's not working properlymemberriyad parvez24 Jun '12 - 1:54 
I am running this algorithm, but the algorithm is labeling every point as a single component
AnswerRe: It's not working properlymemberOmar Gamil24 Jun '12 - 4:25 
can you please provide the picture you are using ?
QuestionHow to use your dll file?membernprabhu8 Apr '12 - 19:30 
How to use your dll file?
AnswerRe: How to use your dll file?memberOmar Gamil8 Apr '12 - 23:03 
using this code (as mentioned in the article)
 
Bitmap input = new Bitmap(path to your image);
            PreProcess target = new PreProcess(input);
            List<Bitmap> actual = target.Extract();
            for (int i = 0; i < actual.Count; i++)
            {
                actual[i].Save(savePath + (i + 1).ToString() + ".bmp");
            }

GeneralMy vote of 5memberAbinash Bishoyi6 Apr '12 - 3:00 
Nice
QuestionYou could do this much faster and in 1 stepmemberleon de boer1 Apr '12 - 21:21 
read the article
 
"A Linear-Time Component-Labeling Algorithm Using Contour Tracing Technique" by Fu Chang, Chun-Jen Chen, and Chi-Jen Lu
 
I have C code coversion but I am using an array of raw bytes bing the pixel colour (0..255).
 
Drop a post if you a hand working it out.
AnswerRe: You could do this much faster and in 1 stepmemberDewey4 Apr '12 - 13:23 
There is NO "C" code in that article.
 
If you have the C code, just post it!
GeneralRe: You could do this much faster and in 1 stepmemberJP van Mackelenbergh5 Apr '12 - 1:58 
Who says that there is ? He just says that he used the article to create the c code. So no need to give such a blunt reply !
GeneralRe: You could do this much faster and in 1 stepmemberleon de boer20 Jun '12 - 16:29 
But he asked so nice Smile | :)
 
Anyhow I have posted the code in an article
 
Connected Component Labeling and Vectorization[^]
AnswerRe: You could do this much faster and in 1 stepmemberyvdh16 Dec '12 - 23:58 
Very correct!
 
I have turned this nice paper years ago into C# code that returns lists of lists of PointF, and its very fast and very robust. The paper is quite straightforward to implement!
GeneralMy vote of 5membermanoj kumar choubey28 Mar '12 - 0:21 
Nice
QuestionHow should I use in my project?memberRayar081223 Mar '12 - 0:21 
Hi,
 

I got CCD cam image with EMGUcv,
 
How should I use in my project?
 
I have already using ur DLL,
 
I want to know how to make it work.
 
thanks for answer.
AnswerRe: How should I use in my project?memberOmar Gamil23 Mar '12 - 5:25 
using this code (as mentioned in the article)
Bitmap input = new Bitmap(path to your image);
            PreProcess target = new PreProcess(input);
            List<Bitmap> actual = target.Extract();
            for (int i = 0; i < actual.Count; i++)
            {
                actual[i].Save(savePath + (i + 1).ToString() + ".bmp");
            }

GeneralNice :)memberHoshiKata8 Mar '12 - 0:48 
I've seen this type of algorithm a few times, OpenCV impliments one. I really liked the explanation, very clear, good examples to help people visualize. Very cool Smile | :)
GeneralRe: Nice :)memberOmar Gamil8 Mar '12 - 3:45 
glad you liked it Phil, Thank you so much for the encouragement Wink | ;)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 4 Feb 2013
Article Copyright 2012 by Omar Gameel Salem
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid