첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
유용한 소스 코드가 있으면 icodebroker@naver.com으로 보내주시면 감사합니다.
블로그 자료는 자유롭게 사용하세요.

■ 무어의 이웃 등고선 추적 (Moore Neighbor Contour Tracing) 알고리즘 사용하기

----------------------------------------------------------------------------------------------------


TestProject.zip


PixelData.cs

 

 

using System;

using System.Drawing;

using System.Drawing.Imaging;

using System.Runtime.InteropServices;

 

namespace TestProject

{

    /// <summary>

    /// 픽셀 데이터

    /// </summary>

    public class PixelData

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region Field

 

        /// <summary>

        /// 비트맵 크기

        /// </summary>

        private Size bitmapDataSize;

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

 

        #region Field

 

        /// <summary>

        /// 픽셀 카운트

        /// </summary>

        protected readonly int pixelCount;

 

        /// <summary>

        /// 픽셀 배열

        /// </summary>

        protected readonly int[] pixelArray;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 비트맵 데이터 크기 - BitmapDataSize

 

        /// <summary>

        /// 비트맵 데이터 크기

        /// </summary>

        public Size BitmapDataSize

        {

            get

            {

                return this.bitmapDataSize;

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Indexer

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 인덱서 - this[point]

 

        /// <summary>

        /// 인덱서

        /// </summary>

        /// <param name="point">좌표</param>

        /// <returns>픽셀 값</returns>

        public int this[Point point]

        {

            get

            {

                int index = (point.Y * this.bitmapDataSize.Width) + point.X;

 

                return this.pixelArray[index];

            }

            protected set

            {

                int index = (point.Y * this.bitmapDataSize.Width) + point.X;

 

                this.pixelArray[index] = value;

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

 

        #region 생성자 - PixelData(bitmapData)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="bitmapData">비트맵 데이터</param>

        protected PixelData(BitmapData bitmapData)

        {

            this.bitmapDataSize = new Size(bitmapData.Width, bitmapData.Height);

 

            this.pixelCount = this.bitmapDataSize.Width * this.bitmapDataSize.Height;

 

            this.pixelArray = new int[this.pixelCount];

 

            Marshal.Copy(bitmapData.Scan0, this.pixelArray, 0, this.pixelCount);

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성하기 - Create(bitmap)

 

        /// <summary>

        /// 생성하기

        /// </summary>

        /// <param name="bitmap">비트맵</param>

        /// <returns>픽셀 데이터</returns>

        public static PixelData Create(Bitmap bitmap)

        {

            BitmapData bitmapData = GetBitmapData(bitmap, true);

 

            PixelData pixelData = new PixelData(bitmapData);

 

            bitmap.UnlockBits(bitmapData);

 

            return pixelData;

        }

 

        #endregion

        #region 생성하기 - Create(filePath)

 

        /// <summary>

        /// 생성하기

        /// </summary>

        /// <param name="filePath">파일 경로</param>

        /// <returns>픽셀 데이터</returns>

        public static PixelData Create(string filePath)

        {

            using(Bitmap bitmap = new Bitmap(filePath))

            {

                BitmapData bitmapData = GetBitmapData(bitmap, true);

 

                PixelData pixelData = new PixelData(bitmapData);

 

                bitmap.UnlockBits(bitmapData);

 

                return pixelData;

            }

        }

 

        #endregion

 

        #region 포함 여부 구하기 - Contains(point)

 

        /// <summary>

        /// 포함 여부 구하기

        /// </summary>

        /// <param name="point">좌표</param>

        /// <returns>포함 여부</returns>

        public bool Contains(Point point)

        {

            return ((point.X < 0) || (point.X >= this.bitmapDataSize.Width) || (point.Y < 0) ||

                (point.Y >= this.bitmapDataSize.Height)) ? false : true;

        }

 

        #endregion

        #region 색상 여부 구하기 - IsColor(point, predicate)

 

        /// <summary>

        /// 색상 여부 구하기

        /// </summary>

        /// <param name="point">좌표</param>

        /// <param name="predicate">판정 함수</param>

        /// <returns>색상 여부</returns>

        public bool IsColor(Point point, Predicate<int> predicate)

        {

            int pixel = this[point];

 

            return predicate(pixel);

        }

 

        #endregion

        #region 투명색 여부 구하기 - IsTransparentColor(argb)

 

        /// <summary>

        /// 투명색 여부 구하기

        /// </summary>

        /// <param name="argb">ARGB 값</param>

        /// <returns>투명색 여부</returns>

        public bool IsTransparentColor(int argb)

        {

            Color color = Color.FromArgb(argb);

 

            return (color.A == 0);

        }

 

        #endregion

        #region 투명색 여부 구하기 - IsTransparentColor(point)

 

        /// <summary>

        /// 투명색 여부 구하기

        /// </summary>

        /// <param name="point">좌표</param>

        /// <returns>투명색 여부</returns>

        public bool IsTransparentColor(Point point)

        {

            if(!Contains(point))

            {

                return true;

            }

 

            return IsColor(point, IsTransparentColor);

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////// Protected

 

        #region 비트맵 데이터 구하기 - GetBitmapData(bitmap, readOnly)

 

        /// <summary>

        /// 비트맵 데이터 구하기

        /// </summary>

        /// <param name="bitmap">비트맵</param>

        /// <param name="readOnly">읽기 전용 여부</param>

        /// <returns>비트맵 데이터</returns>

        protected static BitmapData GetBitmapData(Bitmap bitmap, bool readOnly)

        {

            Rectangle rectangle = new Rectangle(new Point(0, 0), bitmap.Size);

 

            return bitmap.LockBits(rectangle, (readOnly) ? ImageLockMode.ReadOnly : ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

        }

 

        #endregion

    }

}

 

 

PixelData2.cs

 

 

using System.Drawing;

using System.Drawing.Imaging;

using System.Runtime.InteropServices;

 

namespace TestProject

{

    /// <summary>

    /// 픽셀 데이터 2

    /// </summary>

    public class PixelData2 : PixelData

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region Field

 

        /// <summary>

        /// 비트맵

        /// </summary>

        private readonly Bitmap bitmap;

 

        /// <summary>

        /// 비트맵 데이터

        /// </summary>

        private readonly BitmapData bitmapData;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - PixelData2(bitmap, bitmapData)

 

        /// <summary>

        /// 생성자

        /// </summary>

        /// <param name="bitmap">비트맵</param>

        /// <param name="bitmapData">비트맵 데이터</param>

        public PixelData2(Bitmap bitmap, BitmapData bitmapData) : base(bitmapData)

        {

            this.bitmap = bitmap;

 

            this.bitmapData = bitmapData;

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성하기 - Create(bitmap)

 

        /// <summary>

        /// 생성하기

        /// </summary>

        /// <param name="bitmap">비트맵</param>

        /// <returns>픽셀 데이터 2</returns>

        public static new PixelData2 Create(Bitmap bitmap)

        {

            BitmapData bitmapData = GetBitmapData(bitmap, false);

 

            return new PixelData2(bitmap, bitmapData);

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 저장하기 - Save(targetPath)

 

        /// <summary>

        /// 저장하기

        /// </summary>

        /// <param name="targetPath">타겟 경로</param>

        public void Save(string targetPath)

        {

            Marshal.Copy(pixelArray, 0, this.bitmapData.Scan0, pixelCount);

 

            this.bitmap.UnlockBits(this.bitmapData);

 

            this.bitmap.Save(targetPath);

        }

 

        #endregion

        #region 비트맵 구하기 - GetBitmap()

 

        /// <summary>

        /// 비트맵 구하기

        /// </summary>

        /// <returns>비트맵</returns>

        public Bitmap GetBitmap()

        {

            Marshal.Copy(pixelArray, 0, this.bitmapData.Scan0, pixelCount);

 

            this.bitmap.UnlockBits(this.bitmapData);

 

            return this.bitmap;            

        }

 

        #endregion

        #region 색상 설정하기 - SetColor(point, argb)

 

        /// <summary>

        /// 색상 설정하기

        /// </summary>

        /// <param name="point">좌표</param>

        /// <param name="argb">ARGB 값</param>

        public void SetColor(Point point, int argb)

        {

            base[point] = argb;

        }

 

        #endregion

    }

}

 

 

BoundaryTracing.cs

 

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Drawing;

 

namespace TestProject

{

    /// <summary>

    /// 경계선 추적

    /// </summary>

    public static class BoundaryTracing

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성하기 - Create(pixelData)

 

        /// <summary>

        /// 생성하기

        /// </summary>

        /// <param name="pixelData">픽셀 데이터</param>

        /// <returns>좌표 리스트의 리스트</returns>

        public static List<List<Point>> Create(PixelData pixelData)

        {

            Size bitmapDataSize = pixelData.BitmapDataSize;

 

            HashSet<Point> pointHashSet = new HashSet<Point>();

 

            List<Point> pointList = null;

 

            List<List<Point>> pointListList = new List<List<Point>>();

 

            bool inside = false;

 

            int width = bitmapDataSize.Width;

 

            Tuple<Func<Point, Point>, int>[] neighborhoodTupleArray = new Tuple<Func<Point, Point>, int>[]

            {

                new Tuple<Func<Point, Point>, int>(point => new Point(point.X - 1, point.Y    ), 7),

                new Tuple<Func<Point, Point>, int>(point => new Point(point.X - 1, point.Y - 1), 7),

                new Tuple<Func<Point, Point>, int>(point => new Point(point.X    , point.Y - 1), 1),

                new Tuple<Func<Point, Point>, int>(point => new Point(point.X + 1, point.Y - 1), 1),

                new Tuple<Func<Point, Point>, int>(point => new Point(point.X + 1, point.Y    ), 3),

                new Tuple<Func<Point, Point>, int>(point => new Point(point.X + 1, point.Y + 1), 3),

                new Tuple<Func<Point, Point>, int>(point => new Point(point.X    , point.Y + 1), 5),

                new Tuple<Func<Point, Point>, int>(point => new Point(point.X - 1, point.Y + 1), 5)

            };

 

            for(int y = 0; y < bitmapDataSize.Height; ++y)

            {

                for(int x = 0; x < bitmapDataSize.Width; ++x)

                {

                    Point point = new Point(x, y);

 

                    if(pointHashSet.Contains(point) && !inside)

                    {

                        inside = true;

 

                        continue;

                    }

 

                    bool isTransparent = pixelData.IsTransparentColor(point);

 

                    if(!isTransparent && inside)

                    {

                        continue;

                    }

 

                    if(isTransparent && inside)

                    {

                        inside = false;

 

                        continue;

                    }

 

                    if(!isTransparent && !inside)

                    {

                        pointListList.Add(pointList = new List<Point>());

 

                        pointHashSet.Add(point); pointList.Add(point);

 

                        int neighborCount = 1;

 

                        Point startPoint = point;

 

                        int counter1 = 0;

                        int counter2 = 0;

 

                        while(true)

                        {

                            Point checkPoint = neighborhoodTupleArray[neighborCount - 1].Item1(point);

 

                            int newNeighborCount = neighborhoodTupleArray[neighborCount - 1].Item2;

 

                            if(!pixelData.IsTransparentColor(checkPoint))

                            {

                                if(checkPoint == startPoint)

                                {

                                    counter1++;

 

                                    if(newNeighborCount == 1 || counter1 >= 3)

                                    {

                                        inside = true;

 

                                        break;

                                    }

                                }

 

                                neighborCount = newNeighborCount;

 

                                point = checkPoint;

 

                                counter2 = 0;

 

                                pointHashSet.Add(point);

 

                                pointList.Add(point);

                            }

                            else

                            {

                                neighborCount = 1 + (neighborCount % 8);

 

                                if(counter2 > 8)

                                {

                                    counter2 = 0;

 

                                    pointList = null;

 

                                    break;

                                }

                                else

                                {

                                    counter2++;

                                }

                            }

                        }

                    }

                }

            }

 

            return pointListList;

        }

 

        #endregion

        #region 좌표 리스트 구하기 - GetPointList(pointListList)

 

        /// <summary>

        /// 좌표 리스트 구하기

        /// </summary>

        /// <param name="pointListList">좌표 리스트 리스트</param>

        /// <returns>좌표 리스트</returns>

        public static List<Point> GetPointList(List<List<Point>> pointListList)

        {

            pointListList.Sort((x, y) => x.Count.CompareTo(y.Count));

 

            return pointListList.Last();

        }

 

        #endregion

    }

}

 

 

MainForm.cs

 

 

using System.Collections.Generic;

using System.Drawing;

using System.Drawing.Imaging;

using System.Windows.Forms;

 

namespace TestProject

{

    /// <summary>

    /// 메인 폼

    /// </summary>

    public partial class MainForm : Form

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - MainForm()

 

        /// <summary>

        /// 생성자

        /// </summary>

        public MainForm()

        {

            InitializeComponent();

 

 

            Bitmap sourceBitmap = new Bitmap(400, 400, PixelFormat.Format32bppArgb);

 

            Graphics sourceGraphics = Graphics.FromImage(sourceBitmap);

 

            sourceGraphics.DrawEllipse(new Pen(Brushes.LightGray, 20), 50, 50, 300, 300);

 

            sourceGraphics.Dispose();

 

            this.pictureBox1.Image = sourceBitmap;

 

 

            PixelData2 pixelData = PixelData2.Create(sourceBitmap.Clone() as Bitmap);

 

            List<List<Point>> pointListList = BoundaryTracing.Create(pixelData);

 

            foreach(List<Point> pointList in pointListList)

            {

                foreach(Point point in pointList)

                {

                    pixelData.SetColor(point, Color.Red.ToArgb());

                }

            }

 

            this.pictureBox2.Image = pixelData.GetBitmap();

        }

 

        #endregion

    }

}

 

----------------------------------------------------------------------------------------------------

Posted by 사용자 icodebroker

댓글을 달아 주세요