CS21 Lab 5: Image Filtering

In this lab, we’ll write image filters. These programs will manipulate digital images at the pixel level. Along the way, we will practice nested for loops, functions, and think about how color is represented on computers.

gulliver’s travels gulliver’s travels gray1 gullivers gray1 gullivers gray1 gullivers gray1 gullivers gray1 gullivers

In this lab, we will use "Gulliver’s Travels" (1939) as an example. This movie is the second ever animated movie in Technicolor (after "Snow White") and is in the public domain. You are encouraged to try out the filters on your own images as well, but we’ll test your code on the supplied image.

Due October 8, by midnight

Are your files in the correct place?

Make sure all programs are saved to your cs21/labs/05 directory! Files outside that directory will not be graded.

$ update21
$ cd ~/cs21/labs/05
$ pwd
/home/username/cs21/labs/05
$ ls
Questions-05.txt
(should see your program files here)

Goals

The goals for this lab assignment are:

  • Use images as input to, and output from, our programs

  • Learn to iterate over images using for loops

  • Practice using nested for loops

  • Practice writing functions

  • Work with Image objects

  • Use methods and constructors

Image Processing

For this lab we’ll use the Image library in Python (python3) for its graphics rather than Processing. Digital images are a collection of pixels. Each pixel has a red, green and blue value that typically range between 0-255. The following facilities are used to manipulate images and their pixel values in this lab.

Image

from image import Image

img = Image("FILENAME")      # load an image
img = Image(width, height)   # create a (width x height) blank image
img.getWidth()               # width of the image
img.getHeight()              # height of the image
img.save("FILENAME")         # save the image
c = img.getPixel(x, y)       # return [r,g,b] color at (x, y)
img.setPixel(x, y, c)        # change the color at (x, y) in img to c

1. Warm-up: negate

Note that this warm-up is not optional. You must complete it before continuing to the next sections.

You are provided with the filter.py program shown below.

from image import Image

def negate_pixel(img, x, y):
    ''' return the inverted color of (x,y) in img'''
    c = img.getPixel(x, y)
    nc = [255 - c[0], 255 - c[1], 255 - c[2]]
    return nc

def negate(img):
    ''' negate the img '''
    for y in range(img.getHeight()):
        img.setPixel(200, y, negate_pixel(img, 200, y))
        img.setPixel(201, y, negate_pixel(img, 201, y))
        img.setPixel(202, y, negate_pixel(img, 202, y))
        img.setPixel(203, y, negate_pixel(img, 203, y))
        img.setPixel(204, y, negate_pixel(img, 204, y))
        img.setPixel(205, y, negate_pixel(img, 205, y))
        img.setPixel(206, y, negate_pixel(img, 206, y))
        img.setPixel(207, y, negate_pixel(img, 207, y))
        img.setPixel(208, y, negate_pixel(img, 208, y))
        img.setPixel(209, y, negate_pixel(img, 209, y))
        img.setPixel(210, y, negate_pixel(img, 210, y))
        img.setPixel(211, y, negate_pixel(img, 211, y))
        img.setPixel(212, y, negate_pixel(img, 212, y))
        img.setPixel(213, y, negate_pixel(img, 213, y))
        img.setPixel(214, y, negate_pixel(img, 214, y))
        img.setPixel(215, y, negate_pixel(img, 215, y))
        img.setPixel(216, y, negate_pixel(img, 216, y))
        img.setPixel(217, y, negate_pixel(img, 217, y))
        img.setPixel(218, y, negate_pixel(img, 218, y))
        img.setPixel(219, y, negate_pixel(img, 219, y))
        img.setPixel(220, y, negate_pixel(img, 220, y))

def main():
    img = Image("/data/cs21/gulliver/poster.png")
    negate(img)
    img.save("negate_strip.png")

main()

