2008-10-29 18 views
21

Mam blok obrazów produktów, które otrzymaliśmy od klienta. Każdy obraz produktu jest obrazem czegoś i został zrobiony na białym tle. Chciałbym przyciąć wszystkie otaczające części obrazu, ale pozostawić tylko środek na środku. czy to możliwe?Usuń otaczające spacje z obrazu

Jako przykład: [http://www.5dnet.de/media/catalog/product/d/r/dress_shoes_5.jpg][1]

nie chcę wszystkie białe piksele usunięte, jednak chcę obraz przycięte tak, że górna-najbardziej wiersz pikseli zawiera jeden non-biały piksel, lewicę najbardziej pionowy rząd pikseli zawiera jeden inny niż biały piksel, najniższy poziomy rząd pikseli zawiera jeden inny niż biały piksel itp.

Kod w języku C# lub VB.net byłby mile widziany.

Odpowiedz

6

Napisałem kod, aby samemu to zrobić - nie jest to zbyt trudne, aby uzyskać podstawowe informacje.

Zasadniczo należy zeskanować piksele wierszy/kolumn, aby sprawdzić inne niż białe piksele i odizolować granice obrazu produktu, a następnie utworzyć nową bitmapę z tym samym regionem.

Należy pamiętać, że chociaż metoda Bitmap.GetPixel() działa, jest stosunkowo powolna. Jeśli czas przetwarzania jest ważny, musisz użyć Bitmap.LockBits(), aby zablokować bitmapę w pamięci, a następnie użyć prostego wskaźnika wewnątrz bloku unsafe { }, aby uzyskać bezpośredni dostęp do pikseli.

This article na CodeProject podaje więcej szczegółów, które prawdopodobnie przydadzą Ci się.

+0

Czy to zbyt dużo czasu na przetworzenie obrazu do 1000x1000? Proszę o poradę. – techno

+0

Zależy od definicji "za dużo czasu" - która zależy od kontekstu. Sugerowałbym napisanie kodu przy użyciu 'Bitmap.GetPixel()', a następnie porównanie wyników, aby zobaczyć. Należy również pamiętać, że inteligentny algorytm jest ważniejszy niż mikro-optymalizacja poszczególnych odczytów pikseli. – Bevan

5

Jest to z pewnością możliwe. W pseudokod:

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 

I podobnie dla lewej i prawej strony.

Kod dla allWhiteRow wymagałby obejrzenia pikseli w tym rzędzie i upewnienia się, że wszystkie one są zamknij do 255,255,255.

16

Oto mój (dość długie) rozwiązanie:

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); 
    } 
} 
+1

działa idealnie, z wyjątkiem sytuacji, gdy przycięta Width lub croppedHeight jest zero, w tym przypadku ustawiam je odpowiednio na bmp.Width lub bmp.Height i działa jak urok :) –

+0

to działa dla każdego obrazu? jak png jpeg czy gif? – MonsterMMORPG

+0

@MonsterMMORPG tak, to wszystko powinno zostać odczytane w bitmapie, która zapewnia adresowanie pikselowe. zwróć uwagę, że to rozwiązanie nie jest bardzo szybkie i istnieje o wiele szybsze sposoby robienia tego (poza .NET). –

29

znalazłem musiałem dostosować odpowiedź Dmitri w celu zapewnienia, że ​​działa z obrazami, które don” t faktycznie trzeba kadrowanie (poziomo, pionowo lub oba) ...

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); 
     } 
    } 
+0

Dobra robota, dzięki za udostępnienie! – JTtheGeek

+4

Dla plików PNG z "przezroczystymi białymi znakami" dodałem sprawdzanie bmp.GetPixel (i, wiersz) .A! = 0 i bmp.GetPixel (col, i) .A! = 0 (poziom alfa zero). Teraz moje pliki PNG działają świetnie. Dziękuję za kod! Uwaga: Uruchamiaj bmp.GetPixel(), a następnie analizuj właściwości R i A, aby zapobiec podwójnemu skanowaniu. – timmi4sa

+0

Dodam dodatkowy przypadek, który trzeba potraktować: Mam mały obrazek i użyję 'FillRectangle (Brushes.White, ...)' -> wtedy mój obraz będzie miał nie tylko 255, więc powinieneś poprawić to. Wartości dla bieli: 'W: 0 H: 0 a: 255 b: 254 g: 254 r: 254 W: 1 H: 0 a: 255 b: 254 g: 254 r: 254 W: 2 H: 0 a : 255 b: 254 g: 254 r: 254 W: 3 H: 0 a: 255 b: 254 g: 254 r: 254 W: 4 H: 0 a: 255 b: 254 g: 254 r: 254 W: 5 H: 0 a: 255 b: 254 g: 254 r: 254 W: 6 H: 0 a: 255 b: 254 g: 254 r: 254 W: 7 H: 0 a: 255 b: 254 g: 254 r: 254 W: 8 H: 0 a: 255 b: 254 g: 254 r: 254 W: 9 H: 0 a: 255 b: 254 g: 254 r: 254 W: 0 H: 1 a: 255 b: 254 g: 254 r: 254 W: 1 H: 1 a: 255 b: 255 g: 255 r: 255' – HellBaby

7

potrzebowałem rozwiązanie, które pracowały na dużych obrazów (getPixel jest slo w), więc napisałem metodę rozszerzenia poniżej. Wydaje się, że działa dobrze w moich ograniczonych testach. Wadą jest to, że "Pozwól na niebezpieczny kod" musi być sprawdzony w twoim projekcie.

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 
} 
+2

Jeśli chcesz tylko przyciąć przezroczystość, zmodyfikuj "if ((cropColor.A> 0 ...) {"line above to simply" if (a! = 0) {". Możesz również pozbyć się zmiennej cropColor, która działa tak dobrze jak dotąd." – jeromeyers

+0

co masz na myśli Pozwalając na niebezpieczny kod – MonsterMMORPG

+0

@MonsterMMORPG Możesz uzyskać więcej informacji na temat niebezpiecznej flagi i jak ją włączyć dla swojego projektu w Visual Studio na https://msdn.microsoft.com/en-us/library/ ct597kb0.aspx. Zasadniczo pozwala używać wskaźników w tradycyjnym znaczeniu z arytmetyką i innymi. –

1

fix pozostałe 1px białą przestrzeń w górnej i lewej

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); 
     } 
    } 
Powiązane problemy