[C#] 이미지에서 주변 공백을 제거하십시오.


Answers

이 작업을 직접 수행하는 코드를 작성했습니다. 기본 작업을 진행하기가 너무 어렵지 않습니다.

기본적으로 픽셀 행 / 열을 스캔하여 흰색이 아닌 픽셀을 확인하고 제품 이미지의 경계를 격리 한 다음 해당 영역만으로 새로운 비트 맵을 생성해야합니다.

Bitmap.GetPixel() 메서드가 작동하는 동안 상대적으로 느립니다. 처리 시간이 중요한 경우 Bitmap.LockBits() 를 사용하여 비트 맵을 메모리에 잠그고 unsafe { } 블록 내부에서 간단한 포인터를 사용하여 픽셀에 직접 액세스해야합니다.

CodeProject에 대한 이 기사 는 유용하다고 생각되는 몇 가지 세부 정보를 제공합니다.

Question

고객으로부터받은 제품 이미지 블록이 있습니다. 각 제품 이미지는 무언가의 그림이며 흰색 배경으로 찍은 것입니다. 이미지의 모든 주변 부분을 자르고 싶지만 제품은 중간에 두십시오. 이것이 가능한가?

예를 들면 다음과 같습니다 [ http://www.5dnet.de/media/catalog/product/d/r/dress_shoes_5.jpg] [1 ]

모든 흰색 픽셀을 제거하고 싶지는 않지만 가장 위의 픽셀 행에 하나의 흰색이 아닌 픽셀이 포함되도록 이미지를 잘라 내고 싶습니다. 가장 왼쪽의 픽셀 행에는 흰색이 아닌 픽셀 하나가 포함되어 있습니다. 대부분의 수평 픽셀 행은 하나의 비 - 백색 픽셀 등을 포함한다.

C # 또는 VB.net의 코드는 인정 될 것입니다.




나는 큰 이미지 (GetPixel은 느리다)에서 작업 한 솔루션이 필요했기 때문에 아래의 확장 메소드를 작성했다. 제한된 테스트에서 잘 작동하는 것 같습니다. 단점은 프로젝트에서 "안전하지 않은 코드 허용"을 확인해야한다는 것입니다.

public static Image AutoCrop(this Bitmap bmp)
{
    if (Image.GetPixelFormatSize(bmp.PixelFormat) != 32)
        throw new InvalidOperationException("Autocrop currently only supports 32 bits per pixel images.");

    // Initialize variables
    var cropColor = Color.White;

    var bottom = 0;
    var left = bmp.Width; // Set the left crop point to the width so that the logic below will set the left value to the first non crop color pixel it comes across.
    var right = 0;
    var top = bmp.Height; // Set the top crop point to the height so that the logic below will set the top value to the first non crop color pixel it comes across.

    var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);

    unsafe
    {
        var dataPtr = (byte*)bmpData.Scan0;

        for (var y = 0; y < bmp.Height; y++)
        {
            for (var x = 0; x < bmp.Width; x++)
            {
                var rgbPtr = dataPtr + (x * 4);

                var b = rgbPtr[0];
                var g = rgbPtr[1];
                var r = rgbPtr[2];
                var a = rgbPtr[3];

                // If any of the pixel RGBA values don't match and the crop color is not transparent, or if the crop color is transparent and the pixel A value is not transparent
                if ((cropColor.A > 0 && (b != cropColor.B || g != cropColor.G || r != cropColor.R || a != cropColor.A)) || (cropColor.A == 0 && a != 0))
                {
                    if (x < left)
                        left = x;

                    if (x >= right)
                        right = x + 1;

                    if (y < top)
                        top = y;

                    if (y >= bottom)
                        bottom = y + 1;
                }
            }

            dataPtr += bmpData.Stride;
        }
    }

    bmp.UnlockBits(bmpData);

    if (left < right && top < bottom)
        return bmp.Clone(new Rectangle(left, top, right - left, bottom - top), bmp.PixelFormat);

    return null; // Entire image should be cropped, so just return null
}



여기 내 (다소 긴) 해결책이 있습니다.

public Bitmap Crop(Bitmap bmp)
{
  int w = bmp.Width, h = bmp.Height;

  Func<int, bool> allWhiteRow = row =>
  {
    for (int i = 0; i < w; ++i)
      if (bmp.GetPixel(i, row).R != 255)
        return false;
    return true;
  };

  Func<int, bool> allWhiteColumn = col =>
  {
    for (int i = 0; i < h; ++i)
      if (bmp.GetPixel(col, i).R != 255)
        return false;
    return true;
  };

  int topmost = 0;
  for (int row = 0; row < h; ++row)
  {
    if (allWhiteRow(row))
      topmost = row;
    else break;
  }

  int bottommost = 0;
  for (int row = h - 1; row >= 0; --row)
  {
    if (allWhiteRow(row))
      bottommost = row;
    else break;
  }

  int leftmost = 0, rightmost = 0;
  for (int col = 0; col < w; ++col)
  {
    if (allWhiteColumn(col))
      leftmost = col;
    else
      break;
  }

  for (int col = w-1; col >= 0; --col)
  {
    if (allWhiteColumn(col))
      rightmost = col;
    else
      break;
  }

  int croppedWidth = rightmost - leftmost;
  int croppedHeight = bottommost - topmost;
  try
  {
    Bitmap target = new Bitmap(croppedWidth, croppedHeight);
    using (Graphics g = Graphics.FromImage(target))
    {
      g.DrawImage(bmp,
        new RectangleF(0, 0, croppedWidth, croppedHeight),
        new RectangleF(leftmost, topmost, croppedWidth, croppedHeight),
        GraphicsUnit.Pixel);
    }
    return target;
  }
  catch (Exception ex)
  {
    throw new Exception(
      string.Format("Values are topmost={0} btm={1} left={2} right={3}", topmost, bottommost, leftmost, rightmost),
      ex);
  }
}



확실히 가능합니다. 의사 코드에서 :

topmost = 0
for row from 0 to numRows:
    if allWhiteRow(row): 
        topmost = row
    else:
        # found first non-white row from top
        break

botmost = 0
for row from numRows-1 to 0:
    if allWhiteRow(row): 
        botmost = row
    else:
        # found first non-white row from bottom
        break

그리고 마찬가지로 왼쪽과 오른쪽.

allWhiteRow 대한 코드는 해당 행의 픽셀을보고 모두 255,255,255에 가깝 도록해야합니다.




위쪽과 왼쪽에 나머지 1px 공백을 수정하십시오.

    public Bitmap Crop(Bitmap bitmap)
    {
        int w = bitmap.Width;
        int h = bitmap.Height;

        Func<int, bool> IsAllWhiteRow = row =>
        {
            for (int i = 0; i < w; i++)
            {
                if (bitmap.GetPixel(i, row).R != 255)
                {
                    return false;
                }
            }
            return true;
        };

        Func<int, bool> IsAllWhiteColumn = col =>
        {
            for (int i = 0; i < h; i++)
            {
                if (bitmap.GetPixel(col, i).R != 255)
                {
                    return false;
                }
            }
            return true;
        };

        int leftMost = 0;
        for (int col = 0; col < w; col++)
        {
            if (IsAllWhiteColumn(col)) leftMost = col + 1;
            else break;
        }

        int rightMost = w - 1;
        for (int col = rightMost; col > 0; col--)
        {
            if (IsAllWhiteColumn(col)) rightMost = col - 1;
            else break;
        }

        int topMost = 0;
        for (int row = 0; row < h; row++)
        {
            if (IsAllWhiteRow(row)) topMost = row + 1;
            else break;
        }

        int bottomMost = h - 1;
        for (int row = bottomMost; row > 0; row--)
        {
            if (IsAllWhiteRow(row)) bottomMost = row - 1;
            else break;
        }

        if (rightMost == 0 && bottomMost == 0 && leftMost == w && topMost == h)
        {
            return bitmap;
        }

        int croppedWidth = rightMost - leftMost + 1;
        int croppedHeight = bottomMost - topMost + 1;

        try
        {
            Bitmap target = new Bitmap(croppedWidth, croppedHeight);
            using (Graphics g = Graphics.FromImage(target))
            {
                g.DrawImage(bitmap,
                    new RectangleF(0, 0, croppedWidth, croppedHeight),
                    new RectangleF(leftMost, topMost, croppedWidth, croppedHeight),
                    GraphicsUnit.Pixel);
            }
            return target;
        }
        catch (Exception ex)
        {
            throw new Exception(string.Format("Values are top={0} bottom={1} left={2} right={3}", topMost, bottomMost, leftMost, rightMost), ex);
        }
    }



Dmitri의 답변을 조정해야한다는 사실을 발견했습니다. 실제로는 자르기 (수평, 수직 또는 양쪽 모두)가 필요하지 않은 이미지와 함께 작동합니다.

    public static Bitmap Crop(Bitmap bmp)
    {
        int w = bmp.Width;
        int h = bmp.Height;

        Func<int, bool> allWhiteRow = row =>
        {
            for (int i = 0; i < w; ++i)
                if (bmp.GetPixel(i, row).R != 255)
                    return false;
            return true;
        };

        Func<int, bool> allWhiteColumn = col =>
        {
            for (int i = 0; i < h; ++i)
                if (bmp.GetPixel(col, i).R != 255)
                    return false;
            return true;
        };

        int topmost = 0;
        for (int row = 0; row < h; ++row)
        {
            if (allWhiteRow(row))
                topmost = row;
            else break;
        }

        int bottommost = 0;
        for (int row = h - 1; row >= 0; --row)
        {
            if (allWhiteRow(row))
                bottommost = row;
            else break;
        }

        int leftmost = 0, rightmost = 0;
        for (int col = 0; col < w; ++col)
        {
            if (allWhiteColumn(col))
                leftmost = col;
            else
                break;
        }

        for (int col = w - 1; col >= 0; --col)
        {
            if (allWhiteColumn(col))
                rightmost = col;
            else
                break;
        }

        if (rightmost == 0) rightmost = w; // As reached left
        if (bottommost == 0) bottommost = h; // As reached top.

        int croppedWidth = rightmost - leftmost;
        int croppedHeight = bottommost - topmost;

        if (croppedWidth == 0) // No border on left or right
        {
            leftmost = 0;
            croppedWidth = w;
        }

        if (croppedHeight == 0) // No border on top or bottom
        {
            topmost = 0;
            croppedHeight = h;
        }

        try
        {
            var target = new Bitmap(croppedWidth, croppedHeight);
            using (Graphics g = Graphics.FromImage(target))
            {
                g.DrawImage(bmp,
                  new RectangleF(0, 0, croppedWidth, croppedHeight),
                  new RectangleF(leftmost, topmost, croppedWidth, croppedHeight),
                  GraphicsUnit.Pixel);
            }
            return target;
        }
        catch (Exception ex)
        {
            throw new Exception(
              string.Format("Values are topmost={0} btm={1} left={2} right={3} croppedWidth={4} croppedHeight={5}", topmost, bottommost, leftmost, rightmost, croppedWidth, croppedHeight),
              ex);
        }
    }



netpbm 그래픽 유틸리티 라이브러리의 netpbm 유틸리티가 정확하게 작동합니다.

http://netpbm.sourceforge.net/ 에서 구할 수있는 코드를 살펴 보는 것이 좋습니다.