Pergunta

Estou construindo um site para um clube que faz parte de uma organização mãe. Estou baixando (sanguessuga;)) As imagens que colocam em páginas de perfil da organização mãe para mostrar na minha própria página. Mas o site deles tem um belo fundo branco e meu site tem um bom gradiente cinza em segundo plano. Isso não corresponde bem. Então, minha idéia era editar as imagens antes de salvá -las no meu servidor.

Estou usando o GDI+ para aprimorar minhas imagens e, quando uso o método que faz o que faz o bitmap, ele funciona e faz o que deveria fazer, mas ainda tenho esses artefatos de JPEG branco em todo o lugar. Os artefatos tornam a imagem tão ruim que estou melhor não tornando a imagem transparente e deixando -a branca, mas isso é realmente feio no meu próprio site. Eu sempre posso em uma bela borda com um fundo branco, é claro, mas prefiro mudar o fundo para transparente.

Então, eu queria saber se e como posso remover alguns artefatos JPEG simples em C#. Alguém fez isso antes?

Obrigado pelo seu tempo.

Exemplo de imagem:

TB-5404

Imagem transformada:

TB-5404 transformed

Foi útil?

Solução

Bem, tentei algo que está longe de ser perfeito, mas acho que pode ser útil para outra pessoa.

Eu cheguei:

alt text

Problemas encontrados: as sombras estão longe o suficiente de 'Off White' que é difícil convertê -las automaticamente, e mesmo se você fizesse a sombra ainda estaria na própria imagem. O brilho do topo ... coisa do cubo, também está mais perto do branco do que os bits anti -alias. Existem três a sete manchas de branco na imagem que não estão conectadas a nenhum dos cantos primários; E, finalmente, ainda há um pouco de branco nas bordas (provavelmente poderia se livrar dele, ajustando o código, mas não sem tirar parte do tampo do brilho.

C# Código ineficiente:

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

O código funciona começando com dois pontos; Definindo -os para preto e depois verificando se algum vizinho perto deles está perto de branco; Se forem, eles são adicionados a uma lista que é executada contra. Eventualmente, ele fica sem pixels brancos para mudar.

Como alternativa, você pode considerar redesenhar o site para usar um fundo branco.

Outras dicas

Faça um loop através de cada pixel na imagem, se R, G e B for mais alto que, digamos, 230, substitua a cor pela cor desejada (ou transparente). Talvez até pesa a nova cor, dependendo de quão longe é a cor 'verdadeira' a cor antiga.

Espere obter problemas se a imagem real também for branca, em outro lugar, você acabará com um Stormtrooper cinza :)

Você não poderá fazer isso automaticamente com nada como 100% de precisão.

A razão para isso é que a única informação que você tem é a cor que você sabe que algum Os pixels na imagem estão tentando se misturar bem. Apenas alguns pixels na imagem realmente usarão cores ou perto desse valor para fins de sombreamento em segundo plano, outros usarão (no caso branco) porque o objeto real representado é de fato branco (caramba, a precisão de esses tropas de tempestades imperiais).

O tipo de aprendizado de máquina sofisticado para detectar qual é o que é um domínio de problema interessante e pode ser um projeto divertido para você, mas certamente não será uma solução rápida para o seu problema imediato.

O outro problema que você tem é que, mesmo que você possa detectar com boa confiabilidade aquelas áreas da imagem que estão tentando se misturar no fundo, você terá problemas 'desleixados' e depois relembrando -os na sua nova cor de fundo, a menos que as cores são razoavelmente compatíveis. Nesse caso, seu cinza pode funcionar, pois é uma cor ampla de espectro como o branco.

A técnica que você deseja usar é a seguinte:

  • Use um algoritmo de enchimento de inundação para selecionar, das bordas da imagem interior todos os pixels dentro de x%(1) da cor do pano de fundo conhecida.
  • Para esses pixels, coloca seu canal alfa como um valor como uma proporção de sua correspondência com a cor original e elimina o elenco de cores que estava associado a ele.
    • Portanto, se o pano de fundo for o valor RGB A, B, C e o pixel é A+5, B, C-7, o resultado é RGBA 5,0,0, ((a+b+c-7)/(a+ b+c)*256) (1)
  • Composite esta imagem alfa de mistura sobre um quadrado de dor do novo colar de fundo.
  • renderizar o resultado sem canal alfa como a nova imagem.

