概要

数字水印是一种将标识信息嵌入到载体当中,且不影响载体使用的一种技术,主要分为时域水印嵌入算法(将水印嵌入到时域采样数据中)和变换域水印嵌入算法(先对音频做变换,将水印嵌入到变换域系数中)。本文主要介绍的是时域水印嵌入算法—LSB算法。

LSB算法原理

基于不可感知的要求,即数据的变化几乎不会引起使用的者的察觉,将水印信息嵌入到数据的最低有效位(Least Significant Bit),也就是将图片的RGB数值转换为二进制数据,然后用水印替换掉最低位,这种变化对于人眼来说是不可察觉的。当然,水印的形式也是多种多样的,有图片,文字等,这里主要介绍如何把图片作为水印写入到另一张图片中。

注意事项

  • LSB隐写(最低有效位的隐写),是指通过改变图片中像素的最低位来实现信息的隐藏的。这种隐写方式需要图片是无压缩的位图,因此一般用于bmp和png图片。
  • 本文采用python代码实现,下面是需要预先安装的包
    from PIL import Image

加密过程

假设载体图片和水印图片分别是这样的:

  • 获取图片
img1 = Image.open('1.bmp')
img2 = Image.open('2.bmp')
rgb_im1 = img1.convert('RGB')
rgb_im2 = img2.convert('RGB')

除了获取图片外,还要将图片变为RGB通道模式,否则后面是获取不到RGB值的,其中1.bmp是载体图片,2.bmp是水印图片。

  • 获取水印图片每个像素的RGB值
    for i in range(img.size[0]):for j in range(img.size[1]):# 获取水印的每个像素值rgb = img.getpixel((i, j))

采用循环遍历水印图片,获取每个像素的RGB值,可以将值输出来看一下

可以看到,获取到的RGB值是以元组的方式存储的。

  • 将RGB值转为二进制形式
# 将像素值转为二进制后保存
str = str + plus(bin(rgb[0]).replace('0b', ''))
str = str + plus(bin(rgb[1]).replace('0b', ''))
str = str + plus(bin(rgb[2]).replace('0b', ''))

其中bin是python的进制转换函数,能将十进制转换为二进制,唯一不足的就是会自动在转换的数字前面加0b,因此以空字符串替换掉0b。
此外,十进制转换二进制的时候,如果转换的二进制是以0开头的,那么会自动省略前方的0,这不符合我们的要求。因此以255为标准,二进制为1111 1111,其余的不足8位的在最前方补0,如1就是0000 0001。plus函数如下:

def plus(str):# 返回指定长度的字符串,原字符串右对齐,前面填充0。return str.zfill(8)

这样一转换,水印图片的所有RGB值都被转换为二进制并当做字符串存储在str里了,如下图:

这里的每一位,都是要替换掉载体图片每一个像素点的R,G,B三个值的二进制形式的末尾数字,因此载体的图片像素点的RGB值会改变,但变化最大不会超过1,主观上无法察觉。

  • 遍历载体图片
    for i in range(img.size[0]):for j in range(img.size[1]):# 获取到图片的每个像素值data = img.getpixel((i, j))

很明显,每次循环,data就会获得一个RGB值,也可以分别保存。

r = data[0]
g = data[1]
b = data[2]
  • 改变像素点的RGB值
    此时,需要两个信号,一个是上面str的长度,来判断此时替换的是str的哪一位,以及来判断何时替换完毕;另一个是计数器,因为载体图片也有RGB三个数值,每次都需要替换完整的R,G,B三个数值后,才能完成一个像素点的修改,也就是说,str里的3位数字分别替换载体图片一个像素点的R,G,B的最后一位,此时该像素点修改完毕。当计数器数值等于str的长度时,代表水印完全写入载体。
