Python中用SVD分解图像波段数组后重构异常问题排查
问题分析与解决方案
你的代码里有几个关键问题导致图像显示异常,下面逐一说明并给出修正方案:
1. 不必要的图像保存与读取引入误差
你把拆分的RGB通道保存为JPG再读取,JPG是有损压缩格式,会破坏原始像素数据,直接从PIL通道转numpy数组即可,无需中间文件操作:
# 替换原来的sim保存再读取的代码 arsim = np.array(sim[0]) agsim = np.array(sim[1]) absim = np.array(sim[2])
2. 手动实现的矩阵列提取完全冗余
Numpy提供了简洁的索引方式提取矩阵列,ur[:, 0]直接就能拿到U矩阵的第一列,无需自己写extractCols和getcolumn函数。而且numpy.linalg.svd返回的U本身就是由左奇异向量作为列组成的矩阵,直接索引即可。
3. 外积实现冗余 + 数据类型未归一化
你的vectorMultiply虽然实现了外积,但Numpy原生的np.outer()可以直接完成这个操作,更高效且不易出错。另外,SVD计算得到的结果是浮点数,而PIL的Image.fromarray要求输入的灰度图数组是uint8类型(0-255范围),你需要将结果归一化到0-255区间并转换类型,否则会显示异常。
修正后的完整代码
import numpy.linalg as la import numpy as np from PIL import Image # 读取图像并拆分通道 im = Image.open('C:/Users/<user>/Desktop/img/April23.jpg') sim = im.split() # 直接将通道转成numpy数组,跳过保存读取步骤 arsim = np.array(sim[0]) agsim = np.array(sim[1]) absim = np.array(sim[2]) # 对每个通道做SVD分解 ur, sr, vhr = la.svd(arsim, full_matrices=False) ug, sg, vhg = la.svd(agsim, full_matrices=False) ub, sb, vhb = la.svd(absim, full_matrices=False) # 计算前k个dyad的和,这里k=1,你可以调整k值看不同的近似效果 k = 1 result_r = np.zeros_like(arsim, dtype=np.float64) result_g = np.zeros_like(agsim, dtype=np.float64) result_b = np.zeros_like(absim, dtype=np.float64) for i in range(k): # 计算单个dyad: s_i * u_i @ v_i^T,用np.outer实现外积 result_r += sr[i] * np.outer(ur[:, i], vhr[i]) result_g += sg[i] * np.outer(ug[:, i], vhg[i]) result_b += sb[i] * np.outer(ub[:, i], vhb[i]) # 将浮点数结果归一化到0-255,并转换为uint8类型 def normalize(arr): arr = arr - arr.min() arr = arr / arr.max() * 255 return arr.astype(np.uint8) r = Image.fromarray(normalize(result_r), "L") g = Image.fromarray(normalize(result_g), "L") b = Image.fromarray(normalize(result_b), "L") new_image = Image.merge("RGB", (r, g, b)) # 显示或保存结果 new_image.show() new_image.save('svd_approximation.jpg')
额外说明
- 调整
k的值可以控制近似程度:k越大,图像越接近原始图,数据量也越大;k越小,图像越模糊,但数据量越小。 la.svd的full_matrices=False参数可以返回更紧凑的分解结果,适合图像压缩场景,避免不必要的大矩阵。- 归一化步骤非常重要:SVD分解后的数值范围可能远超出0-255,直接转
uint8会导致截断失真,必须先归一化到图像的有效像素范围。
内容的提问来源于stack exchange,提问作者then999
相关产品推荐
相关产品推荐

