Python实现JPEG的8x8窗口DCT与量化后图像损坏问题求助
简易JPEG压缩流程(仅DCT+量化)失真问题解决
问题背景
尝试用Python实现不含霍夫曼编码的简易JPEG压缩,仅完成DCT(离散余弦变换)、量化及对应逆过程,但运行后量化图像损坏,逆处理后图像大部分变白、严重失真,测试图像为包含文字与图案的灰度图。
核心问题分析
- DCT调用方式错误:scipy的
dct默认是一维变换,直接对8x8矩阵调用仅处理每行,且未做正交归一化,不符合JPEG标准DCT要求。 - 量化数据保存丢失精度:量化结果为浮点数(或应取整的整数),直接用图像格式保存会被当作8位灰度值截断/错误映射,丢失原始数据。
- 缺少JPEG预处理步骤:未将像素值从
[0,255]转换到[-128,127],导致DCT后直流分量过大,量化数据范围异常。 - 逆DCT未做正交归一化:逆变换缺少
norm='ortho'参数,无法正确还原图像。
修正后的完整代码
import cv2 as cv from scipy.fftpack import dct, idct import numpy as np from PIL import Image # ---------------------- 压缩流程 ---------------------- image = cv.imread("test.png") gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) # 预处理:将像素值从[0,255]转为[-128,127],符合JPEG标准 arr = np.asarray(gray, float) - 128 image_quant = np.zeros_like(arr) # JPEG标准50%质量量化矩阵 qm = 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] ]) ws = 8 # 8x8分块大小 def quantize(mat, qm): # 量化后取整,符合JPEG整数量化标准 return np.round(mat / qm) # 分块执行DCT与量化 for r in range(0, arr.shape[0] - ws + 1, ws): for c in range(0, arr.shape[1] - ws + 1, ws): window = arr[r:r+ws, c:c+ws] # 二维正交DCT变换(转置实现二维处理) dct_block = dct(dct(window.T, norm='ortho').T, norm='ortho') image_quant[r:r+ws, c:c+ws] = quantize(dct_block, qm) # 用NPY格式保存量化数据,避免图像格式的精度丢失 np.save("quantized_data.npy", image_quant) # ---------------------- 解压缩流程 ---------------------- # 从NPY加载原始量化数据,而非读取损坏的PNG图像 image_quant = np.load("quantized_data.npy") image_dequant = np.zeros_like(image_quant) def dequantize(mat, qm): return mat * qm # 分块执行反量化与逆DCT for r in range(0, image_quant.shape[0] - ws + 1, ws): for c in range(0, image_quant.shape[1] - ws + 1, ws): window = image_quant[r:r+ws, c:c+ws] dequant_block = dequantize(window, qm) # 二维正交逆DCT变换 idct_block = idct(idct(dequant_block.T, norm='ortho').T, norm='ortho') image_dequant[r:r+ws, c:c+ws] = idct_block # 后处理:将像素值转回[0,255],并截断超出范围的数值 image_dequant = np.clip(image_dequant + 128, 0, 255).astype(np.uint8) # 保存并显示还原后的图像 result = Image.fromarray(image_dequant) result.save('test_dequant_dct.png') result.show()
关键修正说明
- 像素值偏移:添加
arr = np.asarray(gray, float) - 128,将像素映射到JPEG标准的[-128,127]范围,避免DCT直流分量过大。 - 二维正交DCT:通过转置实现二维正交变换,添加
norm='ortho'保证变换的可逆性。 - 量化数据保存:用
np.save保存量化数组,避免图像格式的精度损失。 - 量化取整:量化时执行
np.round,符合JPEG整数量化的标准做法。 - 后处理截断:还原后将像素值加128并截断到
[0,255],确保输出为合法的8位灰度图像。
内容的提问来源于stack exchange,提问作者markman8
相关产品推荐
相关产品推荐

