https://mbd.pub/o/bread/ZJ2ampZx
https://mbd.pub/o/bread/ZJ2amppr
《B题 电子资源版权保护问题》
版权又称著作权,包括发表权、署名权、修改权、保护作品完整权、复制权、发行权、出租权、展览权、表演权、放映权、广播权、信息网络传播权、摄制权、改编权、翻译权、汇编权及应当由著作权人享有的其他权利。
在计算机网络广泛应用的今天,越来越多电子资源会通过网络进行快速传递。与此同时,如何保护电子资源的著作权问题也渐渐变得至关重要。这一问题也是信息安全领域中的关键问题之一。数字水印(electronic water mark)技术是解决这一问题的关键技术之一。但因为可见水印(visible watermarking)在应用于电子图片著作权保护时,往往会破坏图片自身的结构,并且因嵌入信息可见而容易被识别剔除。因此,隐写术(steganography)被广为关注和使用。
隐写术一般被认为是信息隐藏学的一个重要分支,它专门研究如何隐藏实际存在的信息。隐写术有悠久的历史,部分案例甚至可追溯到公元前数百年。随着计算机和互联网技术的高速发展,近代隐写技术的研究被认为大约起始于20世纪90年代。因为隐写技术能将特定信息嵌入信息载体且不易被察觉,所以它可被广泛地应用于著作权保护、数据附加等领域。
问题 1 :针对附件1的图片P,建立生成嵌入信息深圳杯数学建模挑战赛的图片SP的数学模型,使得图片SP在人的视觉上尽可能与原图P相近。设计并实现生成图片SP的算法,将生成SP源代码和结果图片SP置于参赛作品的附录A中;给出从图片SP提取著作权信息使用的源代码并置于参赛作品的附录B中。
问题 2 :使用问题1中的模型与算法,能否将《中华人民共和国著作权法》(第三次修正案)[1]中的所有文字信息嵌入附件1的图片中?如果不能,最多能嵌入多少?
问题 3 :在电子图片传递的过程中,可能会被压缩或以不同的图片格式存储,也可能会被缩放、旋转或其他几何变形等。此时,问题1中的算法是否仍然可用?如果不能用,如何改进?
问题 4 :若要保护其他电子图片的著作权,使用问题1中的算法时应注意什么?请给出最多3条注意事项,并说明理由。
参考文献
[1] http://www.gov.cn/guoqing/2021-10/29/content_5647633.htm
首先,我们使用工具如MATLAB对附件1中的图片P进行分析,以深入了解其像素分布、色彩特征、纹理特性等。我们必须仔细考虑将信息嵌入到图片P中的位置、方法以及嵌入的信息量。可以通过微调像素颜色分布或像素值来实现嵌入,以尽量减小对视觉效果的影响。其次,基于前文的图像分析结果,我们可以建立LSB方法的数学模型,将信息嵌入方案形式化,以生成嵌有信息的图片SP。根据这个模型,我们可以编写代码并生成所需的结果图像,并从结果图像提取嵌入信息。最后,我们需要进行相似度检验,计算原始图像与嵌入信息图像之间的均方误差(MSE)和结构相似性指数(SSIM)等值,分别为0.53和0.999,说明我们的隐写技术未破坏原始图像的结构特征。
图像像素分布是指图像中各个像素值的分布情况,即不同像素值在图像中出现的频率或数量。图像像素分布在图像处理和分析中具有重要的意义,它可以提供关于图像内容和特性的有价值信息。
我们对图像P进行像素分布的分析,结果如下图所示:
这样的分布可以在直方图中看出,两边的高峰表示图像中存在较亮和较暗的像素值,而中间的凸出区域则表示某种特定区域或物体的存在。其他部分的凹陷可能表示相应的像素值较少。
不难看出,图像P的纹理图像具有复杂的、重复性强的纹理特征,可以作为信息隐藏的载体。
论文中部分内容为:
高相似度的直方图可能导致信息嵌入的稳定性增加,即隐藏的信息在不同通道上的影响相似。这可能导致隐写信息更难以被检测到,因为各个通道上的变化较小,不容易引起怀疑。在检测隐写信息时,分析通道间的微小差异可能更为困难,因为直方图的相似性可能使得差异不太明显。
上面的方法说明,使用LSB方法将信息嵌入图片的做法是可行的。因此我们建立LSB方法的数学模型,通过LSB方法的原理进行编程。
LSB隐写就是利用图像的最低有效位(Least Significant Bit,LSB)来隐藏信息的技术。图像的每个像素由三种颜色(红绿蓝)组成,每种颜色占8位,也就是一个字节。LSB隐写就是把要隐藏的信息的二进制位替换掉图像每个像素的最低位,从而实现信息的嵌入。由于最低位对图像的质量影响很小,人眼很难察觉出差异,所以这种方法具有较好的隐蔽性 。
可以调用stegano库来进行测试,看看LSB方法效果如何:
使用时,可以直接:pip install stegano
部分代码如下:
# 先将图片转换为PNG格式
def convert_to_png(input_path, output_path):
img = Image.open(input_path)
img.save(output_path, 'PNG')
# 嵌入信息到图片中
def embed_info(input_path, txt, output_path):
# 把中文转换为base64编码
txt = base64.b64encode(txt.encode('utf-8')).decode('ascii')
secret = lsb.hide(input_path, txt)
secret.save(output_path)
# 从图片中提取信息
def extract_info(input_path):
secret_message = lsb.reveal(input_path)
# 把base64编码还原为中文
secret_message = base64.b64decode(secret_message.encode('ascii')).decode('utf-8')
return secret_message
运行结果:
两张图片大小一样,看不出任何差异,因为嵌入的信息量相对于图像的大小很小,只占了0.02%左右(24字节/1228800字节)。
比较两张图片的差异,有多种方法和指标,比如均方误差(MSE)、结构相似性指数(SSIM)等。这些方法都是基于图片的像素值来计算两张图片之间的差异程度,差异越小,说明图片越相似。
部分代码如下:
import cv2
import numpy as np
from skimage.metrics import structural_similarity as ssim
# 定义MSE函数
def mse(imageA, imageB):
# 计算两张图片之间的均方误差
# 两张图片必须有相同的尺寸
err = np.sum((imageA.astype("float") - imageB.astype("float")) ** 2)
err /= float(imageA.shape[0] * imageA.shape[1])
# 返回MSE值,越小越相似
return err
# 读取两张图片
imageA = cv2.imread("B.jpg")
imageB = cv2.imread("SP.PNG")
# 将图片转换为灰度图
imageA = cv2.cvtColor(imageA, cv2.COLOR_BGR2GRAY)
imageB = cv2.cvtColor(imageB, cv2.COLOR_BGR2GRAY)
# 计算两张图片的MSE和SSIM值
m = mse(imageA, imageB)
s = ssim(imageA, imageB)
# 打印结果
print("MSE: %.8f" % m)
print("SSIM: %.8f" % s)
我们的输出结果为:
可见,嵌入“深圳杯数学建模挑战赛”后,两张图片的差异非常非常小。
上面的方法说明,使用LSB方法将信息嵌入图片的做法是可行的。因此我们建立LSB方法的数学模型,通过LSB方法的原理进行编程。
由于题目给的图片是jpg格式,所以本节模型和代码支持原始图片的jpg格式输入。
但是,对jpg原始图片嵌入信息后,不能再保存为jpg格式,因为jpg无法保证完全不丢失细节,这样会对嵌入的信息进行扰乱,所以输出时要无损保存为PNG格式,这样就可以完整记录图片每个像素点rgb值的情况。
数学模型:
这两个函数的基本原理是利用了图像的最低有效位(Least Significant Bit,LSB)来嵌入和提取信息。这种方法是一种简单的隐写术技术,通过修改图像像素的最低有效位来嵌入信息,因为这种修改对图像的视觉效果影响很小,几乎无法察觉。
以下是这两个函数的数学模型:
嵌入信息 :
对于每个字符 c
在消息 message
中,将其转换为8位二进制表示 b(c)
。然后,遍历图像的每个像素 (r, g, b)
,并将每个颜色通道的最低有效位替换为 b(c)
的一个位。这可以用以下公式表示:
r ′ = r − r m o d 2 + b ( c ) i r' = r - r mod 2 + b(c)_i r′=r−rmod2+b(c)i g ′ = g − g m o d 2 + b ( c ) i + 1 g' = g - g mod 2 + b(c)_{i+1} g′=g−gmod2+b(c)i+1 b ′ = b − b m o d 2 + b ( c ) i + 2 b' = b - b mod 2 + b(c)_{i+2} b′=b−bmod2+b(c)i+2
其中 r'
, g'
, b'
是新的像素值,b(c)_i
是 b(c)
的第 i
位,mod
是模运算。

