Spaces:
Sleeping
Sleeping
""" | |
Technical metrics for image quality assessment without using AI models. | |
These metrics evaluate basic technical aspects of images like sharpness, noise, etc. | |
""" | |
import numpy as np | |
import cv2 | |
from skimage.metrics import structural_similarity as ssim | |
from skimage.measure import shannon_entropy | |
from PIL import Image, ImageStat | |
class TechnicalMetrics: | |
"""Class for computing technical image quality metrics.""" | |
def calculate_sharpness(image_array): | |
""" | |
Calculate image sharpness using Laplacian variance. | |
Higher values indicate sharper images. | |
Args: | |
image_array: numpy array of the image | |
Returns: | |
float: sharpness score | |
""" | |
if len(image_array.shape) == 3: | |
gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY) | |
else: | |
gray = image_array | |
# Calculate variance of Laplacian | |
return cv2.Laplacian(gray, cv2.CV_64F).var() | |
def calculate_noise(image_array): | |
""" | |
Estimate image noise level. | |
Lower values indicate less noisy images. | |
Args: | |
image_array: numpy array of the image | |
Returns: | |
float: noise level | |
""" | |
if len(image_array.shape) == 3: | |
gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY) | |
else: | |
gray = image_array | |
# Estimate noise using median filter difference | |
denoised = cv2.medianBlur(gray, 5) | |
diff = cv2.absdiff(gray, denoised) | |
return np.mean(diff) | |
def calculate_contrast(image_array): | |
""" | |
Calculate image contrast. | |
Higher values indicate higher contrast. | |
Args: | |
image_array: numpy array of the image | |
Returns: | |
float: contrast score | |
""" | |
if len(image_array.shape) == 3: | |
gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY) | |
else: | |
gray = image_array | |
# Calculate standard deviation as a measure of contrast | |
return np.std(gray) | |
def calculate_saturation(image_array): | |
""" | |
Calculate color saturation. | |
Higher values indicate more saturated colors. | |
Args: | |
image_array: numpy array of the image | |
Returns: | |
float: saturation score | |
""" | |
if len(image_array.shape) != 3: | |
return 0.0 # Grayscale images have no saturation | |
# Convert to HSV and calculate mean saturation | |
hsv = cv2.cvtColor(image_array, cv2.COLOR_RGB2HSV) | |
return np.mean(hsv[:, :, 1]) | |
def calculate_entropy(image_array): | |
""" | |
Calculate image entropy as a measure of detail/complexity. | |
Higher values indicate more complex images. | |
Args: | |
image_array: numpy array of the image | |
Returns: | |
float: entropy score | |
""" | |
if len(image_array.shape) == 3: | |
gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY) | |
else: | |
gray = image_array | |
return shannon_entropy(gray) | |
def detect_compression_artifacts(image_array): | |
""" | |
Detect JPEG compression artifacts. | |
Higher values indicate more artifacts. | |
Args: | |
image_array: numpy array of the image | |
Returns: | |
float: artifact score | |
""" | |
if len(image_array.shape) == 3: | |
gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY) | |
else: | |
gray = image_array | |
# Apply edge detection to find blocky artifacts | |
edges = cv2.Canny(gray, 100, 200) | |
return np.mean(edges) / 255.0 | |
def calculate_dynamic_range(image_array): | |
""" | |
Calculate dynamic range of the image. | |
Higher values indicate better use of available intensity range. | |
Args: | |
image_array: numpy array of the image | |
Returns: | |
float: dynamic range score | |
""" | |
if len(image_array.shape) == 3: | |
gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY) | |
else: | |
gray = image_array | |
p1 = np.percentile(gray, 1) | |
p99 = np.percentile(gray, 99) | |
return (p99 - p1) / 255.0 | |
def calculate_all_metrics(image_path): | |
""" | |
Calculate all technical metrics for an image. | |
Args: | |
image_path: path to the image file | |
Returns: | |
dict: dictionary with all metric scores | |
""" | |
# Load image with PIL for metadata | |
pil_image = Image.open(image_path) | |
# Convert to numpy array for OpenCV processing | |
image_array = np.array(pil_image) | |
# Calculate all metrics | |
metrics = { | |
'sharpness': TechnicalMetrics.calculate_sharpness(image_array), | |
'noise': TechnicalMetrics.calculate_noise(image_array), | |
'contrast': TechnicalMetrics.calculate_contrast(image_array), | |
'saturation': TechnicalMetrics.calculate_saturation(image_array), | |
'entropy': TechnicalMetrics.calculate_entropy(image_array), | |
'compression_artifacts': TechnicalMetrics.detect_compression_artifacts(image_array), | |
'dynamic_range': TechnicalMetrics.calculate_dynamic_range(image_array), | |
'resolution': f"{pil_image.width}x{pil_image.height}", | |
'aspect_ratio': pil_image.width / pil_image.height if pil_image.height > 0 else 0, | |
'file_size_kb': pil_image.fp.tell() / 1024 if hasattr(pil_image.fp, 'tell') else 0, | |
} | |
return metrics | |