Isso ainda terá problemas para objetos cuja cor é próxima da cor de fundo. * No caso do original, pode ser que o sombreamento esteja sendo usado para implicar as presenças do objeto, como tal o preenchimento da inundação 'invadirá' o interior da imagem. * No caso deste último, a imagem resultante perderá a definição do objeto e nenhum sombreamento sutil, destaques ou apenas linhas simples estarão presentes para indicar onde o objeto termina e o fundo do solo termina.

Esta é uma primeira aproximação muito difícil, mas pode cobrir uma porcentagem razoável da sua meta. As imagens com orifícios transparentes totalmente fechados (como as lacunas no arco externo no seu exemplo) provavelmente nunca funcionarão bem de maneira automática, pois o algoritmo não poderá distinguir entre buracos brancos e stormtrooper branco.
Você pode querer fazer com que o algoritmo destaque as regiões da imagem que ele planeja para relembrar e permitir que a simples seleção de regiões inclua/exclua (usando a ferramenta de seleção de varinhas mágicas do Pain.net como um exemplo de como fazer isso, se quiser Para ser extravagante, permitindo a seleção simples por pixels para um esforço menos adiantado.


  1. O valor para x será algo que você sintoniza - pode ser que, com base em alguns aspectos da imagem (digamos a proporção da imagem que está próxima da cor do fundo do fundo), você pode ajustá -la automaticamente.
  2. Observe que essas fórmulas assumem uma cor branca perto, para perto de preto, você gostaria de inverter

Uma outra abordagem baseada na caixa de diálogo de comentários:

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

Dada máscara:

alt text

Você obtém o resultado:

alt text

Com a borda da máscara ligeiramente limpa no pintura.net com anti -aliasing desativado. Novamente, só é aplicável se você pode discernir qual borda está sendo usada ... mas acabou bem ... exceto o verde ...

Você também terá que lidar com tons de esbranquiçado. Provavelmente, quando eles fizeram o iamges inicialmente e definiram a cor do fundo, houve alguma aliasing e, ao salvar como um JPG, nem todas as cores serão preservadas perfeitamente. Portanto, se você estiver fazendo uma cor específica transparente, não obterá todas as tons dessa cor que deixarão muitos artefatos. Você precisa de algo que fará transparência proporcional à proximidade de uma cor da sua cor chave. Isso pode ser algo mais fácil de fazer como script em lote em algo como o Photoshop, mas não sei se isso é algo que você precisa fazer em tempo real.

Não existe uma maneira (remotamente fácil) de lidar com esse problema programaticamente. As áreas de artefato branco ao redor da borda da imagem são o resultado de pixels que são quase brancos, mas não bem, para que não adquiram o efeito de transparência. Há também alguns pontos na caneca de máscara/café que são brancos puro, para que se tornem transparentes e, portanto, cinza.

Sua melhor aposta é entrar em contato com o webmaster do site original e ver se eles podem enviar as imagens originais, esperançosamente no Photoshop ou em algum outro formato em que as camadas originais sejam preservadas separadamente. Você pode então re-gerar as imagens em um formato que preserva a transparência original (PNG ou algo assim) ou então usa seu gradiente para o fundo (seria muito difícil acertar isso, já que você não sabe exatamente onde dentro o gradiente a imagem será renderizada).

Eu iria com algum tipo de borda nas imagens, como você sugeriu.

Obrigado a todos pelas suas respostas .. todas as boas sugestões ..

Aqui está o que eu cozinhei ontem:

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

Não é o código mais bonito do livro, mas o que faz, é para cada pixel na região de pixels calcula uma distância entre a cor. ..

Este código de código produz o seguinte resultado:

TB-5404 transformed v 2.0

Não é ruim bem você .. mas ainda é uma merda :)

Eu tenho um truque na manga que vou compartilhar com vocês também se for bem -sucedido ...

Aqui está outra tentativa fracassada;)

Eu tive uma ideia .. os filmes usam a tela verde, por que não usar uma sobreposição verde para minha imagem .. então aqui está a sobreposição:

Green overlay with transparent inner thing

E os resultados:

Green stuff all over the place

Então eu aprendi outra lição valiosa sobre a compactação de PNG. Eu também tentei com BMP, mas de alguma forma eu sempre acabo com uma borda verde. Posso tentar apagar isso, mas talvez eu deva deixá -lo no fundo branco, colocar Alguma borda boba ao redor e terminou com isso ...

Ah bem.. ;)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top