我有一块从客户那里收到的产品图片.每个产品图像是一个东西的图片,它是用白色背景拍摄的.我想裁剪图像的所有周围部分,但只留下产品在中间.这可能吗?
举个例子:[ http://www.5dnet.de/media/catalog/product/d/r/dress_shoes_5.jpg] [1 ]
我不希望删除所有白色像素,但是我希望裁剪图像,使最顶部的像素行包含一个非白色像素,最左边的垂直像素行包含一个非白色像素,底部 - 大多数水平像素行包含一个非白色像素等.
C#或VB.net中的代码将不胜感激.
我发现我必须调整Dmitri的答案,以确保它适用于实际上不需要裁剪的图像(水平,垂直或两者)......
public static Bitmap Crop(Bitmap bmp) { int w = bmp.Width; int h = bmp.Height; FuncallWhiteRow = row => { for (int i = 0; i < w; ++i) if (bmp.GetPixel(i, row).R != 255) return false; return true; }; Func 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} cropped, topmost, bottommost, leftmost, rightmost, croppedWidth, croppedHeight), ex); } }
这是我(相当冗长)的解决方案:
public Bitmap Crop(Bitmap bmp) { int w = bmp.Width, h = bmp.Height; FuncallWhiteRow = row => { for (int i = 0; i < w; ++i) if (bmp.GetPixel(i, row).R != 255) return false; return true; }; Func 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); } }
我需要一个适用于大图像的解决方案(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 }
我自己编写了代码来实现这一点 - 让基础知识变得非常困难.
实质上,您需要扫描像素行/列以检查非白色像素并隔离产品图像的边界,然后创建仅包含该区域的新位图.
请注意,虽然该Bitmap.GetPixel()
方法有效,但速度相对较慢.如果处理时间很重要,则需要使用Bitmap.LockBits()
锁定内存中的位图,然后在unsafe { }
块内使用一些简单的指针直接访问像素.
关于CodeProject的这篇文章提供了一些您可能会觉得有用的更多细节.
这当然是可能的.在伪代码中:
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.