# 计数器
count = 0
# 二进制像素值的长度,可以认为要写入图像的文本长度,提取(解密)时也需要此变量
codeLen = len(code)
print(codeLen)
if count == codeLen:break"""下面的是像素值替换,通过取模2得到最后一位像素值(0或1),然后减去最后一位像素值,在将code的值添加过来"""r = (r - r % 2) + int(code[count])
count += 1
if count == codeLen:img.putpixel((i, j), (r, g, b))breakg = (g - g % 2) + int(code[count])
count += 1
if count == codeLen:img.putpixel((i, j), (r, g, b))breakb = (b - b % 2) + int(code[count])
count += 1
if count == codeLen:img.putpixel((i, j), (r, g, b))break# 每3次循环表示一组RGB值被替换完毕,可以进行写入
if count % 3 == 0:img.putpixel((i, j), (r, g, b))
  • 保存图片
img.save('1_out.bmp')

输出的图片和原图片感官上没有什么区别,甚至放大后也看不出来,比如下面分别是两张图片的左上角:


像素点都清晰可见,但肉眼丝毫感觉不到差别,用专业工具(PS等)才能看出它们的RGB的值最多相差1。
至此,加密完成!

解密过程

解密过程稍显复杂,因为载体图片中不是所有像素点都被修改了的,而且就算将水印图片的像素点RGB值还原了,但不知道水印图片的宽和高是根本没办法还原水印的,一个像素的差别就可能导致整个图片错位。因此,解密的密钥很明显了,就是水印图片的宽和高。当然,上文str(code)的长度也算密钥,只是这个可以通过宽和高计算的,具体公式:宽×高×3×8。

  • 获取载体图片
img1 = Image.open('1_out.bmp')
rgb_im1 = img1.convert('RGB')
length = 150144

length就是加密中str的长度,可以通过宽和高计算。水印图片的尺寸是92*68。

  • 提取附加值
    遍历载体图片,提取附加值,直到计数器等于length时停止循环,代表提取完毕。
    for i in range(width):for j in range(height):# 获取像素点的值rgb = img.getpixel((i, j))# 提取R通道的附加值if count % 3 == 0:count += 1wt = wt + str(rgb[0] % 2)if count == length:break# 提取G通道的附加值if count % 3 == 1:count += 1wt = wt + str(rgb[1] % 2)if count == length:break# 提取B通道的附加值if count % 3 == 2:count += 1wt = wt + str(rgb[2] % 2)if count == length:breakif count == length:break

width和height是图片的宽和高,就是img.size[0]和img.size[1]。
这里提取到的wt的值应该与加密中str(code)的值一样。

  • 将wt的值还原为RGB数值
str1 = []
for i in range(0, len(wt), 8):# 以每8位为一组二进制,转换为十进制str1.append(int(wt[i:i + 8], 2))

采用数组存放还原后的RGB数值,很明显,每三个值对应一个RGB。

  • 绘制和显示水印图片
img_out = Image.new("RGB", (94, 70))
flag = 0
for m in range(0, 92):for n in range(0, 68):img_out.putpixel((m, n), (str1[flag], str1[flag + 1], str1[flag + 2]))flag += 3
img_out.show()

这里初始化图片的时候我为了便于观看,宽和高多给了2像素,而循环时是按照原本水印的尺寸,因此呈现的图片右方和下方会有阴影。
最后还原的水印图片如下图所示:

至此,解密完成!

附录

  • 加密
