Click here to Skip to main content
15,881,588 members
Articles / Multimedia / GDI+

Computer Vision Applications with C# - Part IV

Rate me:
Please Sign up or sign in to vote.
4.87/5 (15 votes)
11 Aug 2009CPOL4 min read 68.1K   5.5K   82  
KMeans Clustering
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Collections;
using System.Collections.Specialized;

using ImageProcessor;

namespace CVMeanshift
{
    public unsafe class KMeans
    {
        public class Distance
        {
            public Distance(float d) { _d = d; }
            public float Measure
            {
                get { return _d; }
                set { _d = value; }
            }
            private float _d;
        }

        public class Cluster
        {
            public Cluster(float R, float G, float B) 
            {
                _centroid1 = R;
                _centroid2 = G;
                _centroid3 = B;
            }
            public float CentroidR
            {
                get { return _centroid1; }
                set { _centroid1 = value; }
            }
            public float CentroidG
            {
                get { return _centroid2; }
                set { _centroid2 = value; }
            }
            public float CentroidB
            {
                get { return _centroid3; }
                set { _centroid3 = value; }
            }
            private float _centroid1;
            private float _centroid2;
            private float _centroid3;
        }

        public KMeans(Bitmap bmp, int numCluster, Colour.Types model) 
        {
            _image = (Bitmap)bmp.Clone();
            _processedImage = (Bitmap)bmp.Clone();
            _model = model;

            _previousCluster = new Dictionary<string, Cluster>();
            _currentCluster = new Dictionary<string, Cluster>();
            FindTopXColours(numCluster); //find top X colours in the image
            //create clusters for top X colours
            for (int i = 0; i < _topColours.Length; i++)
            {
                PixelData pd = Colour.GetPixelData(_topColours[i].R, _topColours[i].G, _topColours[i].B, model);

                _previousCluster.Add(_topColours[i].Name, new Cluster(pd.Ch1, pd.Ch2, pd.Ch3));
                _currentCluster.Add(_topColours[i].Name, new Cluster(pd.Ch1, pd.Ch2, pd.Ch3));
            }
        }

        public Bitmap ProcessedImage { get { return _processedImage; } }

        public bool Converged { get { return _converged; } }

        public void Iterate()
        {
            _colourClusterAllocation = new Hashtable(); //for keeping track of colour<->cluster allocation
            _pixelDataClusterAllocation = new Hashtable();
            _clusterColours = new Hashtable();

            UnsafeBitmap fastBitmap = new UnsafeBitmap(_image);
            fastBitmap.LockBitmap();
            Point size = fastBitmap.Size;
            BGRA* pPixel;

            for (int y = 0; y < size.Y; y++)
            {
                pPixel = fastBitmap[0, y];
                for (int x = 0; x < size.X; x++)
                {
                    PixelData pd = Colour.GetPixelData(pPixel, _model);
                    AllocateToCluster(pd);
                    
                    //increment the pointer
                    pPixel++;
                }
            }
            fastBitmap.UnlockBitmap();

            CalculateClusterCentroids();

            _processedImage = (Bitmap)_image.Clone();

            //segment the image based on the cluster
            fastBitmap = new UnsafeBitmap(_processedImage);
            fastBitmap.LockBitmap();
            for (int y = 0; y < size.Y; y++)
            {
                pPixel = fastBitmap[0, y];
                for (int x = 0; x < size.X; x++)
                {
                    PixelData pd = Colour.GetPixelData(pPixel, _model);
                    Color newClr = (Color)_clusterColours[pd.Name];

                    pPixel->red = newClr.R;
                    pPixel->green = newClr.G;
                    pPixel->blue = newClr.B;

                   //increment the pointer
                    pPixel++;
                }
            }

            fastBitmap.UnlockBitmap();

            CheckConvergence();

        }

        private void CheckConvergence()
        {
            //if current and previous cluster centroids are the same then converged
            bool match = true;
            foreach (KeyValuePair<string, Cluster> cluster in _currentCluster)
            {
                if (((int)cluster.Value.CentroidR != (int)_previousCluster[cluster.Key].CentroidR)
                    && ((int)cluster.Value.CentroidG != (int)_previousCluster[cluster.Key].CentroidG)
                    && ((int)cluster.Value.CentroidB != (int)_previousCluster[cluster.Key].CentroidB))
                {
                    match = false;
                    break;
                }
            }
            if (!match)
            {
                foreach (KeyValuePair<string, Cluster> cluster in _currentCluster)
                {
                    _previousCluster[cluster.Key].CentroidR=cluster.Value.CentroidR;
                        _previousCluster[cluster.Key].CentroidG=cluster.Value.CentroidG;
                        _previousCluster[cluster.Key].CentroidB = cluster.Value.CentroidB;                    
                }
            }
            _converged = match;
        }

