信息隐藏-图像盲水印实现

相关知识

目前水印大抵可分为两种,可见和不可见,可见水印大多用来标记或声明版权,或防盗用冒用,但在影院、绘画等场景下,可见性水印会极大影响用户体验,所以产生了盲水印的概念。
盲水印即看不见的水印,多使用信息隐藏技术实现,基于空域或频域隐藏秘密信息,在不影响用户的感官体验下嵌入信息,且只有通过特殊的提取器才可提取。这种技术目前应用十分广泛,相当多的公司都采用这种方法来给外泄的秘密资料找到泄密者。但信息隐藏到底是一门年轻的学科,关于盲水印现成的实现方法还不是太多。
(也看到有说法是盲水印是不依赖原图即可提取水印的方法,也有一定道理,但是该如何可见和不可见水印的是否有区别叫法呢?)

工具

虽然不多,但还是让我找着了。
目前大部分盲水印嵌入算法都是基于频域的(因为其鲁棒性高),但大部分现成工具都不能克服频域转化算法的局限性,需要载体图像像素位2的幂次、载体图像要是方的、载体与水印的大小需相关等等,这无疑是不符合实际的,诶,有这么一个软件就克服了这些局限性。
下载链接附上:
水印制作软件-WaterMakev10.61 绿色版-腾牛下载 (qqtn.com)
该软件解压即可使用,还不依赖其他dll,一个exe打天下,且经过测试其鲁棒性较强,足以应对我认为最强的剪切攻击,使用界面简单易用。
使用界面
该工具的缺点在链接中也提到了,图像大小为2的n次方,如果大小不符的话会自动拉伸,这到也能接受,后面自行再缩放即可。关键的是对大图像的支持不足,目前测试2560×1660的图像还可,但是6000×4000这种级别的图像会直接报错,OutOfMemory,可能图像太大申请的内存不足以存放。
总之该工具在我们日常使用玩玩还是不错的,用起来很方便,但是当图片增大,商用领域就有些捉襟见肘。

现成库

blind-watermark

据说该库由阿里安全人员开发,同样可以克服频域算法的局限性,使用也较简单,相关介绍如下:
Python 实现数字图片盲水印(隐水印) - 知乎 (zhihu.com)
可使用pip install blind-watermark直接安装,国内环境也无需切换源,比较友好。可针对图像嵌入文本和水印,比较强的是水印恢复不依赖于原图,代码没看明白具体咋实现的也不懂。
不过都说该库鲁棒性极强,可从容应对几乎所有攻击,包括剪切旋转拉伸,但可能是我打开方式不对,污损情况下提取效果不错,但一些破坏图像结构的攻击直接就崩了,或提取出乱码或者雪花,或产生红色报错。
使用代码示例如下:

from blind_watermark import WaterMark
def encode(img,str):#嵌入文字信息
    startime = time.time()
    bwm1 = WaterMark(password_img=1, password_wm=1)
    bwm1.read_img(img)
    bwm1.read_wm(str, mode='str')
    bwm1.embed('embedded.png')
    len_wm = len(bwm1.wm_bit)
    print('Put down the length of wm_bit {len_wm}'.format(len_wm=len_wm))
    encodetime = time.time()
    print("encode time ", encodetime - startime)
    return len_wm
def decode(img,len):#解密文字信息
    startime=time.time()
    bwm1 = WaterMark(password_img=1, password_wm=1)
    wm_extract = bwm1.extract(img, wm_shape=len, mode='str')
    endtime = time.time()
    costtime = endtime - startime
    print("time cost:", costtime)
    print(wm_extract)
def encodeimg(carrier,watermark,output):#嵌入图像水印
    bwm1 = WaterMark(password_wm=1, password_img=1)
    # 读取原图
    bwm1.read_img(carrier)
    # 读取水印
    bwm1.read_wm(watermark)
    # 打上盲水印
    bwm1.embed(output)
def decodeimg(img,output):#提取图像水印
    bwm1 = WaterMark(password_wm=1, password_img=1)
    # 注意需要设定水印的长宽wm_shape
    bwm1.extract(filename=img, wm_shape=(150,167),out_wm_name=output)
#然后在主函数中调用就可以了

相比来说该库的鲁棒性确实不错,jpg的压缩也可以轻松应对,不足的是对图像质量的破坏偏大。不论图像大小,都可能对图像造成一定破坏,虽不至于影响整体感官,但也能看出图像差别,透明度要求较高的话不建议使用。