from PIL import Imagedef plus(str):# 返回指定长度的字符串,原字符串右对齐,前面填充0。return str.zfill(8)def getCode(img):str = ""# 获取到水印的宽和高进行遍历for i in range(img.size[0]):for j in range(img.size[1]):# 获取水印的每个像素值rgb = img.getpixel((i, j))# 将像素值转为二进制后保存str = str + plus(bin(rgb[0]).replace('0b', ''))str = str + plus(bin(rgb[1]).replace('0b', ''))str = str + plus(bin(rgb[2]).replace('0b', ''))# print(plus(bin(rgb[0]).replace('0b', ''))+"\n")# print(plus(bin(rgb[1]).replace('0b', '')) + "\n")# print(plus(bin(rgb[2]).replace('0b', '')) + "\n")# print(str)return strdef encry(img, code):# 计数器count = 0# 二进制像素值的长度,可以认为要写入图像的文本长度,提取(解密)时也需要此变量codeLen = len(code)print(codeLen)# 获取到图像的宽、高进行遍历for i in range(img.size[0]):for j in range(img.size[1]):# 获取到图片的每个像素值data = img.getpixel((i, j))# 如果计数器等于长度,代表水印写入完成if count == codeLen:break# 将获取到的RGB数值分别保存r = data[0]g = data[1]b = data[2]"""下面的是像素值替换,通过取模2得到最后一位像素值(0或1),然后减去最后一位像素值,在将code的值添加过来"""r = (r - r % 2) + int(code[count])count += 1if count == codeLen:img.putpixel((i, j), (r, g, b))breakg = (g - g % 2) + int(code[count])count += 1if count == codeLen:img.putpixel((i, j), (r, g, b))breakb = (b - b % 2) + int(code[count])count += 1if count == codeLen:img.putpixel((i, j), (r, g, b))break# 每3次循环表示一组RGB值被替换完毕,可以进行写入if count % 3 == 0:img.putpixel((i, j), (r, g, b))img.save('加密图片.bmp')# 获取图像对象
img1 = Image.open('原始图片.bmp')
img2 = Image.open('水印图片.bmp')# 将图像转换为RGB通道,才能分别获取R,G,B的值
rgb_im1 = img1.convert('RGB')
rgb_im2 = img2.convert('RGB')# 将水印的像素值转为文本
code = getCode(rgb_im2)# 将水印写入图像
encry(rgb_im1, code)
  • 解密
from PIL import Imagedef deEncry(img, length):# 获取图片的宽和高width = img.size[0]height = img.size[1]# 计数器count = 0# 结果文本,从图片中提取到的附加值(加密时附加在每个RGB通道后的二进制数值)wt = ""# 遍历图片for i in range(width):for j in range(height):# 获取像素点的值rgb = img.getpixel((i, j))# 提取R通道的附加值if count % 3 == 0:count += 1wt = wt + str(rgb[0] % 2)if count == length:break# 提取G通道的附加值if count % 3 == 1:count += 1wt = wt + str(rgb[1] % 2)if count == length:break# 提取B通道的附加值if count % 3 == 2:count += 1wt = wt + str(rgb[2] % 2)if count == length:breakif count == length:breakreturn wtdef showImage(wt, img_width, img_height):str1 = []for i in range(0, len(wt), 8):# 以每8位为一组二进制,转换为十进制str1.append(int(wt[i:i + 8], 2))# 图片大于水印图片1个像素,便于对比img_out = Image.new("RGB", (img_width + 1, img_height + 1))flag = 0for m in range(0, img_width):for n in range(0, img_height):if flag == len(str1):breakimg_out.putpixel((m, n), (str1[flag], str1[flag + 1], str1[flag + 2]))flag += 3if flag == len(str1):breakimg_out.save("解密图片.bmp")img_out.show()# 水印图片的尺寸作为解密的密钥
# image_width = 92
# image_height = 68
# length = image_width * image_height * 24# 获取一下水印图片的宽和高,也就是解密的密钥
w_image = Image.open('水印图片.bmp')
image_width = w_image.size[0]
image_height = w_image.size[1]
length = image_width * image_height * 24# 获取图片
img1 = Image.open('加密图片.bmp')
rgb_im1 = img1.convert('RGB')wt = deEncry(rgb_im1, length)
showImage(wt, image_width, image_height)

