【问题标题】:JPEG artifacts removal in C#C# 中的 JPEG 伪影去除
【发布时间】:2010-11-16 04:20:36
【问题描述】:

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

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

所以我想知道是否以及如何在 C# 中删除一些简单的 JPEG 伪影。以前有人做过吗?

感谢您的宝贵时间。

示例图片:

转换后的图像:

【问题讨论】:

  • 您可以发布其中一张图片,以便我们了解您的意思吗?这个结果实际上一点也不奇怪,因为透明度只是使一种特定的颜色透明(在这种情况下为白色)。如果选择白色作为透明色,则所有白色像素都将变为透明。
  • 那是真的.. 它就像一个魅力.. JPEG 太糟糕了,它已经搞砸了 ;).. 发布了一张图片!
  • 我瞎了!抱歉,它没有显示出来。
  • 我也是.. 我无法让图像工作:P
  • 我通常使用freeimagehosting。

标签: c# image-processing gdi+ bitmap


【解决方案1】:

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

我已经:

遇到的问题:阴影与“灰白色”相距甚远,很难自动转换它们,即使您这样做了,阴影仍会在图像本身中。顶部的眩光……集线器的东西,也更接近于白色,然后是抗锯齿位。图像中有 3 到 7 个白色点,它们没有连接到任何主要角;最后边缘上仍然有一点白色(可能可以通过调整代码来消除它,但不能不去掉部分眩光顶部。

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;
    }

代码从两点开始;将它们设置为黑色,然后检查它们附近的邻居是否接近白色;如果是,则将它们添加到列表中,然后对其执行。最终它用完了白色像素来改变。

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

【讨论】:

  • 虽然它并不完美,但您的回答获得了最多的支持,所以我接受了您的回答;我已经放弃了对这种图像解码的追求。我在白色图像周围放置了一个边框,就像一幅画。谢谢你的帮助!! :) 见501st.nl/Members.aspx ;)
【解决方案2】:

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

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

【讨论】:

  • 这可以更改为更通用的解决方案,通过执行伪图遍历不会使 Storm trooper 变灰。从左上角的白色像素开始,然后向左/向下/向上/向右移动到与“足够接近”标准匹配的每个像素。基本上是洪水填充。如果实际图像的边缘存在混合(白色和棕色的混合,在灰色上看起来完全错误),这可能仍然会造成问题。
  • 我喜欢你的回答.. 它干净而简单.. 今天下午我和我的女朋友一起吃饭时,我也很震惊.. 你的解决方案添加了“这里没有变化”区域/块对于冲锋队:)
  • 好的,我也尝试了您的解决方案.. 它仍然看起来像边缘有白色边框的垃圾.. 该死的白色很难看;).. 谢谢你的时间!
【解决方案3】:

您将无法以 100% 的准确度自动执行此操作。

这样做的原因是,您拥有的唯一信息是您知道图像中的 一些 像素试图与之很好地融合的颜色。图像中只有一些像素实际上会使用等于或接近该值的颜色来为背景着色,其他像素将使用(在白色的情况下),因为所表示的实际对象实际上是白色的(该死的精度这些帝国冲锋队)。

一种复杂的机器学习来检测哪个是一个有趣的问题域,对你来说可能是一个有趣的项目,但它肯定不会快速解决你的直接问题。

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

您要使用的技术如下:

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

这对于颜色接近任一背景颜色的对象仍然存在问题。 * 在原件的情况下,可能是阴影被用来暗示物体的存在,因此泛光填充将“侵入”图像的内部。 * 在后者的情况下,生成的图像将失去对象的定义,并且不会出现细微的阴影、高光或仅出现简单的线条来指示对象的结束位置和背景结束的位置。

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


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

