image-eval / modules /technical_metrics.py
VOIDER's picture
Upload 11 files
f89e218 verified
"""
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."""
@staticmethod
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()
@staticmethod
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)
@staticmethod
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)
@staticmethod
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])
@staticmethod
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)
@staticmethod
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
@staticmethod
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
@staticmethod
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