""" CS146 Image Analysis Examples Adapted from: https://ethicalcs.github.io/#manipulators """ import PIL.Image import urllib.request from io import BytesIO # Python has rejected the certificate used for the CS dept. server so we bypass some of those checks import ssl ssl._create_default_https_context = ssl._create_unverified_context BASE_URL = "https://www.cs.middlebury.edu/~mlinderman/courses/cs146/f24/classes/" class Pixel: """Single RGB pixel Public Attributes: red (int): Red color component as integer in range [0,255] green (int): Green color component as integer in range [0,255] blue (int): Blue color component as integer in range [0,255] """ def __init__(self, red=0, green=0, blue=0): """Create a new RGB pixel Args: red (int, optional): Red color component. Defaults to 0. green (int, optional): Green color component. Defaults to 0. blue (int, optional): Blue color component. Defaults to 0. """ self.red = red self.green = green self.blue = blue class Image: """Image class providing a simple interface to a PIL image Public attributes: None """ def __init__(self, url): """Load an image from a URL Args: url: Image URL, e.g,, http://.... """ with urllib.request.urlopen(url) as url: self.img = PIL.Image.open(BytesIO(url.read())) self.img_pix = self.img.load() self.width = self.img.size[0] self.height = self.img.size[1] def get_width(self): """Return image width in pixels""" return self.width def get_height(self): """Return image height in pixels""" return self.height def get_pixel(self, row, col): """Return Pixel object at location starting at (0,0) in upper left corner Args: row: Integer row index col: Integer column index Returns: Pixel object with RGB values """ pix = self.img_pix[col, row] return Pixel(pix[0], pix[1], pix[2]) def set_pixel(self, row, col, pixel): """Set pixel at location starting at (0,0) in upper left corner Args: row: Integer row index col: Integer column index pixel: New pixel value with red, green and blue components """ self.img_pix[col, row] = (pixel.red, pixel.green, pixel.blue) def save_image(self, filename): """Save image to local file Args: filename: String filename with image extension, e.g., "result.jpg" """ try: self.img.save(filename) except IOError: print(f"Cannot save image to {filename}") def show(self): """Display image in a new window""" self.img.show() def red_shift(in_file, out_file): """Red shift image, saving result to local file Args: in_file: String with URL to original image out_file: String filename with image extension to save modified image """ # Load image from URL img = Image(in_file) # Iterate over all pixels, shifting red component by 100 for row in range(img.get_height()): for col in range(img.get_width()): pix = img.get_pixel(row, col) img.set_pixel(row, col, Pixel(pix.red + 100, pix.green, pix.blue)) # Save modified image to local file img.save_image(out_file) # Example testing code for `red_shift` function. # red_shift(BASE_URL + "linderman.jpg", "linderman_red.jpg") # Horizontal blur filter window (declared here to keep examples together) WINDOW = 4 def blur(in_file, out_file): """Apply horizontal blur filter to image, saving result to local file Args: in_file: String with URL to original image out_file: String filename with image extension to save modified image """ # Load image from URL img = Image(in_file) for row in range(img.get_height()): # We will ignore right-most pixels in the image (where blur filter would extend # beyond the image boundary) for col in range(img.get_width()-WINDOW+1): pix = Pixel(0, 0, 0) for idx in range(WINDOW): blur_pix = img.get_pixel(row, col+idx) pix.red += blur_pix.red pix.green += blur_pix.green pix.blue += blur_pix.blue img.set_pixel(row, col, Pixel(pix.red // WINDOW, pix.green // WINDOW, pix.blue // WINDOW)) # Save modified image to local file img.save_image(out_file) # Example testing code for `blur` function. # blur(BASE_URL + "linderman.jpg", "linderman_blur.jpg") def average(in_files, out_file): """Average a set of images, saving result to local file Args: in_files: List of string URLs for original images out_file: String filename with image extension to save modified image """ # Load original images as Image objects from URLs imgs = [] for url in in_files: imgs.append(Image(url)) # Update first image with average of all images first_img = imgs[0] for row in range(first_img.get_height()): for col in range(first_img.get_width()): pix = Pixel() for i in range(len(imgs)): avg_pix = imgs[i].get_pixel(row, col) pix.red += avg_pix.red pix.green += avg_pix.green pix.blue += avg_pix.blue first_img.set_pixel(row, col, Pixel(pix.red // len(imgs), pix.green // len(imgs), pix.blue // len(imgs))) # Save modified image to local file first_img.save_image(out_file) # Example testing code for `average` function. # Experiment with different (sub)sets of faces # # Face files adapted from Peck E. et al. and faceresearch.org # face_files = ["alex.jpg", "alexander.jpg", "alfred.jpg", "ambroz.jpg", "arnold.jpg"] # # face_files = ["zelmira.jpg","zita.jpg", "zlata.jpg", "zlatica.jpg", "zora.jpg"] # # # Transform the list of face files into a list of URLs # face_urls = [] # for face in face_files: # face_urls.append(BASE_URL + "faces/" + face) # # average(face_urls, "average_face.jpg")