简体   繁体   中英

How do i replace tone variations of the same color in a C# Bitmap?

Scope :

Hi everyone, i am trying to convert this captcha to a "black and white" (binarized) image, where the characters are white and the whole rest (background,lines,random pictures) are black.

The link to the captcha can be found here . Refreshing will give you another captcha.

Reason:

I know most of people think it is wrong to mess with captchas so here i am defending myself. This will be used for knowledge / self challenge only. No other use is planned for these images.

Problem:

After studying these images for a while, i figured out that a good aproach would be to replace the colors : "White and Yellow" to "Color.Black", and every other color should be replaced to "Color.White."

After this, i would just "Invert" the colors, leading me to the output i want.

Code Sample:

In this code, i am trying to replace the color "Black" of every image for a SkyBlue Pixel.

        WebRequests wr = new WebRequests(); // My class to handle WebRequests
        Bitmap bmp;
        string url = "http://www.fazenda.rj.gov.br/projetoCPS/codigoImagem";

        bmp = wr.GetBitmap (url);

        for (int i = 1; i < bmp.Height ; i++)
        {
            for (int j = 1 ; j < bmp.Width ; j++)
            {
                if (bmp.GetPixel(j,i).Equals(Color.Black))
                {
                    bmp.SetPixel(j,i, Color.SkyBlue);
                }
            }
        }

This code do not work at all, i'm not sure why, but no pixel is getting replaced in this example.

Question:

How can i make this work ? What am i missing here ?

Also, the ideal scenario for me would be to "reduce" the color pallete of this image to "basic" colors, that would make my job here much easier.

I already tried the AForge Framework , which i am using to reduce colors, but it is not working at all, the result is not the one i expected.

What can i do here in order to binarize this image correctly ?

Thanks in advance,

Marcello Lins.

The reason why your algorithm does not work is that you are looking for an exact match. But the black in the image isn't really black due to JPEG compression artifacts; in three CAPTCHAS I've loaded there was not even a single black (0, 0, 0) pixel. The closest value was (0, 0, 4) which looks black, but isn't.

An approximate approach would be:

  • Remove all grays (channels differing from each other less than 5%) having a channel average below 220 with gray. This kills the thin lightgray lines as well as much of the ringing (JPEG) artifacts against a gray background.
  • Replace all pixels where the red, green, or blue channel is more than 25% above both the other two channels with gray. This takes care of reds, greens and blues as well as most of their ringing artifacts.
  • Run a simple despeckle to remove all nongray pixels surrounded by five grays and by no other pixel of the same colour within 20%.

At the end of this process, the background is all gray, all nongray pixels left are characters.

Some useful functions:

// Very basic (and CIE-incorrect) check
public static int isGray(Color c)
{
    if (Math.Abs(c.R - c.G) > 5 * 2.55) return 0; // Not gray. R and G too different
    if (Math.Abs(c.R - c.B) > 5 * 2.55) return 0;
    if (Math.Abs(c.G - c.B) > 5 * 2.55) return 0;
    return 1;
}

// the blind man's test for shading :-)
public static int isShadeOfRed(Color c)
{
    if (4*c.R < 5*c.G) return 0; // Red not strong enough in respect to green
    if (4*c.R < 5*c.B) return 0; // Red not strong enough in respect to blue
    return 1; // Red is stronger enough than green and blue to be called "shade of red"
}

// (shades of green and blue left as an exercise)

// Very basic (and CIE-incorrect) check
public static int areSameColor(Color a, Color b)
{
    if (Math.Abs(a.R - b.R) > 5 * 2.55) return 0;
    if (Math.Abs(a.G - b.G) > 5 * 2.55) return 0; 
    if (Math.Abs(a.B - b.B) > 5 * 2.55) return 0;
    return 1; // "more or less" the same color
}

// This is more or less pseudo code...
public static int isNoise(int x, int y)
{
    if ((x < 1) || (y < 1)) return 0; // or maybe "return 1"
    if ((x+1 >= bmp.Width)||(y+1 >= bmp.Height)) return 0;
    pix = bmp.GetPixel(x,y);
    for (i = -1; i <= 1; i++)
        for (j = -1; j <= 1; j++)
        {
            if ((i == 0) && (j == 0)) continue;
            test = bmp.GetPixel(x+i, y+j);
            if (isGray(test)) grays++;
            if (isSameColor(pix, test)) same++;
        }
    // Pixel surrounded by grays, and has no neighbours of the same colour
    // (who knows, maybe we could skip gray calculation and check altogether?)
    // is noise.
    if ((grays == 5) && (same == 0))
       return 1;
    return 0;
}

// NOTE: do not immediately set to gray pixels found to be noise, for their neighbours
// will be calculated using the new gray pixel. Damage to image might result.
// Use a copy of bmp instead.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM