{"id":23127659,"url":"https://github.com/hlfshell/cse-455-vision-hw-0","last_synced_at":"2025-09-09T22:36:44.246Z","repository":{"id":146170202,"uuid":"254545940","full_name":"hlfshell/cse-455-vision-hw-0","owner":"hlfshell","description":"Homework 0 for CSE 455 Washington University","archived":false,"fork":false,"pushed_at":"2020-04-10T04:47:50.000Z","size":10131,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-04T05:43:05.731Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hlfshell.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-10T04:47:03.000Z","updated_at":"2025-01-01T20:30:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"02cf1e39-2843-4c49-965d-f7f0798c995c","html_url":"https://github.com/hlfshell/cse-455-vision-hw-0","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hlfshell/cse-455-vision-hw-0","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hlfshell%2Fcse-455-vision-hw-0","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hlfshell%2Fcse-455-vision-hw-0/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hlfshell%2Fcse-455-vision-hw-0/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hlfshell%2Fcse-455-vision-hw-0/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hlfshell","download_url":"https://codeload.github.com/hlfshell/cse-455-vision-hw-0/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hlfshell%2Fcse-455-vision-hw-0/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262476300,"owners_count":23317250,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-17T09:14:24.078Z","updated_at":"2025-06-28T18:32:53.838Z","avatar_url":"https://github.com/hlfshell.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CSE 455 Homework 0 #\n\nWelcome friends,\n\nFor the first assignment we'll just get to know the codebase a little bit and practice manipulating images, transforming things, breaking stuff, it should be fun!\n\n## Image basics ##\n\nWe have a pretty basic datastructure to store images in our library. The `image` struct stores the image metadata like width, height, and number of channels. It also contains the image data stored as a floating point array. You can check it out in `src/image.h`, it looks like this:\n\n    typedef struct{\n        int h,w,c;\n        float *data;\n    } image;\n\nWe have also provided some functions for loading and saving images. Use the function:\n\n    image im = load_image(\"image.jpg\");\n\nto load a new image. To save an image use:\n\n    save_image(im, \"output\");\n\nwhich will save the image as `output.jpg`. If you want to make a new image with dimensions Width x Height x Channels you can call:\n\n    image im = make_image(w,h,c);\n\nYou should also use: \n\n    free_image(im);\n\nwhen you are done with an image. So it goes away. You can check out how all this is implemented in `src/load_image.c`. You probably shouldn't change anything in this file. We use the `stb_image` libary for the actual loading and saving of jpgs because that is, like, REALLY complicated. I think. I've never tried. Anywho....\n\nYou'll be modifying the file `src/process_image.c`. We've also included a python compatability library. `uwimg.py` includes the code to access your C library from python. `tryit.py` has some example code you can run. We will build the library using `make`. Simply run the command:\n\n    make\n    \nafter you make any changes to the code. Then you can quickly test your changes by running:\n\n    ./uwimg test\n\nYou can also try running the example python code to generate some images:\n\n    python tryit.py\n\n## 1. Getting and setting pixels ##\n\nThe most basic operation we want to do is change the pixels in an image. As we talked about in class, we represent an image as a 3 dimensional tensor. We have spatial information as well as multiple channels which combine together to form a color image:\n\n![RGB format](figs/rgb.png)\n\nThe convention is that the coordinate system starts at the top left of the image, like so:\n\n![Image coordinate system](figs/coords.png)\n\nIn our `data` array we store the image in `CHW` format. The first pixel in data is at channel 0, row 0, column 0. The next pixel is channel 0, row 0, column 1, then channel 0, row 0, column 2, etc.\n\nYour first task is to fill out these two functions in `src/process_image.c`:\n\n    float get_pixel(image im, int x, int y, int c);\n    void set_pixel(image im, int x, int y, int c, float v);\n\n`get_pixel` should return the pixel value at column `x`, row `y`, and channel `c`. `set_pixel` should set the pixel to the value `v`. You will need to do bounds checking to make sure the coordinates are valid for the image. `set_pixel` should simply return without doing anything if you pass in invalid coordinates. For `get_pixel` we will perform padding to the image. There are a number of possible padding strategies:\n\n![Image padding strategies](figs/pad.png)\n\nWe will use the `clamp` padding strategy. This means that if the programmer asks for a pixel at column -3, use column 0, or if they ask for column 300 and the image is only 256x256 you will use column 255 (because of zero-based indexing).\n\nWe can test out our pixel-setting code on the dog image by removing all of the red channel. See line 3-8 in `tryit.py`:\n\n    # 1. Getting and setting pixels\n    im = load_image(\"data/dog.jpg\")\n    for row in range(im.h):\n        for col in range(im.w):\n            set_pixel(im, row, col, 0, 0)\n    save_image(im, \"figs/dog_no_red\")\n\nThen try running it. Check out our very not red dog:\n\n![](figs/dog_no_red.jpg)\n\n\n## 2. Copying images ##\n\nSometimes you have an image and you want to copy it! To do this we should make a new image of the same size and then fill in the data array in the new image. You could do this by getting and setting pixels, by looping over the whole array and just copying the floats (pop quiz: if the image is 256x256x3, how many total pixels are there?), or by using the built-in memory copying function `memcpy`.\n\nFill in the function `image copy_image(image im)` in `src/process_image.c` with your code.\n\n## 3. Grayscale image ##\n\nNow let's start messing with some images! People like making images grayscale. It makes them look... old? Or something? Let's do it.\n\nRemember how humans don't see all colors equally? Here's the chart to remind you:\n\n![Eye sensitivity to different wavelengths](figs/sensitivity.png)\n\nThis actually makes a huge difference in practice. Here's a colorbar we may want to convert:\n\n![Color bar](figs/colorbar.png)\n\nIf we convert it using an equally weighted mean K = (R+G+B)/3 we get a conversion that doesn't match our perceptions of the given colors:\n\n![Averaging grayscale](figs/avggray.jpg)\n\nInstead we are going to use a weighted sum. Now, there are a few ways to do this. If we wanted the most accurate conversion it would take a fair amount of work. sRGB uses [gamma compression][1] so we would first want to convert the color to linear RGB and then calculate [relative luminance](https://en.wikipedia.org/wiki/Relative_luminance).\n\nBut we don't care about being toooo accurate so we'll just do the quick and easy version instead. Video engineers use a calculation called [luma][2] to find an approximation of perceptual intensity when encoding video signal, we'll use that to convert our image to grayscale. It operates directly on the gamma compressed sRGB values that we already have! We simply perform a weighted sum:\n\n    Y' = 0.299 R' + 0.587 G' + .114 B'\n\nUsing this conversion technique we get a pretty good grayscale image! Now we can run `tryit.py` to output `graybar.jpg`. See lines 10-13:\n\n    # 3. Grayscale image\n    im = load_image(\"data/colorbar.png\")\n    graybar = rgb_to_grayscale(im)\n    save_image(graybar, \"graybar\")\n\n![Grayscale colorbars](figs/gray.png)\n\nImplement this conversion for the function `rgb_to_grayscale`. Return a new image that is the same size but only one channel containing the calculated luma values.\n\n## 4. Shifting the image colors ##\n\nNow let's write a function to add a constant factor to a channel in an image. We can use this across every channel in the image to make the image brighter or darker. We could also use it to, say, shift an image to be more or less of a given color.\n\nFill in the code for `void shift_image(image im, int c, float v);`. It should add `v` to every pixel in channel `c` in the image. Now we can try shifting all the channels in an image by `.4` or 40%. See lines 15-20 in `tryit.py`:\n\n    # 4. Shift Image\n    im = load_image(\"data/dog.jpg\")\n    shift_image(im, 0, .4)\n    shift_image(im, 1, .4)\n    shift_image(im, 2, .4)\n    save_image(im, \"overflow\")\n\nBut wait, when we look at the resulting image `overflow.jpg` we see something bad has happened! The light areas of the image went past 1 and when we saved the image back to disk it overflowed and made weird patterns:\n\n![Overflow](figs/overflow.jpg)\n\n## 5. Clamping the image values\n\nOur image pixel values have to be bounded. Generally images are stored as byte arrays where each red, green, or blue value is an unsigned byte between 0 and 255. 0 represents none of that color light and 255 represents that primary color light turned up as much as possible.\n\nWe represent our images using floating point values between 0 and 1. However, we still have to convert between our floating point representation and the byte arrays that are stored on disk. In the example above, our pixel values got above 1 so when we converted them back to byte arrays and saved them to disk they overflowed the byte data type and went back to very small values. That's why the very bright areas of the image looped around and became dark.\n\nWe want to make sure the pixel values in the image stay between 0 and 1. Implement clamping on the image so that any value below zero gets set to zero and any value above 1 gets set to one. Fill in `void clamp_image(image im);` to modify the image in-place. Then when we clamp the shifted image and save it we see much better results, see lines 22-24 in `tryit.py`:\n\n    # 5. Clamp Image\n    clamp_image(im)\n    save_image(im, \"fixed\")\n\nand the resulting image, `fixed.jpg`:\n\n![](figs/fixed.jpg)\n\n## 6. RGB to Hue, Saturation, Value ##\n\nSo far we've been focussing on RGB and grayscale images. But there are other colorspaces out there too we may want to play around with. Like [Hue, Saturation, and Value (HSV)](https://en.wikipedia.org/wiki/HSL_and_HSV). We will be translating the cubical colorspace of sRGB to the cylinder of hue, saturation, and value:\n\n![RGB HSV conversion](figs/convert.png)\n\n[Hue](https://en.wikipedia.org/wiki/Hue) can be thought of as the base color of a pixel. [Saturation](https://en.wikipedia.org/wiki/Colorfulness#Saturation) is the intensity of the color compared to white (the least saturated color). The [Value](https://en.wikipedia.org/wiki/Lightness) is the perception of brightness of a pixel compared to black. You can try out this [demo](http://math.hws.edu/graphicsbook/demos/c2/rgb-hsv.html) to get a better feel for the differences between these two colorspaces. For a geometric interpretation of what this transformation:\n\n![RGB to HSV geometry](figs/rgbtohsv.png)\n\nNow, to be sure, there are [lots of issues](http://poynton.ca/notes/colour_and_gamma/ColorFAQ.html#RTFToC36) with this colorspace. But it's still fun to play around with and relatively easy to implement. The easiest component to calculate is the Value, it's just the largest of the 3 RGB components:\n\n    V = max(R,G,B)\n\nNext we can calculate Saturation. This is a measure of how much color is in the pixel compared to neutral white/gray. Neutral colors have the same amount of each three color components, so to calculate saturation we see how far the color is from being even across each component. First we find the minimum value\n\n    m = min(R,G,B)\n\nThen we see how far apart the min and max are:\n\n    C = V - m\n\nand the Saturation will be the ratio between the difference and how large the max is:\n\n    S = C / V\n\nExcept if R, G, and B are all 0. Because then V would be 0 and we don't want to divide by that, so just set the saturation 0 if that's the case.\n\nFinally, to calculate Hue we want to calculate how far around the color hexagon our target color is.\n\n![color hex](figs/hex.png)\n\nWe start counting at Red. Each step to a point on the hexagon counts as 1 unit distance. The distance between points is given by the relative ratios of the secondary colors. We can use the following formula from [Wikipedia](https://en.wikipedia.org/wiki/HSL_and_HSV#Hue_and_chroma):\n\n\u003cimg src=\"figs/eq.svg\" width=\"256\"\u003e\n\nThere is no \"correct\" Hue if C = 0 because all of the channels are equal so the color is a shade of gray, right in the center of the cylinder. However, for now let's just set H = 0 if C = 0 because then your implementation will match mine.\n\nNotice that we are going to have H = \\[0,1) and it should circle around if it gets too large or goes negative. Thus we check to see if it is negative and add one if it is. This is slightly different than other methods where H is between 0 and 6 or 0 and 360. We will store the H, S, and V components in the same image, so simply replace the R channel with H, the G channel with S, etc.\n\n## 7. HSV to RGB ##\n\nOk, now do it all backwards in `hsv_to_rgb`!\n\nFinally, when your done we can mess with some images! In `tryit.py` we convert an image to HSV, increase the saturation, then convert it back, lines 26-32:\n\n    # 6-7. Colorspace and saturation\n    im = load_image(\"data/dog.jpg\")\n    rgb_to_hsv(im)\n    shift_image(im, 1, .2)\n    clamp_image(im)\n    hsv_to_rgb(im)\n    save_image(im, \"dog_saturated\")\n\n![Saturated dog picture](figs/dog_saturated.jpg)\n\nHey that's exciting! Play around with it a little bit, see what you can make. Note that with the above method we do get some artifacts because we are trying to increase the saturation in areas that have very little color. Instead of shifting the saturation, you could scale the saturation by some value to get smoother results!\n\n## 8. A small amount of extra credit ##\n\nImplement `void scale_image(image im, int c, float v);` to scale a channel by a certain amount. This will give us better saturation results. Note, you will have to add the necessary lines to the header and python library, it should be very similar to what's already there for `shift_image`. Now if we scale saturation by `2` instead of just shifting it all up we get much better results:\n\n    im = load_image(\"data/dog.jpg\")\n    rgb_to_hsv(im)\n    scale_image(im, 1, 2)\n    clamp_image(im)\n    hsv_to_rgb(im)\n    save_image(im, \"dog_scale_saturated\")\n    \n![Dog saturated smoother](figs/dog_scale_saturated.jpg)\n\n## 9. Super duper extra credit ##\n\nImplement RGB to [Hue, Chroma, Lightness](https://en.wikipedia.org/wiki/CIELUV#Cylindrical_representation_.28CIELCH.29), a perceptually more accurate version of Hue, Saturation, Value. Note, this will involve gamma decompression, converting to CIEXYZ, converting to CIELUV, converting to HCL, and the reverse transformations. The upside is a similar colorspace to HSV but with better perceptual properties!\n\n## Turn it in ##\n\nYou only need to turn in one file, your `process_image.c`. Use the dropbox link on the class website.\n\n[1]: https://en.wikipedia.org/wiki/SRGB#The_sRGB_transfer_function_(\"gamma\")\n[2]: https://en.wikipedia.org/wiki/Luma_(video)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhlfshell%2Fcse-455-vision-hw-0","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhlfshell%2Fcse-455-vision-hw-0","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhlfshell%2Fcse-455-vision-hw-0/lists"}