【讨论】:

    【解决方案4】:

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

    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");
    }
    

    给定掩码:

    你得到结果:

    在 Paint.NET 中稍微清理了蒙版的边框,并禁用了抗锯齿。同样,它仅适用于您可以辨别正在使用哪个边框...但结果确实很好...除了绿色...

    【讨论】:

    • 是的,我用第二种方法尝试过,但是当我使绿色透明时,它又出错了;)..(见绿色边框)..问题是我自己网站的背景我猜..我们在背景中使用渐变,而不必精确放置图片,透明背景是要走的路..嗯..我们确实有另一个想法。我们让用户上传他们自己的动作图片,添加一个漂亮的冲锋队相框,并让它成为个人资料图片。与这个问题无关,所以我想避免它也可以解决它.. 谢谢你的帮助..非常感谢
    • 我认为您可以修改蒙版图像,使绿色边框透明(在进行手动修饰时);然后只需使用红色区域与原始图像合并;结果是边界上的透明区域,并在中心合并区域。不过,无论哪种方式,它都很有趣。
    【解决方案5】:

    您还必须处理灰白色的阴影。大概在他们最初制作 iamges 并设置背景颜色时,有一些抗锯齿,然后当保存为 jpg 时,并非所有颜色都能完美保留。因此,如果您要使特定颜色透明,则不会获得该颜色的所有阴影,这会留下许多伪影。您需要一些透明度与颜色与您的关键颜色的接近程度成正比的东西。这可能更容易在 Photoshop 之类的批处理脚本中完成,但我不知道这是否是您需要实时执行的操作。

    【讨论】:

    • 嗯..这可能会改变你知道..不时..有一些动态的东西会很好..为未来的成员处理所有图像..
    • 查看图片后,如果您的来源可以以无损格式(如位图)提供它们,您可能不会遇到您遇到的问题。看起来灰白色来自于 jpg 的压缩。也许他们可以在他们的服务器上同时托管 jpg 和 bmp,在他们的站点上使用 jpg,并将 bmp 留在同一位置。所以当你拉取图片时,你可以替换扩展名,拉取bmp,替换颜色。
    • 是的,我可以试试,但我不认为他们有原始来源,因为它是许多制作这些框架的人提交的图片集合.. 不过感谢您的建议..跨度>
    【解决方案6】:

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

    最好的办法是联系原始网站的网站管理员,看看他们是否可以将原始图像发送给您,希望是 Photoshop 或其他格式,原始图层单独保存。然后,您可以以保留原始透明度(PNG 或类似的东西)的格式重新生成图像,或者使用您的渐变作为背景(很难做到这一点,因为您不知道其中的确切位置图片将被渲染的渐变)。

    按照你的建议,我会在图像周围加上某种边框。

    【讨论】:

    • 嗯是的..总会有新成员加入,我真的想一劳永逸地完成它......不过感谢您的时间..非常感谢..也许我毕竟会使用边框解决方案..
    【解决方案7】:

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

    这是我昨天做的:

    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 和我自己的颜色之间的距离,当它低于预定义的距离时,它会被着色透明..

    这段代码产生以下结果:

    介意你还不错..但它仍然很烂:)

    我有一个秘诀,如果成功了,我也会和大家分享......

    【讨论】:

      【解决方案8】:

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

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

      结果:

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

      嗯嗯.. ;)

      【讨论】:

      • 略有不同的问题,奇怪的...边界是不变的吗? (IE 是否大多数/所有图像都有相同的边框,内部有用户提供的图像?)
      • 不,有不同类型的边界...即:将骑自行车的图像(上图)与冲锋队的边界进行比较:images.501stforums.com/memberdata/54/04/tk5404_full.jpg
      • 嗯,思路(现在没那么有用了)是你手动清理边界,(看起来你是用绿色方法做的),然后标记一个内部部分作为“要合并的区域”(绿色、洋红色、荧光粉红色,任何适合您的),然后将中心区域与原始区域合并。你有手工清洁的边界和按定义清洁的中心区域。仅当您知道边界时才有效。
      • 覆盖图像的中心是透明的,尽管它与原始图像完美融合..但显然我的覆盖并不完美..
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-22
      • 1970-01-01
      • 2018-07-15
      • 2018-07-10
      • 2013-02-16
      • 2014-04-26
      • 1970-01-01
      相关资源
      最近更新 更多