Image Logic - If Statements

< CS101

Recap - For-Loop

Our use of loops thus far have allowed us to write a little bit of code which is run for many data points. That's one big theme in computer code. The if-statement will add a second theme: the ability to write a true/false test, and only run a bit of code if the test is true. This will profoundly expand what we can do with code.

If Statement Syntax

The if-statement has a true/false test which controls if some code is run or not. In the context of a for-loop, our first if-statement examples will look like this:

image = ...

for (pixel: image) {
  if (pixel.getRed() > 180) {   // if-statement
    pixel.setRed(0);            // "body" code inside the { }
    pixel.setGreen(0);
    pixel.setBlue(0);
  }
}

print(image);

For each pixel, the if-statement above retrieves the red value, and tests if the value is greater than 180. If the test is true, the if-statement runs the body code inside the curly braces. If the test is false, the body code is skipped.

For this section, we'll use this picture of a stop sign:

Writing an if-statement within an image loop, the if test can select certain pixels within the image to change, rather than changing every pixel as we did previously. Guess what this code does before running it:

If Color > Test

Suppose we want to do something just to the red pixels on the sign, like make red parts of the stop sign into a pure blue color, as you might see on a vacation to a very strange country where they do stop signs their own way. What would an if statement look like that detects the red parts of the sign?

Our first approach is just to test the red value with > : if (pixel.getRed() > 160) { ...
i.e. if the red number is high, the pixel is probably part of the sign. The specific value, 160 or whatever, can be tuned by trying a few different values to get one that works best. This works fairly well, although we will try a better technique later on. Here's code that uses the simple > strategy.


Now You Try It

Consider blue-flower.jpg:

Blue to Yellow

Look at the outer light blue area of the flower. Don't you wish it was yellow instead? Detect that area using the simple > strategy with pixel.getBlue(). Change just those areas to be yellow by doubling the red and green values, and halving the blue value.


Inky Black Background

Another interesting effect on this image is to change the darkish areas to be inky black. Detect the darkish areas by testing if the red value is < (that's "less than") some value. Adjust the test so it at least gets the upper right area of the image. Set the pixels where the test is true to pure black: 0 0 0. Adjust the test so it looks good .. there's not exactly a right or wrong answer here.


Remember "Average" Pixel

Recall this code which, inside a loop, computes the average of the red, blue, and green values and stores that number in a variable "avg".

  avg = (pixel.getRed() + pixel.getGreen() + pixel.getBlue()) / 3;

Experiment: revisit the example above selecting the red stop sign and the outside of the blue flowers. Set the selected pixels to grayscale using the "avg" value (keeping their light/dark, but lacking any color). (Solution: compute the avg in the loop. Then set red, green, and blue to all have that avg value .. this makes it grayscale.)

Color Average Strategy

Recall the stop sign problem, detecting the red area. The problem with that approach is that the red value is high in two kinds of cases -- the red part of the sign we want, but also parts of the scene that are near white. Recall that pure white is (255, 255, 255), so in whitish areas, all three values are high, so our (red > 160) test gets those too -- the white letters inside the sign are a clear example of this problem.

If Color Average

A refinement to detect the "red" areas is to first compute the average value for each pixel (as we did previously). Then compare the red value to the average for each pixel. Rather than checking the literal value of the red (e.g. 160), this checks if the red is relatively large compared to the other two channels .. does the pixel lean towards redish. If the test is (pixel.getRed() > avg) we get all the areas that have every a tiny red cast, which is not restrictive enough. The fix is to multiply the avg by some factor to make it more restrictive, like this: (pixel.getRed() > avg * 1.5). As before, the specific value, 1.5, can be tuned by trying different values. Try the values: 1, 1.5, 2, 2.5. The larger the value, the higher the bar is set to detect red pixels. By adjusting the * 1.5 factor, this technique can nicely zero in on specific color areas.