C# Bitmap객체에는 하나의 픽셀을 다룰 수 있도록 두가지의 함수를 제공합니다.

color GetPixel(x,y) // x,y의 좌표에 해당하는 pixel정보를 color로 리턴합니다.
void SetPixel(x,y,color)  // x,y좌표와 원하는 pixel정보 color를 Set합니다.

이러한 메소드를 제공하기 때문에 몇몇개의 Pixel을 다루기 편하게 이러한 메소드를 제공하고 있습니다.
그러나 이러한 메소드는 다음과 같은 세가지 이유로 추천하지 않습니다.


1. 퍼포먼스
2. 퍼포먼스
3. 퍼포먼스



추천하는 코드는 다음과 같습니다.

추가해야할 코드 1)

    /// <summary>
    /// RGB : LockMemory를 통한 Image포인터를 쉽게 사용하기 위해 만든 클래스
    /// </summary>
    public struct RGB
    {
        public byte B;
        public byte G;
        public byte R;
        public byte A;
        public override string ToString()
        {
            return string.Format("{0}, {1}, {2}, {3}", A, R, G, B);
        }
    }

    /// <summary>
    /// 사용하면 좋은 메서드 모음, 익스텐션메소드로 구현됨
    /// </summary>
public static class ImageUtil
    {
        // BitmapData객체를 보관하기 위한 Dictionary
        public static Dictionary<Bitmap, BitmapData> BitmapDataDictionary = new Dictionary<Bitmap, BitmapData>();
        ////////////////////////////////////////////////////////////////////////////////////
        // GetPointer로 취득한 메모리는 꼭 다시 ReleasePointer를 호출 해줘야합니다.
        ////////////////////////////////////////////////////////////////////////////////////
        // Image로부터 Read/Write가 가능한 Memory Pointer를 가져옵니다.

        public static IntPtr LockMemory(this Bitmap Image)
        {
            return LockMemory(Image, new Rectangle(Point.Empty, Image.Size), ImageLockMode.ReadWrite);
        }

        // Image로부터 Mode에 맞는 작업을 수행 할 수 있는 Memory Pointer를 가져옵니다.
        public static IntPtr LockMemory(this Bitmap Image, ImageLockMode Mode)
        {
            return LockMemory(Image, new Rectangle(Point.Empty, Image.Size), Mode);
        }

        // Image로부터 Rect영역을 Read/Write 할 수 있는 Memory Pointer를 가져옵니다.
        public static IntPtr LockMemory(this Bitmap Image, Rectangle Rect)
        {
            return LockMemory(Image, Rect, ImageLockMode.ReadWrite);
        }

        // Image로부터 Rect영역을 Mode에 맞는 작업을 수행 할 수 있는 Memory Pointer를 가져옵니다.
        public static IntPtr LockMemory(this Bitmap Image, Rectangle Rect, ImageLockMode Mode)
        {
            BitmapData ImageData = null;
            if (BitmapDataDictionary.ContainsKey(Image)) // 기존에 사용한
                ImageData = BitmapDataDictionary[Image];
            else
            {
                ImageData = Image.LockBits(Rect, Mode, Image.PixelFormat);
                BitmapDataDictionary.Add(Image, ImageData);
            }
            return ImageData.Scan0;
        }

        // LockMemory로 취득한 Memory Pointer를 반환 합니다.
        public static void UnLockMemory(this Bitmap Image)
        {
            try
            {
                BitmapData ImageData = BitmapDataDictionary[Image];
                Image.UnlockBits(ImageData);
            }
            catch (Exception)
            {


            }
        }

        // Image의 Memory를 복사해옵니다.
        public static byte[] CopyMemory(this Bitmap Image)
        {
            return CopyMemory(Image, new Rectangle(Point.Empty, Image.Size));
        }

        // Image로부터 Rect영역에 있는 Memroy를 가져옵니다.
        public static byte[] CopyMemory(this Bitmap Image, Rectangle Rect)
        {
            byte[] ReturnMemory = new byte[Rect.Width * Rect.Height];
            IntPtr ImagePointer = Image.LockMemory(Rect, ImageLockMode.ReadOnly);
            Marshal.Copy(ImagePointer, ReturnMemory, 0, ReturnMemory.Length);
            Image.UnLockMemory();

            return ReturnMemory;
        }

        public static Bitmap To32BppBitmap(this Bitmap Image)
        {
            return Image.ConvertBpp(PixelFormat.Format32bppArgb);
        }

        public static Bitmap To24BppBitmap(this Bitmap Image)
        {
            return Image.ConvertBpp(PixelFormat.Format24bppRgb);
        }

        public static Bitmap ConvertBpp(this Bitmap Image, PixelFormat Format)
        {
            Bitmap Result = new Bitmap(Image.Width, Image.Height, Format);
            using (Graphics g = Graphics.FromImage(Result))
            {
                g.DrawImage(Image, new Rectangle(0, 0, Image.Width, Image.Height), new Rectangle(0, 0, Image.Width, Image.Height), GraphicsUnit.Pixel);
            }
            return Result;
        }

    }