The main function loads an image /data/cs21/gulliver/poster.png that is 300 pixels wide and 450 pixels high. Then, the negate function is called, which negates a 21 by 450 strip of the image. Finally, the image is saved to the file negate_strip.png. The resulting negate_strip.png file is shown below.

gray0 gullivers

To run then program and then display the output, you can use these commands:

$ python3 filter.py
$ display negate_strip.png   # will show the image above
Viewing images in Visual Studio Code

Visual studio code allows you to preview images using the 'Explorer' when you open up the folder.

This warm-up has two parts:

  1. Modify the negate function so that it negates the whole image (as shown below)

  2. Modify the negate function so that it returns a new image rather than mutating the original input image. Use the Image(width, height) constructor to make a new Image of the same size as the original image.

gray1 gullivers

If you wrote the negate function properly, your main function should work like this:

def main():
    # read in the input image, i_img
    i_img = Image("/data/cs21/gulliver/poster.png")

    # create the output image, o_img
    o_img = negate(i_img)
    o_img.save("negate_gulliver.png")

2. Gray Scale

Continue using the filter.py file for the rest of the lab. Put your solutions to this function and all other functions you write in this file.

In this part of the lab, you will write a function called grayscale(img) that returns a new image that is a gray scale version of the input image. To help you write that, we have provided the function rgb2gray below.

2.1. rgb2gray

One way to turn a RGB color to gray scale is to average all three channels equally (r+g+b)/3, or 0.333 * r + 0.333 * g + 0.333 * b, as seen in the image below. The function rgb2gray(c), shown below, converts a color, in the form of an [r, g, b] list, into a gray "color".

def rgb2gray(c):
    ''' given a color (list of RGB), return the gray scale color '''
    r = c[0]
    g = c[1]
    b = c[2]
    gray = int((r + g + b) / 3)
    return [gray, gray, gray]

2.2. grayscale

Write a function grayscale(img) that uses the rgb2gray function to produce a grayscale version of the image such as the one shown below.

Gray Scale 0.333 * r + 0.333 * g + 0.333 * b

gray0 gullivers

def main():
    i_img = Image("/data/cs21/gulliver/poster.png")

    # You may want to comment out pieces of code from previous
    # sections as you work. After this example, our demo code
    # will intentionally hide previous code from main in order
    # to make it more readable, and to test solely our new functions.
    # o_img = negate(i_img)
    # o_img.save("negate.png")

    o_img = grayscale(i_img)
    o_img.save("gray_gulliver.png")

main()

2.2.1. (Optional) Grayscale that matches human perception

Rewrite rgb2gray to use a more accurate calculation for how brightness is perceived by human eyes: 0.2125 * red + 0.7154 * green + 0.0721 * blue, as seen in the updated gray scale image below.

Gray Scale 0.2125 * r + 0.7154 * g + 0.0721 * b

gray1 gullivers

3. Binary

Write a function binary(img) that returns a new image that is a binary---either black (0,0,0) or white (255, 255, 255)---version of the input image. If the gray scale value of a pixel is less than 128 it should be black in the new image, and white otherwise.

binary gullivers

def main():
    i_img = Image("/data/cs21/gulliver/poster.png")

    # Previous code from main has been omitted for clarity
    o_img = binary(i_img)
    o_img.save("binary_gulliver.png")

main()

4. Sepia

Write a function, sepia(image, sepia_amount), that applies an effect which raises the red and green channels, and lowers the blue. In particular it adds 2 * sepia_amount to the red, adds sepia_amount to green, and subtracts sepia_amount from blue. 20 is a good value for sepia_amount.

WARNING: Remember RGB values are only valid betweeen 0-255. Consider writing a function like constrain(value, min, max), or simply use conditionals, to assure your colors do not exceed the valid range.

sepia gullivers

The test in main below performs the grayscale effect first, and then applies the sepia effect to the resulting image.