BlindWaterMark

项目地址
盲水印
使用控制台执行,具体操作方法文档都有,比较简单,优点是透明性高,且支持大图处理,加了水印的图像肉眼几乎无法识别。但面对jpg格式的压缩时有些力不从心,水印直接变成雪花,毕竟鲁棒性和透明性不能兼得。算法据说可以抗PS等这种在已有结构上的修改的攻击,没有PS还没法测试,如果是真的话还是相当适合商用的,可以达到防篡改的效果。

自行编写 - 基于离散余弦变换的盲水印

自行编写的好处是可以根据需求调整,在鲁棒性和透明性之间做出最适合自己的决定,但是相对比较繁琐,而且一些异常处理很难像已有系统那么周密,以学习为目的的话还是相当适合的。
主流隐藏方法,且兼顾效率与隐蔽性的算法是基于离散余弦变换的信息隐藏算法(DCT),该算法涉及比较复杂的数学知识,只能简要说下其原理:离散余弦变换是将图像由空间域转化到频率域上,将图像计算为二维余弦波,从效果来说是将图像计算为一个DCT系数矩阵,该矩阵代表图像的频率分布,频率低的为图像中相对重要的主体信息,高频则是图像的一些细节,但该矩阵中的像素与图像像素不存在一一对应关系。 然后将秘密信息根据自己的算法隐藏到DCT系数中即可,隐藏位置多选择中频区域,是在透明性和鲁棒性之间做了折中的选择。
本文设计的算法是利用dct系数的相对变化存储10信息(可用该10信息代表黑白,即可嵌入图片)。
引用一张觉得很不错关于域的分解图
在这里插入图片描述

具体思路

1.嵌入区域选择:首选中频,中频对角区域填满后向高频偏移,故容量最大为载体像素个数的一半。
2.嵌入算法:此步需要传入参数嵌入强度coefficient,根据标记字符串0或1增大或缩小自身的coefficient倍,这种相对变化能让自身统计特征的变化降到最小,且避免了因变化绝对值而产生对图像的破坏。
3.提取算法:用嵌入了秘密信息的DCT系数矩阵与原图像系数矩阵作差,会得到有正有负的新矩阵,因为先前规定了0减小1增大,故可根据目前的新矩阵恢复出先前的01序列,进而可以恢复字符甚至图像。
基于DCT的隐写算法python实现代码如下:

import cv2
import numpy as np
import matplotlib.pyplot as plt#图片展示
import base64 #图片转string
import time
def dct_encode(img1,img,coefficient):  #返回处理好的dct
    starttime=time.time()
    height, width = img.shape
    print("Number of the carrirer can contain:",height*width/2)
    str=watermarkTostring(img1)
    if len(str)>height*width/2:#如果信息数量过多返回错误 只能存储在后二分之一
        print("Too many information to hide!")

    img1=img.astype('float')
    img_dct = cv2.dct(img1)
    h=0
    w=width-1                #行数向下递增 列数向右递增
    last_h=1                 #保存上一次跳转的列数
    print("Embeding...")
    print("隐藏",len(str),"位")
    black=0
    white=0
    for i in range(0,len(str)):#所有水印字符嵌入  范围前闭后开
        if h==height:
            h=last_h
            last_h=last_h+1
            w=width-1
        if str[i]=="0":#白色变小
            #print("白色颜色记录位置",h,w)
            if img_dct[h][w]<0:
                img_dct[h][w]=img_dct[h][w]*(1+coefficient)
            else:
                img_dct[h][w]=img_dct[h][w]*(1-coefficient)
            h=h+1
            w=w-1
            white=white+1
        else: #黑色变大
            if img_dct[h][w]<0:
                img_dct[h][w]=img_dct[h][w]*(1-coefficient)
            else:
                img_dct[h][w]=img_dct[h][w]*(1+coefficient)
            #print("黑色颜色记录位置", h, w)
            h=h+1
            w=w-1
            black=black+1
    print("stop caculuation location ", h, w)
    print("number of white is ",white)
    print("number of black is ",black)
    print("Embeding complete.")   #嵌入完成
    encode_img=cv2.idct(img_dct)
    cv2.imwrite('D:/carrywatermark.png',encode_img)
    endtime=time.time()
    costtime=endtime-starttime
    print("encode cost",costtime)
    show("carrier",img)
    show("encode",encode_img)
    return  encode_img

