{"id":22458078,"url":"https://github.com/yeong-hwan/image-enhancer","last_synced_at":"2025-03-27T13:44:12.442Z","repository":{"id":224983353,"uuid":"763179019","full_name":"yeong-hwan/image-enhancer","owner":"yeong-hwan","description":"Image enhancement program for damaged image","archived":false,"fork":false,"pushed_at":"2024-02-28T19:45:48.000Z","size":13801,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-01T18:13:29.522Z","etag":null,"topics":["average-filter","bilateral-filter","gaussian-filter","histogram-equalization","image-enhancement","median-filter","opencv2","pass-filter","sobel-filter"],"latest_commit_sha":null,"homepage":"","language":"Jupyter Notebook","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/yeong-hwan.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":"2024-02-25T18:57:25.000Z","updated_at":"2024-02-28T19:35:13.000Z","dependencies_parsed_at":"2024-12-06T08:10:48.176Z","dependency_job_id":null,"html_url":"https://github.com/yeong-hwan/image-enhancer","commit_stats":null,"previous_names":["yeong-hwan/image-enhancer"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeong-hwan%2Fimage-enhancer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeong-hwan%2Fimage-enhancer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeong-hwan%2Fimage-enhancer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeong-hwan%2Fimage-enhancer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yeong-hwan","download_url":"https://codeload.github.com/yeong-hwan/image-enhancer/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245858515,"owners_count":20684054,"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":["average-filter","bilateral-filter","gaussian-filter","histogram-equalization","image-enhancement","median-filter","opencv2","pass-filter","sobel-filter"],"created_at":"2024-12-06T08:10:42.262Z","updated_at":"2025-03-27T13:44:12.421Z","avatar_url":"https://github.com/yeong-hwan.png","language":"Jupyter Notebook","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Image-enhancer\n\n## Overview: Image Denoising\nThere are many ways to denoise damaged image.  \nAt this project, we implement three main method to denoise image.\n- Filter denoising\n- Pass Filter using FFT(Fast Fourier Transform)\n- Histogram equallization\n\n### Notice: RMSE(Root Mean Square Error)\nBy rooting the MSE(Mean Square Error), the distortion of the value caused by squaring the error is reduced.\n- The smaller the value, the more similar the two images are.\n\n```python\ndef calculate_rmse(img1, img2):\n    \"\"\"\n    Calculates RMS error between two images. Two images should have same sizes.\n    \"\"\"\n    if (img1.shape[0] != img2.shape[0]) or \\\n            (img1.shape[1] != img2.shape[1]) or \\\n            (img1.shape[2] != img2.shape[2]):\n        raise Exception(\"img1 and img2 should have sime sizes.\")\n\n    diff = np.abs(img1.astype(dtype=int) - img2.astype(dtype=int))\n    return np.sqrt(np.mean(diff ** 2))\n```\n\n- - -\n\n## Average Filter\n\nBecause padding occurs when avg filter is applied, the padding distance is set as the edge variable in the code.  \nIn the first two iterations, each row and column of the given img are traversed. Each circuit traverses the rows and columns of the kernel, stores the RGB values of each channel in the temp variable, and divides them by the kernel size (adj_val) when the kernel is complete, and stores them in the image (img_result) to be returned  \n\n```python\ndef apply_average_filter(img, kernel_size):\n    \"\"\"\n    It takes 2 arguments, \n    'img' is input image.\n    'kernel_size' is size of kernel for average filter.\n    \"\"\"\n    edge = int((kernel_size - 1) / 2)\n\n    row_len = len(img)\n    col_len = len(img[0])\n\n    adj_value = 1 / (kernel_size ** 2)\n\n    img_result = np.full((row_len, col_len, 3), 0)\n\n    for row in range(edge, row_len-edge):\n        for col in range(edge, col_len-edge):\n            temp_0, temp_1, temp_2 = 0, 0, 0\n            \n            for result_row in range(row-edge, row+edge+1):\n                for result_col in range(col-edge, col+edge+1):\n                    temp_0 += img[result_row, result_col, 0]\n                    temp_1 += img[result_row, result_col, 1]\n                    temp_2 += img[result_row, result_col, 2]\n\n            temp_0 = temp_0 * adj_value\n            temp_1 = temp_1 * adj_value\n            temp_2 = temp_2 * adj_value\n            img_result[row, col] = [temp_0, temp_1, temp_2]\n\n    return img_result\n```\n\n### Result: Average\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"docs_imgs/average_result.png\" alt=\"drawing\" width=700\"/\u003e\n\u003c/div\u003e\n\n- Each pixel is reallocated to an average value by referring to the surrounding value by the kernel size. As a result, the image is blurred.\n\n\n\n## Sobel filter\n\nIt is the same principle as the average filter, but the blue filter and the derivative column filter are specified.\n\n```python\ndef apply_sobel_filter(img, kernel_size, is_vertical):\n    \"\"\"\n    It takes 3 arguments,\n    'img' is input image.\n    'kernel_size' is size of kernel for sobel filter.\n    'is_vertical' is boolean value. If it is True, you should apply vertical sobel filter.\n    Otherwise, you should apply horizontal sobel filter.\n    \"\"\"\n\n    # sobel kernel setting\n    sobel_kernel = np.full((kernel_size, kernel_size), 0)\n    blur, derivative = [], []\n\n\n    if kernel_size == 3:\n        blur = [1, 2, 1]\n        derivative = [-1, 0, 1]\n    elif kernel_size == 5:\n        blur = [1, 4, 6 ,4 ,1]\n        derivative = [-1, -2, 0, 2, 1]\n    elif kernel_size == 7:\n        blur = [1, 6, 15, 20, 15, 6, 1]\n        derivative = [-1, -4, -5, 0, 5, 4, 1]\n\n\n    for row in range(kernel_size):\n        for col in range(kernel_size):\n            ver_val = blur[row] * derivative[col]\n            hor_val = derivative[row] * blur[col]\n\n            if is_vertical:\n                sobel_kernel[row][col] = ver_val \n            else:\n                sobel_kernel[row][col] = hor_val\n\n    # result setting\n    edge = int((kernel_size - 1) / 2)\n\n    row_len = len(img)\n    col_len = len(img[0])\n\n    img_result = np.full((row_len, col_len, 1), 0)\n\n    for row in range(edge, row_len-edge):\n        for col in range(edge, col_len-edge):\n            temp = 0\n\n            for result_row in range(row-edge, row+edge+1):\n                for result_col in range(col-edge, col+edge+1):\n                    # 0 ~ kernel_size\n                    kernel_row = result_row - (row-edge)\n                    kernel_col = result_col - (col-edge)\n\n                    img_value = img[result_row, result_col, 0]\n                    sobel_value = sobel_kernel[kernel_row][kernel_col]\n                    temp += img_value * sobel_value\n\n            img_result[row, col, 0] = np.clip(temp, 0, 255)\n\n    return img_result\n```\n\n### Result: Sobel\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"docs_imgs/sobel_result.png\" alt=\"drawing\" width=700\"/\u003e\n\u003c/div\u003e\n\n- The Sobel filter applies differentiation in the vertical and horizontal directions, and visualizes by allocating darker values with negative changes and brighter values with positive changes. Consequently, it is advantageous for edge detection.\n\n\n## Median filter\n```python\ndef apply_median_filter(img, kernel_size):\n    edge = int((kernel_size - 1) / 2)\n\n    row_len = len(img)\n    col_len = len(img[0])\n\n    img_result = np.full((row_len, col_len, 3), 0)\n\n    for row in range(row_len):\n        for col in range(col_len):\n            temp_0, temp_1, temp_2 = [], [], []\n\n            for result_row in range(row-edge, row+edge+1):\n                for result_col in range(col-edge, col+edge+1):\n                    try:\n                        temp_0.append(img[result_row, result_col, 0])\n                    except:\n                        pass\n                    try:\n                        temp_1.append(img[result_row, result_col, 1])\n                    except:\n                        pass\n                    try:\n                        temp_2.append(img[result_row, result_col, 2])\n                    except:\n                        pass\n\n            color_0, color_1, color_2 = np.median(\n                temp_0), np.median(temp_1), np.median(temp_2)\n            img_result[row, col] = [color_0, color_1, color_2]\n\n    return img_result\n```\n\n## Gaussian filter\n```python\ndef apply_gaussian_filter(img, k_size=3, sigma=1):\n    rows, cols, channels = img.shape\n\n    # Check the source code for more information about gaussian_kernel\n    gaussian_filter = gaussian_kernel(k_size, sigma)\n\n    pad_img = padding(img, k_size)\n    filtered_img = np.zeros((rows, cols, channels), dtype=np.float32)\n\n    for ch in range(0, channels):\n        for i in range(rows):\n            for j in range(cols):\n                filtered_img[i, j, ch] = np.sum(\n                    gaussian_filter * pad_img[i:i+k_size, j:j+k_size, ch])\n\n    return filtered_img.astype(np.uint8)\n```\n\n## Test images\n**Clean**\n\u003cdiv align=\"center\"\u003e\n  \u003cfigure class=\"third\"\u003e \n    \u003cimg src=\"test_imgs/cat_clean.jpg\" alt=\"drawing\" width=\"200\"/\u003e\n    \u003cimg src=\"docs_imgs/blank_space.png\" alt=\"drawing\" width=\"20\"/\u003e\n    \u003cimg src=\"test_imgs/fox_clean.jpg\" alt=\"drawing\" width=\"215\"/\u003e\n    \u003cimg src=\"docs_imgs/blank_space.png\" alt=\"drawing\" width=\"20\"/\u003e\n    \u003cimg src=\"test_imgs/snowman_clean.jpg\" alt=\"drawing\" width=\"200\"/\u003e\n    \u003c/figure\u003e\n\u003c/div\u003e\n\n**Noisy**\n\u003cdiv align=\"center\"\u003e\n  \u003cfigure class=\"third\"\u003e \n    \u003cimg src=\"test_imgs/cat_noisy.jpg\" alt=\"drawing\" width=\"200\"/\u003e\n    \u003cimg src=\"docs_imgs/blank_space.png\" alt=\"drawing\" width=\"20\"/\u003e\n    \u003cimg src=\"test_imgs/fox_noisy.jpg\" alt=\"drawing\" width=\"215\"/\u003e\n    \u003cimg src=\"docs_imgs/blank_space.png\" alt=\"drawing\" width=\"20\"/\u003e\n    \u003cimg src=\"test_imgs/snowman_noisy.jpg\" alt=\"drawing\" width=\"200\"/\u003e\n    \u003c/figure\u003e\n\u003c/div\u003e\n\n## Results\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"docs_imgs/result_imgs.png\" alt=\"drawing\" width=800\"/\u003e\n\u003c/div\u003e\n\n- cat(median, 3) → 7.87(RMSE) : Advanced\n- fox(gaussian, 3, 1) → 11.58(RMSE) : Basic\n- snowman(median, 7) → 9.76(RMSE) : Advanced\n\n### Why Median and Gaussian?\nThe given cat and snowman noise images are representative cases of **salt and pepper**. It was determined that a median filter would be suitable when extreme pixel values alternately appear, such as in the Salt and pepper image.\n \nOn the other hand, in the case of the original fox image, the background is **blurred** because it is focused on foxes and trees. Therefore, if the image with noise is slightly blurred, it would be similar to the original image, so we chose a Gaussian filter that blur the image.\n\n- - -\n\n## Fourier Transform\n\n### Why using FFT?\n\n**Spatial domain filtering**\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"docs_imgs/fft_example.png\" alt=\"drawing\" width=700\"/\u003e\n\u003c/div\u003e\n\nUsing the FFT technique, it is possible to move an image of a spatial domain to the frequency domain. Then, large-scale patterns within the original image correspond to low frequencies, and small-scale noise-like patterns correspond to high frequencies. In other words, the frequency domain can be divided into low-frequency and high-frequency parts, and processing such high-frequency parts as removal or attenuation and then inverse transforming them back to the spatial domain is a typical processing method of FFT-based noise removal.\n\nAlso FFT can reduce time and computational complexity when compared to kernel filtering.\n\n**fftshift \u0026 ifftshift**\n```python\ndef fftshift(img):\n    '''\n    This function should shift the spectrum image to the center.\n    You should not use any kind of built in shift function. Please implement your own.\n    '''\n    row_len, col_len = img.shape\n    fft_shifted = np.roll(img, (row_len//2, col_len//2), axis=(0, 1))\n\n    return fft_shifted\n\n\ndef ifftshift(img):\n    '''\n    This function should do the reverse of what fftshift function does.\n    You should not use any kind of built in shift function. Please implement your own.\n    '''\n    row_len, col_len = np.shape(img)\n    fft_unshifted = np.roll(img, (-row_len//2, -col_len//2), axis=(0, 1))\n\n    return fft_unshifted\n\n```\n\n### Low/High pass filter\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"docs_imgs/pass_filter_formula.png\" alt=\"drawing\" width=400\"/\u003e\n\u003c/div\u003e\n\n- Image preprocessing with fft, ftshift\n- lpf: 0 elements not included in the reference distance (r) relative to the image center\n- hpf: 0 elements included in the reference distance (r) relative to the image center\n- Return the image by post-processing iftshift, ifft in the reverse order of preprocessing\n\n\n```python\ndef low_pass_filter(img, r=30):\n    '''\n    This function should return an image that goes through low-pass filter.\n    '''\n    fft_img = np.fft.fft2(img)\n    fftshift_img = fftshift(fft_img)\n\n    row_len, col_len = fftshift_img.shape\n    row_center, col_center = int(row_len/2), int(col_len/2)\n\n    result_fft_img = fftshift_img.copy()\n\n    for row in range(row_len):\n        for col in range(col_len):\n            edge_radius = np.sqrt((row_center-row)**2 + (col_center-col)**2)\n\n            if edge_radius \u003e r:\n                result_fft_img[row, col] = 0\n\n    ifft_img = ifftshift(result_fft_img)\n    result_img = np.fft.ifft2(ifft_img).real\n\n    return result_img\n\n\ndef high_pass_filter(img, r=20):\n    '''\n    This function should return an image that goes through high-pass filter.\n    '''\n    fft_img = np.fft.fft2(img)\n    fftshift_img = fftshift(fft_img)\n\n    row_len, col_len = fftshift_img.shape\n    row_center, col_center = int(row_len/2), int(col_len/2)\n\n    result_fft_img = fftshift_img.copy()\n\n    for row in range(row_len):\n        for col in range(col_len):\n            edge_radius = np.sqrt((row_center-row)**2 + (col_center-col)**2)\n\n            if edge_radius \u003c r:\n                result_fft_img[row, col] = 0\n\n    ifft_img = ifftshift(result_fft_img)\n    result_img = np.fft.ifft2(ifft_img).real\n\n    return result_img\n```\n## Test image\n**Lena_clean**\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"test_imgs/lena.png\" alt=\"drawing\" width=200\"/\u003e\n\u003c/div\u003e\n\n### Result\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"docs_imgs/fft_result.png\" alt=\"drawing\" width=400\"/\u003e\n\u003c/div\u003e\n\n- LPF: image of dull edge and blurred by removing high frequency part  \n- HPF: edge-oriented image by removing low frequency part\n\n- - - \n\n## Histogram equallization\nHistogram equalization uses a low contrast input image with a narrow range of pixel values to obtain a high contrast output image with a wide range of pixel values. In other words, because the brightness values are concentrated, a clearer image is obtained by smoothing a dim or bright image.\n\n```python\nhist, bins = np.histogram(img.ravel(), 256, [0, 256])\nhist_sum = hist.cumsum()\n\n# except 0 by mask\nhist_m0 = np.ma.masked_equal(hist_sum, 0)\n\n# equallization\nhist_m0 = (hist_m0 - hist_m0.min()) * 255 / (hist_m0.max() - hist_m0.min())\n\n# restore 0 from mask excepted\nhist_sum = np.ma.filled(hist_m0, 0).astype('uint8')\n\nresult_img = hist_sum[img]\n\nreturn result_img\n```\n\n- stretch histogram using below equation\n\u003e (hist_m0- hist_m0.min())*255/(hist_m0.max()- hist_m0.min())\n\n## Test image \u0026 Result\n\n\u003cdiv align=\"center\"\u003e\n  \u003cfigure class=\"third\"\u003e \n    \u003cimg src=\"test_imgs/engi_hall_low_light.jpg\" alt=\"drawing\" width=300\"/\u003e\n    \u003cimg src=\"docs_imgs/blank_space.png\" alt=\"drawing\" width=\"20\"/\u003e\n    \u003cimg src=\"docs_imgs/histogram_result.png\" alt=\"drawing\" width=305\"/\u003e\n    \u003c/figure\u003e\n\u003c/div\u003e\n\n- The histogram of the dark image was concentrated on the left and the bright image on the right, so if you spread this distribution widely, you will get an image of moderate brightness.\n\n\n- - -\n\n## Libraries\n- [OpenCV](https://opencv.org/)\n- [Numpy](https://numpy.org/)\n\n\n## References\nSome images are from [SeonJoo Kim](https://sites.google.com/site/seonjookim/), Yonsei University\n\n### Environment\nOS: Mac Ventura  \nLanguage: Python(3.9.12)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyeong-hwan%2Fimage-enhancer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyeong-hwan%2Fimage-enhancer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyeong-hwan%2Fimage-enhancer/lists"}