裁剪/删除图像边缘不需要的空间
Posted
技术标签:
【中文标题】裁剪/删除图像边缘不需要的空间【英文标题】:Crop/Remove Unwanted Space at the edges of image 【发布时间】:2012-11-25 06:51:19 【问题描述】:我进行了很多搜索以删除不需要的空间,但找不到。我只找到了可以用来删除黑白背景空间的链接。但我的背景图像可以是任何东西。所以,如果我有这些图片,
如何提取我需要的图像部分。例如,
【问题讨论】:
首先,我会选择图像角落的颜色,如果它们相同(或至少 3 个),然后迭代像素,将与角落匹配的颜色变黑,美白别人。 你保证有单色/纯色背景吗?背景的总面积是否总是大于包含的图片?边缘是否总是有至少 1 个像素的背景颜色边框? 我坚信这很简单,实现可能会因您使用的库而异。 @Kal_Torak,颜色大多是单一的 你处于什么样的环境中?窗体? WPF?还有什么?它们是什么样的图像? PNG?其他格式? 【参考方案1】:这是我对您问题的解决方案:
我已经声明了一个获取原始图像的方法,然后它通过检查提供的图像的角来查找背景颜色,如果至少 3 个角具有相似的颜色(最多 10% 偏移) 然后我们找到了背景颜色,然后它试图找到图像中那些形状的边界,这些形状的颜色当然与背景颜色不同
在找到边界后,函数裁剪图像并将新的裁剪区域作为新的位图返回!
这是演示文件:Download
完整解决方案:Download
结果如下:
Image 1
:
Image 2
:
Image 3
:
这是ImageProcessingTools
类中的函数
简化,
public class ImageHelper
#region CropUnwantedBackground
public static Bitmap CropUnwantedBackground(Bitmap bmp)
var backColor = GetMatchedBackColor(bmp);
if (backColor.HasValue)
var bounds = GetImageBounds(bmp, backColor);
var diffX = bounds[1].X - bounds[0].X + 1;
var diffY = bounds[1].Y - bounds[0].Y + 1;
var croppedBmp = new Bitmap(diffX, diffY);
var g = Graphics.FromImage(croppedBmp);
var destRect = new Rectangle(0, 0, croppedBmp.Width, croppedBmp.Height);
var srcRect = new Rectangle(bounds[0].X, bounds[0].Y, diffX, diffY);
g.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
bmp.Dispose();
return croppedBmp;
else
bmp.Dispose();
return null;
#endregion
#region Private Methods
#region GetImageBounds
private static Point[] GetImageBounds(Bitmap bmp, Color? backColor)
//--------------------------------------------------------------------
// Finding the Bounds of Crop Area bu using Unsafe Code and Image Proccesing
Color c;
int width = bmp.Width, height = bmp.Height;
bool upperLeftPointFounded = false;
var bounds = new Point[2];
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
c = bmp.GetPixel(x, y);
bool sameAsBackColor = ((c.R <= backColor.Value.R * 1.1 && c.R >= backColor.Value.R * 0.9) &&
(c.G <= backColor.Value.G * 1.1 && c.G >= backColor.Value.G * 0.9) &&
(c.B <= backColor.Value.B * 1.1 && c.B >= backColor.Value.B * 0.9));
if (!sameAsBackColor)
if (!upperLeftPointFounded)
bounds[0] = new Point(x, y);
bounds[1] = new Point(x, y);
upperLeftPointFounded = true;
else
if (x > bounds[1].X)
bounds[1].X = x;
else if (x < bounds[0].X)
bounds[0].X = x;
if (y >= bounds[1].Y)
bounds[1].Y = y;
return bounds;
#endregion
#region GetMatchedBackColor
private static Color? GetMatchedBackColor(Bitmap bmp)
// Getting The Background Color by checking Corners of Original Image
var corners = new Point[]
new Point(0, 0),
new Point(0, bmp.Height - 1),
new Point(bmp.Width - 1, 0),
new Point(bmp.Width - 1, bmp.Height - 1)
; // four corners (Top, Left), (Top, Right), (Bottom, Left), (Bottom, Right)
for (int i = 0; i < 4; i++)
var cornerMatched = 0;
var backColor = bmp.GetPixel(corners[i].X, corners[i].Y);
for (int j = 0; j < 4; j++)
var cornerColor = bmp.GetPixel(corners[j].X, corners[j].Y);// Check RGB with some offset
if ((cornerColor.R <= backColor.R * 1.1 && cornerColor.R >= backColor.R * 0.9) &&
(cornerColor.G <= backColor.G * 1.1 && cornerColor.G >= backColor.G * 0.9) &&
(cornerColor.B <= backColor.B * 1.1 && cornerColor.B >= backColor.B * 0.9))
cornerMatched++;
if (cornerMatched > 2)
return backColor;
return null;
#endregion
#endregion
这是 ASP.NET 中的一个简单用法,
if (IsPostBack && Request.Files.Count > 0)
var file = Request.Files[0];
var bmp = new Bitmap(file.InputStream);
var croppedBmp = ImageHelper.CropUnwantedBackground(bmp);
Response.ContentType = file.ContentType;
croppedBmp.Save(Response.OutputStream, ImageFormat.Jpeg);
Response.End();
最后我要提一下,这些很棒的教程在图像处理方面帮助了我很多:
Image Processing for Dummies with C# and GDI+
Image Processing using C#
希望对你有帮助:)
【讨论】:
@Mehran 提供的链接不起作用。您还有可下载的解决方案吗? @LP13 幸运的是我找到了解决方案!这是可下载解决方案的链接:dropbox.com/s/wpf78g3lmzq3jg4/Demo.rar?dl=0【参考方案2】:这是一种更可靠的方法,它使用 Sobel 能量滤波器和快速边界框检测例程(从 WhitespaceTrimmer plugin 中提取 ImageResizer)。
namespace ImageResizer.Plugins.WhitespaceTrimmer
public class BoundingBoxFinder
/// <summary>
/// Returns a rectangle inside 'lookInside' that bounds any energy greater than 'threshold'.
/// </summary>
/// <param name="image"></param>
/// <param name="lookInside">A rectangle of 'image' to look inside. </param>
/// <param name="threshold">1-255, the energy threshold to detect activity. 80-150 is a good range.</param>
/// <returns></returns>
public Rectangle FindBoxSobel(Bitmap originalImage, Rectangle lookInside, byte threshold)
Bitmap image = originalImage;
try
//Convert if needed (makes an extra copy)
if (image.PixelFormat != PixelFormat.Format24bppRgb &&
image.PixelFormat != PixelFormat.Format32bppArgb &&
image.PixelFormat != PixelFormat.Format32bppRgb)
image = AForge.Imaging.Image.Clone(image, PixelFormat.Format24bppRgb);
//Crop if needed (makes an extra copy unless we converted too, then only 1 extra copy)
if (!lookInside.Equals(new Rectangle(0, 0, image.Width, image.Height)))
Bitmap oldImage = image;
try
image = new Crop(lookInside).Apply(image);
finally
if (oldImage != originalImage) oldImage.Dispose(); //Dispose the cloned
//Makes 1 more copy at 1/3rd the size, in grayscale
Rectangle result = FindBoxSobel(image, threshold);
return new Rectangle(lookInside.X + result.X, lookInside.Y + result.Y, result.Width, result.Height);
finally
if (image != originalImage) image.Dispose();
/// <summary>
/// Requires 24 bit or 32 bit (A) RGB image.
/// </summary>
/// <param name="rgb"></param>
/// <param name="threshold"></param>
/// <returns></returns>
public Rectangle FindBoxSobel(Bitmap rgb, byte threshold)
using (Bitmap gray = Grayscale.CommonAlgorithms.Y.Apply(rgb))
//Apply sobel operator to grayscale image
new SobelEdgeDetector().ApplyInPlace(gray);
//Threshold into black and white.
new Threshold(threshold).ApplyInPlace(gray);
//Trim only exact black pixels
// lock source bitmap data
BitmapData data = gray.LockBits(new Rectangle(0, 0, gray.Width, gray.Height), ImageLockMode.ReadOnly, gray.PixelFormat);
try
return FindBoxExactGrayscale(data, 0);
finally
gray.UnlockBits(data);
/// <summary>
/// Returns a bounding box that only excludes the specified color.
/// Only works on 8-bit images.
/// </summary>
/// <param name="sourceData"></param>
/// <param name="colorToRemove">The palette index to remove.</param>
/// <returns></returns>
public Rectangle FindBoxExactGrayscale(BitmapData sourceData, byte indexToRemove)
if (sourceData.PixelFormat != PixelFormat.Format8bppIndexed) throw new ArgumentOutOfRangeException("FindBoxExact only operates on 8-bit grayscale images");
// get source image size
int width = sourceData.Width;
int height = sourceData.Height;
int offset = sourceData.Stride - width;
int minX = width;
int minY = height;
int maxX = 0;
int maxY = 0;
// find rectangle which contains something except color to remove
unsafe
byte* src = (byte*)sourceData.Scan0;
for (int y = 0; y < height; y++)
if (y > 0) src += offset; //Don't adjust for offset until after first row
for (int x = 0; x < width; x++)
if (x > 0 || y > 0) src++; //Don't increment until after the first pixel.
if (*src != indexToRemove)
if (x < minX)
minX = x;
if (x > maxX)
maxX = x;
if (y < minY)
minY = y;
if (y > maxY)
maxY = y;
// check
if ((minX == width) && (minY == height) && (maxX == 0) && (maxY == 0))
minX = minY = 0;
return new Rectangle(minX,minY,maxX - minX + 1, maxY - minY + 1);
【讨论】:
谢谢。我会检查这个。但我认为对于简单的使用,我应该使用不依赖任何库的 GDI+ 方法。 我想这取决于准确性对您来说是否重要。以上是关于裁剪/删除图像边缘不需要的空间的主要内容,如果未能解决你的问题,请参考以下文章