| 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 |
| """ |
| |
| 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 |
| """ |
| |
| 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) |
|
|