Fork me on GitHub

二值图像的隐写

算法思想

把一个二值图像分成矩形图像区域Bi,若某块p1bi > 50% 则嵌入一个1,若p0bi > 50% 则嵌入一个0

但是在嵌入的过程中需要对一些像素的颜色进行调整。

准备1——计算可以利用的块

由上图可以看出,只有当一个块中0或者1的比例落在某个区间并且结合消息是0or1才可以决定这一块究竟是藏0还是藏1

为了简化我们将一幅图片分为10*10的大小,同时我们采用8*8检测,于是可以严格8*8检测的就只剩下中间的64个块了

我们假设有:

  1. msg 要隐藏的消息
  2. count 消息的长度
  3. image 二值图像
  4. R1,R0,lumda 是我们设置的参数
  5. computep1bi函数可以计算出某个块的p1bi
  6. row,col是为了随机隐藏我们随机选择的块首地址

我们需要确定的就是随机选出来的rowcol是否可以用,于是很自然地想到需要对8*8的块进行遍历,同时标记colrow是否可用,不可用的直接将其设为-1

1
2
3
4
5
6
7
% 最后计算哪些是可以用的存起来
j = 1;
for i = 1:m*n
if row(i) ~= -1
availabler(j) = row(i);
availablec(j) = col(i);
j = j+1;

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
%分析可用的图像块与秘密信息对应
% msg count 为密码消息及其数量
% row col 存放的是随机选块后的块首地址的行列地址
% m*n 为总体数量
% image 为载体图像
% R1,R0,lumda 为参数
% randr randc 是在8*8范围内随机置乱的行列标
function [availabler, availablec, image] = available(msg, count, row, col, m, n, image, R1, R0, lumda, randr, randc)
msgquan = 1;
unable = 0;
difficult = 0;
for blockquan = 1:m*n
% 计算这一块的p1bi
p1bi = compute1bi(row(blockquan), col(blockquan), image); %计算p1(bi)
if p1bi >= R1+3*lumda || p1bi <= R0 -3*lumda
row(blockquan) = -1; % 标记为无用
col(blockquan) = -1;
unable = unable + 1;
msgquan = msgquan - 1; % 该消息还未找到可以隐藏的块
elseif msg(msgquan, 1) == 1 && p1bi <= R0
% 调整p1bi变的更小
image = editp1bi(row(blockquan), col(blockquan), image, 1, 3*lumda, randr, randc);
row(blockquan) = -1;
col(blockquan) = -1;
difficult = difficult + 1;
msgquan = msgquan - 1;
elseif msg(msgquan, 1) == 0 && p1bi >= R1
image = editp1bi(row(blockquan),col(blockquan),image,0,3*lumda,randr,randc);
row(blockquan) = -1;
col(blockquan) = -1;
difficult = difficult + 1;
msgquan = msgquan - 1;
else
row(blockquan) = row(blockquan);
col(blockquan) = col(blockquan);
end
msgquan = msgquan + 1;
if msgquan == count + 1;%该消息已经读取完成
for i = (blockquan + 1):m*n %将其余的都设置为不可用
row(i) = -1;
col(i) = -1;
end
disp(['消息长度:', num2str(msgquan - 1), 'bits;用到的块数:', num2str(blockquan),';其中不可用的块有:', num2str(unable),';另有',num2str(difficult),'块难以调整已修改为不可用块'])
break;
end
end
%载体分析完但消息还没有读完
if msgquan <= count
disp(['消息长度:', num2str(msgquan - 1), 'bits;用到的块数:', num2str(blockquan),';其中不可用的块有:', num2str(unable),';另有',num2str(difficult),'块难以调整已修改为不可用块'])
disp('请根据以上数据更换载体');
error('载体太小');
end
%计算可用块的数量
%disp(row)
quan = 0;
for i = 1:m*n
if row(i) ~= -1
quan = quan + 1;
end
end

if quan < count
error('可用快太小');
end
disp(['可用图像块为:', num2str(quan)]);
%生成可用块的行标列标并于消息对应
image = round(image);
availabler = zeros([1, quan]);
availablec = zeros([1, quan]);
j = 1;
for i = 1:m*n
if row(i) ~= -1
availabler(j) = row(i);
availablec(j) = col(i);
j = j+1;
end
end

准备2——修改像素点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
%修改像素的函数
% headr为块首行地址
% headc为块首列地址
% image 为图像
% pixel 为要修改的像素
% count 为修改的数量
% randr randc 是随机置乱的结果
function image = editp1bi(headr, headc, image, pixel, count, randr, randc)
c = 0;
for i = 1:64
if image(headr+randr(i), headc+randc(i)) == pixel
%八连接检测
if image(headr+randr(i)-1, headc+randc(i)) == ~pixel ||...
image(headr+randr(i)+1,headc+randc(i)) == ~pixel ||...
image(headr+randr(i),headc+randc(i) - 1) ~= pixel || ...
image(headr+randr(i),headc+randc(i) + 1) ~= pixel || ...
image(headr+randr(i)-1,headc+randc(i) - 1) ~= pixel|| ...
image(headr+randr(i) -1,headc+randc(i) + 1) ~=pixel||...
image(headr+randr(i)+ 1,headc+randc(i) - 1) == ~pixel ||...
image(headr+randr(i) + 1,headc+randc(i) + 1) == ~pixel...
image(headr+randr(i), headc+randc(i)) = ~pixel+0.01;
c = c+1;
end

end
if c==count
return
end
end
if c ~= count
disp('warning! 参数选择不当 未能。。。建议重做');
end

这里其实就是针对中间的64个块进行8连接检测。关于8连接检测边界的做法,我的理解就是只要一个像素点周围的8个点有一个不一样就说明这个点是可以修改的,并且为了防止边界扩散,我们并不直接取反,而是取反后+0.01

准备3——计算p1bi的函数

1
2
3
4
5
6
7
8
9
function p1bi = compute1bi(headr,headc,image)
p1bi = 0;
for i = 1:10
for j = 1:10
if image(headr+i-1, headc+j-1) == 1
p1bi = p1bi + 1;
end
end
end