위대하신 분의 블로그에서 퍼온 코드에, 필요한 내용을 조금 더 추가하였습니다.
추가된 내용은 RGB클래스와 이미지를 Convert하는 부분입니다.
사용하는 법은 이렇습니다.

사용하는 방법 1)

              unsafe      // 포인터를 사용하시는 것이기 때문에 unsafe를 사용합니다.
            {
                // unsafe한 Pointer를 이용하여 각각의 값을 가져옴
                RGB* RGBImage = (RGB*)image.LockMemory(ImageLockMode.ReadOnly).ToPointer();
               // 여기에 RGB*를 이용한 코드를 집어넣으시면 됩니다.
                image.UnLockMemory(); // 사용한 메모리를 닫아줌
            }        



다만 , 이렇게만 막연하게 코드를 넣어놓으면
이게 얼마나 나은 퍼포먼스인지, 어떻게 사용하는것이 좋은지 잘 알 수 없기에 간단한 예제를 만들어 보았습니다.

선택된 색은 몇개일까요?


간단하게 뭘 만들까 생각을 했는데 , 정말 간단하게 만들어버렸습니다-_-;;
코드를 첨부합니다.


핵심이 되는 코드는 다음과 같습니다.

 GetPixel을 이용한 코드  Pointer를 이용한 코드

 public static int CountPixelbyGetPixel(Bitmap image, Color selectedColor)
        {
            int PixelCount = 0;

            for (int Y = 0; Y < image.Height; Y++)
            {
                for (int X = 0; X < image.Width; X++)
                {
                    // GetPixel이라는 함수를 통해 Pixel의 값을 읽어옴
                    Color ImageColor = image.GetPixel(X, Y);
                    if (IsSameColor(ImageColor,selectedColor) == true)
                    {
                        PixelCount++;
                    }
                }
            }

            return PixelCount;
        }

  public static int CountPixelbyPointer(Bitmap image, Color selectedColor)
        {
            int ImageIndex = 0;
            int PixelCount = 0;
            RGB ImageRGB;

            unsafe
            {
                // unsafe한 Pointer를 이용하여 각각의 값을 가져옴
                RGB* RGBImage = (RGB*)image.LockMemory(ImageLockMode.ReadOnly).ToPointer();

                for (int Y = 0; Y < image.Height; Y++)
                {
                    for (int X = 0; X < image.Width; X++)
                    {
                        ImageIndex = Y * image.Width + X;
                        ImageRGB = RGBImage[ImageIndex];

                        if (IsSameColor(selectedColor,ImageRGB) == true)
                        {
                            PixelCount++;
                        }
                    }
                }

                image.UnLockMemory();
            }

            return PixelCount;
        }


(단, Pointer방법을 사용하려면, 이미지가 32bit형식으로 load되어야 하며, 별도의 로드가 불편하시면 다음과 같이 사용하시면 됩니다.

 PixelCounter.CountPixelbyPointer(image.To32BppBitmap(), SelectedColor);
편하게 사용하기 위해서 ImageUtil에 Extention Method로 구현 해놨습니다.)