def main():
    i_img = Image("/data/cs21/gulliver/poster.png")

    # Previous code from main has been omitted for clarity

    # The sepia effect works best if you grayscale the image
    # first, as shown below.
    o_img = sepia(grayscale(i_img), 20)
    o_img.save("sepia_gulliver.png")

main()

5. Fairey

Write a function that applies an effect similar to Shepard Fairey’s iconic Obama "HOPE" poster. Each pixel is colored one of four colors depending upon the sum of its RGB values. It assigns roughly equal intervals for each of the four colors.

RGB Sum      Color
---------    ----------------------------
0 – 181      darkBlue (0, 51, 76)
182 – 363    red (217, 26, 33)
364 – 545    lightBlue (112, 150, 158)
546 – 765    yellow (252, 227, 166)

fairey gullivers

def main():
    i_img = Image("/data/cs21/gulliver/poster.png")

    # Previous code from main has been omitted for clarity

    # Notice that you can directly save the image to a file
    # without storing it in a variable first. This is true for
    # the other functions (e.g. negate, binary, sepia) that
    # you've already written because the fairey function
    # *evaluates* to be an Image which can then be saved.
    fairey(i_img).save("fairey_gulliver.png")

main()

6. Mirror

Create a function mirror(image) that returns a version of the image that is reversed horizontally. Note: if you are stuck, try writing down the the algorithm for small 6 x 4 image on a piece of paper, and then generalize that idea to any sized image.

mirrored gullivers

def main():
    i_img = Image("/data/cs21/gulliver/poster.png")
    # Previous code from main has been omitted for clarity
    mirror(i_img).save("mirror_gulliver.png")

main()

7. (Optional) Flip

Create a function flip(image) that returns a version of the image that is reversed vertically.

flipped gullivers

def main():
    i_img = Image("/data/cs21/gulliver/poster.png")
    # Previous code from main has been omitted for clarity
    flip(i_img).save("flip_gulliver.png")

main()

8. (Optional) Photobooth

Create a function that takes a list of images and collects them into a single image such as the image shown below.

gulliver filmstrip

Or you can make a square image like this image:

gulliver square

You can also try to make a horizontal strip or even a collage if you’d like!

def main():
    i_img = Image("/data/cs21/gulliver/poster.png")
    # Previous code from main has been omitted for clarity

    imgs = [flip(i_img), fairey(mirror(i_img)), sepia(grayscale(i_img), 20)]
    o_img = photobooth(imgs)
    o_img.save("photobooth_gulliver.png")

main()

9. Final main()

The final main using all your functions above should be the following:

def main():
    img = Image("/data/cs21/gulliver/poster.png")
    neg_img = negate(img)
    neg_img.save("negate.png")

    gray_img = grayscale(img)
    gray_img.save("gray.png")

    binary_img = binary(img)
    binary_img.save("binary.png")

    sepia_img = sepia(gray_img, 20)
    sepia_img.save("sepia.png")

    fairey_img = fairey(img)
    fairey_img.save("fairey.png")

    mirror_img = mirror(img)
    mirror_img.save("mirror.png")

main()

Answer the Questionnaire

Each lab will have a short questionnaire at the end. Please edit the Questions-05.txt file in your cs21/labs/05 directory and answer the questions in that file.

Once you’re done with that, you should run handin21 again.

Submitting lab assignments

Remember to run handin21 to turn in your lab files! You may run handin21 as many times as you want. Each time it will turn in any new work. We recommend running handin21 after you complete each program or after you complete significant work on any one program.

Logging out

When you’re done working in the lab, you should log out of the computer you’re using.

First quit any applications you are running, like the browser and the terminal. Then click on the logout icon (logout icon or other logout icon) and choose "log out".

If you plan to leave the lab for just a few minutes, you do not need to log out. It is, however, a good idea to lock your machine while you are gone. You can lock your screen by clicking on the lock xlock icon. PLEASE do not leave a session locked for a long period of time. Power may go out, someone might reboot the machine, etc. You don’t want to lose any work!