数字水印技术:LSB加密详解(附python代码)相关推荐

  1. Linux用户、权限及改变文件所有者及文件所属组多例详解 附python代码

    https://blog.csdn.net/hanhanwanghaha宝藏女孩 欢迎您的关注! 欢迎关注微信公众号:宝藏女孩的成长日记 如有转载,请注明出处(如不注明,盗者必究) Linux用户.权 ...

  2. python直线拟合_RANSAC算法详解(附Python拟合直线模型代码)

    之前只是简单了解RANSAC模型,知道它是干什么的.然后今天有个课程设计的报告,上去讲了一下RANSAC,感觉这个东西也没那么复杂,所以今天就总结一些RASAC并用Python实现一下直线拟合. RA ...

  3. 视频教程-深度学习原理详解及Python代码实现-深度学习

    深度学习原理详解及Python代码实现 大学教授,美国归国博士.博士生导师:人工智能公司专家顾问:长期从事人工智能.物联网.大数据研究:已发表学术论文100多篇,授权发明专利10多项 白勇 ¥88.0 ...

  4. 2022年全国大学生数学建模竞赛E题目-小批量物料生产安排详解+思路+Python代码时序预测模型(三)

    目录 前言 一.六种物料挑选 二.周数处理 三.时序预测模型 模型预测结果 建模的部分后续将会写出,想要了解更多的欢迎加博主微信,免费获取更多细化思路+模型! 点关注,防走丢,如有纰漏之处,请留言指教 ...

  5. kmeans算法详解和python代码实现

    kmeans算法详解和python代码实现 kmeans算法 无监督学习和监督学习 监督学习: 是通过已知类别的样本分类器的参数,来达到所要求性能的过程 简单来说,就是让计算机去学习我们已经创建好了的 ...

  6. 编辑距离算法详解和python代码

    编辑距离(Levenshtein Distance)算法详解和python代码 最近做NLP用到了编辑距离,网上学习了很多,看到很多博客写的有问题,这里做一个编辑距离的算法介绍,步骤和多种python ...

  7. sklearn预测评估指标:混淆矩阵计算详解-附Python计算代码

    目录 前言 混淆矩阵 python代码 前言 很多时候需要对自己模型进行性能评估,对于一些理论上面的知识我想基本不用说明太多,关于校验模型准确度的指标主要有混淆矩阵.准确率.精确率.召回率.F1 sc ...

  8. 随机森林原理详解及python代码实现

    随机森林(RF)算法 1.算法原理 2.对数据的要求(无需规范化) 3.算法的优缺点 4.算法需要注意的点 5.python代码实现(待更......) 导入相关包 读取数据并预处理(必须处理缺失值) ...

  9. 决策树原理详解及python代码实现

    决策树算法(信贷中常用来寻找规则) 1.算法原理 1.1 ID3(多叉树分类) 1.2 C4.5(多叉树分类) 1.3 Cart(二叉树分类+回归) 2.ID3.C4.5与Cart比较 3.算法优缺点 ...

最新文章

  1. loss和accuracy的关系
  2. 进阶指令——df指令(作用:查看磁盘的空间)、free指令(作用:查看内存使用情况)、head指令(作用:查看一个文件的前n行,如果不指定n,则默认显示前10行)、tail指令、less指令
  3. 漫画:什么是MD5算法
  4. STM32那点事(6)_定时器(上)
  5. php7 viewmodel,【初念科技】| php框架实例: Laravel之Model Observer模型
  6. Nginx for Zabbix 3.2官方监控模板
  7. zstuoj 4245 KI的斐波那契
  8. 程序不能使用中文名_理线好帮手 骨伽 Bunker RGB 鼠标线夹使用心得
  9. java课设单链式存储心得报告_队列的链式存储方式的实现(Java语言描述)
  10. 《从程序员到项目经理》学习笔记
  11. H264编码器性能测试
  12. 74ls138和与非门设计全减器_用74ls138和门电路设计1位二进制全减器
  13. 估值模型不适用_揭开市盈率估值法的真正面纱 | 实战案例系列
  14. 转:明茨伯格:组织不需要英雄,只需要有雅量的领导
  15. c# EASYUI+MVC filebox示例
  16. 人工神经网络通俗解释
  17. HTML期末学生作业~html+css+javascript仿猫眼电影在线网站[毕业设计]
  18. 临床公共数据库挖掘系列1---seer数据库注册
  19. Android Killer--安卓反编译工具
  20. 【STM32】RTC实时时钟,步骤超细详解,一文看懂RTC

热门文章

  1. 基于PHP的网络教学平台设计与实现
  2. 唯一邀请码生成(Java版本)
  3. 47 python - 面向对象(老王开枪案例)
  4. 腹肌怎么画出三维效果?零基础绘画入门教程
  5. 今日头条这次瞄准了爱奇艺,只因张一鸣内心住着小马哥
  6. 运行pytorch时报错——AttributeError: module 'torch.nn' has no attribute 'ModuleDict'
  7. 微博推广方法解答,要注意这些!
  8. Oracle11g:补充日志
  9. TP getField 用法
  10. 【渝粤题库】国家开放大学2021春2390古代汉语(1)题目