        private void CalculateClusterCentroids()
        {
            foreach (KeyValuePair<string, Cluster> cluster in _currentCluster)
            {  
                List<PixelData> clrList = (List<PixelData>)_pixelDataClusterAllocation[cluster.Key];
                float cR = 0;
                float cG = 0;
                float cB = 0;
                foreach (PixelData clr in clrList)
                {
                    cR += clr.Ch1;
                    cG += clr.Ch2;
                    cB += clr.Ch3;

                    if (!_clusterColours.ContainsKey(clr.Name))
                    {
                        _clusterColours.Add(clr.Name, Color.FromArgb((int)cluster.Value.CentroidR, (int)cluster.Value.CentroidG, (int)cluster.Value.CentroidB));                        
                    }
                }
                float count = clrList.Count + 1; //total of colours plus 1 for the existing centroid
                cluster.Value.CentroidR = (cluster.Value.CentroidR + cR) / count; //average to find new centroid
                cluster.Value.CentroidG = (cluster.Value.CentroidG + cG) / count;
                cluster.Value.CentroidB = (cluster.Value.CentroidB + cB) / count;
            }
        }

        private void AllocateToCluster(PixelData pd)
        {
            //find distance of this colour from each cluster centroid
            Dictionary<string, Distance> distances = new Dictionary<string, Distance>();

            foreach (KeyValuePair<string, Cluster> c in _currentCluster)
            {
                float d = (float)Math.Sqrt(
                    (double)Math.Pow((c.Value.CentroidR - pd.Ch1), 2) +
                    (double)Math.Pow((c.Value.CentroidG - pd.Ch2), 2) +
                    (double)Math.Pow((c.Value.CentroidB - pd.Ch3), 2)
                );
                distances.Add(c.Key, new Distance(d));
            }

            //allocate this colour to the closest cluster based on distance
            List<KeyValuePair<string, Distance>> list = new List<KeyValuePair<string, Distance>>();
            list.AddRange(distances);

            list.Sort(delegate(KeyValuePair<string, Distance> kvp1, KeyValuePair<string, Distance> kvp2)
            { return Comparer<float>.Default.Compare(kvp1.Value.Measure, kvp2.Value.Measure); });

            //assign to closest cluster
            if (_pixelDataClusterAllocation.ContainsKey(list[0].Key))
            {
                ((List<PixelData>)_pixelDataClusterAllocation[list[0].Key]).Add(pd);
            }
            else
            {
                List<PixelData> clrList = new List<PixelData>();
                clrList.Add(pd);
                _pixelDataClusterAllocation.Add(list[0].Key, clrList);
            }
        }

        private void FindTopXColours(int numColours)
        {
            Dictionary<string, ColourCount> colours = new Dictionary<string, ColourCount>();
            UnsafeBitmap fastBitmap = new UnsafeBitmap(_image);
            fastBitmap.LockBitmap();
            Point size = fastBitmap.Size;
            BGRA* pPixel;

            for (int y = 0; y < size.Y; y++)
            {
                pPixel = fastBitmap[0, y];
                for (int x = 0; x < size.X; x++)
                {
                    //get the bin index for the current pixel colour
                    Color clr = Color.FromArgb(pPixel->red, pPixel->green, pPixel->blue);

                    if (colours.ContainsKey(clr.Name))
                    {
                        ((ColourCount)colours[clr.Name]).Count++;
                    }
                    else
                        colours.Add(clr.Name, new ColourCount(clr, 1));

                    //increment the pointer
                    pPixel++;
                }
            }

            fastBitmap.UnlockBitmap();

            //instantiate using actual colours found - which might be less than numColours
            if (colours.Count < numColours)
                numColours = colours.Count;

            _topColours = new Color[numColours];

            List<KeyValuePair<string, ColourCount>> summaryList = new List<KeyValuePair<string, ColourCount>>();
            summaryList.AddRange(colours);

            summaryList.Sort(delegate(KeyValuePair<string, ColourCount> kvp1, KeyValuePair<string, ColourCount> kvp2)
            { return Comparer<int>.Default.Compare(kvp2.Value.Count, kvp1.Value.Count); });


            for (int i = 0; i < _topColours.Length; i++)
            {
                _topColours[i] = Color.FromArgb(summaryList[i].Value.Colour.R, summaryList[i].Value.Colour.G, summaryList[i].Value.Colour.B);
            }
        }

        private Color[] _topColours;
        private Colour.Types _model;
        private Dictionary<string, Cluster> _previousCluster;
        private Dictionary<string, Cluster> _currentCluster;
        private Hashtable _colourClusterAllocation;
        private Hashtable _pixelDataClusterAllocation;
        private Hashtable _clusterColours;
        private bool _converged = false;
        private Bitmap _image;
        private Bitmap _processedImage;
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
Australia Australia
I have been in the IT industry since April 1996. My main expertise is in Microsoft space.

Coming from engineering background, any application of programming to engineering and related fields easily excites me. I like to use OO and design patterns and find them very useful.

I have been an avid reader of CodeProject. I decided it was time to make a commitment to make my contribution to the community - so here I am.

My Website: http://www.puresolutions-online.com

Comments and Discussions