在C#中删除JPEG工件

时间:2009-08-02 14:20:11

标签: c# image-processing gdi+ bitmap

我正在为一个母亲组织的俱乐部建立一个网站。我正在下载(leeching;))放在母组织的个人资料页面上的图像,以显示在我自己的页面上。但他们的网站有一个漂亮的白色背景,我的网站背景上有一个漂亮的灰色渐变。这不太匹配。所以我的想法是在将图像保存到我的服务器之前编辑图像。

我正在使用GDI +来增强我的图像,当我使用Bitmap的MakeTransparent方法时,它确实有效,它确实做了它应该做的事情,但我仍然有这些白色的jpeg工件到处都是。工件使图像变得如此糟糕,我最好不要使图像透明,只留下白色,但这在我自己的网站上真的很难看。当然,我总是可以使用白色背景的漂亮边框,但我宁愿将背景更改为透明。

所以我想知道是否以及如何在C#中删除一些简单的JPEG工件。有没有人曾经这样做过?

感谢您的时间。

示例图片:

TB-5404

转换后的图片:

TB-5404 transformed

8 个答案:

答案 0 :(得分:5)

好吧,我尝试了一些远非完美的东西,但我认为它可能对其他人有用。

我已经到了:

alt text

遇到的问题:阴影远离'白色'很难自动转换它们,即使你做了阴影仍然会在图像本身。顶部眩光......毂的东西,也更接近白色和抗锯齿位。图像中有三到七个白点,它们没有连接到任何一个主角;最后,边缘仍然有一点白色(可能通过调整代码来摆脱它,但不是没有取下部分眩光顶部。

C#低效代码:

    static void Main()
    {
        Bitmap bmp=new Bitmap("test.jpg");

        int width = bmp.Width;
        int height = bmp.Height;
        Dictionary<Point, int> currentLayer = new Dictionary<Point, int>();
        currentLayer[new Point(0, 0)] = 0;
        currentLayer[new Point(width - 1, height - 1)] = 0;
        while (currentLayer.Count != 0)
        {
            foreach (Point p in currentLayer.Keys)
                bmp.SetPixel(p.X, p.Y, Color.Black);
            Dictionary<Point, int> newLayer = new Dictionary<Point, int>();
            foreach (Point p in currentLayer.Keys)
                foreach (Point p1 in Neighbors(p, width, height))
                    if (Distance(bmp.GetPixel(p1.X, p1.Y), Color.White) < 40)
                        newLayer[p1] = 0;
            currentLayer = newLayer;
        }

        bmp.Save("test2.jpg");
    }

    static int Distance(Color c1, Color c2)
    {
        int dr = Math.Abs(c1.R - c2.R);
        int dg = Math.Abs(c1.G - c2.G);
        int db = Math.Abs(c1.B - c2.B);
        return Math.Max(Math.Max(dr, dg), db);
    }

    static List<Point> Neighbors(Point p, int maxX, int maxY)
    {
        List<Point> points=new List<Point>();
        if (p.X + 1 < maxX) points.Add(new Point(p.X + 1, p.Y));
        if (p.X - 1 >= 0) points.Add(new Point(p.X - 1, p.Y));
        if (p.Y + 1 < maxY) points.Add(new Point(p.X, p.Y + 1));
        if (p.Y - 1 >= 0) points.Add(new Point(p.X, p.Y - 1));
        return points;
    }

代码的工作方式是从两点开始;将它们设置为黑色,然后检查它们附近的邻居是否接近白色;如果他们被添加到列表然后执行。最终它会耗尽白色像素来改变。

作为替代方案,您可能需要考虑重新设计网站以使用白色背景。

答案 1 :(得分:1)

循环遍历图像中的每个像素,如果R,G和B高于,例如230,则用您想要的颜色(或透明)替换颜色。甚至可能会根据旧颜色与“真实”白色的距离来加权新颜色。

如果实际图像也是白色,则会出现问题,否则你最终会得到一个灰色的冲锋队:)

答案 2 :(得分:1)

您将无法使用100%准确度等任何内容自动执行此操作。

这样做的原因是,您所拥有的唯一信息是您知道图像中的某些像素试图与之完美融合的颜色。为了遮蔽背景,图像中只有一些像素实际上会使用此值或接近此值的颜色,其他像素将使用(在白色情况下),因为实际表示的对象实际上是白色的(该死的精度为这些帝国风暴骑兵。)

一种先进的机器学习,可以检测哪个是一个有趣的问题领域,对你来说可能是一个有趣的项目,但它肯定无法快速解决你的问题。

你遇到的另一个问题是,即使你能够很好地检测出那些试图融入背景的图像区域,你也会发现问题“不混合”,然后将它们重新混合成新的背景颜色。除非颜色合理兼容。在这种情况下,你的灰色可能会起作用,因为它是一种像白色一样的广谱颜色。

您要使用的技术如下:

  • 使用填充填充算法从图像边缘向内选择已知背景颜色的x%(1)内的所有像素。
  • 对于这些像素,将其Alpha通道设置为与原始颜色匹配的比例值,并消除与其关联的色偏。
    • 因此,如果背景是RGB值a,b,c并且像素是+ 5,b,c-7那么结果是 RGBA 5,0,0,((a + b + c-7)/(a + b + c)* 256)(1)
  • 将此Alpha混合图像合成在新背景颜色的疼痛方格上。
  • 将没有Alpha通道的结果渲染为新图像。

