LPX55
add: jpeg compression estimator
4380e0f
import cv2 as cv
import numpy as np
from PIL import Image
DCT_SIZE = 8
TABLE_SIZE = DCT_SIZE ** 2
ZIG_ZAG = [
[0, 0],
[0, 1],
[1, 0],
[2, 0],
[1, 1],
[0, 2],
[0, 3],
[1, 2],
[2, 1],
[3, 0],
[4, 0],
[3, 1],
[2, 2],
[1, 3],
[0, 4],
[0, 5],
[1, 4],
[2, 3],
[3, 2],
[4, 1],
[5, 0],
[6, 0],
[5, 1],
[4, 2],
[3, 3],
[2, 4],
[1, 5],
[0, 6],
[0, 7],
[1, 6],
[2, 5],
[3, 4],
[4, 4],
[5, 3],
[6, 2],
[7, 1],
[7, 2],
[6, 3],
[5, 4],
[4, 5],
[3, 5],
[2, 6],
[1, 7],
[2, 7],
[3, 6],
[4, 5],
[5, 4],
[6, 3],
[7, 2],
[7, 3],
[6, 4],
[5, 5],
[4, 6],
[3, 7],
[4, 7],
[5, 6],
[6, 5],
[7, 4],
[7, 5],
[6, 6],
[5, 7],
[6, 7],
[7, 6],
[7, 7],
]
def compress_jpg(image: Image.Image, quality, color=True):
"""Compress a PIL image to JPEG format with specified quality.
Args:
image: Input PIL image (RGB format)
quality: JPEG compression quality (1-100)
color: Whether to preserve color (BGR format)
Returns:
np.ndarray: Decompressed image in BGR or grayscale format
"""
# Convert PIL image to OpenCV BGR format
img_np = np.array(image)
if color:
img_np = cv.cvtColor(img_np, cv.COLOR_RGB2BGR)
_, buffer = cv.imencode(".jpg", img_np, [cv.IMWRITE_JPEG_QUALITY, quality])
return cv.imdecode(buffer, cv.IMREAD_COLOR if color else cv.IMREAD_GRAYSCALE)
def loss_curve(image: Image.Image, qualities=tuple(range(1, 101)), normalize=True):
"""Calculate JPEG compression loss curve for quality estimation.
Args:
image: Input PIL image (RGB format)
qualities: Quality values to test (1-100)
normalize: Whether to normalize the output curve
Returns:
np.ndarray: Mean absolute difference values across quality levels
"""
# Convert input image to grayscale BGR for compression testing
img_np = np.array(image)
if len(img_np.shape) == 3:
x = cv.cvtColor(img_np, cv.COLOR_RGB2GRAY)
else:
x = img_np
c = np.array(
[cv.mean(cv.absdiff(compress_jpg(x, q, False), x))[0] for q in qualities]
)
if normalize:
c = cv.normalize(c, None, 0, 1, cv.NORM_MINMAX).flatten()
return c
def estimate_qf(image):
return np.argmin(loss_curve(image))
def get_tables(quality):
luma = np.array(
[
[16, 11, 10, 16, 24, 40, 51, 61],
[12, 12, 14, 19, 26, 58, 60, 55],
[14, 13, 16, 24, 40, 57, 69, 56],
[14, 17, 22, 29, 51, 87, 80, 62],
[18, 22, 37, 56, 68, 109, 103, 77],
[24, 35, 55, 64, 81, 104, 113, 92],
[49, 64, 78, 87, 103, 121, 120, 101],
[72, 92, 95, 98, 112, 100, 103, 99],
]
)
chroma = np.array(
[
[17, 18, 24, 47, 99, 99, 99, 99],
[18, 21, 26, 66, 99, 99, 99, 99],
[24, 26, 56, 99, 99, 99, 99, 99],
[47, 66, 99, 99, 99, 99, 99, 99],
[99, 99, 99, 99, 99, 99, 99, 99],
[99, 99, 99, 99, 99, 99, 99, 99],
[99, 99, 99, 99, 99, 99, 99, 99],
[99, 99, 99, 99, 99, 99, 99, 99],
]
)
quality = np.clip(quality, 1, 100)
if quality < 50:
quality = 5000 / quality
else:
quality = 200 - quality * 2
tables = np.concatenate((luma[:, :, np.newaxis], chroma[:, :, np.newaxis]), axis=2)
tables = (tables * quality + 50) / 100
return np.clip(tables, 1, 255).astype(int)