C# 从图像中删除周围的空白
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/248141/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Remove surrounding whitespace from an image
提问by Kyle B.
I have a block of product images we received from a customer. Each product image is a picture of something and it was taken with a white background. I would like to crop all the surrounding parts of the image but leave only the product in the middle. Is this possible?
我有一组我们从客户那里收到的产品图片。每张产品图片都是某物的图片,它是用白色背景拍摄的。我想裁剪图像的所有周围部分,但只将产品留在中间。这可能吗?
As an example: [http://www.5dnet.de/media/catalog/product/d/r/dress_shoes_5.jpg][1]
例如:[ http://www.5dnet.de/media/catalog/product/d/r/dress_shoes_5.jpg][1]
I don't want all white pixels removed, however I do want the image cropped so that the top-most row of pixels contains one non-white pixel, the left-most vertical row of pixels contains one non-white pixel, bottom-most horizontal row of pixels contains one non-white pixel, etc.
我不希望删除所有白色像素,但是我确实希望裁剪图像,以便最上面的像素行包含一个非白色像素,最左侧的垂直像素行包含一个非白色像素,底部-大多数水平像素行包含一个非白色像素等。
Code in C# or VB.net would be appreciated.
C# 或 VB.net 中的代码将不胜感激。
采纳答案by Bevan
I've written code to do this myself - it's not too difficult to get the basics going.
我自己编写了代码来做到这一点 - 掌握基础知识并不太难。
Essentially, you need to scan pixel rows/columns to check for non-white pixels and isolate the bounds of the product image, then create a new bitmap with just that region.
本质上,您需要扫描像素行/列以检查非白色像素并隔离产品图像的边界,然后创建一个仅包含该区域的新位图。
Note that while the Bitmap.GetPixel()
method works, it's relatively slow. If processing time is important, you'll need to use Bitmap.LockBits()
to lock the bitmap in memory, and then some simple pointer use inside an unsafe { }
block to access the pixels directly.
请注意,虽然该Bitmap.GetPixel()
方法有效,但速度相对较慢。如果处理时间很重要,则需要使用Bitmap.LockBits()
将位图锁定在内存中,然后在unsafe { }
块内使用一些简单的指针来直接访问像素。
This articleon CodeProject gives some more details that you'll probably find useful.
回答by Claudiu
It's certainly possible. In pseudocode:
这当然是可能的。在伪代码中:
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
And similarly for left and right.
左右类似。
The code for allWhiteRow
would involve looking at the pixels in that row and making sure they're all closeto 255,255,255.
的代码allWhiteRow
将涉及查看该行中的像素并确保它们都接近255,255,255。
回答by Alnitak
The pnmcrop
utility from the netpbm
graphics utilities library does exactly that.
在pnmcrop
从实用netpbm
的图形库的实用程序不正是这么做的。
I suggest looking at their code, available from http://netpbm.sourceforge.net/
我建议查看他们的代码,可从http://netpbm.sourceforge.net/ 获得
回答by Dmitri Nesteruk
Here's my (rather lengthy) solution:
这是我的(相当冗长的)解决方案:
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);
}
}
回答by Darren
I found I had to adjust Dmitri's answer to ensure it works with images that don't actually need cropping (either horizontally, vertically or both)...
我发现我必须调整 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);
}
}
回答by Brian Hasden
I needed a solution that worked on large images (GetPixel is slow), so I wrote the extension method below. It seems to work well in my limited testing. The drawback is that "Allow Unsafe Code" has to be checked in your project.
我需要一个适用于大图像的解决方案(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
}
回答by user6064120
fix remaining 1px white space at the top and left
修复顶部和左侧剩余的 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);
}
}
回答by Trung
public void TrimImage() {
int threshhold = 250;
int topOffset = 0;
int bottomOffset = 0;
int leftOffset = 0;
int rightOffset = 0;
Bitmap img = new Bitmap(@"e:\Temp\Trim_Blank_Image.png");
bool foundColor = false;
// Get left bounds to crop
for (int x = 1; x < img.Width && foundColor == false; x++)
{
for (int y = 1; y < img.Height && foundColor == false; y++)
{
Color color = img.GetPixel(x, y);
if (color.R < threshhold || color.G < threshhold || color.B < threshhold)
foundColor = true;
}
leftOffset += 1;
}
foundColor = false;
// Get top bounds to crop
for (int y = 1; y < img.Height && foundColor == false; y++)
{
for (int x = 1; x < img.Width && foundColor == false; x++)
{
Color color = img.GetPixel(x, y);
if (color.R < threshhold || color.G < threshhold || color.B < threshhold)
foundColor = true;
}
topOffset += 1;
}
foundColor = false;
// Get right bounds to crop
for (int x = img.Width - 1; x >= 1 && foundColor == false; x--)
{
for (int y = 1; y < img.Height && foundColor == false; y++)
{
Color color = img.GetPixel(x, y);
if (color.R < threshhold || color.G < threshhold || color.B < threshhold)
foundColor = true;
}
rightOffset += 1;
}
foundColor = false;
// Get bottom bounds to crop
for (int y = img.Height - 1; y >= 1 && foundColor == false; y--)
{
for (int x = 1; x < img.Width && foundColor == false; x++)
{
Color color = img.GetPixel(x, y);
if (color.R < threshhold || color.G < threshhold || color.B < threshhold)
foundColor = true;
}
bottomOffset += 1;
}
// Create a new image set to the size of the original minus the white space
//Bitmap newImg = new Bitmap(img.Width - leftOffset - rightOffset, img.Height - topOffset - bottomOffset);
Bitmap croppedBitmap = new Bitmap(img);
croppedBitmap = croppedBitmap.Clone(
new Rectangle(leftOffset - 3, topOffset - 3, img.Width - leftOffset - rightOffset + 6, img.Height - topOffset - bottomOffset + 6),
System.Drawing.Imaging.PixelFormat.DontCare);
// Get a graphics object for the new bitmap, and draw the original bitmap onto it, offsetting it do remove the whitespace
//Graphics g = Graphics.FromImage(croppedBitmap);
//g.DrawImage(img, 1 - leftOffset, 1 - rightOffset);
croppedBitmap.Save(@"e:\Temp\Trim_Blank_Image-crop.png", ImageFormat.Png);
}
I have got code from other post in ms, but that has bugs, I have changed something, now it works good.
我从 ms 的其他帖子中获得了代码,但是有错误,我已经更改了一些内容,现在它运行良好。
The post from http://msm2020-sc.blogspot.com/2013/07/c-crop-white-space-from-around-image.html
来自http://msm2020-sc.blogspot.com/2013/07/c-crop-white-space-from-around-image.html的帖子