자, 다음과 같은 같은 일을 하는두 코드는 다음과 같은 차이를 냅니다.

 
 
 
 
대충 다른 이미지로 네번을 돌려봤습니다만, 눈에 띄는 퍼포먼스를 보여주고 있으며,
한장의 이미지가 아니라 많은 이미지를 다룰때, 큰 이미지를 다룰 때 더 큰 효과를 나타낼 것으로 기대됩니다.
특히, 성능이 좋지 못한 상황에서라면 더더욱 빛을 바랄 것으로 생각합니다.
실제로 윈도우즈 모바일상에서 이미지를 간단하게 변환할 일이 있었는데, SetPixel을 사용한 경우 너무 퍼포먼스가 나오지 않아서
(그냥,, 프로그램이 멈춘줄..) 사용하지 못한 경험이 있습니다.


* 추가입니다.
정신없어서 깜박하고 빼먹고 작성하지 않았던 내용인데,
대체 왜 그렇다면 같은 일을 하는 두 코드가 이렇게 다른 속도를 내는가 에 관한 이야기입니다.
간단하게 말하자면, Get 혹은 Set Pixel이라는 메서드를 사용 할 경우 ,
내부적으로 그 함수 내에서 Pointer를 사용하는 것 처럼 동작하기 때문에 상대적으로 더 많은 시간이 소요된다고 보시면 됩니다.
즉, Pointer의 방법으로 내용을 구현하게 되면, 내용이 실행되기 전에 딱 한번 메모리를 열고, 내용이 다 끝난뒤에 메모리를 닫아주지만.. (실수로 빼먹고 안닫아주면 안됩니다.~ )
GetPixel,SetPixel같은 경우, 하나의 Pixel에 접근할 때 마다 메모리를 열고 닫고를 반복하는 것입니다.
예를 들어서 300*300의 이미지를 가지고 같은 일을 한다면, Pointer의 경우 그 이미지의 메모리를 열고 닫기를 1번 하지만, Get,SetPixel의 경우는 300*300 = 90000번 메모리를 열고 닫는다고 보시면 됩니다.
그렇기 때문에, 몇몇개 한두개의 Pixel에 접근할 경우, 코드의 깔끔함을 위해서? 편하게 사용하기 위해서?? 사용하는 것이 좋지만,
큰 이미지를 다룬다던가, 속도의 개선이 필요한 경우에는 (메모리 오류도 날 수 있는 문제이기도 하니 사용하긴 까다롭지만..) 다음과 포인터를 사용하는것이 좋습니다.



정리입니다.
C#은 '포인터를 사용하지 않아, 메모리 관리하기 편해서 좋다' 라는 평을 듣기는 하지만,
그만큼 퍼포먼스가 떨어진다는 이야기는 항상 이슈입니다.

더욱이,
"누가 이미지 프로세싱을 하는데 C#이나 Java를 써? 퍼포먼스가 디지게 안나오는데!!"
라는 소리도 몇번 들었습니다. 하지만, 다음과 같이 사용한다면,
가상머신을 거쳐서 생기는 것 외에는 C++에서 포인터를 사용하는것과 같은 퍼포먼스를 보장합니다.

물론, 한두개 정도 쓰는것 이라면 코드도 깔끔하고 좋습니다만,
이미지 전체를 다룰 생각이라면 (요즘, 해상도가 장난없죠? ^-^* 모공이 보이는 해상도입니다.)
절대적으로 퍼포먼스를 위해서 Get,Set Pixel은 추천하지 않습니다.


훌륭하신 분의 포스팅을 참고하여 응용해 본 이야기입니다.
좀 더 자세한 이야기는 이 포스팅에서 참고하시는게 좋을 것 같습니다. ^^
http://whatisthat.co.kr/141, http://whatisthat.co.kr/54

저작자 표시
신고
Posted by 천재소녀*
이전버튼 1 이전버튼

블로그 이미지
꿈꾸는아이, ㅋ Tasha의 완전범죄 구상소
천재소녀*

공지사항

Yesterday28
Today23
Total154,181

달력

 « |  » 2017.09
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

최근에 받은 트랙백

글 보관함