Init Commit
This commit is contained in:
parent
16c991b3dd
commit
7b47850f9e
162
.gitignore
vendored
Normal file
162
.gitignore
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
### Python template
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
.idea/
|
||||
.dataset/
|
||||
22
README.md
22
README.md
@ -1,2 +1,22 @@
|
||||
# ImageOptimizer
|
||||
# Image Optimizer
|
||||
UIC DPW 24S **Group 11** course project
|
||||
|
||||
## Set Up
|
||||
1. **Install packages using *requirements.txt*:** `pip install -r requirements.txt`.
|
||||
2. **Ensure `.\icon` folder is correctly placed** in the same directory with the python files.
|
||||
3. The prepared JSON filter datasets are placed in `.\dataset` folder. Although the program can run without these dataset files, you may not be able to observe the dataset and do image auto optimization as it requires dataset for analysis and training.
|
||||
4. **Run** `main.py`.
|
||||
|
||||
## Usage: Image Optimization
|
||||
1. **Adjust Atomic Parameter of an Image:** Click in and change the value in a text box located at the left-bottom of `Main` frame. Then click out of the canvas or simply click the *Image histogram* on the top to modify the image & refresh the window.
|
||||
2. **Auto Optimization:** Choose a filter in the combo box located at the right-bottom of the `Main` frame, and click `Auto Optimizzation` button.
|
||||
3. **Export Image:** Simple click `Export` button, then a new window will appear. You may crop, compress, and choose clarity of the output image.
|
||||
4. **Revert Image:** We offer `Revert To Original` button to restart the editing process.
|
||||
|
||||
## Usage: Dataset Management
|
||||
1. **New Dataset Creation:** Simply Create a new folder in `.\dataset` and push the `Update Dataset` button on the `Dataset Manamgement` frame.
|
||||
2. **Dataset Update:** Add new images into the corresponding filter folder in`.\dataset` and push the `Update Dataset` button on the `Dataset Manamgement` frame.
|
||||
3. **Update Dataset By Unsplash Crawler:**
|
||||
Open the `Dataset Management` frame, click `Get More Images from Unsplash`.
|
||||
Specify which filter datasets you want to update, each inner API can only crawl *500 images/hour*.
|
||||
After crawling, click `Dataset Update` button.
|
||||
|
||||
379
analyze_func.py
Normal file
379
analyze_func.py
Normal file
@ -0,0 +1,379 @@
|
||||
import os
|
||||
import json
|
||||
import cv2 as cv
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
# Normalization: Scale 0-100
|
||||
def normalize_value(value, min_value, max_value):
|
||||
# Ensure the values are within the correct range
|
||||
if value < min_value:
|
||||
value = min_value
|
||||
elif value > max_value:
|
||||
value = max_value
|
||||
# Normalize the value to a 0-100 scale
|
||||
normalized_value = 100.0 * (value - min_value) / (max_value - min_value)
|
||||
return normalized_value
|
||||
|
||||
|
||||
def denormalize_value(normalized_value, min_value, max_value):
|
||||
denormalized_value = (normalized_value / 100.0) * (max_value - min_value) + min_value
|
||||
return denormalized_value
|
||||
|
||||
|
||||
# Get & Modify Functions
|
||||
def get_noise(img):
|
||||
grayscale_image = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
||||
noise_level = np.std(grayscale_image)
|
||||
normalized_noise = normalize_value(noise_level, 0, 255)
|
||||
return normalized_noise
|
||||
|
||||
|
||||
def modify_noise(img, noise_factor):
|
||||
noise_factor_denormalized = denormalize_value(noise_factor, 0, 15)
|
||||
gaussian_noise = np.random.normal(0, noise_factor_denormalized, img.shape)
|
||||
noisy_img = np.clip(img + gaussian_noise, 0, 255).astype(np.uint8)
|
||||
return noisy_img
|
||||
|
||||
|
||||
def get_hue(img):
|
||||
hsv_image = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||||
hue_values = hsv_image[:, :, 0]
|
||||
average_hue = np.mean(hue_values)
|
||||
normalized_hue = normalize_value(average_hue, 0, 179)
|
||||
return normalized_hue
|
||||
|
||||
|
||||
def modify_hue(img, hue_factor):
|
||||
hsv_image = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||||
hue_values = hsv_image[:, :, 0]
|
||||
target_hue = denormalize_value(hue_factor, 0, 179)
|
||||
hue_adjustment = target_hue - np.mean(hue_values)
|
||||
# Ensure hue_values and hue_adjustment are numpy arrays
|
||||
if isinstance(hue_values, np.ndarray) and isinstance(hue_adjustment, (int, float, np.ndarray)):
|
||||
hsv_image[:, :, 0] = (hue_values + hue_adjustment) % 180
|
||||
modified_img = cv.cvtColor(hsv_image, cv.COLOR_HSV2BGR)
|
||||
return modified_img
|
||||
|
||||
|
||||
def get_brightness(img):
|
||||
average_brightness = np.mean(img)
|
||||
normalized_brightness = normalize_value(average_brightness, 0, 255)
|
||||
return normalized_brightness
|
||||
|
||||
|
||||
def modify_brightness(img, brightness_factor):
|
||||
hsv_image = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||||
current_avg_brightness = np.mean(hsv_image[:, :, 2])
|
||||
target_brightness = denormalize_value(brightness_factor, 0, 255)
|
||||
if current_avg_brightness == 0:
|
||||
brightness_adjustment = 1
|
||||
else:
|
||||
brightness_adjustment = target_brightness / current_avg_brightness
|
||||
hsv_image[:, :, 2] = np.clip(hsv_image[:, :, 2] * brightness_adjustment, 0, 255).astype(np.uint8)
|
||||
modified_img = cv.cvtColor(hsv_image, cv.COLOR_HSV2BGR)
|
||||
return modified_img
|
||||
|
||||
|
||||
def get_perceived_avg_brightness(img):
|
||||
hsv_image = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||||
value_values = hsv_image[:, :, 2]
|
||||
average_value = np.mean(value_values)
|
||||
normalized_value = normalize_value(average_value, 0, 255)
|
||||
return normalized_value
|
||||
|
||||
|
||||
def modify_perceived_avg_brightness(img, target_avg_value):
|
||||
hsv_image = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||||
value_values = hsv_image[:, :, 2]
|
||||
current_avg_value = np.mean(value_values)
|
||||
target_brightness = denormalize_value(target_avg_value, 0, 255)
|
||||
if current_avg_value == 0:
|
||||
brightness_adjustment = 1
|
||||
else:
|
||||
brightness_adjustment = target_brightness / current_avg_value
|
||||
adjusted_value = np.clip(value_values * brightness_adjustment, 0, 255)
|
||||
hsv_image[:, :, 2] = adjusted_value.astype(np.uint8)
|
||||
adjusted_img = cv.cvtColor(hsv_image, cv.COLOR_HSV2BGR)
|
||||
return adjusted_img
|
||||
|
||||
|
||||
def get_white_balance(img):
|
||||
# Calculate mean values for each color channel
|
||||
mean_red = np.mean(img[:, :, 2])
|
||||
mean_green = np.mean(img[:, :, 1])
|
||||
mean_blue = np.mean(img[:, :, 0])
|
||||
# Calculate mean intensity of the grayscale image
|
||||
mean_gray = np.mean(cv.cvtColor(img, cv.COLOR_BGR2GRAY))
|
||||
# Avoid division by zero
|
||||
if mean_red == 0 or mean_green == 0 or mean_blue == 0:
|
||||
return 0, 0, 0
|
||||
# Calculate balance ratios for each channel
|
||||
balance_ratio_red = mean_gray / mean_red
|
||||
balance_ratio_green = mean_gray / mean_green
|
||||
balance_ratio_blue = mean_gray / mean_blue
|
||||
# Normalize each ratio separately
|
||||
normalized_red = normalize_value(balance_ratio_red, 0, 2)
|
||||
normalized_green = normalize_value(balance_ratio_green, 0, 2)
|
||||
normalized_blue = normalize_value(balance_ratio_blue, 0, 2)
|
||||
return normalized_red, normalized_green, normalized_blue
|
||||
|
||||
|
||||
def modify_white_balance(image, balance_ratio_red=-1, balance_ratio_green=-1, balance_ratio_blue=-1):
|
||||
image = image.astype(np.float32)
|
||||
b, g, r = cv.split(image)
|
||||
# If balance ratios are given as normalized values (0-100), convert them to appropriate ratios
|
||||
if balance_ratio_red != -1:
|
||||
balance_ratio_red = denormalize_value(balance_ratio_red, 0, 2)
|
||||
r = r * balance_ratio_red
|
||||
if balance_ratio_green != -1:
|
||||
balance_ratio_green = denormalize_value(balance_ratio_green, 0, 2)
|
||||
g = g * balance_ratio_green
|
||||
if balance_ratio_blue != -1:
|
||||
balance_ratio_blue = denormalize_value(balance_ratio_blue, 0, 2)
|
||||
b = b * balance_ratio_blue
|
||||
r = np.clip(r, 0, 255)
|
||||
g = np.clip(g, 0, 255)
|
||||
b = np.clip(b, 0, 255)
|
||||
balanced_image = cv.merge((b, g, r))
|
||||
balanced_image = balanced_image.astype(np.uint8)
|
||||
return balanced_image
|
||||
|
||||
|
||||
def get_color_temperature(img):
|
||||
# Calculate the mean values for each color channel
|
||||
mean_red = np.mean(img[:, :, 0])
|
||||
mean_green = np.mean(img[:, :, 1])
|
||||
mean_blue = np.mean(img[:, :, 2])
|
||||
# Calculate color temperature based on the ratio of blue to red
|
||||
color_temperature = (mean_blue - mean_red) / (mean_blue + mean_green + mean_red)
|
||||
normalized_color_temperature = normalize_value(color_temperature, -100, 100)
|
||||
return normalized_color_temperature
|
||||
|
||||
|
||||
def modify_color_temperature(img, temperature_factor):
|
||||
modified_img = img.copy().astype(np.float32)
|
||||
temperature_factor = denormalize_value(temperature_factor, -100, 100)
|
||||
# Adjust color temperature by scaling blue and red channels
|
||||
modified_img[:, :, 0] += temperature_factor
|
||||
modified_img[:, :, 2] -= temperature_factor
|
||||
# Clip values to ensure they remain in valid range
|
||||
modified_img = np.clip(modified_img, 0, 255)
|
||||
return modified_img.astype(np.uint8)
|
||||
|
||||
|
||||
def get_contrast(img):
|
||||
grayscale_image = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
||||
contrast = np.std(grayscale_image)
|
||||
# Normalize to the range 0-100
|
||||
normalized_contrast = normalize_value(contrast, 0, 128)
|
||||
return normalized_contrast
|
||||
|
||||
|
||||
def modify_contrast(img, contrast_factor):
|
||||
grayscale_image = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
||||
mean_intensity = np.mean(grayscale_image)
|
||||
contrast_target = denormalize_value(contrast_factor, 0, 128)
|
||||
contrast_adjustment = (2 * contrast_target / 128) - 1
|
||||
# Adjust contrast for each channel separately
|
||||
adjusted_channels = []
|
||||
for i in range(3):
|
||||
adjusted_channel = np.clip((img[:, :, i] - mean_intensity) * (1 + contrast_adjustment) + mean_intensity, 0,
|
||||
255).astype(np.uint8)
|
||||
adjusted_channels.append(adjusted_channel)
|
||||
# Merge the adjusted channels back into a BGR image
|
||||
modified_img = cv.merge((adjusted_channels[0], adjusted_channels[1], adjusted_channels[2]))
|
||||
return modified_img
|
||||
|
||||
|
||||
def get_saturation(img):
|
||||
hsv_image = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||||
saturation_values = hsv_image[:, :, 1]
|
||||
average_saturation = np.mean(saturation_values)
|
||||
normalized_saturation = normalize_value(average_saturation, 0, 255)
|
||||
return normalized_saturation
|
||||
|
||||
|
||||
def modify_saturation(img, saturation_factor):
|
||||
hsv_image = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||||
saturation_values = hsv_image[:, :, 1]
|
||||
current_avg_saturation = np.mean(saturation_values)
|
||||
target_saturation = denormalize_value(saturation_factor, 0, 255)
|
||||
if current_avg_saturation == 0:
|
||||
saturation_adjustment = 1
|
||||
else:
|
||||
saturation_adjustment = target_saturation / current_avg_saturation
|
||||
hsv_image[:, :, 1] = np.clip(saturation_values * saturation_adjustment, 0, 255).astype(np.uint8)
|
||||
modified_img = cv.cvtColor(hsv_image, cv.COLOR_HSV2BGR)
|
||||
return modified_img
|
||||
|
||||
|
||||
def get_sharpness(img):
|
||||
grayscale_image = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
||||
laplacian_var = cv.Laplacian(grayscale_image, cv.CV_64F).var()
|
||||
# Adjusted max value for Laplacian variance based on typical image sharpness
|
||||
normalized_sharpness = normalize_value(laplacian_var, 0, 1000)
|
||||
return normalized_sharpness
|
||||
|
||||
|
||||
def modify_sharpness(img, sharpen_factor):
|
||||
if sharpen_factor < 0:
|
||||
raise ValueError("sharpen_factor must be non-negative")
|
||||
sharpness_adjustment = denormalize_value(sharpen_factor, 0, 1000)
|
||||
sharpness_adjustment = (5 * sharpness_adjustment / 1000) - 1
|
||||
blurred_img = cv.GaussianBlur(img, (0, 0), 3)
|
||||
sharp_img = cv.addWeighted(img, 1 + sharpness_adjustment, blurred_img, -sharpness_adjustment, 0)
|
||||
return sharp_img
|
||||
|
||||
|
||||
def get_highlights(img):
|
||||
hsv_image = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||||
highlights = hsv_image[:, :, 2].astype(np.float32)
|
||||
highlighted_pixels = highlights[highlights > 200]
|
||||
if highlighted_pixels.size == 0:
|
||||
return 0
|
||||
average_highlights = np.mean(highlighted_pixels)
|
||||
normalized_highlights = normalize_value(average_highlights, 200, 255)
|
||||
return normalized_highlights
|
||||
|
||||
|
||||
def modify_highlights(img, highlights_factor):
|
||||
hsv_image = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||||
value_channel = hsv_image[:, :, 2]
|
||||
current_avg_highlights = np.mean(value_channel)
|
||||
target_highlights = denormalize_value(highlights_factor, 25, 255)
|
||||
if current_avg_highlights == 0:
|
||||
highlights_adjustment = 1
|
||||
else:
|
||||
highlights_adjustment = target_highlights / current_avg_highlights
|
||||
value_channel = np.clip(value_channel * highlights_adjustment, 0, 225)
|
||||
hsv_image[:, :, 2] = value_channel.astype(np.uint8)
|
||||
modified_img = cv.cvtColor(hsv_image, cv.COLOR_HSV2BGR)
|
||||
return modified_img
|
||||
|
||||
|
||||
def get_shadows(img):
|
||||
hsv_image = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||||
shadows = hsv_image[:, :, 2].astype(np.float32)
|
||||
shadowed_pixels = shadows[shadows < 50]
|
||||
if shadowed_pixels.size == 0:
|
||||
return 0
|
||||
average_shadows = np.mean(shadowed_pixels)
|
||||
normalized_shadows = normalize_value(average_shadows, 0, 50)
|
||||
return normalized_shadows
|
||||
|
||||
|
||||
def modify_shadows(img, shadows_factor):
|
||||
hsv_image = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||||
value_channel = hsv_image[:, :, 2]
|
||||
current_avg_shadows = np.mean(value_channel)
|
||||
target_shadows = denormalize_value(shadows_factor, 0, 225)
|
||||
if current_avg_shadows == 0:
|
||||
shadows_adjustment = 1
|
||||
else:
|
||||
shadows_adjustment = target_shadows / current_avg_shadows
|
||||
value_channel = np.clip(value_channel * shadows_adjustment, 0, 255)
|
||||
hsv_image[:, :, 2] = value_channel.astype(np.uint8)
|
||||
modified_img = cv.cvtColor(hsv_image, cv.COLOR_HSV2BGR)
|
||||
return modified_img
|
||||
|
||||
|
||||
def get_exposure(img):
|
||||
grayscale_image = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
||||
average_exposure = np.mean(grayscale_image)
|
||||
normalized_exposure = normalize_value(average_exposure, 0, 255)
|
||||
return normalized_exposure
|
||||
|
||||
|
||||
def modify_exposure(img, exposure_factor):
|
||||
hsv_image = cv.cvtColor(img, cv.COLOR_BGR2HSV)
|
||||
value_channel = hsv_image[:, :, 2]
|
||||
current_avg_exposure = np.mean(value_channel)
|
||||
target_exposure = denormalize_value(exposure_factor, 0, 255)
|
||||
if current_avg_exposure == 0:
|
||||
exposure_adjustment = 1
|
||||
else:
|
||||
exposure_adjustment = target_exposure / current_avg_exposure
|
||||
value_channel = np.clip(value_channel * exposure_adjustment, 0, 255)
|
||||
hsv_image[:, :, 2] = value_channel.astype(np.uint8)
|
||||
modified_img = cv.cvtColor(hsv_image, cv.COLOR_HSV2BGR)
|
||||
return modified_img
|
||||
|
||||
|
||||
# Image Full-Analyze Function
|
||||
def image_analysis(img_path, res_path):
|
||||
src = cv.imread(img_path)
|
||||
|
||||
# Get image parameter values
|
||||
WB_red, WB_green, WB_blue = get_white_balance(src)
|
||||
average_brightness = get_brightness(src)
|
||||
contrast = get_contrast(src)
|
||||
average_hue = get_hue(src)
|
||||
average_saturation = get_saturation(src)
|
||||
average_perceived_brightness = get_perceived_avg_brightness(src)
|
||||
average_sharpen = get_sharpness(src)
|
||||
average_highlights = get_highlights(src)
|
||||
average_shadow = get_shadows(src)
|
||||
average_temperature = get_color_temperature(src)
|
||||
average_noisy = get_noise(src)
|
||||
average_exposure = get_exposure(src)
|
||||
|
||||
# Store img parameters into a Pandas DataFrame
|
||||
results_df = pd.DataFrame({
|
||||
"File": [img_path],
|
||||
"contrast": [contrast],
|
||||
"WB_red": [WB_red],
|
||||
"WB_green": [WB_green],
|
||||
"WB_blue": [WB_blue],
|
||||
"avg_brightness": [average_brightness],
|
||||
"avg_perceived_brightness": [average_perceived_brightness],
|
||||
"avg_hue": [average_hue],
|
||||
"avg_saturation": [average_saturation],
|
||||
"avg_sharpness": [average_sharpen],
|
||||
"avg_highlights": [average_highlights],
|
||||
"avg_shadow": [average_shadow],
|
||||
"avg_temperature": [average_temperature],
|
||||
"avg_noisy": [average_noisy],
|
||||
"avg_exposure": [average_exposure]
|
||||
})
|
||||
|
||||
# Convert DataFrame to JSON and save to file
|
||||
results_dict = results_df.to_dict(orient='records')[0]
|
||||
results_json = json.dumps(results_dict, indent=4)
|
||||
with open(res_path, 'a') as f:
|
||||
f.write(results_json)
|
||||
f.write('\n')
|
||||
|
||||
# Successfully executed notification
|
||||
print("Image " + img_path + " parameters have been extracted to " + res_path)
|
||||
|
||||
|
||||
# Dataset Update Function
|
||||
def process_images_in_folders(root_dir):
|
||||
for sub_folder in os.listdir(root_dir):
|
||||
sub_folder_path = os.path.join(root_dir, sub_folder)
|
||||
if os.path.isdir(sub_folder_path):
|
||||
# Define the output JSON file path
|
||||
output_file = os.path.join(sub_folder_path, f"{sub_folder}_result.json")
|
||||
if not os.path.exists(output_file):
|
||||
with open(output_file, 'w'):
|
||||
pass
|
||||
# Gather all image file paths in the sub-folder
|
||||
image_paths = [os.path.join(sub_folder_path, f) for f in os.listdir(sub_folder_path) if
|
||||
os.path.isfile(os.path.join(sub_folder_path, f)) and f != f"{sub_folder}_result.json"]
|
||||
if not image_paths:
|
||||
print(f"Dataset {sub_folder} is up to date.")
|
||||
else:
|
||||
# Perform image analysis and save the results
|
||||
for image_path in image_paths:
|
||||
image_analysis(image_path, output_file)
|
||||
os.remove(image_path)
|
||||
print(f"Processed {sub_folder} and saved results to {output_file}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Define the root dataset directory
|
||||
dataset_dir = ".\dataset"
|
||||
# Process the images in each sub-folder
|
||||
process_images_in_folders(dataset_dir)
|
||||
198
crawler.py
Normal file
198
crawler.py
Normal file
@ -0,0 +1,198 @@
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
import customtkinter as ctk
|
||||
from tkinter import messagebox, StringVar
|
||||
|
||||
# Unsplash API
|
||||
api_url = "https://api.unsplash.com/search/photos"
|
||||
default_access_key = ['-7mzLi8x261zMRKU-Rw_Qz_vIvaYvf2sWHV1l1pO5ZE',
|
||||
'y0TRzwdfxA4PpZBPw3Lr8a6bYyAugiKItS7b_80UTA4']
|
||||
|
||||
# Crawl parameter
|
||||
categories = {
|
||||
'animals': 'animals',
|
||||
'people': 'people',
|
||||
'food-drink': 'food drink',
|
||||
'nature': 'nature',
|
||||
'architecture-interior': 'architecture interior'
|
||||
}
|
||||
|
||||
# Save directory and changelog file
|
||||
os.makedirs('dataset', exist_ok=True)
|
||||
changelog_path = os.path.join('dataset', 'changelog.txt')
|
||||
|
||||
# Initialize page and images_downloaded
|
||||
category_state = {category: {'page': 1, 'images_downloaded': 0} for category in categories}
|
||||
|
||||
# Check if changelog file exists and read the state if it does
|
||||
if os.path.exists(changelog_path):
|
||||
with open(changelog_path, 'r') as changelog_file:
|
||||
for line in changelog_file:
|
||||
category, page, images_downloaded = line.strip().split(',')
|
||||
category_state[category]['page'] = int(page)
|
||||
category_state[category]['images_downloaded'] = int(images_downloaded)
|
||||
# Create changelog file
|
||||
else:
|
||||
with open(changelog_path, 'w') as changelog_file:
|
||||
for category in categories:
|
||||
changelog_file.write(f'{category},1,0\n')
|
||||
|
||||
# Create directories for categories if they don't exist
|
||||
for category in categories:
|
||||
os.makedirs(os.path.join('dataset', category), exist_ok=True)
|
||||
|
||||
|
||||
# Crawling
|
||||
def crawl_images(category, query, download_num, access_key):
|
||||
if download_num == 0:
|
||||
print("No need to update: " + category)
|
||||
return
|
||||
|
||||
global category_state
|
||||
img_index = category_state[category]['images_downloaded']
|
||||
cur_page = category_state[category]['page']
|
||||
flag = 0
|
||||
if img_index % 10 != 0:
|
||||
cur_page -= 1
|
||||
real_img_index = int((cur_page - 1) * 10)
|
||||
|
||||
try:
|
||||
while download_num > 0:
|
||||
params = {
|
||||
'query': query,
|
||||
'client_id': access_key,
|
||||
'per_page': 50,
|
||||
'page': cur_page
|
||||
}
|
||||
response = requests.get(api_url, params=params)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
images = data['results']
|
||||
print(f'Found {len(images)} images for category {category} on page {cur_page}')
|
||||
|
||||
for idx, img in enumerate(images):
|
||||
if download_num <= 0:
|
||||
break
|
||||
img_url = img['urls']['regular']
|
||||
# Ignore crawled images
|
||||
if flag == 0 and img_index > real_img_index:
|
||||
real_img_index += 1
|
||||
print(img_index)
|
||||
print(real_img_index)
|
||||
continue
|
||||
else:
|
||||
flag = 1
|
||||
# Crawls and Store images
|
||||
try:
|
||||
img_data = requests.get(img_url).content
|
||||
img_filename = os.path.join('dataset', category, f'{category}_{img_index + 1}.jpg')
|
||||
with open(img_filename, 'wb') as handler:
|
||||
handler.write(img_data)
|
||||
print(f'Downloaded {img_filename}')
|
||||
img_index += 1
|
||||
download_num -= 1
|
||||
except Exception as e:
|
||||
messagebox.showerror("Download Error", f'Failed to download {img_url}: {e}')
|
||||
break
|
||||
|
||||
cur_page += 1
|
||||
time.sleep(1)
|
||||
else:
|
||||
messagebox.showerror("Download Error",
|
||||
f'Failed to fetch images for category {category}: {response.status_code}. '
|
||||
f'It can be caused by the API limit. Please try again after 1hr.')
|
||||
break
|
||||
finally:
|
||||
# Update changelog file after finishing each category
|
||||
category_state[category]['page'] = cur_page
|
||||
category_state[category]['images_downloaded'] = img_index
|
||||
with open(changelog_path, 'w') as new_changelog_file:
|
||||
for cat in categories:
|
||||
new_changelog_file.write(
|
||||
f'{cat},{category_state[cat]["page"]},{category_state[cat]["images_downloaded"]}\n')
|
||||
|
||||
print("Completed: " + category)
|
||||
|
||||
|
||||
def start_crawling():
|
||||
start_button.configure(text="Processing", fg_color="red")
|
||||
start_button.update()
|
||||
|
||||
access_key = access_key_combo.get()
|
||||
try:
|
||||
total_images_to_download = {
|
||||
'animals': int(animals_var.get()),
|
||||
'people': int(people_var.get()),
|
||||
'food-drink': int(food_drink_var.get()),
|
||||
'nature': int(nature_var.get()),
|
||||
'architecture-interior': int(architecture_interior_var.get())
|
||||
}
|
||||
|
||||
# Validate input values
|
||||
total_count = sum(total_images_to_download.values())
|
||||
if total_count <= 0:
|
||||
messagebox.showerror("Invalid input", "Total number of images to download must be greater than 0.")
|
||||
return
|
||||
if total_count > 500:
|
||||
messagebox.showerror("Invalid input", "The total number of images to download must not exceed 500.")
|
||||
return
|
||||
|
||||
# Start Crawling
|
||||
for curr_category, curr_query in categories.items():
|
||||
print(curr_category)
|
||||
crawl_images(curr_category, curr_query, total_images_to_download[curr_category], access_key)
|
||||
|
||||
except ValueError:
|
||||
messagebox.showerror("Invalid input", "Please enter valid integer values.")
|
||||
return
|
||||
|
||||
finally:
|
||||
start_button.configure(text="Start Crawling", fg_color="#3b8ed0")
|
||||
start_button.update()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Create the main window
|
||||
root = ctk.CTk()
|
||||
root.title("Image Crawler")
|
||||
root.iconbitmap('./icon/icon.ico')
|
||||
|
||||
# Create StringVar instances for each entry
|
||||
animals_var = StringVar(value="0")
|
||||
people_var = StringVar(value="0")
|
||||
food_drink_var = StringVar(value="0")
|
||||
nature_var = StringVar(value="0")
|
||||
architecture_interior_var = StringVar(value="0")
|
||||
|
||||
# Create and place widgets
|
||||
ctk.CTkLabel(root, text="Unsplash API Access Key:").grid(row=0, column=0, padx=10, pady=5)
|
||||
access_key_combo = ctk.CTkComboBox(root, values=default_access_key, width=200)
|
||||
access_key_combo.grid(row=0, column=1, padx=10, pady=5)
|
||||
|
||||
ctk.CTkLabel(root, text="Animals:").grid(row=1, column=0, padx=10, pady=5)
|
||||
animals_entry = ctk.CTkEntry(root, textvariable=animals_var, width=100)
|
||||
animals_entry.grid(row=1, column=1, padx=10, pady=5)
|
||||
|
||||
ctk.CTkLabel(root, text="People:").grid(row=2, column=0, padx=10, pady=5)
|
||||
people_entry = ctk.CTkEntry(root, textvariable=people_var, width=100)
|
||||
people_entry.grid(row=2, column=1, padx=10, pady=5)
|
||||
|
||||
ctk.CTkLabel(root, text="Food & Drink:").grid(row=3, column=0, padx=10, pady=5)
|
||||
food_drink_entry = ctk.CTkEntry(root, textvariable=food_drink_var, width=100)
|
||||
food_drink_entry.grid(row=3, column=1, padx=10, pady=5)
|
||||
|
||||
ctk.CTkLabel(root, text="Nature:").grid(row=4, column=0, padx=10, pady=5)
|
||||
nature_entry = ctk.CTkEntry(root, textvariable=nature_var, width=100)
|
||||
nature_entry.grid(row=4, column=1, padx=10, pady=5)
|
||||
|
||||
ctk.CTkLabel(root, text="Architecture & Interior:").grid(row=5, column=0, padx=10, pady=5)
|
||||
architecture_interior_entry = ctk.CTkEntry(root, textvariable=architecture_interior_var, width=100)
|
||||
architecture_interior_entry.grid(row=5, column=1, padx=10, pady=5)
|
||||
|
||||
start_button = ctk.CTkButton(root, text="Start Crawling", command=start_crawling)
|
||||
start_button.grid(row=6, column=0, columnspan=2, padx=10, pady=20)
|
||||
|
||||
# Start Window
|
||||
root.mainloop()
|
||||
@ -0,0 +1,204 @@
|
||||
{
|
||||
"File": ".\\dataset\\animal_customize_cat_filter\\animals_1.jpg",
|
||||
"contrast": 55.64813561220745,
|
||||
"WB_red": 48.79154254513347,
|
||||
"WB_green": 49.67338099850525,
|
||||
"WB_blue": 55.445994623748476,
|
||||
"avg_brightness": 50.97080509158396,
|
||||
"avg_perceived_brightness": 54.94651189179376,
|
||||
"avg_hue": 21.001697315332095,
|
||||
"avg_saturation": 21.201111262406197,
|
||||
"avg_sharpness": 76.98735656410744,
|
||||
"avg_highlights": 48.31948020241477,
|
||||
"avg_shadow": 62.753570556640625,
|
||||
"avg_temperature": 50.0209656113776,
|
||||
"avg_noisy": 27.933181797500207,
|
||||
"avg_exposure": 52.13299745824255
|
||||
}
|
||||
{
|
||||
"File": ".\\dataset\\animal_customize_cat_filter\\animals_131.jpg",
|
||||
"contrast": 25.575424890091362,
|
||||
"WB_red": 49.63131523072817,
|
||||
"WB_green": 50.173830506769654,
|
||||
"WB_blue": 50.07804686065642,
|
||||
"avg_brightness": 72.4241426611797,
|
||||
"avg_perceived_brightness": 76.07639141047365,
|
||||
"avg_hue": 51.81023188380808,
|
||||
"avg_saturation": 9.948864278221578,
|
||||
"avg_sharpness": 14.309853830366915,
|
||||
"avg_highlights": 38.10055819424716,
|
||||
"avg_shadow": 67.8313217163086,
|
||||
"avg_temperature": 50.001496629194015,
|
||||
"avg_noisy": 12.837860336987037,
|
||||
"avg_exposure": 72.36611948277253
|
||||
}
|
||||
{
|
||||
"File": ".\\dataset\\animal_customize_cat_filter\\animals_224.jpg",
|
||||
"contrast": 39.1498678028228,
|
||||
"WB_red": 47.29628466204506,
|
||||
"WB_green": 48.95383763655193,
|
||||
"WB_blue": 67.38785796866736,
|
||||
"avg_brightness": 22.083168963363427,
|
||||
"avg_perceived_brightness": 25.93154401678367,
|
||||
"avg_hue": 14.333392085788802,
|
||||
"avg_saturation": 40.62220787720666,
|
||||
"avg_sharpness": 40.72319300374671,
|
||||
"avg_highlights": 36.70202081853693,
|
||||
"avg_shadow": 45.966304779052734,
|
||||
"avg_temperature": 50.055875026241594,
|
||||
"avg_noisy": 19.6516983480836,
|
||||
"avg_exposure": 23.488490814707227
|
||||
}
|
||||
{
|
||||
"File": ".\\dataset\\animal_customize_cat_filter\\animals_230.jpg",
|
||||
"contrast": 54.56607631557097,
|
||||
"WB_red": 46.28730494004484,
|
||||
"WB_green": 50.419968991233524,
|
||||
"WB_blue": 60.01410652544925,
|
||||
"avg_brightness": 33.361521199362684,
|
||||
"avg_perceived_brightness": 39.675422119333504,
|
||||
"avg_hue": 25.318248680358046,
|
||||
"avg_saturation": 44.76403236739782,
|
||||
"avg_sharpness": 5.690056793933645,
|
||||
"avg_highlights": 47.28293678977273,
|
||||
"avg_shadow": 46.007930755615234,
|
||||
"avg_temperature": 50.042525097995146,
|
||||
"avg_noisy": 27.390030464286603,
|
||||
"avg_exposure": 34.45230614808433
|
||||
}
|
||||
{
|
||||
"File": ".\\dataset\\animal_customize_cat_filter\\animals_359.jpg",
|
||||
"contrast": 35.961782934957434,
|
||||
"WB_red": 47.56014736554645,
|
||||
"WB_green": 50.310628718141814,
|
||||
"WB_blue": 55.69945451520712,
|
||||
"avg_brightness": 66.65015263993114,
|
||||
"avg_perceived_brightness": 71.47217279916082,
|
||||
"avg_hue": 12.380429253741637,
|
||||
"avg_saturation": 16.6490866819979,
|
||||
"avg_sharpness": 11.862548218358471,
|
||||
"avg_highlights": 16.92868319424716,
|
||||
"avg_shadow": 58.21811294555664,
|
||||
"avg_temperature": 50.02610191778624,
|
||||
"avg_noisy": 18.051404767351183,
|
||||
"avg_exposure": 67.94562656338255
|
||||
}
|
||||
{
|
||||
"File": ".\\dataset\\animal_customize_cat_filter\\animals_360.jpg",
|
||||
"contrast": 55.386977042781304,
|
||||
"WB_red": 48.705522563381415,
|
||||
"WB_green": 50.33593358687364,
|
||||
"WB_blue": 51.83252641949891,
|
||||
"avg_brightness": 83.1379568446562,
|
||||
"avg_perceived_brightness": 85.97517945924808,
|
||||
"avg_hue": 6.733860813818914,
|
||||
"avg_saturation": 11.420945158690257,
|
||||
"avg_sharpness": 26.167174272129426,
|
||||
"avg_highlights": 94.18936989524148,
|
||||
"avg_shadow": 60.86544418334961,
|
||||
"avg_temperature": 50.01037550206043,
|
||||
"avg_noisy": 27.80209043716081,
|
||||
"avg_exposure": 83.56836060635081
|
||||
}
|
||||
{
|
||||
"File": ".\\dataset\\animal_customize_cat_filter\\animals_405.jpg",
|
||||
"contrast": 40.250392191647165,
|
||||
"WB_red": 48.1303710472222,
|
||||
"WB_green": 50.52159860131352,
|
||||
"WB_blue": 52.48379378916028,
|
||||
"avg_brightness": 74.13857565202578,
|
||||
"avg_perceived_brightness": 79.14378118359825,
|
||||
"avg_hue": 28.333157914147336,
|
||||
"avg_saturation": 13.746253345302456,
|
||||
"avg_sharpness": 41.256679482885595,
|
||||
"avg_highlights": 52.15839732776988,
|
||||
"avg_shadow": 63.26673126220703,
|
||||
"avg_temperature": 50.01445226480612,
|
||||
"avg_noisy": 20.204118433454262,
|
||||
"avg_exposure": 74.6062108313833
|
||||
}
|
||||
{
|
||||
"File": ".\\dataset\\animal_customize_cat_filter\\animals_457.jpg",
|
||||
"contrast": 50.118629081160215,
|
||||
"WB_red": 48.59653016399227,
|
||||
"WB_green": 50.154565418912696,
|
||||
"WB_blue": 53.17862784572248,
|
||||
"avg_brightness": 45.11712712703588,
|
||||
"avg_perceived_brightness": 49.89648023060663,
|
||||
"avg_hue": 25.375147066844317,
|
||||
"avg_saturation": 23.12239987683694,
|
||||
"avg_sharpness": 57.9748659930197,
|
||||
"avg_highlights": 25.001609108664773,
|
||||
"avg_shadow": 58.68082046508789,
|
||||
"avg_temperature": 50.01494469539881,
|
||||
"avg_noisy": 25.15758636230787,
|
||||
"avg_exposure": 45.63392210544014
|
||||
}
|
||||
{
|
||||
"File": ".\\dataset\\animal_customize_cat_filter\\animals_472.jpg",
|
||||
"contrast": 37.2433756644065,
|
||||
"WB_red": 56.72581769880244,
|
||||
"WB_green": 46.27228585692787,
|
||||
"WB_blue": 55.78318321562122,
|
||||
"avg_brightness": 17.128579009705703,
|
||||
"avg_perceived_brightness": 25.33156634096554,
|
||||
"avg_hue": 38.82791521475317,
|
||||
"avg_saturation": 77.63395336912063,
|
||||
"avg_sharpness": 18.86898592879606,
|
||||
"avg_highlights": 28.46865567294034,
|
||||
"avg_shadow": 56.69831085205078,
|
||||
"avg_temperature": 49.997394516595314,
|
||||
"avg_noisy": 18.694714058996205,
|
||||
"avg_exposure": 17.97759646977387
|
||||
}
|
||||
{
|
||||
"File": ".\\dataset\\animal_customize_cat_filter\\animals_486.jpg",
|
||||
"contrast": 53.733759767356325,
|
||||
"WB_red": 44.10767460546046,
|
||||
"WB_green": 52.090758016192964,
|
||||
"WB_blue": 58.33425965926219,
|
||||
"avg_brightness": 45.32772320003958,
|
||||
"avg_perceived_brightness": 53.68828772280673,
|
||||
"avg_hue": 16.45729986230206,
|
||||
"avg_saturation": 40.52376807786208,
|
||||
"avg_sharpness": 36.691715126649115,
|
||||
"avg_highlights": 35.14423717151988,
|
||||
"avg_shadow": 46.660186767578125,
|
||||
"avg_temperature": 50.0468484400274,
|
||||
"avg_noisy": 26.97224019694749,
|
||||
"avg_exposure": 46.086897511036184
|
||||
}
|
||||
{
|
||||
"File": ".\\dataset\\animal_customize_cat_filter\\animals_57.jpg",
|
||||
"contrast": 56.9853220854583,
|
||||
"WB_red": 58.8159180716154,
|
||||
"WB_green": 46.67058087480473,
|
||||
"WB_blue": 48.67295490245864,
|
||||
"avg_brightness": 36.35092047183147,
|
||||
"avg_perceived_brightness": 41.29948985538431,
|
||||
"avg_hue": 39.25816627200343,
|
||||
"avg_saturation": 42.936078162403504,
|
||||
"avg_sharpness": 21.978832384315563,
|
||||
"avg_highlights": 49.4837812943892,
|
||||
"avg_shadow": 34.542518615722656,
|
||||
"avg_temperature": 49.969960705781396,
|
||||
"avg_noisy": 28.60439696838691,
|
||||
"avg_exposure": 36.98316948635878
|
||||
}
|
||||
{
|
||||
"File": ".\\dataset\\animal_customize_cat_filter\\animals_76.jpg",
|
||||
"contrast": 35.59637450704945,
|
||||
"WB_red": 45.67628053796047,
|
||||
"WB_green": 51.19210783079706,
|
||||
"WB_blue": 57.38785140417896,
|
||||
"avg_brightness": 47.177013905699454,
|
||||
"avg_perceived_brightness": 53.02168540484323,
|
||||
"avg_hue": 18.52858530473366,
|
||||
"avg_saturation": 27.550239158306212,
|
||||
"avg_sharpness": 64.15141294937406,
|
||||
"avg_highlights": 12.703718705610795,
|
||||
"avg_shadow": 75.76203155517578,
|
||||
"avg_temperature": 50.037958380459116,
|
||||
"avg_noisy": 17.867984066283643,
|
||||
"avg_exposure": 48.096659628643415
|
||||
}
|
||||
8687
dataset/animals/animals_result.json
Normal file
8687
dataset/animals/animals_result.json
Normal file
File diff suppressed because it is too large
Load Diff
8670
dataset/architecture-interior/architecture-interior_result.json
Normal file
8670
dataset/architecture-interior/architecture-interior_result.json
Normal file
File diff suppressed because it is too large
Load Diff
5
dataset/changelog.txt
Normal file
5
dataset/changelog.txt
Normal file
@ -0,0 +1,5 @@
|
||||
animals,54,511
|
||||
people,51,500
|
||||
food-drink,52,510
|
||||
nature,51,500
|
||||
architecture-interior,52,510
|
||||
8670
dataset/food-drink/food-drink_result.json
Normal file
8670
dataset/food-drink/food-drink_result.json
Normal file
File diff suppressed because it is too large
Load Diff
8500
dataset/nature/nature_result.json
Normal file
8500
dataset/nature/nature_result.json
Normal file
File diff suppressed because it is too large
Load Diff
8500
dataset/people/people_result.json
Normal file
8500
dataset/people/people_result.json
Normal file
File diff suppressed because it is too large
Load Diff
74
dataset_analysis.py
Normal file
74
dataset_analysis.py
Normal file
@ -0,0 +1,74 @@
|
||||
import pandas as pd
|
||||
import json
|
||||
|
||||
|
||||
def dataset_desc(file_path):
|
||||
# Stores a list of JSON objects
|
||||
json_objects = []
|
||||
|
||||
# A string that builds a single JSON object
|
||||
current_json_str = ""
|
||||
|
||||
# Split different image data in json file
|
||||
with open(file_path, 'r') as file:
|
||||
for line in file:
|
||||
# Removes whitespace at the beginning and end of a line
|
||||
line = line.strip()
|
||||
# If the line begins with '{' , it means that a new JSON object starts
|
||||
if line.startswith("{"):
|
||||
current_json_str = line
|
||||
# If this line ends with '}' , it means that a JSON object ends
|
||||
elif line.endswith("}"):
|
||||
current_json_str += line
|
||||
try:
|
||||
# Parsing JSON strings
|
||||
json_obj = json.loads(current_json_str)
|
||||
json_objects.append(json_obj)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error decoding JSON: {e}")
|
||||
current_json_str = ""
|
||||
# If it is part of a JSON object, it is added to the current JSON string
|
||||
else:
|
||||
current_json_str += line
|
||||
|
||||
# Convert a list of JSON objects to pandas DataFrame
|
||||
df = pd.DataFrame(json_objects)
|
||||
|
||||
# Filter out numeric columns
|
||||
numeric_df = df.select_dtypes(include='number')
|
||||
|
||||
# Create a list to collect data for all numeric columns
|
||||
boxplot_data = []
|
||||
|
||||
# Create an empty DataFrame to store statistics
|
||||
stats_df = pd.DataFrame(columns=[
|
||||
'Parameter', 'Min', 'Max', 'Mean', 'Median', 'Mode', 'Range',
|
||||
'Q1', 'Q3', 'IQR', 'Variance', 'Standard Deviation', 'Skewness'
|
||||
])
|
||||
|
||||
# Generate statistics and draw graphs
|
||||
for column in numeric_df.columns:
|
||||
data = numeric_df[column].dropna()
|
||||
boxplot_data.append(numeric_df[column].dropna())
|
||||
|
||||
# Computational statistics
|
||||
stats = {
|
||||
'Parameter': column,
|
||||
'Min': data.min(),
|
||||
'Max': data.max(),
|
||||
'Mean': data.mean(),
|
||||
'Median': data.median(),
|
||||
'Mode': data.mode().values[0] if not data.mode().empty else float('nan'),
|
||||
'Range': data.max() - data.min(),
|
||||
'Q1': data.quantile(0.25),
|
||||
'Q3': data.quantile(0.75),
|
||||
'IQR': data.quantile(0.75) - data.quantile(0.25),
|
||||
'Variance': data.var(),
|
||||
'Standard Deviation': data.std(),
|
||||
'Skewness': data.skew()
|
||||
}
|
||||
|
||||
# Append the statistics to the stats DataFrame using concat
|
||||
stats_df = pd.concat([stats_df, pd.DataFrame([stats])], ignore_index=True)
|
||||
|
||||
return stats_df, df
|
||||
102
dataset_predict.py
Normal file
102
dataset_predict.py
Normal file
@ -0,0 +1,102 @@
|
||||
import json
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
from sklearn.neural_network import MLPRegressor
|
||||
|
||||
|
||||
def remove_outliers(df, threshold=3):
|
||||
z_scores = np.abs((df - df.mean()) / df.std())
|
||||
df_cleaned = df[(z_scores < threshold).all(axis=1)]
|
||||
return df_cleaned
|
||||
|
||||
|
||||
def optimal_val_predict(file_path, contrast, WB_red, WB_green, WB_blue, avg_brightness, avg_perceived_brightness,
|
||||
avg_hue, avg_saturation, avg_sharpness, avg_highlights, avg_shadow, avg_temperature,
|
||||
avg_noisy, avg_exposure):
|
||||
# Stores a list of JSON objects
|
||||
json_objects = []
|
||||
|
||||
# A string that builds a single JSON object
|
||||
current_json_str = ""
|
||||
|
||||
# Split different image data in json file
|
||||
with open(file_path, 'r') as file:
|
||||
for line in file:
|
||||
# Removes whitespace at the beginning and end of a line
|
||||
line = line.strip()
|
||||
# If the line begins with { , it means that a new JSON object starts
|
||||
if line.startswith("{"):
|
||||
current_json_str = line
|
||||
# If this line ends with } , it means that a JSON object ends
|
||||
elif line.endswith("}"):
|
||||
current_json_str += line
|
||||
try:
|
||||
# Parsing JSON strings
|
||||
json_obj = json.loads(current_json_str)
|
||||
json_objects.append(json_obj)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error decoding JSON: {e}")
|
||||
current_json_str = ""
|
||||
# If it is part of a JSON object, it is added to the current JSON string
|
||||
else:
|
||||
current_json_str += line
|
||||
|
||||
# Convert good image feature data to DataFrame
|
||||
excellent_df = pd.DataFrame(json_objects).select_dtypes(include='number')
|
||||
|
||||
# Remove outliers from your DataFrame
|
||||
cleaned_df = remove_outliers(excellent_df)
|
||||
|
||||
# Use excellent image feature data as training data
|
||||
df = cleaned_df.copy()
|
||||
target_df = cleaned_df
|
||||
|
||||
# Standardized feature
|
||||
scaler_X = StandardScaler()
|
||||
scaler_y = StandardScaler()
|
||||
X_scaled = scaler_X.fit_transform(df)
|
||||
y_scaled = scaler_y.fit_transform(target_df)
|
||||
|
||||
# Initialize and train the neural network model
|
||||
model = MLPRegressor(hidden_layer_sizes=(100, 100), max_iter=1000, random_state=42)
|
||||
model.fit(X_scaled, y_scaled)
|
||||
|
||||
# Feature data for new images
|
||||
new_data = {
|
||||
"contrast": [contrast],
|
||||
"WB_red": [WB_red],
|
||||
"WB_green": [WB_green],
|
||||
"WB_blue": [WB_blue],
|
||||
"avg_brightness": [avg_brightness],
|
||||
"avg_perceived_brightness": [avg_perceived_brightness],
|
||||
"avg_hue": [avg_hue],
|
||||
"avg_saturation": [avg_saturation],
|
||||
"avg_sharpness": [avg_sharpness],
|
||||
"avg_highlights": [avg_highlights],
|
||||
"avg_shadow": [avg_shadow],
|
||||
"avg_temperature": [avg_temperature],
|
||||
"avg_noisy": [avg_noisy],
|
||||
"avg_exposure": [avg_exposure]
|
||||
}
|
||||
|
||||
# convert to DataFrame
|
||||
new_df = pd.DataFrame(new_data)
|
||||
|
||||
# Standardize feature data for new images
|
||||
new_X_scaled = scaler_X.transform(new_df)
|
||||
|
||||
# Use the model to predict optimized features
|
||||
optimized_features_scaled = model.predict(new_X_scaled)
|
||||
|
||||
# The features after anti-standardization optimization
|
||||
optimized_features = scaler_y.inverse_transform(optimized_features_scaled)
|
||||
|
||||
# # Calculate the mean square error between the predicted value and the true value (Mean Squared Error, MSE)
|
||||
# import numpy as np
|
||||
# mse = np.mean((y_scaled - model.predict(X_scaled)) ** 2)
|
||||
# print("Mean Squared Error (MSE):", mse)
|
||||
|
||||
# Displays optimized features
|
||||
optimized_df = pd.DataFrame(optimized_features, columns=target_df.columns)
|
||||
return optimized_df
|
||||
199
export_func.py
Normal file
199
export_func.py
Normal file
@ -0,0 +1,199 @@
|
||||
import os
|
||||
import sys
|
||||
import tkinter as tk
|
||||
from PIL import Image, ImageEnhance, ImageTk
|
||||
from tkinter import filedialog, messagebox
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
def save_image(image, output_image_path):
|
||||
if image.mode == 'RGBA':
|
||||
image = image.convert('RGB')
|
||||
ext = os.path.splitext(output_image_path)[1].lower()
|
||||
if ext in ['.jpg', '.jpeg']:
|
||||
image.save(output_image_path, format='JPEG')
|
||||
elif ext == '.png':
|
||||
image.save(output_image_path, format='PNG')
|
||||
else:
|
||||
image.save(output_image_path)
|
||||
root.quit()
|
||||
root.destroy()
|
||||
|
||||
|
||||
def compress_image(image, quality=85):
|
||||
buffer = BytesIO()
|
||||
if image.mode == 'RGBA':
|
||||
image = image.convert('RGB')
|
||||
image.save(buffer, format="JPEG", quality=quality)
|
||||
buffer.seek(0)
|
||||
return Image.open(buffer)
|
||||
|
||||
|
||||
def adjust_sharpness(image, sharpness_factor=2.0):
|
||||
enhancer = ImageEnhance.Sharpness(image)
|
||||
return enhancer.enhance(sharpness_factor)
|
||||
|
||||
|
||||
def resize_image(image, max_width, max_height):
|
||||
ratio = min(max_width / image.width, max_height / image.height)
|
||||
new_width = int(image.width * ratio)
|
||||
new_height = int(image.height * ratio)
|
||||
return image.resize((new_width, new_height), Image.LANCZOS), new_width, new_height
|
||||
|
||||
|
||||
def update_image():
|
||||
global img_tk
|
||||
img = processed_image.copy()
|
||||
|
||||
# Apply compression
|
||||
quality = compress_scale.get()
|
||||
img = compress_image(img, int(quality))
|
||||
|
||||
# Apply sharpness
|
||||
sharpness = clarity_scale.get() / 50.0 # scale from 0-100 to 0-2
|
||||
img = adjust_sharpness(img, sharpness)
|
||||
|
||||
# Update the displayed image
|
||||
img_tk = ImageTk.PhotoImage(img)
|
||||
canvas_export.delete("current_image") # Delete the previous image
|
||||
canvas_export.create_image(0, 0, anchor=tk.NW, image=img_tk, tags="current_image") # Add the new image with a tag
|
||||
|
||||
# Draw crop rectangle
|
||||
canvas_export.delete("rect")
|
||||
canvas_export.create_rectangle(start_x, start_y, end_x, end_y, outline='red', tag="rect")
|
||||
|
||||
|
||||
def on_canvas_click(event):
|
||||
global start_x, start_y
|
||||
start_x = event.x
|
||||
start_y = event.y
|
||||
canvas_export.delete("rect")
|
||||
|
||||
|
||||
def on_canvas_drag(event):
|
||||
global start_x, start_y, end_x, end_y
|
||||
end_x = event.x
|
||||
end_y = event.y
|
||||
canvas_export.delete("rect")
|
||||
canvas_export.create_rectangle(start_x, start_y, end_x, end_y, outline='red', tag="rect")
|
||||
|
||||
|
||||
def on_canvas_release(event):
|
||||
global end_x, end_y
|
||||
end_x = event.x
|
||||
end_y = event.y
|
||||
|
||||
|
||||
def crop_and_process_image(filename):
|
||||
if processed_image is None:
|
||||
messagebox.showwarning("Warning", "Please select an image first!")
|
||||
return
|
||||
|
||||
# Get the coordinates of the selected region
|
||||
x1 = min(start_x, end_x)
|
||||
y1 = min(start_y, end_y)
|
||||
x2 = max(start_x, end_x)
|
||||
y2 = max(start_y, end_y)
|
||||
|
||||
cropped_img = processed_image.crop((x1, y1, x2, y2))
|
||||
compress_quality = compress_scale.get()
|
||||
sharpness_factor = clarity_scale.get() / 50.0 # scale from 0-100 to 0-2
|
||||
cropped_img = compress_image(cropped_img, int(compress_quality))
|
||||
cropped_img = adjust_sharpness(cropped_img, sharpness_factor)
|
||||
|
||||
base_name, extension = os.path.splitext(filename)
|
||||
suggested_filename = base_name + "_processed" + extension
|
||||
|
||||
output_path = filedialog.asksaveasfilename(
|
||||
title="Choose a file",
|
||||
initialfile=suggested_filename,
|
||||
filetypes=(("JPEG files", "*.jpg;*.jpeg"), ("PNG files", "*.png"), ("All files", "*.*"))
|
||||
)
|
||||
|
||||
if output_path:
|
||||
save_image(cropped_img, output_path)
|
||||
messagebox.showinfo("Info", f"Image processed and saved to {output_path}")
|
||||
|
||||
|
||||
def create_export_window(filename):
|
||||
global original_image, processed_image, compress_scale, clarity_scale, canvas_export, start_x, start_y, end_x, end_y, canvas_frame, root, img_tk
|
||||
root = tk.Tk()
|
||||
root.title("Image Export Window")
|
||||
root.configure(bg="#004c66")
|
||||
root.iconbitmap('./icon/icon.ico')
|
||||
|
||||
# Convert the image buffer to a PIL Image
|
||||
original_image = Image.open(filename)
|
||||
|
||||
# Resize the image to fit the canvas
|
||||
processed_image, new_width, new_height = resize_image(original_image, 800, 600)
|
||||
|
||||
# Initialize cropping variables
|
||||
start_x = 0
|
||||
start_y = 0
|
||||
end_x = new_width
|
||||
end_y = new_height
|
||||
|
||||
# Set the root window geometry based on the image size
|
||||
root.geometry(f"{new_width + 20}x{new_height + 200}")
|
||||
|
||||
# Set up the canvas frame
|
||||
canvas_frame = tk.Frame(root, bg='white')
|
||||
canvas_frame.pack(pady=10, fill=tk.BOTH, expand=True)
|
||||
|
||||
# Set up the canvas
|
||||
canvas_export = tk.Canvas(canvas_frame, width=new_width, height=new_height, bg='white')
|
||||
canvas_export.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Bind canvas events
|
||||
canvas_export.bind("<Button-1>", on_canvas_click)
|
||||
canvas_export.bind("<B1-Motion>", on_canvas_drag)
|
||||
canvas_export.bind("<ButtonRelease-1>", on_canvas_release)
|
||||
|
||||
# Display the initial image
|
||||
img_tk = ImageTk.PhotoImage(processed_image)
|
||||
canvas_export.image = img_tk
|
||||
canvas_export.create_image(0, 0, anchor=tk.NW, image=img_tk)
|
||||
|
||||
# Set up compression scale
|
||||
compress_label = tk.Label(root, text="Compression", bg="yellow")
|
||||
compress_label.pack(anchor='w', padx=10)
|
||||
|
||||
compress_scale = tk.Scale(root, from_=0, to=100, orient=tk.HORIZONTAL, bg="yellow",
|
||||
command=lambda x: update_image())
|
||||
compress_scale.set(85) # Set default value
|
||||
compress_scale.pack(fill='x', padx=10)
|
||||
|
||||
# Set up clarity scale
|
||||
clarity_label = tk.Label(root, text="Clarity", bg="yellow")
|
||||
clarity_label.pack(anchor='w', padx=10)
|
||||
|
||||
clarity_scale = tk.Scale(root, from_=0, to=100, orient=tk.HORIZONTAL, bg="yellow", command=lambda x: update_image())
|
||||
clarity_scale.set(50) # Set default value
|
||||
clarity_scale.pack(fill='x', padx=10)
|
||||
|
||||
# Set up button frame
|
||||
button_frame = tk.Frame(root, bg="#004c66")
|
||||
button_frame.pack(pady=10)
|
||||
|
||||
# Set up confirm button
|
||||
confirm_button = tk.Button(button_frame, text="Confirm", command=lambda: crop_and_process_image(filename),
|
||||
bg="yellow")
|
||||
confirm_button.pack(side='left', padx=10)
|
||||
|
||||
# Set up cancel button
|
||||
cancel_button = tk.Button(button_frame, text="Cancel", command=root.destroy, bg="yellow")
|
||||
cancel_button.pack(side='right', padx=10)
|
||||
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
# Assuming the image file path is passed directly for testing
|
||||
img_path = filedialog.askopenfilename(title="Select an Image", filetypes=(("Image files", "*.jpg;*.jpeg;*.png"), ("All files", "*.*")))
|
||||
if img_path:
|
||||
create_export_window(img_path)
|
||||
else:
|
||||
tmp_file_path = sys.argv[1]
|
||||
create_export_window(tmp_file_path)
|
||||
BIN
icon/edit.png
Normal file
BIN
icon/edit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
BIN
icon/icon.ico
Normal file
BIN
icon/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
BIN
icon/select.png
Normal file
BIN
icon/select.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
633
main.py
Normal file
633
main.py
Normal file
@ -0,0 +1,633 @@
|
||||
import os
|
||||
import uuid
|
||||
import tempfile
|
||||
import subprocess
|
||||
import seaborn as sns
|
||||
import cv2 as cv
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog, messagebox
|
||||
import customtkinter as ctk
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from PIL import Image
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
import analyze_func as img_func
|
||||
import dataset_predict as predict
|
||||
import dataset_analysis
|
||||
|
||||
|
||||
def raise_frame(next_frame):
|
||||
next_frame.tkraise()
|
||||
|
||||
|
||||
def on_close():
|
||||
root.quit()
|
||||
root.destroy()
|
||||
|
||||
|
||||
def select_img():
|
||||
# Select image file
|
||||
filename = filedialog.askopenfilename(
|
||||
title="Select An Image",
|
||||
filetypes=(("Image", "*.png"), ("Image", "*.jpg"), ("Image", "*.jpeg"))
|
||||
)
|
||||
if filename:
|
||||
# Read image file
|
||||
global img_name
|
||||
global original_img
|
||||
img_name = os.path.basename(filename)
|
||||
img_pil = Image.open(filename)
|
||||
img = cv.cvtColor(np.array(img_pil), cv.COLOR_RGB2BGR)
|
||||
original_img = img
|
||||
if img is not None:
|
||||
raise_frame(f_main)
|
||||
plot_img_hist(img, f_main_left_top, 7, 3)
|
||||
display_image(img, f_main_right_top)
|
||||
# Get image parameter values
|
||||
WB_red, WB_green, WB_blue = img_func.get_white_balance(img)
|
||||
average_brightness = img_func.get_brightness(img)
|
||||
contrast = img_func.get_contrast(img)
|
||||
average_hue = img_func.get_hue(img)
|
||||
average_saturation = img_func.get_saturation(img)
|
||||
average_perceived_brightness = img_func.get_perceived_avg_brightness(img)
|
||||
average_sharpen = img_func.get_sharpness(img)
|
||||
average_highlights = img_func.get_highlights(img)
|
||||
average_shadow = img_func.get_shadows(img)
|
||||
average_temperature = img_func.get_color_temperature(img)
|
||||
average_noisy = img_func.get_noise(img)
|
||||
average_exposure = img_func.get_exposure(img)
|
||||
# Display image parameter values
|
||||
parameters = {
|
||||
"Red": WB_red,
|
||||
"Green": WB_green,
|
||||
"Blue": WB_blue,
|
||||
"Contrast": contrast,
|
||||
"Brightness": average_brightness,
|
||||
"Perceived Brightness": average_perceived_brightness,
|
||||
"Hue": average_hue,
|
||||
"Saturation": average_saturation,
|
||||
"Sharpness": average_sharpen,
|
||||
"Highlight": average_highlights,
|
||||
"Shadow": average_shadow,
|
||||
"Temperature": average_temperature,
|
||||
"Noise": average_noisy,
|
||||
"Exposure": average_exposure
|
||||
}
|
||||
display_parameters(f_main_left_bottom, parameters, img)
|
||||
|
||||
|
||||
def display_parameters(frame, parameters, img):
|
||||
# Clear previous content
|
||||
for widget in frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
global img_buffer
|
||||
img_buffer = img
|
||||
|
||||
# Create a scrollable canvas
|
||||
canvas = ctk.CTkCanvas(frame, highlightthickness=0, bg="white")
|
||||
scrollbar = ctk.CTkScrollbar(frame, fg_color="white", command=canvas.yview)
|
||||
scrollable_frame = ctk.CTkFrame(canvas, fg_color="white")
|
||||
scrollable_frame.bind("<Configure>", lambda e=None: canvas.configure(scrollregion=canvas.bbox("all")))
|
||||
|
||||
# Function to change background color on focus
|
||||
def on_focus_in(event):
|
||||
event.widget.configure(foreground="black")
|
||||
|
||||
def on_focus_out(event):
|
||||
event.widget.configure(foreground="gray")
|
||||
|
||||
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
||||
canvas.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
# Pack the canvas and scrollbar
|
||||
canvas.pack(side=ctk.LEFT, fill=ctk.BOTH, expand=True)
|
||||
scrollbar.pack(side=ctk.LEFT, fill=ctk.Y)
|
||||
|
||||
# Adding parameters to the scrollable frame
|
||||
for i, (param, value) in enumerate(parameters.items()):
|
||||
# Parameter's name label
|
||||
label = ctk.CTkLabel(scrollable_frame, text=f"{param}", anchor="w", fg_color="white")
|
||||
label.grid(row=i, column=0, padx=10, pady=5, sticky="w")
|
||||
|
||||
# Parameter's value textbox
|
||||
value_str = f"{value}"
|
||||
textbox = ctk.CTkTextbox(scrollable_frame, height=1, width=160, wrap="none", fg_color="#f0f0f0",
|
||||
text_color="gray")
|
||||
textbox.insert("1.0", value_str)
|
||||
textbox.grid(row=i, column=1, padx=10, pady=5, sticky="w")
|
||||
|
||||
# Bind focus in and focus out events
|
||||
textbox.bind("<FocusIn>", on_focus_in)
|
||||
textbox.bind("<FocusOut>", on_focus_out)
|
||||
textbox.bind("<FocusOut>",
|
||||
lambda event=None, param1=param, textbox1=textbox: param_updator(param1, textbox1))
|
||||
|
||||
scrollable_frame.update_idletasks()
|
||||
|
||||
|
||||
def param_updator(param, textbox):
|
||||
# Get new parameter value
|
||||
try:
|
||||
new_val = float(textbox.get("1.0", "end-1c"))
|
||||
except ValueError:
|
||||
messagebox.showerror("Invalid input", f"Invalid value for {param}: {textbox.get('1.0', 'end-1c')}")
|
||||
return
|
||||
# Pass it to its corresponding 'modify' function
|
||||
global img_buffer
|
||||
modified_img = None
|
||||
if param == "Red":
|
||||
modified_img = img_func.modify_white_balance(img_buffer, new_val, -1, -1)
|
||||
elif param == "Green":
|
||||
modified_img = img_func.modify_white_balance(img_buffer, -1, new_val, -1)
|
||||
elif param == "Blue":
|
||||
modified_img = img_func.modify_white_balance(img_buffer, -1, -1, new_val)
|
||||
elif param == "Contrast":
|
||||
modified_img = img_func.modify_contrast(img_buffer, new_val)
|
||||
elif param == "Brightness":
|
||||
modified_img = img_func.modify_brightness(img_buffer, new_val)
|
||||
elif param == "Perceived Brightness":
|
||||
modified_img = img_func.modify_perceived_avg_brightness(img_buffer, new_val)
|
||||
elif param == "Hue":
|
||||
modified_img = img_func.modify_hue(img_buffer, new_val)
|
||||
elif param == "Saturation":
|
||||
modified_img = img_func.modify_saturation(img_buffer, new_val)
|
||||
elif param == "Sharpness":
|
||||
modified_img = img_func.modify_sharpness(img_buffer, new_val)
|
||||
elif param == "Highlight":
|
||||
modified_img = img_func.modify_highlights(img_buffer, new_val)
|
||||
elif param == "Shadow":
|
||||
modified_img = img_func.modify_shadows(img_buffer, new_val)
|
||||
elif param == "Temperature":
|
||||
modified_img = img_func.modify_color_temperature(img_buffer, new_val)
|
||||
if param == "Noise":
|
||||
modified_img = img_func.modify_noise(img_buffer, new_val)
|
||||
elif param == "Exposure":
|
||||
modified_img = img_func.modify_exposure(img_buffer, new_val)
|
||||
else:
|
||||
pass
|
||||
# Update img_buffer & Refresh the window
|
||||
if modified_img is not None:
|
||||
img_buffer = modified_img
|
||||
plot_img_hist(img_buffer, f_main_left_top, 7, 3)
|
||||
display_image(img_buffer, f_main_right_top)
|
||||
|
||||
|
||||
def display_image(img, frame):
|
||||
# Clear previous image
|
||||
for widget in frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
# Convert the image to RGB format
|
||||
img_rgb = cv.cvtColor(img, cv.COLOR_BGR2RGB)
|
||||
img_pil = Image.fromarray(img_rgb)
|
||||
|
||||
# Get the height and width of the image
|
||||
height, width = img.shape[:2]
|
||||
|
||||
# Calculate the maximum width and height
|
||||
if height > width:
|
||||
max_width = 380
|
||||
else:
|
||||
max_width = 500
|
||||
img_ratio = width / height
|
||||
max_height = int(max_width / img_ratio)
|
||||
|
||||
# Display image in the frame
|
||||
img_tk = ctk.CTkImage(img_pil, size=(max_width, max_height))
|
||||
label = ctk.CTkLabel(frame, image=img_tk, text="")
|
||||
label.image = img_tk
|
||||
label.pack(padx=10, pady=10, fill='x')
|
||||
|
||||
|
||||
def plot_img_hist(img, frame, width, height):
|
||||
# Clear previous plots
|
||||
for widget in frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
plt.figure(figsize=(width, height))
|
||||
|
||||
# Plot grayscale hist
|
||||
plt.hist(img.ravel(), 256, [0, 256])
|
||||
# Plot color hist
|
||||
color = ('blue', 'green', 'red')
|
||||
for i, color in enumerate(color):
|
||||
hist = cv.calcHist([img], [i], None, [256], [0, 256])
|
||||
plt.plot(hist, color=color)
|
||||
|
||||
plt.xlabel("Bins")
|
||||
plt.ylabel("Pixel Number")
|
||||
|
||||
# Hide the top and right spines
|
||||
ax = plt.gca()
|
||||
ax.spines['top'].set_visible(False)
|
||||
ax.spines['right'].set_visible(False)
|
||||
|
||||
# Get the current figure & draw & close
|
||||
figure = plt.gcf()
|
||||
canvas = FigureCanvasTkAgg(figure, master=frame)
|
||||
canvas.draw()
|
||||
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
|
||||
plt.close()
|
||||
|
||||
|
||||
def revert_to_original():
|
||||
# Revert to original_img
|
||||
global original_img
|
||||
plot_img_hist(original_img, f_main_left_top, 7, 3)
|
||||
display_image(original_img, f_main_right_top)
|
||||
# Get image parameter values
|
||||
WB_red, WB_green, WB_blue = img_func.get_white_balance(original_img)
|
||||
average_brightness = img_func.get_brightness(original_img)
|
||||
contrast = img_func.get_contrast(original_img)
|
||||
average_hue = img_func.get_hue(original_img)
|
||||
average_saturation = img_func.get_saturation(original_img)
|
||||
average_perceived_brightness = img_func.get_perceived_avg_brightness(original_img)
|
||||
average_sharpen = img_func.get_sharpness(original_img)
|
||||
average_highlights = img_func.get_highlights(original_img)
|
||||
average_shadow = img_func.get_shadows(original_img)
|
||||
average_temperature = img_func.get_color_temperature(original_img)
|
||||
average_noisy = img_func.get_noise(original_img)
|
||||
average_exposure = img_func.get_exposure(original_img)
|
||||
# Display image parameter values
|
||||
parameters = {
|
||||
"Red": WB_red,
|
||||
"Green": WB_green,
|
||||
"Blue": WB_blue,
|
||||
"Contrast": contrast,
|
||||
"Brightness": average_brightness,
|
||||
"Perceived Brightness": average_perceived_brightness,
|
||||
"Hue": average_hue,
|
||||
"Saturation": average_saturation,
|
||||
"Sharpness": average_sharpen,
|
||||
"Highlight": average_highlights,
|
||||
"Shadow": average_shadow,
|
||||
"Temperature": average_temperature,
|
||||
"Noise": average_noisy,
|
||||
"Exposure": average_exposure
|
||||
}
|
||||
display_parameters(f_main_left_bottom, parameters, original_img)
|
||||
|
||||
|
||||
def auto_optimize():
|
||||
# Get current image
|
||||
global img_buffer
|
||||
# Check filter data availability
|
||||
json_dataset_path = ".\\dataset\\" + combobox.get() + "\\" + combobox.get() + "_result.json"
|
||||
if not os.path.exists(json_dataset_path):
|
||||
tk.messagebox.showinfo("Cannot find dataset",
|
||||
"Cannot find json data file for this dataset. Check & Update Dataset.")
|
||||
return
|
||||
# Get image parameter values
|
||||
WB_red, WB_green, WB_blue = img_func.get_white_balance(img_buffer)
|
||||
avg_brightness = img_func.get_brightness(img_buffer)
|
||||
contrast = img_func.get_contrast(img_buffer)
|
||||
avg_hue = img_func.get_hue(img_buffer)
|
||||
avg_saturation = img_func.get_saturation(img_buffer)
|
||||
avg_perceived_brightness = img_func.get_perceived_avg_brightness(img_buffer)
|
||||
avg_sharpness = img_func.get_sharpness(img_buffer)
|
||||
avg_highlights = img_func.get_highlights(img_buffer)
|
||||
avg_shadow = img_func.get_shadows(img_buffer)
|
||||
avg_temperature = img_func.get_color_temperature(img_buffer)
|
||||
avg_noisy = img_func.get_noise(img_buffer)
|
||||
avg_exposure = img_func.get_exposure(img_buffer)
|
||||
|
||||
# Pass the parameters & Predict optimal values
|
||||
optimal_vals = predict.optimal_val_predict(json_dataset_path, contrast, WB_red, WB_green, WB_blue,
|
||||
avg_brightness, avg_perceived_brightness, avg_hue,
|
||||
avg_saturation, avg_sharpness, avg_highlights, avg_shadow,
|
||||
avg_temperature, avg_noisy, avg_exposure)
|
||||
|
||||
# Ensure optimal_vals contains scalar values
|
||||
optimal_vals = {key: val.item() if isinstance(val, pd.Series) else val for key, val in optimal_vals.items()}
|
||||
|
||||
# Modify image with optimal values
|
||||
modified_img = img_func.modify_hue(img_buffer, optimal_vals["avg_hue"])
|
||||
modified_img = img_func.modify_sharpness(modified_img, optimal_vals["avg_sharpness"])
|
||||
modified_img = img_func.modify_color_temperature(modified_img, optimal_vals["avg_temperature"])
|
||||
modified_img = img_func.modify_exposure(modified_img, optimal_vals["avg_exposure"])
|
||||
modified_img = img_func.modify_white_balance(modified_img, optimal_vals["WB_red"],
|
||||
optimal_vals["WB_green"], optimal_vals["WB_blue"])
|
||||
modified_img = img_func.modify_contrast(modified_img, optimal_vals["contrast"])
|
||||
modified_img = img_func.modify_saturation(modified_img, optimal_vals["avg_saturation"])
|
||||
modified_img = img_func.modify_highlights(modified_img, optimal_vals["avg_highlights"])
|
||||
modified_img = img_func.modify_noise(modified_img, optimal_vals["avg_noisy"])
|
||||
modified_img = img_func.modify_brightness(modified_img, optimal_vals["avg_brightness"])
|
||||
modified_img = img_func.modify_shadows(modified_img, optimal_vals["avg_shadow"])
|
||||
modified_img = img_func.modify_perceived_avg_brightness(modified_img, optimal_vals["avg_perceived_brightness"])
|
||||
|
||||
# Update Window
|
||||
if modified_img is not None:
|
||||
img_buffer = modified_img
|
||||
plot_img_hist(img_buffer, f_main_left_top, 7, 3)
|
||||
display_image(img_buffer, f_main_right_top)
|
||||
# Display image parameter values
|
||||
parameters = {
|
||||
"Red": WB_red,
|
||||
"Green": WB_green,
|
||||
"Blue": WB_blue,
|
||||
"Contrast": contrast,
|
||||
"Brightness": avg_brightness,
|
||||
"Perceived Brightness": avg_perceived_brightness,
|
||||
"Hue": avg_hue,
|
||||
"Saturation": avg_saturation,
|
||||
"Sharpness": avg_sharpness,
|
||||
"Highlight": avg_highlights,
|
||||
"Shadow": avg_shadow,
|
||||
"Temperature": avg_temperature,
|
||||
"Noise": avg_noisy,
|
||||
"Exposure": avg_exposure
|
||||
}
|
||||
display_parameters(f_main_left_bottom, parameters, img_buffer)
|
||||
|
||||
|
||||
def export_img():
|
||||
if img_buffer is not None:
|
||||
# Create a temporary address & Pass the temporarily stored image file
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Encode the original image name using UTF-8
|
||||
if all(ord(char) < 128 for char in img_name):
|
||||
# If all characters in img_name are ASCII characters
|
||||
encoded_img_name = img_name.encode('utf-8')
|
||||
else:
|
||||
# If img_name contains non-ASCII characters
|
||||
name, extension = os.path.splitext(img_name)
|
||||
uuid_bytes = str(uuid.uuid4()).encode('utf-8')
|
||||
encoded_img_name = uuid_bytes + extension.encode('utf-8')
|
||||
encoded_img_name_str = encoded_img_name.decode('utf-8')
|
||||
tmp_file_path = os.path.join(temp_dir, encoded_img_name_str)
|
||||
cv.imwrite(tmp_file_path, img_buffer)
|
||||
# Start Export Window
|
||||
try:
|
||||
subprocess.run(['python', 'export_func.py', tmp_file_path], capture_output=True, text=True)
|
||||
except Exception as e:
|
||||
messagebox.showerror("Exception", "Error occurred: " + str(e))
|
||||
|
||||
|
||||
def dataset_select(value):
|
||||
# Load the dataset
|
||||
json_dataset_path = os.path.join(".\\dataset", value, f"{value}_result.json")
|
||||
if not os.path.exists(json_dataset_path):
|
||||
tk.messagebox.showinfo("Cannot find dataset",
|
||||
"Cannot find json data file for this dataset. Check & Update Dataset.")
|
||||
return
|
||||
stats, original_stats = dataset_analysis.dataset_desc(json_dataset_path)
|
||||
|
||||
# Create a combobox to select the parameter
|
||||
parameter_label = ctk.CTkLabel(f_dataset, text="Select Parameter:")
|
||||
parameter_label.pack(padx=20, pady=10, anchor="w")
|
||||
|
||||
# Clear previous widgets
|
||||
for widget in f_dataset.winfo_children():
|
||||
if widget != home and widget != update and widget != crawler and widget != combobox_m:
|
||||
widget.destroy()
|
||||
|
||||
# Display the box plot of all numeric columns
|
||||
plt.figure(figsize=(18, 4))
|
||||
plt.xticks(fontsize=8)
|
||||
sns.boxplot(data=original_stats, color='blue')
|
||||
plt.ylabel('Values')
|
||||
plt.grid(True)
|
||||
plt.tight_layout()
|
||||
boxplot_canvas = FigureCanvasTkAgg(plt.gcf(), master=f_dataset)
|
||||
boxplot_canvas.draw()
|
||||
boxplot_canvas.get_tk_widget().pack()
|
||||
plt.close()
|
||||
|
||||
# Update & Pack parameter_combobox
|
||||
global parameter_combobox
|
||||
parameter_combobox = ctk.CTkComboBox(f_dataset, values=stats['Parameter'].tolist(),
|
||||
command=lambda param: display_parameter_stats(param, stats,
|
||||
parameter_combobox))
|
||||
parameter_combobox.pack(padx=20, pady=10, fill="x")
|
||||
|
||||
# Initial display of the first parameter's stats
|
||||
if not stats.empty:
|
||||
display_parameter_stats(stats['Parameter'].iloc[0], stats, parameter_combobox)
|
||||
|
||||
|
||||
def display_parameter_stats(param, stats, parameter_combobox):
|
||||
# Find the row corresponding to the selected parameter
|
||||
param_stats = stats[stats['Parameter'] == param]
|
||||
if param_stats.empty:
|
||||
return
|
||||
|
||||
# Extract the parameter stats
|
||||
param_values = param_stats.iloc[0].to_dict()
|
||||
for widget in f_dataset.winfo_children():
|
||||
if isinstance(widget, (ctk.CTkCanvas, ctk.CTkScrollbar)) and widget != parameter_combobox:
|
||||
widget.destroy()
|
||||
|
||||
# Create a canvas and a scrollbar
|
||||
canvas = ctk.CTkCanvas(f_dataset, bg="white", highlightthickness=0)
|
||||
scrollbar = ctk.CTkScrollbar(f_dataset, fg_color="white", command=canvas.yview)
|
||||
scrollable_frame = ctk.CTkFrame(canvas, fg_color="white")
|
||||
scrollable_frame.bind(
|
||||
"<Configure>",
|
||||
lambda e: canvas.configure(
|
||||
scrollregion=canvas.bbox("all")
|
||||
)
|
||||
)
|
||||
|
||||
# Place the scrollable frame in the canvas
|
||||
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
||||
canvas.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
# Pack the canvas and scrollbar
|
||||
canvas.pack(side=ctk.LEFT, fill=ctk.BOTH, expand=True)
|
||||
scrollbar.pack(side=ctk.RIGHT, fill=ctk.Y)
|
||||
|
||||
# Display the stats for the selected parameter inside the scrollable frame
|
||||
for key, value in param_values.items():
|
||||
if key == 'Parameter':
|
||||
continue
|
||||
# Parameter name label
|
||||
label = ctk.CTkLabel(scrollable_frame, text=f"{key}:", anchor="w", fg_color="white")
|
||||
label.pack(padx=20, pady=5, anchor="w")
|
||||
|
||||
# Parameter value textbox
|
||||
value_str = f"{value:.4f}" if isinstance(value, (int, float)) else f"{value}"
|
||||
textbox = ctk.CTkTextbox(scrollable_frame, height=1, width=160, wrap="none", fg_color="#f0f0f0",
|
||||
text_color="black")
|
||||
textbox.insert("1.0", value_str)
|
||||
textbox.pack(padx=20, pady=5, anchor="w")
|
||||
textbox.configure(state="disabled")
|
||||
|
||||
# Display the histogram for the parameter
|
||||
plt.figure(figsize=(6, 4))
|
||||
sns.histplot(data=stats[key].dropna(), kde=True, bins=30, color='blue')
|
||||
plt.ylabel('Frequency')
|
||||
plt.grid(True)
|
||||
plt.tight_layout()
|
||||
hist_canvas = FigureCanvasTkAgg(plt.gcf(), master=scrollable_frame)
|
||||
hist_canvas.draw()
|
||||
hist_canvas.get_tk_widget().pack()
|
||||
plt.close()
|
||||
|
||||
|
||||
def dataset_update():
|
||||
# Create waiting window
|
||||
update.configure(text="Processing", fg_color="red")
|
||||
update.update()
|
||||
# Start Tasks
|
||||
img_func.process_images_in_folders(root_dir)
|
||||
global subfolders
|
||||
subfolders = [sub_folder for sub_folder in os.listdir(root_dir) if
|
||||
os.path.isdir(os.path.join(root_dir, sub_folder))]
|
||||
combobox.configure(values=subfolders)
|
||||
combobox_m.configure(values=subfolders)
|
||||
combobox.update()
|
||||
combobox_m.update()
|
||||
dataset_select(combobox_m.get())
|
||||
# End Waiting Window
|
||||
update.configure(text="Update Dataset", fg_color="#3b8ed0")
|
||||
update.update()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Main window
|
||||
img_buffer = None
|
||||
bg_color = "white"
|
||||
root = ctk.CTk(bg_color)
|
||||
root.title("Image Optimizer")
|
||||
root.minsize(800, 500)
|
||||
root.iconbitmap('./icon/icon.ico')
|
||||
root.protocol("WM_DELETE_WINDOW", on_close)
|
||||
|
||||
# Frames
|
||||
f_wizard = ctk.CTkFrame(root, fg_color=bg_color)
|
||||
f_main = ctk.CTkFrame(root, fg_color=bg_color)
|
||||
f_dataset = ctk.CTkFrame(root, fg_color=bg_color)
|
||||
|
||||
for f in (f_wizard, f_main, f_dataset):
|
||||
f.grid(row=0, column=0, sticky="nsew")
|
||||
|
||||
# Configure grid to expand
|
||||
root.grid_rowconfigure(0, weight=1)
|
||||
root.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Predefined root directory
|
||||
root_dir = "./dataset"
|
||||
subfolders = [sub_folder for sub_folder in os.listdir(root_dir) if os.path.isdir(os.path.join(root_dir, sub_folder))]
|
||||
|
||||
# Wizard Interface
|
||||
f_wizard_bg_color = "white"
|
||||
|
||||
# Wizard Interface Left
|
||||
f_wizard_left = ctk.CTkFrame(f_wizard, fg_color=f_wizard_bg_color)
|
||||
f_wizard_left.grid(row=0, column=0, sticky="nsew")
|
||||
f_wizard_left.grid_rowconfigure(0, weight=1)
|
||||
f_wizard_left.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Wizard Interface Right
|
||||
f_wizard_right = ctk.CTkFrame(f_wizard, fg_color=f_wizard_bg_color)
|
||||
f_wizard_right.grid(row=0, column=1, sticky="nsew")
|
||||
f_wizard_right.grid_rowconfigure(0, weight=1)
|
||||
f_wizard_right.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Configure grid in f_wizard
|
||||
f_wizard.grid_rowconfigure(0, weight=1)
|
||||
f_wizard.grid_columnconfigure(0, weight=1)
|
||||
f_wizard.grid_columnconfigure(1, weight=1)
|
||||
|
||||
# Load Wizard Interface Icons
|
||||
icon_select = ctk.CTkImage(Image.open("./icon/select.png"), size=(100, 100))
|
||||
icon_db_man = ctk.CTkImage(Image.open("./icon/edit.png"), size=(100, 100))
|
||||
|
||||
# Buttons with icons and custom styles
|
||||
select_img_button = ctk.CTkButton(
|
||||
f_wizard_left, text="Select An Image", command=select_img,
|
||||
image=icon_select, compound="top", fg_color="transparent", text_color="black", hover_color="lightblue"
|
||||
)
|
||||
select_img_button.grid(row=0, column=0, padx=20, pady=20, sticky="nsew")
|
||||
|
||||
db_manage_button = ctk.CTkButton(
|
||||
f_wizard_right, text="Dataset Management", command=lambda: raise_frame(f_dataset),
|
||||
image=icon_db_man, compound="top", fg_color="transparent", text_color="black", hover_color="lightblue"
|
||||
)
|
||||
db_manage_button.grid(row=0, column=0, padx=20, pady=20, sticky="nsew")
|
||||
|
||||
# Main Interface
|
||||
f_main_bg_color = "white"
|
||||
|
||||
# Main Interface Left
|
||||
f_main_left = ctk.CTkFrame(f_main, fg_color=f_main_bg_color)
|
||||
f_main_left.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
|
||||
f_main_left.grid_rowconfigure(0, weight=1)
|
||||
f_main_left.grid_rowconfigure(1, weight=1) # Button
|
||||
f_main_left.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Main Interface Left Top
|
||||
f_main_left_top = ctk.CTkFrame(f_main_left, fg_color=f_main_bg_color)
|
||||
f_main_left_top.grid(row=0, column=0, sticky="nsew")
|
||||
f_main_left_top.grid_rowconfigure(0, weight=1)
|
||||
f_main_left_top.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Main Interface Left Bottom
|
||||
f_main_left_bottom = ctk.CTkFrame(f_main_left, fg_color=f_main_bg_color)
|
||||
f_main_left_bottom.grid(row=1, column=0, sticky="nsew") # Button
|
||||
f_main_left_bottom.grid_rowconfigure(0, weight=1)
|
||||
f_main_left_bottom.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Main Interface Right
|
||||
f_main_right = ctk.CTkFrame(f_main, fg_color=f_main_bg_color)
|
||||
f_main_right.grid(row=0, column=1, padx=10, pady=10, sticky="nsew")
|
||||
f_main_right.grid_rowconfigure(0, weight=1)
|
||||
f_main_right.grid_rowconfigure(1, weight=1)
|
||||
f_main_right.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Main Interface Right Top
|
||||
f_main_right_top = ctk.CTkFrame(f_main_right, fg_color=f_main_bg_color)
|
||||
f_main_right_top.grid(row=0, column=0, sticky="nsew")
|
||||
f_main_right_top.grid_rowconfigure(0, weight=1)
|
||||
f_main_right_top.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Main Interface Right Bottom
|
||||
f_main_right_bottom = ctk.CTkFrame(f_main_right, fg_color=f_main_bg_color)
|
||||
f_main_right_bottom.grid(row=1, column=0, sticky="nsew")
|
||||
f_main_right_bottom.grid_rowconfigure(0, weight=1)
|
||||
f_main_right_bottom.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Combobox to display sub-folder names
|
||||
combobox = ctk.CTkComboBox(f_main_right_bottom, values=subfolders)
|
||||
combobox.grid(row=1, column=0, padx=20, pady=10, sticky="ew")
|
||||
|
||||
auto_button = ctk.CTkButton(f_main_right_bottom, text="Auto Optimization", command=auto_optimize)
|
||||
auto_button.grid(row=2, column=0, padx=20, pady=10, sticky="ew")
|
||||
|
||||
revert_button = ctk.CTkButton(f_main_right_bottom, text="Revert To Original", command=lambda: revert_to_original())
|
||||
revert_button.grid(row=3, column=0, padx=20, pady=10, sticky="ew")
|
||||
|
||||
export_button = ctk.CTkButton(f_main_right_bottom, text="Export Image", command=export_img)
|
||||
export_button.grid(row=4, column=0, padx=20, pady=10, sticky="ew")
|
||||
|
||||
export_button = ctk.CTkButton(f_main_right_bottom, text="Discard", command=lambda: raise_frame(f_wizard))
|
||||
export_button.grid(row=5, column=0, padx=20, pady=10, sticky="ew")
|
||||
|
||||
# Configure grid in f_main
|
||||
f_main.grid_rowconfigure(0, weight=1)
|
||||
f_main.grid_columnconfigure(0, weight=1)
|
||||
f_main.grid_columnconfigure(1, weight=1)
|
||||
|
||||
# Database Management Interface
|
||||
# Back To Wizard Button
|
||||
home = ctk.CTkButton(f_dataset, text="Back To Wizard", command=lambda: raise_frame(f_wizard))
|
||||
home.pack(padx=20, pady=5, fill='x')
|
||||
|
||||
# Crawler Button
|
||||
crawler = ctk.CTkButton(f_dataset, text="Get More Images from Unsplash",
|
||||
command=lambda: subprocess.run(['python', 'crawler.py']))
|
||||
crawler.pack(padx=20, pady=5, fill='x')
|
||||
|
||||
# Dataset Update Button
|
||||
update = ctk.CTkButton(f_dataset, text="Update Dataset", command=dataset_update)
|
||||
update.pack(padx=20, pady=5, fill='x')
|
||||
|
||||
# Combobox to display sub-folder names
|
||||
combobox_m = ctk.CTkComboBox(f_dataset, values=subfolders, command=lambda value: dataset_select(value))
|
||||
combobox_m.pack(padx=20, pady=5, fill='x')
|
||||
dataset_select(combobox_m.get())
|
||||
|
||||
raise_frame(f_wizard)
|
||||
root.mainloop()
|
||||
9
requirements.txt
Normal file
9
requirements.txt
Normal file
@ -0,0 +1,9 @@
|
||||
customtkinter==5.2.2
|
||||
matplotlib==3.8.2
|
||||
numpy==1.24.3
|
||||
opencv_python==4.9.0.80
|
||||
pandas==1.5.3
|
||||
Pillow==10.3.0
|
||||
Requests==2.32.2
|
||||
scikit_learn==1.4.0
|
||||
seaborn==0.13.2
|
||||
Loading…
x
Reference in New Issue
Block a user