Удаление артефактов JPEG в C # - PullRequest
5 голосов
/ 02 августа 2009

Я создаю веб-сайт для клуба, который является частью материнской организации. Я загружаю (leeching;)) изображения, которые были размещены на страницах профиля материнской организации, чтобы показать их на своей странице. Но у их сайта приятный белый фон, а у моего сайта приятный серый градиент на заднем плане. Это не соответствует красиво. Поэтому моя идея состояла в том, чтобы отредактировать изображения перед сохранением их на моем сервере.

Я использую GDI + для улучшения своих изображений, и когда я использую метод MakeTransparent для Bitmap, он работает и делает то, что должен, но у меня все еще есть эти белые артефакты jpeg повсюду. Артефакты делают изображение настолько плохим, что мне лучше не делать изображение прозрачным, а просто оставлять его белым, но это действительно ужасно на моем собственном сайте. Конечно, я всегда могу на красивой границе с белым фоном, но я предпочитаю менять фон на прозрачный.

Так что мне было интересно, если и как я могу удалить некоторые простые артефакты JPEG в C #. Кто-нибудь когда-нибудь делал это раньше?

Спасибо за ваше время.

Пример изображения:

TB-5404

Преобразованное изображение:

TB-5404 transformed

Ответы [ 8 ]

5 голосов
/ 03 августа 2009

Ну, я пытался сделать что-то, что далеко от совершенства, но я думаю, что это может быть полезно для кого-то еще.

Я получил:

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 голос
/ 04 августа 2009

Еще один подход, основанный на диалоге комментариев:

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 с отключенным сглаживанием. Опять же, это применимо только в том случае, если вы можете различить, какая граница используется ... но получилось неплохо ... за исключением зеленых ...

1 голос
/ 03 августа 2009

Вы не сможете сделать это автоматически со 100% точностью.

Причина этого в том, что единственная информация, которая у вас есть, - это цвет, о котором вы знаете, что некоторые пиксели на изображении пытаются красиво смешаться. Только некоторые пиксели в изображении будут фактически использовать цвета на или близко к этому значению для целей затенения на заднем плане, другие будут использовать (в случае белый), потому что фактически представленный объект на самом деле белый (чертовски точно эти имперские штурмовики).

Это сложное машинное обучение для обнаружения, которое представляет собой интересную проблемную область и может быть интересным проектом для вас, но оно, безусловно, не поможет быстро решить вашу непосредственную проблему.

Другая проблема, с которой вы столкнулись, заключается в том, что даже если вы сможете с высокой степенью достоверности обнаружить те области изображения, которые пытаются смешаться с фоном, у вас возникнут проблемы с их «разглаживанием», а затем с повторным смешиванием в новый цвет фона если цвета не являются разумно совместимыми. В этом случае ваш серый может работать, так как это широкий спектр цветов, как белый.

Техника, которую вы хотите использовать, выглядит следующим образом:

  • Используйте алгоритм заливки, чтобы выбрать по краям изображения все пиксели в пределах x% (1) от известного цвета фона.
  • Для этих пикселей установите для их альфа-канала значение, соответствующее пропорции их соответствия исходному цвету, и устраните цветовой оттенок, который был с ним связан.
    • Таким образом, если фон имеет значение RGB a, b, c, а пиксель равен a + 5, b, c-7, то результат RGBA 5,0,0, ((a + b + c-7) / (a ​​+ b + c) * 256) (1)
  • Скомбинируйте это альфа-смешение с квадратом боли нового заднего фона.
  • отображает результат без альфа-канала в качестве нового изображения.

Это по-прежнему будет иметь проблемы для объектов, цвет которых близок к любому из цветов фона. * в случае с оригиналом, возможно, затенение используется для обозначения присутствия объекта, так как заливка будет «проникать» внутрь изображения. * в случае последнего полученное изображение потеряет определение объекта, и не будет присутствовать тонкое затенение, выделение или просто простые линии, указывающие, где заканчивается объект и заканчивается фон.