对于颜色接近背景颜色的对象,仍然会出现问题。   *在原件的情况下,可能是阴影被用于暗示物体的存在,因此洪水填充将“侵入”图像的内部。   *在后者的情况下,生成的图像将失去对象的定义,并且不会出现细微的阴影,高光或仅仅是明线,以指示对象的终点和背景的终点。

这是一个非常粗略的初步近似值,但可能会覆盖目标的合理百分比。这些具有透明全封闭孔的图片(如示例中外拱的间隙)不太可能以自动方式很好地工作,因为算法将无法区分白洞和白色冲锋队。 您可能希望让算法突出显示其计划重新混合的图片区域,并允许简单选择区域以包含/排除(使用Pain.Net中的魔棒选择工具作为如何执行此操作的示例,如果您需要看似简单,允许简单的每像素选择,以减少前期工作量。


  1. x的值将是你调整的东西 - 可能是,基于图像的某些方面(比如图像的接近底色的比例),你可以自动调整它。
  2. 请注意,此公式假定接近白色,接近黑色,您想要反转

答案 3 :(得分:1)

另一种基于评论对话框的方法:

static void Main()
{
    Bitmap mask = new Bitmap(@"mask.bmp");
    Bitmap bmp=new Bitmap(@"test.jpg");
    int width = bmp.Width;
    int height = bmp.Height;

    for(int x=0; x<width; x++)
        for (int y = 0; y < height; y++)
            if (mask.GetPixel(x, y).R < 250)
                bmp.SetPixel(x,y,mask.GetPixel(x,y));
    bmp.Save(@"test3.jpg");
}

给出面具:

alt text

你得到了结果:

alt text

在Paint.NET中稍微清理掩码的边框并禁用抗锯齿。同样,它只适用于你能辨别出正在使用哪个边框......但它确实很好......除了绿色......

答案 4 :(得分:0)

你还必须处理灰白色调。可能当他们最初创建iamges并设置背景颜色时,会出现一些消除锯齿现象,然后当保存为jpg时,并非所有颜色都会被完美保留。因此,如果您使特定颜色透明,则不会获得该颜色的所有阴影,这将留下许多工件。你需要的东西会与颜色与你的关键颜色的接近程度成比例。这可能比像Photoshop这样的批处理脚本更容易,但我不知道这是否是您需要实时进行的。

答案 5 :(得分:0)

没有(远程简单的)方式以编程方式处理此问题。图像边缘周围的白色伪影区域是几乎是白色但不完全的像素的结果,因此它们不会获得透明效果。面具/咖啡杯上还有几个纯白色的斑点,因此它们变得透明,因此变成灰色。

您最好的选择是联系原始网站的网站管理员,看看他们是否可以向您发送原始图片,希望以photoshop或其他格式保存原始图层。然后你可以用保留原始透明度的格式(PNG或类似的东西)重新生成图像,或者使用渐变作为背景(这很难做到这一点,因为你不知道究竟在哪里将呈现图片的渐变。)

正如你所建议的那样,我会在图像周围使用某种边框。

答案 6 :(得分:0)

感谢大家的回答......所有好建议......

这是我昨天做的:

public const int PIXEL_REGION = 60;
public const int TRANSPARENT_DISTANCE = 60;
public static readonly Color TRANSPARENT_COLOR = Color.White;

private System.Drawing.Image ProcessImage(System.Drawing.Image image)
{
    Bitmap result = new Bitmap(image.Width, image.Height);

    Bitmap workImage = new Bitmap(image);

    for (int x = 0; x < image.Width; x++)
    {
        for (int y = 0; y < image.Height; y++)
        {
            Color color = workImage.GetPixel(x, y);

            if (x < PIXEL_REGION || x > image.Width - PIXEL_REGION || y < PIXEL_REGION || y > image.Height - PIXEL_REGION)
            {
                double distance = CalculateColourDistance(TRANSPARENT_COLOR, color);

                if(distance < TRANSPARENT_DISTANCE)
                {
                    result.SetPixel(x, y, Color.Transparent);
                    continue;
                }
            }

            result.SetPixel(x, y, color);
        }
    }

    return result;
}

private double CalculateColourDistance(Color c1, Color c2)
{
    int a = c2.A - c1.A;
    int r = c2.R - c1.R;
    int g = c2.G - c1.G;
    int b = c2.B - c1.B;

    return Math.Sqrt(Math.Pow(a, 2) + Math.Pow(r, 2) + Math.Pow(g, 2) + Math.Pow(b, 2));
}

它不是本书中最漂亮的代码,但它的作用是,对于像素区域中的每个像素,它计算Color.White和我自己的颜色之间的距离,当它低于预定距离时,它将被涂上透明的颜色..

这段代码产生以下结果:

TB-5404 transformed v 2.0

对你来说这并不坏......但它仍然很糟糕:)

我有一个技巧,我会与你们分享,如果成功的话......

答案 7 :(得分:0)

这是另一次失败的尝试;)

我有一个想法..电影使用绿屏,为什么不为我的图像使用绿色叠加.. 所以这是叠加层:

Green overlay with transparent inner thing

结果:

Green stuff all over the place

所以我学到了关于PNG压缩的另一个有价值的教训..我也尝试过用bmp,但不知怎的,我总是以绿色边框结束..我可以尝试擦除它,但也许我应该把它留在白色背景,在它周围放置一些愚蠢的边框并完成它......

啊好......;)