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

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

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

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

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

TB-5404

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

TB-5404 transformed

5
Arcturus 2 Авг 2009 в 18:20

8 ответов

Лучший ответ

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

Я получил:

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

Код работает, начиная с двух точек; установить для них черный цвет, а затем проверить, не близки ли к белому соседи рядом с ними; если они есть, они добавляются в список, который затем выполняется. В конце концов у него заканчиваются белые пиксели, которые нужно изменить.

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

5
Community 8 Фев 2017 в 14:14

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

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

1
peakpeak 2 Авг 2009 в 16:06

Вы не сможете сделать это автоматически со 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
ShuggyCoUk 3 Авг 2009 в 12:22

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

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
Community 8 Фев 2017 в 14:14

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

0
AaronLS 2 Авг 2009 в 14:26

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

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

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

0
MusiGenesis 2 Авг 2009 в 15:44

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

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

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
Community 8 Фев 2017 в 14:14

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

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

Green overlay with transparent inner thing

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

Green stuff all over the place

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

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

0
Community 8 Фев 2017 в 14:14