def dct_decode(watermark,img,orginal_img):  #现在图像减原图像 负数为白色 正数为黑色
    starttime=time.time()
    height,width=watermark.shape
    str_len=height*width
    print("需解析",str_len,"位")
    print("组成高为",height,"宽为",width,"的图像")
    carrier_height, carrier_width = img.shape
    carrier_h=0
    carrier_w=carrier_width-1
    last_carrierh=1
    height2,width2=orginal_img.shape
    '''if height1<height2:  #以最小的计算
        height=height1
    else:
        height=height2
    if width1<width2:
        width=width1
    else:
        width=width2'''
    black=0
    white=0
    img1 = img.astype('float')
    img1_dct = cv2.dct(img1)
    img2=orginal_img.astype('float')
    img2_dct=cv2.dct(img2)
    watermark_list=[[]for i in range(0,height)]    #生成空二维数组
    h=0
    w=width-1
    last_h=1
    list_height=0
    for i in range(0,str_len):#所有水印字符嵌入  范围前闭后开
        if carrier_h==carrier_height:#到底了
            carrier_h=last_carrierh
            carrier_w=carrier_width-1
            last_carrierh=last_carrierh+1

        flag=img1_dct[carrier_h][carrier_w]-img2_dct[carrier_h][carrier_w]
        carrier_w=carrier_w-1
        carrier_h=carrier_h+1
        if flag>0:#黑色
            black=black+1
            if len(watermark_list[list_height])==width:
                list_height=list_height+1
            watermark_list[list_height].append(0)
            h=h+1
            w=w-1
        else: #白色
            white=white+1
            if len(watermark_list[list_height]) == width:
                list_height = list_height + 1
            watermark_list[list_height].append(255)
            h=h+1
            w=w-1
    print("stop caculuation location ", carrier_h, carrier_w)
    print(len(watermark_list[0]))
    print("number of white is ", white)
    print("number of black is ", black)
    watermark=np.array(watermark_list)              #list 类型转为numpy.ndarray
    #print(watermark)
    print("Extract complete!")   #提取完成
    cv2.imwrite('D:/getwatermark.png', watermark)
    endtime=time.time()
    costtime=endtime-starttime
    print("decode cost",costtime)
    show("get_watermark",watermark)
    return watermark

代码写了这么长还是有相当的不足,
1.受限于DCT算法本身计算方法,输入的载体图像必须是方的。
2.只测试了黑白图像。
3.鲁棒性一般,可以应对一些修改攻击,但剪切旋转等格式变化未有考虑。
优点是时间效率和透明性还可,因为只修改了嵌入密文长度的dct系数,1024×1024的图像只需要0.3秒即可完成,不论从人眼还是计算机角度来看,透明性也都是比较优异的。

评价标准

盲水印能嵌入后其主要评价指标就是透明度,这需要从两个角度来评价。
1.视觉感官,需要人主观判断,由于人注意的重点不同,所以可能会有不同的细节被忽略,最好让多个人分别评价打分,取他们的平均值来判断。
2.计算机分析,人看不到的细节往往会被计算机捕获,这时我们需要引入一个评价标准–峰值信噪比PSNR,具体计算方法网上随便一查全都是,PSNR高于40dB说明图像质量极好(即非常接近原始图像),在30-40dB通常表示图像质量是好的(即失真可以察觉但可以接受),在20-30dB说明图像质量差;PSNR低于20dB时说明图像不可接受。

总结

盲水印是实现溯源和防盗版的一个渠道,但是其局限性也比较多。
首先是效率问题,一张1080的图片使用频域算法嵌入水印可能需要两秒左右,而商用的海报可能需要几分钟时间,如果觉得这还好的话想一下盲水印用于视频的防伪,目前视频帧率大多为60,一秒钟的盲水印嵌入可能就需要两分钟,十分钟呢?两个小时的电影呢?这样的时间效率能接受吗?且目前大的视频平台都会对用户上传的视频进行压缩,这种压缩可能是很强力的,大部分嵌入水印可能都无法存活。
其次是易被攻击,虽然嵌入的水印其他人很难破解和修改,但是让图像中的水印消失还是比较好达到的。把图片打印出来再扫描、拍摄展示图像的屏幕、甚至利用qq微信社交软件进行发送再接受等,都可能导致水印的失效,所以只从可用性的角度来说防盗用是很难的。但是这一切是建立在对图像质量要求不高的前提下,不论是扫描还是拍摄,得到的图像终究不是原图,当对图像质量有一定要求必须使用原图时,我们的水印就会再次出现。

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值