2011-01-27 2 views
11

32bpp ARGB 모드에서 System.Drawing.Bitmap이 있다고 가정합니다. 큰 비트 맵이지만 대부분 중간에 어딘가에 비교적 작은 이미지가있는 대부분 투명 픽셀입니다.자동으로 비트 맵을 최소 크기로 트림합니까?

"진짜"이미지의 테두리를 감지하는 빠른 알고리즘은 무엇입니까? 그래서 주변의 모든 투명 픽셀을 잘라낼 수 있습니까?

또는 여기에 사용할 수있는 .Net의 기능이 있습니까?

+2

컷오프가 직선입니까? 그렇다면 L-> R 및 T-> B에서 픽셀을 읽는 것이 매우 빠르게 작동합니다. –

+0

사각형 일 경우 더 많은 시간을 절약 할 수 있으며 4 개 측면 모두에서 중심에서 바깥 쪽을 검색 할 수 있습니다 (적어도 픽셀 조사를 줄임). –

+0

작은 포함 된 이미지에도 투명 픽셀이 포함될 수 있습니까? –

답변

23

기본 아이디어는 이미지의 모든 픽셀을 확인하여 이미지의 위쪽, 왼쪽, 오른쪽 및 아래쪽 경계를 찾는 것입니다. 이 작업을 효율적으로 수행하려면 GetPixel 메서드를 사용하지 마십시오. 속도가 매우 느립니다. 대신 LockBits을 사용하십시오. 여기

내가 생각 해낸 구현의 : ... 그것은 아마 최적화 할 수

static Bitmap TrimBitmap(Bitmap source) 
{ 
    Rectangle srcRect = default(Rectangle); 
    BitmapData data = null; 
    try 
    { 
     data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 
     byte[] buffer = new byte[data.Height * data.Stride]; 
     Marshal.Copy(data.Scan0, buffer, 0, buffer.Length); 
     int xMin = int.MaxValue; 
     int xMax = 0; 
     int yMin = int.MaxValue; 
     int yMax = 0; 
     for (int y = 0; y < data.Height; y++) 
     { 
      for (int x = 0; x < data.Width; x++) 
      { 
       byte alpha = buffer[y * data.Stride + 4 * x + 3]; 
       if (alpha != 0) 
       { 
        if (x < xMin) xMin = x; 
        if (x > xMax) xMax = x; 
        if (y < yMin) yMin = y; 
        if (y > yMax) yMax = y; 
       } 
      } 
     } 
     if (xMax < xMin || yMax < yMin) 
     { 
      // Image is empty... 
      return null; 
     } 
     srcRect = Rectangle.FromLTRB(xMin, yMin, xMax, yMax); 
    } 
    finally 
    { 
     if (data != null) 
      source.UnlockBits(data); 
    } 

    Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height); 
    Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height); 
    using (Graphics graphics = Graphics.FromImage(dest)) 
    { 
     graphics.DrawImage(source, destRect, srcRect, GraphicsUnit.Pixel); 
    } 
    return dest; 
} 

,하지만 난 GDI + 전문가가 아니라, 그래서 내가 더 연구하지 않고 할 수있는 최선의


