不想研究数学公式相关的插件了,下次一定
矩阵的奇异值分解
奇异值分解(Singular Value Decomposition,以下简称SVD)是一种对于矩阵进行分解的算法。是对矩阵的特征值分解的进一步演化,不需要要求分解的矩阵是方阵。
我们定义矩阵A为m×n的矩阵,它的SVD为:
A=UΣV^T
其中U是一个m×m的矩阵,Σ是m×n的矩阵,除了主对角线上的值均为0,主对角线上每个元素都称为奇异值。U和V均满足
U×U^T=I
V×V^T=I
对于奇异值,与特征值类似,在奇异值矩阵中也是由大到小排列,并且减小的非常快,在很多情况下,前10%甚至1%就占到了全部奇异值之和的99%以上
因此,我们可以用最大的k个奇异值和对应的左右奇向量近似描述矩阵。
基于奇异值分解的图像压缩
实现原理
由奇异值快速下降的状态,我们可以将图片按照矩阵形式读取数据(如读取灰度图,或者读取彩色图并对它的各个颜色通道分别奇异值分解),并对其进行奇异值分解,通过舍弃较小奇异值的方式将图像在较小损失的情况下进行压缩。
示例图片 1
示例图片 1
对其进行奇异值分解,并选择前0.5%,1%,5%,10%,20%,30%,50%,70%奇异值,观察压缩效果
压缩比例 | 结果图片 |
---|---|
0.5% | |
1% | |
5% | |
10% | |
20% | |
30% | |
50% | |
70% |
可以看出,仅选取前10%奇异值时,图片仍没有较严重的模糊。到仅选择5%奇异值时,图片才出现了明显噪点;仅选择1%、0.5%奇异值时,图片已经无法正确辨认。即使用奇异值分解可以将图片压缩到一个较高的压缩度。
在网络中,可以选择仅传输部分奇异值的方法,向用户传输压制后的图片来降低服务器压力的同时让用户能看到较清晰的图片。
实现代码
使用python语言模拟实现SVD进行图片压缩
def chan_compress(mat, rate):
# 对单通道奇异值分解
u, sigma, vt = np.linalg.svd(mat)
row = u.shape[0]
col = vt.shape[0]
# 初始化一个 row × col 的全0矩阵
new_mat = np.zeros((row, col))
for i in range(len(sigma)):
# 取前i个奇异值,依照近似公式重建矩阵
new_mat = new_mat + sigma[i] * np.dot(u[:, i].reshape(row, 1), vt[i, :].reshape(1, col))
if float(i) / len(sigma) > rate:
# 将超出范围的值回归到0-255区间内
new_mat[new_mat < 0] = 0
new_mat[new_mat > 255] = 255
break
# 返回值需取整,使用无符号8位整形
return np.rint(new_mat).astype(int)
def img_compress(img, rate):
r, g, b = img.split()
# R通道压缩
r_new = chan_compress(r, rate)
# G通道压缩
g_new = chan_compress(g, rate)
# B通道压缩
b_new = chan_compress(b, rate)
# 重建压缩后的图片
img_new = np.stack((r_new, g_new, b_new), 2)
Image.fromarray(np.uint8(img_new)).save("{}".format(rate)+"new.jpg")
基于奇异值分解的数据隐藏
实现原理
原理分析
将原图像和水印进行SVD后,将得到的水印奇异值加到原图像SVD得到的Σ矩阵中,从而实现水印的嵌入。
安全性分析
由奇异值的特性可以知道,隐藏在图片中的相关信息不会因为受到图片本身的压缩、扩张等情况影响到水印隐藏的效果。同时,我们可以通过打乱水印奇异值插入顺序的方法提升水印插入的安全性
实现代码
将sigma转化成m*n大小的对角矩阵
def diagonal_mat(m, n, sigma):
v = np.zeros((m, n))
for i in range(len(sigma)):
v[i, i] = sigma[i]
return v
不同大小的矩阵相加,默认加在左上角的部分(水印插入左上角)
def add_mat(a, b):
ma = a.shape[0]
na = a.shape[1]
mb = b.shape[0]
nb = b.shape[1]
c = np.zeros((max(ma, mb), max(na, nb)))
for i in range(max(ma, mb)):
for j in range(max(na, nb)):
if i < ma and j < na:
c[i, j] += a[i, j]
if i < mb and j < nb:
c[i, j] += b[i, j]
return c
截取矩阵中左上角m*n的部分(从左上角提取水印)
def extract_mat(a, m, n):
assert(m < a.shape[0] and n < a.shape[1])
b = a[:m, :n]
return b
插入水印
def mark_insert(img, mark):
# 将图片转化为灰度图(若要插入彩色水印,应对三个通道做相同处理)
img.convert('L')
mark.convert('L')
mark_mat = numpy.ma.array(mark)
# 对图片进行奇异值分解,并将sigma矩阵转化为对角阵(原返回值为向量)
u_img, sigma_img, vt_img = np.linalg.svd(img)
sigma_img = diagonal_mat(u_img.shape[0], vt_img.shape[0], sigma_img)
# 插入水印并再次奇异值分解(这里没有考虑水印强度。如要考虑,应给水印乘上强度系数)
new_mat = add_mat(sigma_img, mark_mat)
u_new, sigma_new, vt_new = np.linalg.svd(new_mat)
sigma_new = diagonal_mat(u_new.shape[0], vt_new.shape[0], sigma_new)
img_new = np.dot(np.dot(u_img, sigma_new), vt_img)
Image.fromarray(np.uint8(img_new)).save("marked.jpg")
return img_new
再次向原图片插入原水印,若结果图片相同,说明水印插入成功
def is_marked(img, mark, marked):
img_mark = mark_insert(img, mark)
marked_mat = numpy.ma.array(marked)
if img_mark.all() == marked_mat.all():
return True
else:
return False
完整实现代码
示例数据
图片压缩示例图片
示例图片 1
数据隐藏实例灰度图片及水印
示例图片:
示例图片2
示例水印:
示例水印
完整代码
import numpy as np
import numpy.ma
from PIL import Image
# 将sigma转化成m*n大小的对角矩阵
def diagonal_mat(m, n, sigma):
v = np.zeros((m, n))
for i in range(len(sigma)):
v[i, i] = sigma[i]
return v
# 不同大小的矩阵相加,默认加在左上角的部分(水印插入左上角)
def add_mat(a, b):
ma = a.shape[0]
na = a.shape[1]
mb = b.shape[0]
nb = b.shape[1]
c = np.zeros((max(ma, mb), max(na, nb)))
for i in range(max(ma, mb)):
for j in range(max(na, nb)):
if i < ma and j < na:
c[i, j] += a[i, j]
if i < mb and j < nb:
c[i, j] += b[i, j]
return c
# 截取矩阵中左上角m*n的部分(从左上角提取水印)
def extract_mat(a, m, n):
assert(m < a.shape[0] and n < a.shape[1])
b = a[:m, :n]
return b
def mark_insert(img, mark):
# 将图片转化为灰度图(若要插入彩色水印,应对三个通道做相同处理)
img.convert('L')
mark.convert('L')
mark_mat = numpy.ma.array(mark)
# 对图片进行奇异值分解,并将sigma矩阵转化为对角阵(原返回值为向量)
u_img, sigma_img, vt_img = np.linalg.svd(img)
sigma_img = diagonal_mat(u_img.shape[0], vt_img.shape[0], sigma_img)
# 插入水印并再次奇异值分解(这里没有考虑水印强度。如要考虑,应给水印乘上强度系数)
new_mat = add_mat(sigma_img, mark_mat)
u_new, sigma_new, vt_new = np.linalg.svd(new_mat)
sigma_new = diagonal_mat(u_new.shape[0], vt_new.shape[0], sigma_new)
img_new = np.dot(np.dot(u_img, sigma_new), vt_img)
Image.fromarray(np.uint8(img_new)).save("marked.jpg")
return img_new
# 再次向原图片插入原水印,若结果图片相同,说明水印插入成功
def is_marked(img, mark, marked):
img_mark = mark_insert(img, mark)
marked_mat = numpy.ma.array(marked)
if img_mark.all() == marked_mat.all():
return True
else:
return False
def chan_compress(mat, rate):
# 对单通道奇异值分解
u, sigma, vt = np.linalg.svd(mat)
row = u.shape[0]
col = vt.shape[0]
# 初始化一个 row × col 的全0矩阵
new_mat = np.zeros((row, col))
for i in range(len(sigma)):
# 取前i个奇异值,依照近似公式重建矩阵
new_mat = new_mat + sigma[i] * np.dot(u[:, i].reshape(row, 1), vt[i, :].reshape(1, col))
if float(i) / len(sigma) > rate:
# 将超出范围的值回归到0-255区间内
new_mat[new_mat < 0] = 0
new_mat[new_mat > 255] = 255
break
# 返回值需取整,使用无符号8位整形
return np.rint(new_mat).astype(int)
def img_compress(img, rate):
r, g, b = img.split()
# R通道压缩
r_new = chan_compress(r, rate)
# G通道压缩
g_new = chan_compress(g, rate)
# B通道压缩
b_new = chan_compress(b, rate)
# 重建压缩后的图片
img_new = np.stack((r_new, g_new, b_new), 2)
Image.fromarray(np.uint8(img_new)).save("{}".format(rate)+"new.jpg")
def main():
img = Image.open('img.jpg', 'r')
for i in [0.005, 0.01, 0.05, 0.1, 0.2, 0.3, 0.5, 0.7]:
img_compress(img, i)
print(i, "\n")
mark = Image.open('mark.jpg', 'r').convert('L')
grey_img = Image.open('grey_img.jpg', 'r').convert('L')
print(is_marked(grey_img, mark, mark_insert(grey_img, mark)), "\n")
print("finish")
if __name__ == "__main__":
main()