一、前言
最近在实验个很早就提出来的差异扩展(difference expance )可逆水印,将水印信息嵌入至图像,再做完好提取以及完好恢复图像。
二、原理
(1)嵌入过程
图像中取一对像素相邻对(x,y),可以水平相邻,也可以垂直相邻
计算均值 l = [ (x+y )/2 ] , 差值 h = x-y , 中括号为取整运算
假设要隐藏的二值信息为 b , b为0 或 1
则计算 hs = 2*h+b
接着需要判断 hs 和 l 还原出来的值有没溢出超过0 或 255, 只需判断 hs 属不属于[ 0 , min(2(255-l) , 2l+1)] , 如果属于的话,则为可扩展,再计算出嵌入好信息的 xs,ys,公式(1)为
xs = l+[(hs+l)/2],ys=l-[hs/2 ] | hs>=0
ys = l+[hs/2],ys=l-[(hs-1)/2] | hs<0
如果上面不属于,则是不可扩展,再使用直接改变lsb的形式,需要先记下原先的lsb位,例入 11%2 =1 , lsb(11) = 1
令 d = lsb(h) , 再计算 hs = 2*[ h/2]+b , 这个实际直接改变了 h的 lsb位
然后再判读hs 是否属于[ 0 , min(2(255-l) , 2l+1)] , 属于的话,则为可改变,同样计算公式(1)可得到嵌入好信息的 xs,ys
(2)提取过程
提取的话,假设嵌入好信息的像素对为 xs,ys , 同样计算均值和差值:
均值 l = [ (xs+ys )/2 ] , 差值 hs = xs-ys , 中括号为取整运算
首先提取水印信息,只需将 hs 对2求余数, b = hs%2
然后如果为可扩展的嵌入方式,则恢复真实差值
h = ( hs-b)/2
如果为可改变的嵌入方式,则恢复真实差值,下面的 d 是在嵌入过程需要保存起来的,是真实lsb位
h = hs-b+d
再依据公式(1) ,由 l 和 h 计算出 恢复的 x , y ,如下:
x = l+[(h+l)/2],y=l-[h/2 ] | h>=0
y = l+[h/2],y=l-[(h-1)/2] | h<0
这样就可以得到提取的水印二值信息 b , 和 恢复出来的 x,y
三、代码
# -*- coding: utf-8 -*-
"""
Created on Tue Nov 9 18:19:45 2021
"""
import cv2
import numpy as np
def test():
img = cv2.imread( "butterfly.png" )
gray = cv2.cvtColor( img , cv2.COLOR_BGR2GRAY)
h,w = gray.shape[:2]
for r in range(h):
for c in range(w):
#print( gray[ r , c ] )
pass
cv2.imshow("src" , gray )
cv2.waitKey()
cv2.destroyAllWindows()
n = 2000
p = np.random.randint( 0 , 2 , n) #嵌入的信息, 0,1 交替的序列,n为长度
info_p = []
info_a = []
def convHL( h, l ):
if h>=0:
x = l+int( ( h+1 )/2 )
y = l-int( h/2 )
else:
x = l+int( ( h )/2 )
y = l-int( (h-1)/2 )
return x, y
def embedMsg():
print( "嵌入的信息:" , p )
img = cv2.imread( "butterfly.png" )
img = cv2.cvtColor( img , cv2.COLOR_BGR2GRAY)
img_src = img.copy()
H,W = img.shape[:2]
img_data = img.reshape(-1)
loc_1 = []
loc_2 = []
lsb_2 = { }
for i in range( n ):
x = img_data[2*i]*1.0
y = img_data[2*i+1]*1.0
h = x-y
l = int((x+y)/2)
hs_1 = 2*h+p[i] #可扩展
hs_2 = 2*int( h/2 ) + p[i] #不可扩展,可改变,直接改变lsb位
if abs( hs_1 ) <= min( 2*(255-l) ,2*l+1 ):
xs,ys = convHL( hs_1, l )
loc_1.append( 2*i )
elif abs( hs_2 ) <= min( 2*(255-l) ,2*l+1 ):
xs,ys = convHL( hs_2, l )
loc_2.append( 2*i )
lsb_2[ 2*i ] = int(h)%2 #原来的lsb位
else:
print( "no_type" )
img_data[2*i] = xs
img_data[2*i+1] = ys
global info_p
info_p.append( [ x , y , p[i]] )
out = img_data.reshape( H,W )
cv2.imshow("src" , img_src )
cv2.imshow("embed_img" , out )
return loc_1 , loc_2 ,lsb_2 , out,img_src
def extractMsg( loc_1 , loc_2 , lsb_2 , img_emd ):
H,W = img_emd.shape[:2]
img_data = img_emd.copy().reshape(-1)
n = len(loc_1+ loc_2)
p_ext = []
for i in range( n ):
loc = 2*i
xs = img_data[2*i]*1.0
ys = img_data[2*i+1]*1.0
hs = xs-ys
ls = int( (xs + ys)/2)
b = int( hs%2 )
if loc in loc_1: #属于可扩展情况
hs = (hs-b)/2 #恢复真正差值
x,y = convHL( hs, ls )
elif loc in loc_2: #属于可改变
hs = (hs-b)+ lsb_2[ loc ] #恢复真正差值
x,y = convHL( hs, ls )
p_ext.append( b )
img_data[2*i] = x
img_data[2*i+1] = y
global info_a
info_a.append( [ x , y , p[i]] )
res = img_data.reshape( H,W )
cv2.imshow("extract_img" , res )
print("提取的信息:" , p_ext )
return p_ext, res
if __name__ =="__main__":
loc_1 , loc_2 , lsb_2 , img_emd ,img_src = embedMsg()
p_ext , ext_img = extractMsg( loc_1 , loc_2 , lsb_2 , img_emd )
"""#比对原始图像和恢复图像,像素值一样,可完美恢复
H,W = img_src.shape
for h in range(H):
for w in range(W):
if img_src[ h ,w ] != ext_img[ h ,w ]:
print("difference pixel!")
"""
cv2.waitKey()
cv2.destroyAllWindows()
四、结果
可看到可以完好提取和完好恢复