Lab 4: MARPAT

In the early 2000s, the U.S. Marine Corps commissioned a new camoflage pattern for its uniforms, known now officially as MARPAT, or informally as "digi-cammies". You can read much more about this on Camopedia, which of course is a website that exists.

In today's lab, you will write a computer program that generates a camoflage pattern image similar to MARPAT. Your program will make use of random numbers so that the pattern generated is slightly different every time. You will build on your understanding of images from Lab 2, as well as your more recently-developed skills writing loops.

Step 0: Folder and files

This should be familiar by now. Create a new folder inside your SI268 folder for this lab. Today you will work on only a single Python program that you write, called camo.py.

Step 1: Field of green

We will be using OpenCV to create images for this lab. You may want to review your code from Lab 2. The main difference is, instead of combining images from files, your program will be creating an image out of nothing!

Remember that each pixel of an image prepresents a single color as a triple of numbers (blue, green, red), where each number goes from 0 up to 255. In the "woodland" version of MARPAT, there are four colors which you will want to use:

grass = (76, 121, 82)
trees = (60, 86, 102)
sand = (115, 175, 187)
shadow = (63, 56, 53)

To start with, make a blank image that is 150 pixels wide and 100 pixels tall, filled with the "grass" color only. To do this, you will use the np.full function. This function is in the NumPy package that you actually used in Lab 2 but didn't realize it (OpenCV functions return matrices using NumPy). It's a powerful linear algebra package, very popular in machine learning applications. For our use here, we can create a white picture (the color white is three 255's) which is 400 pixels wide and 500 pixels tall by writing:

import numpy as np     # names a variable 'np' for you to use

img = np.full( (500,400,3), (255,255,255), np.uint8 )

Now create your 150x100 dimension image of grass, and then use OpenCV to display it on the screen until the user presses any key (remember Lab 2?). IMPORTANT: it should appear wider than it is tall. The 150 is width, 100 height.

After this step, your camo.py program should create and display an image 100 pixels tall and 150 pixels wide, of the green "grass" color given above.

Step 2: Random dots

Sometimes we need our Python programs to create random numbers. We want these numbers to be different every time we run the program, but we also usually have some constraints on how large the numbers should be.

The function randrange returns one randomly generated integer. It has the same arguments like the range() function that you use to write loops, except that randrange() just returns a single integer, randomly chosen. You can call it with randrange(6) or the equivalent randrange(0,6) just like we can with range().

To use randrange(), you first need to import the library random. For example, here is a very small program that chooses two random numbers from 1 up to 6 and adds them, like rolling two dice:

import random                # remember, this now gives you a variable named 'random'

d1 = random.randrange(1, 7)  # mimics a single die roll from 1 to 6
d2 = random.randrange(1, 7)  # you can make this range 1-7 anything you want!
both = d1 + d2

print("You rolled", d1, "and", d2, "which add up to", both)

Now that we have randrange(), your task is to use it in your camo.py program to change random pixels to be trees, sand, or shadow colors.

Specifically, write a loop that repeats the following steps 80 times:

  1. Select a random x-coordinate
  2. Select a random y-coordinate
  3. Select one of the three non-tree colors at random (hint, you might want to use randrange(3) and some if/else statements!)
  4. Change the pixel at the chosen coordinate to the chosen color

After this, you will have a field of green grass with some random dots scattered in, but it will be kind of hard to see. To make this look right, you need to scale up your image to 5x the original size. To do that, you can use the following OpenCV command:

img = cv2.resize(img, (new_x_dimension, new_y_dimension), interpolation=cv2.INTER_NEAREST)

(where new_x_dimension and new_y_dimension should be replaced by what you need for your code of course!) Important, note that this requires x,y (width,height) which is backwards from the numpy function in Part 1. The last argument interpolation=cv2.INTER_NEAREST is very important, and will give your image the "digital" look of tiny squares that we want.

After this step, your camo.py program should create and display an image which is 750 pixels wide and 500 pixels tall, containing 80 random 5x5 squares of random trees, sand, and shadow colors.

Step 3: Grow the dots into blobs

At this point, your image has the right color scheme, and the digital dot shape, but it doesn't look much like MARPAT. For one thing, it is still mostly the green "grass" background color.

Go ahead and try increasing the 80 random dots to much more, like 1000 random dots. No really, try it. Don't just keep reading and not try. You should see that the image is now much less grass, but it still doesn't look like MARPAT.

The key idea to make this look right is to create randomly-shaped "blobs" of a single color, by letting your random dots "grow" into the adjacent areas. One way to do this is to treat the non-grass dots as a bacteria, which will infect (colorize) the adjacent dots randomly during each evolution (iteration of a loop).

Specifically, after your loop that created the 80 random dots (don't change that one!), you are now going to make another loop which goes over every (y,x) coordinate in your image, and does the following:

  1. If the pixel at (y,x) is not grass, then don't change it.
  2. If the pixel at (y,x) is grass, then pick one of its surrounding pixels at random, and copy that color to pixel (y,x).

How do you loop over every (y,x) pixel? Well this will be two loops, one nested inside the other. Your first loop should loop over every y, and the inner loop should loop over every x. Nested loops are very common in cases like this:

for y in range(?):
  for x in range(?):
    do stuff

Inside the loops you want to change your (y,x) grass pixel based on its surrounding pixels. How do we do that?

img[y,x,0] == grass[0]     # returns True or False

We generally don't like giving you raw code, so please pause and make sure you understand why this checks for grass. Note that img[y,x] is a single pixel, right? Well it has 3 RGB values, so if you ask for img[y,x,0] then you get a single integer value of one color of that pixel. Test for equality with grass's corresponding color, and we assume that no other color shares that exact value (which is true in our current camo design).

Once you verify that your current y,x is a grass pixel, you then overwrite it with a neighbor's color. You may simplifly the search and only look at the 4 pixels directly above/below or left/right of it. So, your code will need to pick a random number up to 4, and use that to decide whether to copy from the left, right, above, or below it.

This works most of the time, except when the pixel is on an edge of the image. For example, if the x coordinate is 149 then this pixel is on the right edge of the image (150 pixels wide), and there is no pixel to the right of it. You will need some if statements to check for these "edge cases" and NOT try to access pixels outside of the correct range. Accessing outside an edge will crash your program because that location doesn't exist!

Grow them more!

Now, if you get that working, your image will have 80 very small blobs, maybe some which are now 2 pixels in size. So repeat it! Take the two for loops you just wrote, and put that whole thing inside a (third!) loop to repeat these "evolutions" many times, maybe 15 times to start out with. (This will test your text-editor skills - can you re-indent the entire block of code easily?)

After this step, your camo.py program should create 80 random dots and then "grow" them randomly in 15+ iterations as described above.

Step 4: Experiment and improve

Your MARPAT is getting much better, but it's still not quite right. Maybe you think the blobs are too large, or too small, or there are too many of them, or too few. Experiment with changing the parameters in your program so that the images it produces look more like an actual digital camoflage pattern.

Required: comment your code at the top of your program and clearly state how you changed your program so that it improves your output camoflouge. Make sure the rest of your code has code comments too! This is getting a little longer, so different parts should have short 1-2 line explanations.

(optional) Step 5: Change the shape and the colors

Wait: Submit your original camo.py program before continuing on this part. You might want to save a backup of your program also, because the next step might break it!

By now, your camo.py program is good at making 150x100 images for this camoflage pattern. Great! But sometimes we want a different shape besides 150x100. And there is also a different color scheme used in desert settings:

Add code to the beginning of your camo.py program to ask the user three questions:

  1. How many pixels wide?
  2. How many pixels tall?
  3. Which pattern (woodland or desert)?

Then create and display your camoflage according to what they selected. (Note, you will have to use your skills to figure out the (blue,green,red) color values used in the desert pattern!)

This part is probably going to be very difficult because of how you wrote your original program. Originally, you probably had repeated the color values and the pixel dimensions at many places in your code, which you now have to unwind to use variables based on user input instead. You also made some arbitrary choices about the amount of random dots and the number of generations to grow the blobs, which only worked well for 150x100 images. Now you have to change those to be based on the arbitrary dimensions as well, which is hard.

This process is called refactoring code, and it's always painful when we want to try and change code which already works, to work in a slightly different way. This is why we encourage you to comment code, use variables instead of copying values, and so on - it makes the program easier to understand and modify later.

What to turn in

Having fun? Great! (Maybe you should go tell your Lab 3 program about your day...)

Be sure to test your program extensively and make sure it never produces any errors, and always makes a different camoflage image each time you run it. Make sure you have code comments, including what you did for Step 4.

Visit the submit website and upload your camo.py file to Lab04 for grading.