... here now follow the classes for the ChainCode, add them to your project
(Code for the WinForm in the last answer):
Make sure, you reference the following namespaces:
using System.Collections;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
// This class implements a chaincode finder (crack code), as an adaption of
// V. Kovalevsky's crack-code development.
// (PLEASE NOTE THAT THESE ARE *NOT* HTTPS CONNECTIONS! It's an old web-site.)
// http://www.miszalok.de/Samples/CV/ChainCode/chain_code.htm and
// http://www.miszalok.de/Lectures/L08_ComputerVision/CrackCode/CrackCode_d.htm (german only). See also
// http://www.miszalok.de/Samples/CV/ChainCode/chaincode_kovalev_e.htm
//As the name crackcode says, we are moving on the (invisible) "cracks" in between the pixels to find the outlines of objects
//Please note that I dont use the /unsafe switch and byte-pointers, since I dont know, whether you are allowed to use that in your code...
public class ChainFinder
{
private int _threshold = 0;
private bool _nullCells = false;
private Point _start = new Point(0, 0);
private int _height = 0;
public bool AllowNullCells
{
get
{
return _nullCells;
}
set
{
_nullCells = value;
}
}
public List<ChainCode>? GetOutline(Bitmap bmp, int threshold, bool grayscale, int range, bool excludeInnerOutlines, int initialValueToCheck, bool doReverse)
{
BitArray? fbits = null; //Array to hold the information about processed pixels
_threshold = threshold;
_height = bmp.Height;
try
{
List<ChainCode> fList = new List<ChainCode>();
//Please note that the bitarray is one "column" larger than the bitmap's width
fbits = new BitArray((bmp.Width + 1) * bmp.Height, false);
//is the condition so, that the collected coordinate's pixel/color channel values are greater than the threshold,
//or lower (then use the reversed switch, maybe with an approppriate initial value set)
if (doReverse)
FindChainCodeRev(bmp, fList, fbits, grayscale, range, excludeInnerOutlines, initialValueToCheck);
else
FindChainCode(bmp, fList, fbits, grayscale, range, excludeInnerOutlines, initialValueToCheck);
return fList;
}
catch /*(Exception exc)*/
{
if (fbits != null)
fbits = null;
}
return null;
}
// PLEASE NOTE THAT THIS IS *NOT* A HTTPS CONNECTION! It's an old web-site.
// Adaption von Herrn Prof. Dr.Ing. Dr.med. Volkmar Miszalok, siehe: http://www.miszalok.de/Samples/CV/ChainCode/chain_code.htm
private void FindChainCode(Bitmap b, List<ChainCode> fList, BitArray fbits, bool grayscale, int range, bool excludeInnerOutlines, int initialValueToCheck)
{
SByte[,] Negative = new SByte[,] { { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, -1 } };
SByte[,] Positive = new SByte[,] { { 0, 0 }, { -1, 0 }, { -1, -1 }, { 0, -1 } };
Point LeftInFront = new Point();
Point RightInFront = new Point();
bool LeftInFrontGreaterTh;
bool RightInFrontGreaterTh;
int direction = 1;
BitmapData? bmData = null;
//if (!AvailMem.AvailMem.checkAvailRam(b.Width * b.Height * 4L))
// return;
try
{
bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int stride = bmData.Stride;
//copy the BitmapBits to a byte-array for processing
byte[]? p = new byte[(bmData.Stride * bmData.Height) - 1 + 1];
Marshal.Copy(bmData.Scan0, p, 0, p.Length);
while (start_crack_search(bmData, p, fbits, grayscale, range, initialValueToCheck))
{
//setup and add the first found pixel to our results list
ChainCode cc = new ChainCode();
cc.start = _start;
// cc.Coord.Add(_start)
int x = _start.X;
int y = _start.Y + 1;
direction = 1;
cc.Chain.Add(direction);
//as long as we have not reached the starting pixel again, do processing steps
while (x != _start.X || y != _start.Y)
{
LeftInFront.X = x + Negative[direction, 0];
LeftInFront.Y = y + Negative[direction, 1];
RightInFront.X = x + Positive[direction, 0];
RightInFront.Y = y + Positive[direction, 1];
//add the correct pixel
switch (direction)
{
case 0:
{
cc.Coord.Add(new Point(LeftInFront.X - 1, LeftInFront.Y));
break;
}
case 1:
{
cc.Coord.Add(new Point(LeftInFront.X, LeftInFront.Y - 1));
break;
}
case 2:
{
cc.Coord.Add(new Point(LeftInFront.X + 1, LeftInFront.Y));
break;
}
case 3:
{
cc.Coord.Add(new Point(LeftInFront.X, LeftInFront.Y + 1));
break;
}
}
//now do the core algorithm steps, description above
LeftInFrontGreaterTh = false;
RightInFrontGreaterTh = false;
if (LeftInFront.X >= 0 && LeftInFront.X < b.Width && LeftInFront.Y >= 0 && LeftInFront.Y < b.Height)
{
if (!grayscale)
LeftInFrontGreaterTh = p[LeftInFront.Y * stride + LeftInFront.X * 4 + 3] > _threshold;
else if (range > 0)
LeftInFrontGreaterTh = ((p[LeftInFront.Y * stride + LeftInFront.X * 4] > _threshold) && (p[LeftInFront.Y * stride + LeftInFront.X * 4] <= _threshold + range));
else
LeftInFrontGreaterTh = p[LeftInFront.Y * stride + LeftInFront.X * 4] > _threshold;
}
if (RightInFront.X >= 0 && RightInFront.X < b.Width && RightInFront.Y >= 0 && RightInFront.Y < b.Height)
{
if (!grayscale)
RightInFrontGreaterTh = p[RightInFront.Y * stride + RightInFront.X * 4 + 3] > _threshold;
else if (range > 0)
RightInFrontGreaterTh = ((p[RightInFront.Y * stride + RightInFront.X * 4] > _threshold) && (p[RightInFront.Y * stride + RightInFront.X * 4] <= _threshold + range));
else
RightInFrontGreaterTh = p[RightInFront.Y * stride + RightInFront.X * 4] > _threshold;
}
//set new direction (3 cases, but only 2 of them change the direction
//(LeftInFrontGreaterTh + !RightInFrontGreaterTh = move straight on))
if (RightInFrontGreaterTh && (LeftInFrontGreaterTh || _nullCells))
direction = (direction + 1) % 4;
else if (!LeftInFrontGreaterTh && (!RightInFrontGreaterTh || !_nullCells))
direction = (direction + 3) % 4;
cc.Chain.Add(direction);
// fbits (always record upper pixel)
switch (direction)
{
case 0:
{
x += 1;
cc.Area += y;
break;
}
case 1:
{
y += 1;
fbits.Set((y - 1) * (b.Width + 1) + x, true);
break;
}
case 2:
{
x -= 1;
cc.Area -= y;
break;
}
case 3:
{
y -= 1;
fbits.Set(y * (b.Width + 1) + x, true);
break;
}
}
//if we finally reach the starting pixel again, add a final coord and chain-direction if one of the distance-constraints below is met.
//This happens always due to the setup of the algorithm (adding the coord to the ChainCode for the last set direction)
if (x == _start.X && y == _start.Y)
{
if (Math.Abs(cc.Coord[cc.Coord.Count - 1].X - x) > 1 || Math.Abs(cc.Coord[cc.Coord.Count - 1].Y - y) > 1)
{
if (Math.Abs(cc.Coord[cc.Coord.Count - 1].X - x) > 1)
{
cc.Coord.Add(new Point(cc.Coord[cc.Coord.Count - 1].X + 1, cc.Coord[cc.Coord.Count - 1].Y));
cc.Chain.Add(0);
}
if (Math.Abs(cc.Coord[cc.Coord.Count - 1].Y - y) > 1)
{
cc.Coord.Add(new Point(cc.Coord[cc.Coord.Count - 1].X, cc.Coord[cc.Coord.Count - 1].Y + 1));
cc.Chain.Add(1);
}
break;
}
}
}
bool isInnerOutline = false;
if (excludeInnerOutlines)
{
if (cc.Chain[cc.Chain.Count - 1] == 0)
{
isInnerOutline = true;
break;
}
}
//add the list to the results list
if (!isInnerOutline)
{
cc.Coord.Add(_start);
fList.Add(cc);
}
}
p = null;
b.UnlockBits(bmData);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
try
{
if(bmData != null)
b.UnlockBits(bmData);
}
catch
{
}
}
}
private bool start_crack_search(BitmapData bmData, byte[] p, BitArray fbits, bool grayscale, int range, int initialValueToCheck)
{
int left = 0;
int stride = bmData.Stride;
for (int y = _start.Y; y <= bmData.Height - 1; y++)
{
for (int x = 0; x <= bmData.Width - 1; x++)
{
if (x > 0)
{
if (!grayscale)
left = p[y * stride + (x - 1) * 4 + 3];
else
left = p[y * stride + (x - 1) * 4];
}
else
left = initialValueToCheck;
if (!grayscale)
{
if ((left <= _threshold) && (p[y * stride + x * 4 + 3] > _threshold) && (fbits.Get(y * (bmData.Width + 1) + x) == false))
{
_start.X = x;
_start.Y = y;
fbits.Set(y * (bmData.Width + 1) + x, true);
//OnProgressPlus();
return true;
}
}
else if (range > 0)
{
if ((left <= _threshold) && (p[y * stride + x * 4] > _threshold) && (p[y * stride + x * 4] <= _threshold + range) && (fbits.Get(y * (bmData.Width + 1) + x) == false))
{
_start.X = x;
_start.Y = y;
fbits.Set(y * (bmData.Width + 1) + x, true);
//OnProgressPlus();
return true;
}
}
else if ((left <= _threshold) && (p[y * stride + x * 4] > _threshold) && (fbits.Get(y * (bmData.Width + 1) + x) == false))
{
_start.X = x;
_start.Y = y;
fbits.Set(y * (bmData.Width + 1) + x, true);
//OnProgressPlus();
return true;
}
}
}
return false;
}
// PLEASE NOTE THAT THIS IS *NOT* A HTTPS CONNECTION! It's an old web-site.
// Adaption von Herrn Prof. Dr.Ing. Dr.med. Volkmar Miszalok, siehe: http://www.miszalok.de/Samples/CV/ChainCode/chain_code.htm
private void FindChainCodeRev(Bitmap b, List<ChainCode> fList, BitArray fbits, bool grayscale, int range, bool excludeInnerOutlines, int initialValueToCheck)
{
SByte[,] Negative = new SByte[,] { { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, -1 } };
SByte[,] Positive = new SByte[,] { { 0, 0 }, { -1, 0 }, { -1, -1 }, { 0, -1 } };
Point LeftInFront = new Point();
Point RightInFront = new Point();
bool LeftInFrontGreaterTh;
bool RightInFrontGreaterTh;
int direction = 1;
BitmapData? bmData = null;
//if (!AvailMem.AvailMem.checkAvailRam(b.Width * b.Height * 4L))
// return;
try
{
bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int stride = bmData.Stride;
byte[]? p = new byte[(bmData.Stride * bmData.Height) - 1 + 1];
Marshal.Copy(bmData.Scan0, p, 0, p.Length);
while (start_crack_searchRev(bmData, p, fbits, grayscale, range, initialValueToCheck))
{
ChainCode cc = new ChainCode();
cc.start = _start;
int x = _start.X;
int y = _start.Y + 1;
direction = 1;
cc.Chain.Add(direction);
while (x != _start.X || y != _start.Y)
{
LeftInFront.X = x + Negative[direction, 0];
LeftInFront.Y = y + Negative[direction, 1];
RightInFront.X = x + Positive[direction, 0];
RightInFront.Y = y + Positive[direction, 1];
switch (direction)
{
case 0:
{
cc.Coord.Add(new Point(LeftInFront.X - 1, LeftInFront.Y));
break;
}
case 1:
{
cc.Coord.Add(new Point(LeftInFront.X, LeftInFront.Y - 1));
break;
}
case 2:
{
cc.Coord.Add(new Point(LeftInFront.X + 1, LeftInFront.Y));
break;
}
case 3:
{
cc.Coord.Add(new Point(LeftInFront.X, LeftInFront.Y + 1));
break;
}
}
LeftInFrontGreaterTh = false;
RightInFrontGreaterTh = false;
if (LeftInFront.X >= 0 && LeftInFront.X < b.Width && LeftInFront.Y >= 0 && LeftInFront.Y < b.Height)
{
if (!grayscale)
LeftInFrontGreaterTh = p[LeftInFront.Y * stride + LeftInFront.X * 4 + 3] < _threshold;
else if (range > 0)
LeftInFrontGreaterTh = ((p[LeftInFront.Y * stride + LeftInFront.X * 4] < _threshold) && (p[LeftInFront.Y * stride + LeftInFront.X * 4] >= _threshold + range));
else
LeftInFrontGreaterTh = p[LeftInFront.Y * stride + LeftInFront.X * 4] < _threshold;
}
if (RightInFront.X >= 0 && RightInFront.X < b.Width && RightInFront.Y >= 0 && RightInFront.Y < b.Height)
{
if (!grayscale)
RightInFrontGreaterTh = p[RightInFront.Y * stride + RightInFront.X * 4 + 3] < _threshold;
else if (range > 0)
RightInFrontGreaterTh = ((p[RightInFront.Y * stride + RightInFront.X * 4] < _threshold) && (p[RightInFront.Y * stride + RightInFront.X * 4] >= _threshold + range));
else
RightInFrontGreaterTh = p[RightInFront.Y * stride + RightInFront.X * 4] < _threshold;
}
if (RightInFrontGreaterTh && (LeftInFrontGreaterTh || _nullCells))
direction = (direction + 1) % 4;
else if (!LeftInFrontGreaterTh && (!RightInFrontGreaterTh || !_nullCells))
direction = (direction + 3) % 4;
cc.Chain.Add(direction);
// fbits (immer oberen punkt aufzeichnen)
switch (direction)
{
case 0:
{
x += 1;
cc.Area += y;
break;
}
case 1:
{
y += 1;
fbits.Set((y - 1) * (b.Width + 1) + x, true);
break;
}
case 2:
{
x -= 1;
cc.Area -= y;
break;
}
case 3:
{
y -= 1;
fbits.Set(y * (b.Width + 1) + x, true);
break;
}
}
if (x == _start.X && y == _start.Y)
{
if (Math.Abs(cc.Coord[cc.Coord.Count - 1].X - x) > 1 || Math.Abs(cc.Coord[cc.Coord.Count - 1].Y - y) > 1)
{
if (Math.Abs(cc.Coord[cc.Coord.Count - 1].X - x) > 1)
{
cc.Coord.Add(new Point(cc.Coord[cc.Coord.Count - 1].X + 1, cc.Coord[cc.Coord.Count - 1].Y));
cc.Chain.Add(0);
}
if (Math.Abs(cc.Coord[cc.Coord.Count - 1].Y - y) > 1)
{
cc.Coord.Add(new Point(cc.Coord[cc.Coord.Count - 1].X, cc.Coord[cc.Coord.Count - 1].Y + 1));
cc.Chain.Add(1);
}
break;
}
}
}
bool isInnerOutline = false;
if (excludeInnerOutlines)
{
if (cc.Chain[cc.Chain.Count - 1] == 0)
{
isInnerOutline = true;
break;
}
}
if (!isInnerOutline)
{
cc.Coord.Add(_start);
fList.Add(cc);
}
}
p = null;
b.UnlockBits(bmData);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
try
{
if (bmData != null)
b.UnlockBits(bmData);
}
catch
{
}
}
}
private bool start_crack_searchRev(BitmapData bmData, byte[] p, BitArray fbits, bool grayscale, int range, int initialValueToCheck)
{
int left = 0;
int stride = bmData.Stride;
for (int y = _start.Y; y <= bmData.Height - 1; y++)
{
for (int x = 0; x <= bmData.Width - 1; x++)
{
if (x > 0)
{
if (!grayscale)
left = p[y * stride + (x - 1) * 4 + 3];
else
left = p[y * stride + (x - 1) * 4];
}
else
left = initialValueToCheck;
if (!grayscale)
{
if ((left >= _threshold) && (p[y * stride + x * 4 + 3] < _threshold) && (fbits.Get(y * (bmData.Width + 1) + x) == false))
{
_start.X = x;
_start.Y = y;
fbits.Set(y * (bmData.Width + 1) + x, true);
//OnProgressPlus();
return true;
}
}
else if (range > 0)
{
if ((left >= _threshold) && (p[y * stride + x * 4] < _threshold) && (p[y * stride + x * 4] >= _threshold + range) && (fbits.Get(y * (bmData.Width + 1) + x) == false))
{
_start.X = x;
_start.Y = y;
fbits.Set(y * (bmData.Width + 1) + x, true);
//OnProgressPlus();
return true;
}
}
else if ((left >= _threshold) && (p[y * stride + x * 4] < _threshold) && (fbits.Get(y * (bmData.Width + 1) + x) == false))
{
_start.X = x;
_start.Y = y;
fbits.Set(y * (bmData.Width + 1) + x, true);
//OnProgressPlus();
return true;
}
}
}
return false;
}
public void Reset()
{
this._start = new Point(0, 0);
}
}
public class ChainCode
{
public static int F { get; set; }
public Point start
{
get
{
return m_start;
}
set
{
m_start = value;
}
}
private Point m_start;
private List<Point> _coord = new List<Point>();
private List<int> _chain = new List<int>();
public List<Point> Coord
{
get
{
return _coord;
}
set
{
_coord = value;
}
}
public List<int> Chain
{
get
{
return _chain;
}
set
{
_chain = value;
}
}
public int Area
{
get
{
return m_Area;
}
set
{
m_Area = value;
}
}
private int m_Area;
private int _id;
public int Perimeter
{
get
{
return _chain.Count;
}
}
public int ID
{
get
{
return this._id;
}
}
public void SetId()
{
if (ChainCode.F < Int32.MaxValue)
{
ChainCode.F += 1;
this._id = ChainCode.F;
}
else
throw new OverflowException("The type of the field for storing the ID reports an overflow error.");
}
public void ResetID()
{
ChainCode.F = 0;
}
public ChainCode()
{
}
public override string ToString()
{
return "x = " + start.X.ToString() + "; y = " + start.Y.ToString() + "; count = " + _coord.Count.ToString() + "; area = " + this.Area.ToString();
}
}
Regards,
Thorsten