Это очень грубое первое приближение, но оно может покрывать разумный процент от вашей цели. Те изображения с прозрачными полностью закрытыми отверстиями (как, например, промежутки во внешней арке в вашем примере) вряд ли когда-нибудь будут работать в автоматическом режиме, поскольку алгоритм не сможет отличить белые дыры от белых штурмовиков.
Возможно, вы захотите заставить свой алгоритм выделить области изображения, которые он планирует переименовать, и разрешить простой выбор областей для включения / исключения (используя инструмент выбора волшебной палочки из Pain.Net в качестве примера того, как это сделать, если вы хотите чтобы быть модным, позволяя простой выбор за пиксель для меньших предварительных усилий.


  1. значение для x будет тем, что вы настраиваете - возможно, из-за некоторых аспектов изображения (например, пропорции изображения, близкого к фоновому цвету) вы можете настроить его автоматически.
  2. Обратите внимание, что эта формула принимает цвет, близкий к белому, для цвета, близкого к черному, вы хотите инвертировать
1 голос
/ 02 августа 2009

Прокрутите каждый пиксель в изображении, если R, G и B выше, скажем, 230, то замените цвет на желаемый (или прозрачный). Может быть, даже вес нового цвета зависит от того, насколько далек от «истинного» белого старый цвет.

Ожидайте проблем, если фактическое изображение также белое, в противном случае у вас будет серый штурмовик:)

0 голосов
/ 04 августа 2009

Вот еще одна неудачная попытка;)

У меня была идея ... фильмы используют зеленый экран, почему бы не использовать зеленое наложение для моего изображения ... Итак, вот наложение:

Green overlay with transparent inner thing

И результаты:

Green stuff all over the place

Итак, я усвоил еще один ценный урок о сжатии PNG ... Я также попробовал это с bmp, но каким-то образом я всегда получаю зеленую рамку ... Я могу попытаться стереть это, но, возможно, я просто должен оставить его на белом фон, поместите вокруг него какую-нибудь глупую границу и покончите с этим ...

Ах, хорошо ..;)

0 голосов
/ 03 августа 2009

Спасибо всем за ваши ответы .. все хорошие предложения ..

Вот что я приготовил вчера:

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

Это неплохо, но ты все равно отстой :)

У меня есть один трюк в рукаве, которым я поделюсь с вами, ребята, если это удастся ...

0 голосов
/ 02 августа 2009

Нет (удаленно простого) способа решить эту проблему программно. Белые области артефакта-y по краю изображения являются результатом пикселей, которые почти белые, но не совсем, поэтому они не улавливают эффект прозрачности. На маске / кофейной кружке также есть несколько пятен, которые являются чисто белыми, поэтому они становятся прозрачными и, следовательно, серыми.

Лучше всего связаться с веб-мастером исходного сайта и выяснить, могут ли они прислать вам исходные изображения, будем надеяться, в фотошопе или в каком-либо другом формате, где исходные слои сохраняются отдельно. Затем вы можете заново сгенерировать изображения в формате, который сохраняет исходную прозрачность (PNG или что-то в этом роде), или же использовать свой градиент для фона (было бы очень сложно сделать это правильно, поскольку вы точно не знаете, где в градиент, изображение будет отображаться).

Я бы пошел с какими-то границами вокруг изображений, как вы предложили.

0 голосов
/ 02 августа 2009

Вам также придется обрабатывать оттенки не совсем белого цвета. Возможно, когда они изначально делали iamges и устанавливали цвет фона, было какое-то сглаживание, а затем при сохранении в формате jpg не все цвета будут сохранены идеально. Так что, если вы делаете прозрачный определенный цвет, он не получит все оттенки этого цвета, которые оставят много артефактов. Вам нужно что-то, что сделает прозрачность пропорциональной тому, насколько цвет близок к вашему ключевому цвету. Это может быть проще сделать в виде пакетного скрипта в чем-то вроде Photoshop, но я не знаю, нужно ли вам это делать в реальном времени.

...