,并从每个颜色通道的最低有效位中提取信息位。然后,将这些信息位组合成8位二进制表示,转换为字符。这可以用以下公式表示:
b ( c ) i = r m o d 2 b(c)_i = r mod 2 b(c)i=rmod2 b ( c ) i + 1 = g m o d 2 b(c)_{i+1} = g mod 2 b(c)i+1=gmod2 b ( c ) i + 2 = b m o d 2 b(c)_{i+2} = b mod 2 b(c)i+2=bmod2
其中 b(c)_i
是 b(c)
的第 i
位,mod
是模运算。然后,我们将 b(c)
转换为字符 c
。
部分代码如下:
def embed_info(image_path, message, output_path):
# 把中文转换为base64编码
message = base64.b64encode(message.encode('utf-8')).decode('ascii')
# 把信息转换为二进制位
bits = ''.join(format(ord(x), '08b') for x in message)
info_len = len(bits)
img = Image.open(image_path)
width, height = img.size
pixels = img.load()
index = 0
for x in range(width):
for y in range(height):
r, g, b = pixels[x, y]
# 如果还有未嵌入的信息位
if index < info_len:
# 把当前像素的红色分量的最低有效位替换为信息位
r = int(format(r, '08b')[:-1] + bits[index], 2)
index += 1
if index < info_len:
# 把当前像素的绿色分量的最低有效位替换为信息位
g = int(format(g, '08b')[:-1] + bits[index], 2)
index += 1
if index < info_len:
# 把当前像素的蓝色分量的最低有效位替换为信息位
b = int(format(b, '08b')[:-1] + bits[index], 2)
index += 1
pixels[x, y] = (r, g, b)
if index == len(bits):
break
else:
continue
break
img.save(output_path)
return info_len
第二种代码:
#替换最后一位的数据,source是被替换数据,target是目标数据,就是batarget放到source最后一位
def repLstBit(source,target):
return replace_reg.sub(target,source)
#字符串转换二进制,不够八位的话补齐8位
def encode(s):
return ''.join(bin(ord(c)).replace('0b','').rjust(8,'0') for c in s)
#切割从图像中收集到的数据,就是把载密图像的对应最后一位提取出来之后需要进行切割
def cut_text(text,lenth):
textArr = re.findall('.{'+str(lenth)+'}',text)
tempStr = text[(len(textArr) * lenth):]
if len(tempStr)!=0:
textArr.append(text[(len(textArr)*lenth):])
return textArr
#二进制转换成字符串,看上面切割方法的注释即可理解该方法存在的意义
def decode(s):
bitArr = cut_text(s,8)
return "".join(chr(int(i,2)) for i in bitArr)
相似度检验:均方误差比使用库低,结构相似性指数则稍高一点点,依旧是肉眼无法分辨。
输出图片:
结果是可以的。
1280*1896
。按照本文的lsb模型,可以用来存储信息的二进制位总共有:3 * 1280*1896
个。7w
个二进制位来表示。信息嵌入效果:
问题一中的算法在图像传递过程中经历了压缩、格式变换、缩放、旋转或其他几何变形等操作后,可能会遇到一些挑战,导致嵌入和提取信息的困难。
图像压缩可能会导致原始图像中的LSB信息丢失,嵌入的信息变得不可读或无法提取。如果图像需要压缩,可以在压缩之前先嵌入信息,然后再进行压缩。这样可以减少信息丢失的可能性。
不同的图片格式可能使用不同的压缩算法和颜色空间,嵌入信息的方式可能会受到格式变换的影响。在格式变换之前,可以将图像还原为原始格式,再嵌入信息。然后,再将其转换为所需的格式。
缩放、旋转和几何变形这些操作可能导致像素值的变化,影响信息的嵌入和提取。在进行这些操作之前,可以记录图像的原始状态,包括像素值和位置。然后,应用这些变换后,再将信息嵌入到已变换的图像中。在提取信息时,需要对变换进行反向操作,以还原原始图像的状态。
在图像经历多次变换和处理后,可能会引入噪声或其他视觉问题,使得信息的嵌入和提取更加困难。使用更稳健的隐写技术,可以容忍一定程度的图像变化和质量损失。此外,可以进行更严格的质量控制,以确保在图像传递过程中尽量减小视觉损失。
在图像传递过程中,可能会出现数据传输错误或丢失,导致信息不完整。为了保证信息的完整性,可以在信息嵌入时添加错误检测和纠正码,以检测和修复在传递过程中引入的错误。
总之,问题1中的算法在图像传递过程中可能需要针对不同情况进行适应和改进。重要的是根据具体情况,综合考虑图像处理操作、数据完整性、加密和隐写技术,以确保信息的可靠嵌入和提取。随着图像传递过程中涉及的操作增多,需要更多的注意和技术手段来处理潜在的问题。
以下是一些可能的改进方法:
使用更复杂的嵌入技术:例如,使用离散余弦变换(DCT)或离散小波变换(DWT)等频域方法进行信息嵌入。这些方法将信息隐藏在图像的频域中,而不是像LSB那样直接在空间域中隐藏。这样,即使图像经过一些处理,隐藏的信息也能被提取出来。
使用错误纠正编码:例如,使用汉明码或里德-所罗门码等错误纠正编码对隐藏的信息进行编码。这样,即使图像处理过程中一部分信息被破坏,也能通过错误纠正编码恢复出原始信息。
使用水印技术:水印是一种特殊的信息隐藏技术,它的目标是在图像中隐藏一个标识符,即使图像经过处理,这个标识符也能被检测出来。水印技术通常使用一些复杂的嵌入和提取算法,以提高鲁棒性。
使用更强大的机器学习方法:例如,使用深度学习进行信息隐藏和提取。深度学习可以学习到如何在不同的图像处理操作下保持信息的隐藏和提取,从而提高鲁棒性。
以上这些方法都可以提高信息隐藏技术的鲁棒性,但也需要注意,提高鲁棒性通常会牺牲一些隐藏信息的容量。不过在用于保护著作权时,需要往图片中嵌入的信息也不会很多。
本文选择使用第三种,使用水印技术。
主要步骤为:
Arnold
置乱(可选,防止水印被提取和篡改)。主要代码:
def create_watermark(text, font_path, font_size=26, opacity=100):
n = int(math.sqrt(len(text))) + 1
lines = [text[i:i + n] for i in range(0, len(text), n)]
width, height = n * font_size, n * font_size
img = Image.new('RGBA', (width, height), (255, 255, 255))
font = ImageFont.truetype(font_path, font_size)
draw = ImageDraw.Draw(img)
text_color = (0, 0, 0, opacity)
for i, line in enumerate(lines):
text_bbox = draw.textbbox((0, 0), line, font)
line_width = text_bbox[2] - text_bbox[0]
x = (width - line_width) / 2
draw.text((x, i * font_size), line, font=font, fill=text_color)
img.save('./2023_SZ_Cup/Problem_3/output/watermark.png', 'PNG')
水印图片:
即打乱水印信息,使得信息分布均匀,减小可能得损失,同时防止水印被他人提取和篡改。
Arnold置乱是一种图像加密技术,它是由V.I. Arnold提出的一种二维图像的置乱变换方法。它的基本思想是将图像看作是在二维整数平面上的一个函数,然后通过一定的几何变换,将原图像的像素位置进行置乱,从而达到图像加密的目的。
Arnold置乱的基本公式如下:
对于图像中的每一个像素点(x, y),经过Arnold置乱后,该像素点的新位置(x’, y’)可以通过以下公式计算得到:
x ′ = ( x + y ) m o d N x' = (x + y) mod N x′=(x+y)modN y ′ = ( x + 2 y ) m o d N y' = (x + 2y) mod N y′=(x+2y)modN
其中,N是图像的宽度或高度(假设图像是正方形的),mod是取模运算。
Arnold置乱的逆操作,也就是解密过程,可以通过以下公式进行:
x = ( 2 x ′ − y ′ ) m o d N x = (2x' - y') mod N x=(2x′−y′)modN y = ( − x ′ + y ′ ) m o d N y = (-x' + y') mod N y=(−x′+y′)modN
这两组公式就是Arnold置乱及其逆操作的基本公式。通过这两组公式,可以实现图像的加密和解密操作。
效果:
一次:
def arnold_scramble(image, iterations):
array = np.array(image)
height, width, _ = array.shape
scrambled_array = np.empty_like(array)
for _ in range(iterations):
for y in range(height):
for x in range(width):
scrambled_array[x,y] = array[(x + y) % height, (x + 2 * y) % width]
array = scrambled_array.copy()
scrambled_image = Image.fromarray(np.clip(scrambled_array, 0, 255).astype('uint8'))
return scrambled_image
本文使用的是基于DCT的暗水印技术。即将水印图片嵌入到图片的频域而不是前面LSB的空域。
在离散余弦变换(DCT)的结果中,低频部分通常包含了图像的大部分信息,如颜色和亮度变化。这是因为图像的大部分区域通常有相似的颜色和亮度,这些信息在频域中表现为低频成分。因此,你可以看到在DCT图像的左上角(低频部分)有更多的亮点。
离散余弦变换(DCT)[9]是一种常用于数据或图像压缩的变换方法。它的主要特点是将空域的信号转换到频域上,因此在压缩过程中具有良好的去相关性性能。DCT变换本身是无损的,且具有对称性。当对原始图像应用离散余弦变换时,DCT系数的能量主要集中在左上角,而其余大部分系数接近于零。在图像压缩中,一般会进行量化操作,即将小于一定阈值的系数归零。这个过程被称为图像量化,接着进行逆DCT运算,以还原压缩后的图像。
DCT变换先将整幅图像分成 大小的像素块,然后对每个像素分块逐一进行DCT变换。在数字图像领域中使用的是二维DCT变换,其中,二维DCT变换的简单公式为:
相反,高频部分包含了图像的细节和纹理信息,如边缘和纹理。这些信息在频域中表现为高频成分。因此,你可以看到在DCT图像的右下角(高频部分)有一些亮点,但通常比低频部分少。
中频部分则介于两者之间,包含了一些颜色和亮度的变化,以及一些细节和纹理信息。
在图像的频域表示中,高频部分通常包含的信息最少。这是因为高频部分对应于图像的细节和纹理,而这些信息在整个图像中的占比通常较小。然而,直接在高频部分嵌入水印可能会导致水印在图像压缩或降低分辨率时丢失,因为这些操作通常会丢弃高频信息。
相反,低频部分包含了图像的大部分信息,如颜色和亮度变化。在低频部分嵌入水印可能会显著改变图像的视觉效果,因为这会改变图像的基本特性。
因此,中频部分通常被认为是嵌入水印的最佳位置。 中频部分包含了一些颜色和亮度的变化,以及一些细节和纹理信息,因此在这个部分嵌入水印不太可能显著改变图像的视觉效果。同时,由于中频部分的信息在图像压缩或降低分辨率时不太可能被丢弃,因此在这个部分嵌入的水印也更有可能保留下来。
因此,本文即是将水印嵌入图像在DCT变换后的中频部分。
效果演示:
alpha = 0.1
def perform_dct(original_array):
height, width, _ = original_array.shape
dct_blocks = np.empty_like(original_array, dtype=np.float64)
for i in range(0, height, 8):
for j in range(0, width, 8):
dct_blocks[i:i + 8, j:j + 8] = dct(dct(original_array[i:i + 8, j:j + 8], axis=0, norm='ortho'), axis=1,
norm='ortho')
return dct_blocks
def embed_watermark(dct_blocks, watermark_array, alpha=0.05):
dct_blocks_with_watermark = dct_blocks.copy()
dct_blocks_with_watermark[::8, ::8] += alpha * watermark_array
return dct_blocks_with_watermark
第一种情况针对被旋转图片进行水印信息的提取,首先通过Canny算子实现边缘提取,再使用Hough变换对图像进行几何形状检测,通过合理设置阈值筛选出合适的旋转角度并将其恢复水平。最后裁剪掉无效的图像边框得到的图片即可进行提取水印操作。
第二种情况针对被裁剪图片进行水印信息的提取,将被裁剪后的图片与不包含水印的原始图片计算截图匹配,使用OpenCV所提供的matchTemplate()函数实现,找出二者最大匹配位置后,将待检测图片恢复拼接。最后得到的图片即可进行提取水印操作。
第三种情况针对被缩放的图片,通过不断缩放图片并与原始图片进行匹配检测,找到最合适的缩放比例,最后使用OpenCV的扩展库cv2所提供的zoom操作函数得到的图片即可进行提取水印操作。
提取水印的过程中,首先需要将图片再次进行8×8分块,再对子块进行水印的提取。由于在之前的操作中对水印图片进行了加密置乱,故在此处需要进行Arnold逆变换来实现水印信息的解密复原,最终提取出一张水印图片。
效果:
我们下图展示了水印的提取效果,从左到右分别对应0.1,0.5,0.9
部分代码:
def process_images(image_with_watermark_path, original_image_path, alpha=0.05):
image_with_watermark = load_image(image_with_watermark_path)
original_image = load_image(original_image_path)
image_with_watermark_array = image_to_array(image_with_watermark)
original_array = image_to_array(original_image)
# 对图像执行DCT
dct_blocks_with_watermark = perform_dct(image_with_watermark_array)
original_dct_blocks = perform_dct(original_array)
watermark_array = extract_watermark(dct_blocks_with_watermark, original_dct_blocks, alpha)
watermark_array = clip_and_convert(watermark_array)
watermark_image = array_to_image(watermark_array)
return watermark_image
LSB(Least Significant Bit)是一种常见的信息隐藏技术,通常用于数字水印和隐写术。在使用LSB进行信息嵌入时,需要注意以下几点:
选择合适的嵌入位置:LSB通常是将信息嵌入到图像的最低有效位,因为这样对图像的影响最小,人眼难以察觉。然而,如果图像可能会受到压缩或其他形式的处理,这些处理可能会改变最低有效位,导致嵌入的信息丢失。因此,如果预计图像可能会受到这种处理,可能需要选择其他的嵌入位置,例如更高的位。
保护嵌入信息的安全性:虽然LSB嵌入可以隐藏信息,但如果攻击者知道使用了LSB嵌入,他们可能会尝试提取或破坏这些信息。因此,可能需要使用加密或其他形式的保护来确保嵌入信息的安全性。(比如先对要嵌入的信息进行加密再进行嵌入)
避免过度嵌入:虽然LSB嵌入对图像的影响较小,但如果嵌入的信息过多,可能会导致图像质量明显下降。因此,需要在隐藏信息的需要和保持图像质量之间找到平衡。
在使用最低有效位(LSB)隐写技术进行信息嵌入时,首先需要注意的是隐蔽性和安全性。确保嵌入的信息不会在视觉上或统计上引起异常,避免泄露信息的存在。对敏感信息进行加密,然后再嵌入,以增加信息的安全性。
其次是容量与质量平衡。根据需求选择适当的容量,嵌入过多信息可能导致图像质量下降,容易被察觉。在嵌入信息前后进行图像质量评估,确保不会引起明显的视觉损失。
再次是随机性和伪装。随机选择LSB嵌入的位置,而不是在固定的位置进行嵌入,以增加隐蔽性。在信息嵌入之前,可以在LSB位置上加入随机噪声或微小的变化,以使嵌入更难被检测。
最后要考虑的是检测和提取。记录或嵌入元数据以标识图像中嵌入信息的存在,以便在需要时提取。嵌入前可以添加错误检测和纠正码,以确保信息在提取时的完整性和正确性。
我们问题四的思路如下图所示: