可逆数据隐藏python实现(Reversible Data Hiding)

本文介绍了可逆数据隐藏的算法,通过在图像灰度直方图中找到最大值和最小值,调整像素值来嵌入和提取二进制数据。算法首先找到最大灰度值a和最小灰度值b,对最小值非零的情况进行特殊处理,然后移动直方图,将数据嵌入到图像中。信息提取时,根据灰度值变化读取隐藏数据,并恢复原图像。代码已上传至百度云,通过Python实现,最终验证了数据提取和图像复原的成功。
摘要由CSDN通过智能技术生成

这是一篇针对可逆数据隐藏的经典研究论文(IEEE TRANSACTIONS ON CIRCUITS AND SYSTEMS FOR VIDEO TECHNOLOGY , VOL. 16, NO. 3, MARCH 2006),笔者使用python将原论文中的算法进行实现。

实现代码已上传至百度云:

        链接:https://pan.baidu.com/s/13FbDkLrRrVlZLYyLbzUJ8w 
        提取码:wlsc

代码详细解释见第一部分算法介绍之后的内容,如果本文对你有所帮助,请多多点赞收藏。

一,算法介绍

1.算法目的:

        该算法分为信息隐藏信息提取两个部分。前者是将一组二进制数据嵌入到原图像中,且不影响原图像的显示内容。后者是将已嵌入信息的处理图像进行信息提取并还原图像信息。

2.信息隐藏:

        1)生成原图像的灰度直方图H(x),寻找H(x)中的最大值H(a)和最小值H(b),其中的最大值点a和最小值点b对应原图像中数量最多和数量最少的灰度值。

        2)如果最小值H(b)是非零的,需要将原图像中该最小灰度值的像素信息进行另存(即开销信息O),然后将这些像素的灰度值重新编码(具体操作在算法实现中我会讲解),使最小灰度值的像素数量为0,即H(b) = 0,得到的开销信息后面和隐藏信息一起嵌入到图像中。

        3)为了保持一般性,假设最小值点b > 最大值点a,将原图像中灰度值在(b,a)之间的像素灰度值加1(如果b<a,则减1),对于图像像素的整体变换,而且变换大小只有一个灰度值,这对图像的正常内容显示不会产生影响。此时,直方图中H(a+1)=0,而H(a)是最大值,则这一组(a,b)中的数据嵌入量C为:C=H(a) - O,其中O表示H(b)的开销信息,在2)中已经进行另存处理。

        4)开始嵌入操作,扫描整幅图像,当遇到灰度值为a的像素,检查嵌入数据,如果嵌入数据为1,则将该像素灰度值加1;如果嵌入数据为0,则保持不变。

3.信息提取:

3.1提取隐藏数据:

        扫描整幅图像,当遇到像素灰度值为a+1时,提取”1”,当遇到像素灰度值为a时,提取”0”。

3.2恢复图像数据:

        1)复原开销信息:获取开销信息长度,用以提取后面的开销信息。根据开销信息中保存的原像素位置,将对应像素的灰度值置为b。

        2)将直方图中灰度值在[b,a)间的像素整体减1(b>a则加1),即左移一个单位。经过以上两步操作,图像的原始信息和隐藏信息得到分离,图像没有任何损失。

二,嵌入操作(算法实现)

1.所使用的库函数

import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np

2.读取数据

为了满足读取图像以及保存图像的无损过程,本文采用格式为PNG的图像。(JPEG图像使用imwrite函数无法无损保存)

为便于操作,本文使用“Lena”的灰度图像,获取图像各像素灰度值后将其转化为一维数组,便于后续操作。

img = cv.imread("lena.png",0)  # Use the classical Image 'Lena'
h, w = img.shape[:2]  # 512 * 512 
pixelSequence = img.reshape([h * w, ])	# [262144,]

3.寻找直方图最值点

通过直方图,判断最大值点a和最小值点b。如果最小值非零,即将原图像中灰度值为该最小值的像素位置打包为开销信息(另外保存),然后将像素值置为(b,a)之外的值,因为最小值的数量少,不会影响图像整体显示。(本文将最小值左移一位,即b-1)

numberBins = [i + 0.5 for i in list(range(0, 256))]  # set the range of bins
histogram, bins, patch = plt.hist(pixelSequence, numberBins, 
facecolor='blue', histtype='stepfilled')  # histograming

max_a = max(histogram)  # max pixel number
min_b = min(histogram)	# min pixel number
histogram_list = histogram.tolist()
MaxPoint = histogram_list.index(max(histogram_list))+1  # max gray value
MinPoint = histogram_list.index(min(histogram_list))+1  # min gray value

# judge the min pixel number is 0 or not
BookKeeping = []
if min_b != 0:
	for i in range(len(pixelSequence)):
		if pixelSequence[i] == MinPoint:
			BookKeeping.append(i)
			pixelSequence[i] = MinPoint - 1

4.移动直方图