편집 :

  1. 검색이 맞 왼쪽 : 실제로, 이미지의 일부분을 스캔하지 않음으로써,이를 최적화 할 수있는 간단한 방법이있다 투명하지 않은 픽셀을 찾을 때까지 t; (xMin, yMin의 경우)
  2. 투명하지 않은 픽셀을 찾을 때까지 위에서 아래로 스캔합니다 (x> = xMin에만 해당). y를 yMin에 저장합니다.
  3. 투명하지 않은 픽셀을 찾을 때까지 오른쪽에서 왼쪽으로 스캔합니다 (y> = yMin에만 해당). x를 xMax에 저장
  4. 투명하지 않은 픽셀을 찾을 때까지 아래쪽으로 스캔합니다 (xMin에만 해당) < = x < = xMax); 불투명 부분은 물론 작은 경우 때문에,

    static Bitmap TrimBitmap(Bitmap source) 
    { 
        Rectangle srcRect = default(Rectangle); 
        BitmapData data = null; 
        try 
        { 
         data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 
         byte[] buffer = new byte[data.Height * data.Stride]; 
         Marshal.Copy(data.Scan0, buffer, 0, buffer.Length); 
    
         int xMin = int.MaxValue, 
          xMax = int.MinValue, 
          yMin = int.MaxValue, 
          yMax = int.MinValue; 
    
         bool foundPixel = false; 
    
         // Find xMin 
         for (int x = 0; x < data.Width; x++) 
         { 
          bool stop = false; 
          for (int y = 0; y < data.Height; y++) 
          { 
           byte alpha = buffer[y * data.Stride + 4 * x + 3]; 
           if (alpha != 0) 
           { 
            xMin = x; 
            stop = true; 
            foundPixel = true; 
            break; 
           } 
          } 
          if (stop) 
           break; 
         } 
    
         // Image is empty... 
         if (!foundPixel) 
          return null; 
    
         // Find yMin 
         for (int y = 0; y < data.Height; y++) 
         { 
          bool stop = false; 
          for (int x = xMin; x < data.Width; x++) 
          { 
           byte alpha = buffer[y * data.Stride + 4 * x + 3]; 
           if (alpha != 0) 
           { 
            yMin = y; 
            stop = true; 
            break; 
           } 
          } 
          if (stop) 
           break; 
         } 
    
         // Find xMax 
         for (int x = data.Width - 1; x >= xMin; x--) 
         { 
          bool stop = false; 
          for (int y = yMin; y < data.Height; y++) 
          { 
           byte alpha = buffer[y * data.Stride + 4 * x + 3]; 
           if (alpha != 0) 
           { 
            xMax = x; 
            stop = true; 
            break; 
           } 
          } 
          if (stop) 
           break; 
         } 
    
         // Find yMax 
         for (int y = data.Height - 1; y >= yMin; y--) 
         { 
          bool stop = false; 
          for (int x = xMin; x <= xMax; x++) 
          { 
           byte alpha = buffer[y * data.Stride + 4 * x + 3]; 
           if (alpha != 0) 
           { 
            yMax = y; 
            stop = true; 
            break; 
           } 
          } 
          if (stop) 
           break; 
         } 
    
         srcRect = Rectangle.FromLTRB(xMin, yMin, xMax, yMax); 
        } 
        finally 
        { 
         if (data != null) 
          source.UnlockBits(data); 
        } 
    
        Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height); 
        Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height); 
        using (Graphics graphics = Graphics.FromImage(dest)) 
        { 
         graphics.DrawImage(source, destRect, srcRect, GraphicsUnit.Pixel); 
        } 
        return dest; 
    } 
    

    상당한 이득이되지 않습니다 : yMax 인


EDIT2에 저장 Y : 여기에 위의 접근 방식의 구현이다 여전히 대부분의 픽셀을 스캔합니다. 그러나 크기가 클 경우 불투명 한 부분 주위의 직사각형 만 스캔됩니다.

+0

가장 실용적인 접근법처럼 보인다. 나는 이것보다 더 좋아 보이지 않는다. LockBits 메서드를 사용한 멋진 팁. +1 –

+2

BTW, 나는 Graphics를 사용하지 않고 이미지를 잘라내는 더 간단한 방법이 있음을 깨달았다 :'return source.Clone (srcRect, source.PixelFormat);' –

+2

위대한 해답은 매우 유용하지만, 내 이미지를 발견했다. 한 픽셀 씩 너무 많이 클리핑됩니다. 논리적으로 당신의 말이 맞는 것 같지만, 전화를 ** Rectangle.FromLTRB **로 변경하여 ** srcRect = Rectangle.FromLTRB (xMin, yMin, xMax + 1, yMax + 1) **로 바꿨습니다. –

1

내가 나누기 & 정복 방법 제안하고 싶습니다 :

  1. 중간에 이미지를 분할 (예를 들어수직)
  2. 체크 그렇다면, 박스)
  3. 분할 좌측 절반의 경계를위한 최소/최대 기억 (커트 라인 불투명 화소가있는 경우 다시 수직
  4. 커트라인 비 투명 픽셀을 포함하는 경우 -> 경계 상자 업데이트
  5. 만약 그렇지 않다면, 아마도 가장 왼쪽 절반을 버릴 수 있습니다. (사진을 모른다)
  6. 왼쪽에서 오른쪽 절반까지 계속 진행합니다 (이미지가 중간에 있음을 알았습니다). 이미지의 가장 왼쪽 경계
  7. 오른쪽 절반도 마찬가지입니다.
+3

당신의 5 번째 포인트가 잘못되었다고 생각합니다. 투명하지 않은 픽셀을 가진 몇 가지 별개의 영역이있을 수 있습니다. 따라서 잘라진 라인에 투명하지 않은 픽셀이 없기 때문에 아무런 의미가 없습니다. –

+0

감사합니다. bjoernz, 예 : 바이너리 검색이 가능합니다. 내 이미지에 항상 작동하는 것은 아닙니다. 예를 들어 공백으로 구분 된 두 개의 이미지가있을 수 있습니다. – Blorgbeard

관련 문제