Pergunta

Estou digitalizando documentos para imagens JPG. O scanner deve digitalizar todas as páginas como cor ou todas as páginas como preto e branco. Como muitas das minhas páginas são cores, devo digitalizar todas as páginas como cor. Após a conclusão da digitalização, gostaria de examinar as imagens com .NET e tentar detectar quais imagens são preto e branco para que eu possa converter essas imagens em escala de cinza e economizar no armazenamento.

Alguém sabe como detectar uma imagem em escala de cinza com .NET?

Por favor, me avise.

Foi útil?

Solução

Um algoritmo simples para testar a cor: caminhar a imagem Pixel por pixel em um loop aninhado (largura e altura) e teste se os valores de RGB do pixel são iguais. Se não forem, a imagem tem informações de cores. Se você passar por todos os pixels sem encontrar essa condição, terá uma imagem em escala de cinza.

Revisão com um algoritmo mais complexo:

Na primeira rota deste post, propus um algoritmo simples que pressupõe que os pixels sejam escala de cinza se os valores RGB de cada pixel forem iguais. Portanto, RGBs de 0,0,0 ou 128.128.128 ou 230.230,230 testariam como cinza, enquanto 123,90,78 não o fizeram. Simples.

Aqui está um trecho de código que testa uma variação de Gray. Os dois métodos são uma pequena subseção de um processo mais complexo, mas devem fornecer código bruto suficiente para ajudar na pergunta original.

/// <summary>
/// This function accepts a bitmap and then performs a delta
/// comparison on all the pixels to find the highest delta
/// color in the image. This calculation only works for images
/// which have a field of similar color and some grayscale or
/// near-grayscale outlines. The result ought to be that the
/// calculated color is a sample of the "field". From this we
/// can infer which color in the image actualy represents a
/// contiguous field in which we're interested.
/// See the documentation of GetRgbDelta for more information.
/// </summary>
/// <param name="bmp">A bitmap for sampling</param>
/// <returns>The highest delta color</returns>
public static Color CalculateColorKey(Bitmap bmp)
{
    Color keyColor = Color.Empty;
    int highestRgbDelta = 0;

    for (int x = 0; x < bmp.Width; x++)
    {
        for (int y = 0; y < bmp.Height; y++)
        {
            if (GetRgbDelta(bmp.GetPixel(x, y)) <= highestRgbDelta) continue;

            highestRgbDelta = GetRgbDelta(bmp.GetPixel(x, y));
            keyColor = bmp.GetPixel(x, y);
        }
    }

    return keyColor;
}

/// <summary>
/// Utility method that encapsulates the RGB Delta calculation:
/// delta = abs(R-G) + abs(G-B) + abs(B-R) 
/// So, between the color RGB(50,100,50) and RGB(128,128,128)
/// The first would be the higher delta with a value of 100 as compared
/// to the secong color which, being grayscale, would have a delta of 0
/// </summary>
/// <param name="color">The color for which to calculate the delta</param>
/// <returns>An integer in the range 0 to 510 indicating the difference
/// in the RGB values that comprise the color</returns>
private static int GetRgbDelta(Color color)
{
    return
        Math.Abs(color.R - color.G) +
        Math.Abs(color.G - color.B) +
        Math.Abs(color.B - color.R);
}

Outras dicas

Se você não conseguir encontrar uma biblioteca para isso, pode tentar pegar um grande número (ou todos) dos pixels para uma imagem e ver se os valores R, G e B estão dentro de um determinado limite (que você pode definir empiricamente , ou ter como cenário) um do outro. Se forem, a imagem é escala de cinza.

Eu definitivamente tornaria o limite para um teste um pouco maior que 0, porém ... então eu não testaria r = g, por exemplo, mas (abs (rg) <e) onde e é o seu limite. Dessa forma, você pode manter suas cores falsas positivas para baixo ... como eu suspeito que você receberá um número decente, a menos que sua imagem original e técnicas de digitalização dêem precisamente escala de cinza.

Uma versação mais rápida. Teste com um limite de 8. trabalhe bem para o meu

Usar:

bool grayScale;
Bitmap bmp = new Bitmap(strPath + "\\temp.png");
grayScale = TestGrayScale(bmp, 8);
if (grayScale)
   MessageBox.Show("Grayscale image");


/// <summary>Test a image is in grayscale</summary>
/// <param name="bmp">The bmp to test</param>
/// <param name="threshold">The threshold for maximun color difference</param>
/// <returns>True if is grayscale. False if is color image</returns>
public bool TestGrayScale(Bitmap bmp, int threshold)
{
    Color pixelColor = Color.Empty;
    int rgbDelta;

    for (int x = 0; x < bmp.Width; x++)
    {
        for (int y = 0; y < bmp.Height; y++)
        {
            pixelColor = bmp.GetPixel(x, y);
            rgbDelta = Math.Abs(pixelColor.R - pixelColor.G) + Math.Abs(pixelColor.G - pixelColor.B) + Math.Abs(pixelColor.B - pixelColor.R);
            if (rgbDelta > threshold) return false;
        }
    }
    return true;
}

Você tem um mais rápido?

Como o JPEG tem suporte para metadados, você deve primeiro verificar se o software do scanner coloca alguns dados especiais sobre imagens salvas e se você pode confiar nessas informações.

o Resposta que publiquei na seção Python pode ser útil. Imagens que você encontra, por exemplo, na web que um humano consideraria a escala de cinza geralmente não possui valores idênticos, G, B. Você precisa de algum cálculo da variação e algum tipo de processo de amostragem para não precisar verificar um milhão de pixels. A solução que Paulo deu é baseada na diferença máxima, para que um único artefato de pixel vermelho de um scanner possa transformar uma imagem em escala de cinza em escala de escala. A solução que publiquei obteve 99,1% de precisão e 92,5% de recall em 13.000 imagens.

Eu acho que essa abordagem deve exigir o menor código, ela foi testada em JPEGs. Bimage abaixo é uma matriz de bytes.

 MemoryStream ms = new MemoryStream(bImage);
 System.Drawing.Image returnImage = System.Drawing.Image.FromStream(ms);
 if (returnImage.Palette.Flags == 2)
 {
      System.Diagnostics.Debug.WriteLine("Image is greyscale");
 }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top