在不丧失一般性的情况下,且根据max/min函数的特性,本文的最小值点b < 最大值点a。因此,只需将灰度值在(b,a)以内(不包括a,b)的像素灰度值减1即可。

for i in range(len(pixelSequence)):
	if MinPoint < pixelSequence[i] < MaxPoint:
		pixelSequence[i] -= 1

5.嵌入数据

根据算法原理,嵌入数据为二进制数组。扫描整幅图像,遇到灰度值为最大值点a时进行判断(假设此时扫描到像素n):

        1)如果嵌入值为1时,像素n的灰度值-1;

        2)如果嵌入值为0时,像素n的灰度值保存不变。

Hidden_Data = [1,1,0,0,1,0,1,0,0,1,1] # hidden data
n = 0
for i in range(len(pixelSequence)):
	if pixelSequence[i] == MaxPoint:
		if Hidden_Data[n] == 1:
			pixelSequence[i] -= 1
		else:
			pass
		n += 1	
		if n == len(Hidden_Data):
			break

6.生成处理图像

使用cv.imwrite函数,其中[cv.IMWRITE_PNG_COMPRESSION, Compression Ratio]的第二个参数表示压缩比例,从0~9,数值越高压缩比例越高。(笔者测试:无论设置压缩比例为多少,图像的像素序列以及灰度值都不会变,但是图像的内存会减小)

Marked_Image = pixelSequence.reshape(h, w)
cv.imwrite('Marked_Image.png', Marked_Image, [cv.IMWRITE_PNG_COMPRESSION, 0])

三.提取还原操作(算法实现)

上一部分已经将数据嵌入到图像中,提取还原只需要对之前的步骤进行逆向操作即可。

1.读取图像

img = cv.imread("Marked_Image.png",0)  # Use the classical Image 'Lena'
h, w = img.shape[:2]  # get the pixel's high->512 and wide->512
pixelSequence = img.reshape([h * w, ])	# [262144,]

2.提取隐藏数据

扫描整幅图像:

        1)当遇到像素灰度值为a-1,提取1;

        2)当遇到像素灰度值为a,提取0。

Recover_Data = []
count = 0
for i in range(len(pixelSequence)):
	if pixelSequence[i] == MaxPoint-1:
		Recover_Data.append(1)
		count += 1
	if pixelSequence[i] == MaxPoint:
		Recover_Data.append(0)
		count += 1
	if count == len(Hidden_Data):
		break

现在,我们打印隐藏数据和恢复数据:

print('Hidden_Data:',Hidden_Data)		
print('Recover_Data:',Recover_Data)	

显然,二者应该相等。

Hidden_Data: [1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1]
Recover_Data: [1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1]

3.恢复原图像数据

该恢复过程分为三步:

        1)复原开销信息:根据开销信息中保存的原像素位置,将对应位置像素灰度值置为b。

for i in range(len(BookKeeping)):
	pixelSequence[BookKeeping[i]] = MinPoint

        2)直方图[b,a)整体右移一个单位。

for i in range(len(pixelSequence)):
	if MinPoint <= pixelSequence[i] <= MaxPoint-1:
		pixelSequence[i] += 1

        3)恢复原图像。

Recover_Image = pixelSequence.reshape(h,w)
cv.imwrite('Recover_Image.png', Recover_Image, [cv.IMWRITE_PNG_COMPRESSION, 0])

四.验证算法

        为了验证算法的正确性,随机生成不同长度的二进制数组,先进行嵌入操作,然后保存嵌入数据后的图像。重新加载处理后的图像,从图像中进行数据提取和图像复原,分别查看提取后的数据和原数据是否一致,再将复原后的图像像素值与原图像像素值进行比较。

1.验证隐藏数据与提取数据:

        设置隐藏数据长度为最大数据其嵌入量,并随机排序0和1,将隐藏数据与复原数一一比较。

随机二进制数组生成如下:

Hidden_Data = np.random.randint(0,2,int(max_a)-len(BookKeeping))
# 验证隐藏数据与提取数据一致性
Flag = 0
for i in range(len(Hidden_Data)):
	if Hidden_Data[i] != Recover_Data[i]:
		Flag = 1
		break
if Flag == 0 :
	print('数据提取成功')
else:
	print('数据提取失败')

2.验证原图像与复原图像:

        分别取原图像和复原图像的像素灰度值进行一一比较。

​
# 验证原图像与复原图像像素一致性
Flag = 0
for i in range(len(originpixel)):
	if originpixel[i] != pixelSequence[i]:
		Flag = 1
		break
if Flag == 0 :
	print('图像复原成功')
else:
	print('图像复原失败')

​

最后我们执行程序,执行结果如下所示:

the max number: 3041.0 
the min number: 0.0 
the max gray value: 37 
the min gray value: 1
数据提取成功
图像复原成功
[Finished in 3.7s]

可以在文件夹中查看生成的处理图像和最后的复原图像,二者通过人眼看不出任何区别。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值